Conditional declaration by array of records - modelica

I try to create many components depending on the value of constant elements. These elements are organized in an array of records.
Dymola prints the translation log for the example below:
But I'm sure to use fixed conditions because I only perform allowed operations on constant values.
Here is the simple example of what I wantet to do:
model ConditionalComponent
type Enum = enumeration(one,two,three);
record Tmp
parameter Integer ID;
parameter Boolean active;
end Tmp;
record TmpNamed
parameter Enum name;
extends Tmp;
end TmpNamed;
function reorder
input TmpNamed inp[:];
output Tmp out[size(inp,1)];
algorithm
for elem in inp loop
out[elem.name] := Tmp(elem.ID, elem.active);
end for;
end reorder;
constant TmpNamed testIn[:] = {
TmpNamed(Enum.two,20,true),
TmpNamed(Enum.one,10,true),
TmpNamed(Enum.three,30,true)};
constant Tmp testOut1[:] = reorder({
TmpNamed(Enum.two,20,true),
TmpNamed(Enum.one,10,true),
TmpNamed(Enum.three,30,true)});
constant Tmp testOut2[:] = reorder(testIn);
constant Boolean active1 = testOut1[Enum.one].active;
constant Boolean active2 = testOut2[Enum.one].active;
Real t1=0 if testOut1[Enum.one].active;
//Real t2=0 if testOut2[Enum.one].active;
//Real t3=0 if active1;
//Real t4=0 if active2;
end ConditionalComponent;
The function reorder is intended to ease the management of large lists of named active components. Normally the constant testOut2 is used and created within the package ConditionalComponent. But for testing purposes ConditionalComponent is a model here. Actually I only want to use the line
Real t2=0 if testOut2[choice].active;
parameter Enum choice = Enum.one;
within other components, that have a parameter of type Enum. The declarations for t1, t3, t4 are only some tests that work, depending on what is left uncommented.
For example leaving the declaration for t1 and t3 uncommented works. But if one uses only the declaration for t1, it is not translated by Dymola.
The difference between t1 and t2 is, that the argument for reorder is passed directly or via the constant testIn.
I'm sure, that most parameter and constant prefixes are unnecessary and I tried hard to figure out the problem. But unfortunately I cannot decide whether Dymola is not working correctly or I did something wrong. And I've got no idea how to debug the translation process to figure it out by myself.
Can anyone tell me, what am I doing wrong?

Not something wrong, but it's just currently seen as too complicated and not handled.
A work-around is to split subscripting and element access:
constant Tmp testOut1_one=testOut1[Enum.one];
Real t1=0 if testOut1_one.active;

Related

Converting arrays from signed to integer in VHDL?

I have declared an array
type datastream is array(0 to 10) of signed (5 downto 0);
For simulation, I want to display the array as integer-numbers
So I created
type datastream_int is array(0 to 10) of integer;
and
signal DIN_ARRAY: datastream;
signal DIN_ARRAY_int: datastream_int;
...
DIN_ARRAY_real <= datastream_int(DIN_ARRAY);
But it fails. How to convert it? Dont want to use a for loop
The numeric_std package, that I assume you are using, provides a to_integer function to convert from a single signed value to a single integer object. For an array, you're going to have to use a for loop. Something like this:
for i in DIN_ARRAY'range loop
DIN_ARRAY_int <= to_integer(DIN_ARRAY(i));
end loop;
You could also provide a conversion function (it will also contain a for loop)
function datastream_to_datastream_int( d : datastream ) return datastream_int is
variable r : datastream_int;
begin
for i in d'range loop
r(i) := to_integer(d(i));
end loop;
return r;
end function;
....
--no loop required
DIN_ARRAY_int <= datastream_to_datastream_int(DIN_ARRAY);
So, there will be a for loop somewhere.
Your code fails because you have attempted a type conversion, which is only allowed between similar types - ie. array or record types where the element types match between the two types.
PS. VHDL 2008 provides an integer_vector type in the std.standard library (which is included by default) which may help by allowing you to do this:
signal DIN_ARRAY_int: integer_vector(DIN_ARRAY'range);
If you did decide to keep datastream_int as per your original, you could type convert it to an integer_vector, because the types are similar:
my_iv <= integer_vector(DIN_ARRAY_int);

How to make use of the unit attribute within a model in Modelica?

Motivation
Modelica does store units of measurement (e.g. SI units and Non-SI units) as an attribute with regard to a variable. Here is an example for a Non-SI-unit:
type Time_months = Real( quantity = "Time", unit = "mo", displayUnit = "months" )
Since for models in economics it will be rather akward to give rates in seconds, I would like to write a rather general unit conversion function that will allow to convert units of time. So ideally a function to convert to another time base should work with three inputs and one output:
input Real timeValue "the value of time to be converted";
input String timeBaseA "the time base for timeValue, e.g. \"mo\" ";
input String timeBaseB "the time base to convert to, e.g. \"yr\" ";
output Real convertedTimeValue "the result of the conversion";
Questions
If we assume that a variable for some time value already has a specific unit attribute (e.g. "mo") it would make sense to use that meta information within a model.
Question 1: How can meta information like unit be accessed within a model?
Ideally something like the following would be great:
String timeBaseA := timeValue.unit;
or
String timeBaseA := getUnit( timeValue ) "some function to read unit information";
Question 2: How can meta information like unit be assigned within a function?
In the example we would of course like to return the output value with the correct unit of time. So ideally we would like to have:
output Real convertedTime( quantity = "Time", unit = strTimeBaseB )
Unfortunately, using an input will give rise to an error as the variability is different: The unit attribute should have constant variability but the input variable has parameter variability. (Using a function - which would be nice - also fails for the same reason.)
Regarding Question 1:
I have never used Wolfram SystemModeler, but the Modelica Language Specification 3.4 says in chapter 4.8 (Predefined Types and Classes):
The attributes of the predefined variable types (Real, Integer, Boolean, String) ... cannot be accessed using dot notation, and are not constrained by equations and algorithm sections.
Regarding Question 2:
I think it is only possible to define the unit of a variable on declaration from a literal or from a final parameter - at least this is what I observed in Dymola.
Alternative - use operator records
You could use operator records for your task. This will allow you to store the time in seconds and convert it to what ever needed when the value comes to use.
Operator records allow you to define several function to create them, compare or add them, convert to String, etc.
See the brief example below, where a operator record Time is defined, which can be created with two different constructor functions from seconds or days and can be converted to Strings with day or seconds
operator record Time
Integer s "Second";
encapsulated operator 'constructor'
import Time;
function from_s
input Integer s "Seconds";
output Time t(s=s);
algorithm
end from_s;
function from_d
input Integer d "Days";
output Time t(s=d*24*3600);
algorithm
end from_d;
end 'constructor';
encapsulated operator 'String' "Convert Time to string"
import Time;
function formated
input Time t;
input String format = "s" annotation(choices(choice="s" "seconds", choice="d" "days"));
output String str;
algorithm
if format == "d" then
str :=String(t.s/24/3600);
else
str :=String(t.s);
end if;
end formated;
end 'String';
encapsulated operator function '==' "Compare time records"
import Time;
input Time t1;
input Time t2;
output Boolean result "= t1 == t2";
algorithm
result := t1.s == t2.s;
end '==';
end Time;
Usage:
import Modelica.Utilities.Streams.print
t1 = Time(d=12) // create record using day constructor
t2 = Time(s=3600*24*2) // create record using second constructor
print(String(t1, format="s")) // prints 1036800
print(String(t1, format="d")) // prints 12
print(String(t2, format="s")) // prints 172800
print(String(t2, format="d")) // prints 2
See Modelica Spec 3.4 Chapter 14 "Overloaded Operators" for details.
Note: This was tested with Dymola 2019, not with Wolfram SystemModeler
In Modelica usually every variable is computed based on SI units. Then you have displayUnits to plot them in a different unit (not affecting the actual computation).
I don't know about SystemModeler, but in Dymola the conversion between the unit (of computation) and the displayUnit (only for plotting) is handled by a pre-defined script (displayUnit.mos). It can be extended by the user to contain custom displayUnits. The code for the display units related to time is shown below. I extended it to have week (w) additionally to the predefined ones.
// Syntax:
// defineUnitConversion(<unit>, <derived unit>, <scale>, <opt. offset>);
// Time
defineUnitConversion("s", "ms", 1000);
defineUnitConversion("s", "min", 1/60);
defineUnitConversion("s", "h", 1/3600);
defineUnitConversion("s", "d", 1/86400);
defineUnitConversion("s", "w", 1/604800);
This can then be selected in plots manually or as the default ´displayUnit´ via Modelica.SIunits.Time t(displayUnit = "w") = ...;
The disadvantage is, that this extension has to be done in a file in the install directory. So it has to be changed again after re-installing the tool or when using a different computer.
If there are numerical reasons to not compute solutions in seconds (e.g. because values would get to big), the solution would be the nominal attribute, which enables a scaling of the variables.
BTW: I think months are not a very good unit of time as they can have 28 to 31 days. That's why I chose weeks in my example.
You could use conversion like is done in the MSL, for example the function Modelica.SIunits.Conversions.to_degC which has the signature:
function to_degC
input Temperature Kelvin "Kelvin value";
output NonSIunits.Temperature_degC Celsius "Celsius value";
end to_degC;
This works, but you need one such function for each unit you want to convert between (which is why most calculations are done using SI-units).

How to generate ascending values during randomisation

Please help to resolve one randomization-constraint related issue that I am facing.
So in my seqItem, I have a write_addr random variable. This variable controls the location in memory where the data should be written.
I want to implement different writing address changing modes like random-address, given range address, ascending and descending type.
I have params_pkg, where user defines the address change type and my TB generates write_addr values correspondingly.
I was thinking to implement this using constraints, like by enableing/disabling the constrains get the required behavioral:
class seqItem extends uvm_sequence_item;
`uvm_object_param_utils(seqItem)
randc logic [541-1:515] wfifo_addr;
if (params_pkg::writeAddressType == "WriteGivenRangeAddress") begin
constraint wArrdGivnRangCnstr {
this.wfifo_addr inside {[params_pkg::addrLowValue:params_pkg::addrHighValue]};
}
end
function new (string name="seqItem");
super.new(name);
this.wArrdGivnRangCnstr.constraint_mode(0);
endfunction
endclass
However there is no way to generate ascending or descending address values using constraints. Because to have ascending address, the seqitem code needs to know the write_addr variable value from the previous randomization, which I could not implement.
My question is: whether it is possible to have ascending write_addr values using constraints?
And second, the example code that I posted is not working, simulator gives error saying that generate constraints are not allowed. Most probably this is something not supported in System Verilog. Am I right?
Second part first: I suspect "Generate constraints" are constraints contained within a generate block. Generate blocks are only allowed within modules, programs, and checkers not classes, hence "Generate constraints" are illegal (although the term is oddly specific. I'd expect an error saying "Generates are not allowed in classes"). You can rewrite your constraints by moving the conditional inside the constraint block:
constraint wArrdGivnRangCnstr {
if (params_pkg::writeAddressType == "WriteGivenRangeAddress") {
this.wfifo_addr inside {[params_pkg::addrLowValue:params_pkg::addrHighValue]
};
}
BTW: you might want to consider an enum for the writeAddressType variable. That way typos are caught at compile time.
Another thing...
You have your random variable defined as randc.
Variables declared with the randc keyword are random-cyclic variables that cycle through all the values in a random permutation of their declared range.
If you limit the range on a randc variable, it can't "cycle through all the values...of [it's] declared range". It's not clear from the SystemVerilog LRM what will happen here, but I suspect that once all the values between low and high are exhausted randomisation will fail.
Also, the variable is 26-bits in size. That's 67,108,864 different values you're asking the simulator to keep track of to see if they've been used before. It will need 8MB of flags just for this one variable.
I expect what you really want here is to define the variable as rand and not randc.
On to your main question...
You are right, you need some kind of storage of the last value in order to get incrementing values, and because this is a sequence item I suspect that you're creating a new instance each time, hence we can't store the last value in an instance variable as all instance variables are destroyed.
So there's two options:
Store the last value in a static instance variable.
class seqItem extends uvm_sequence_item;
`uvm_object_param_utils(seqItem)
rand logic [541-1:515] write_addr;
static logic [541-1:515] last_write_addr = 0;
constraint wAddrIncr {
write_addr > last_write_addr;
}
function new (string name="seqItem");
super.new(name);
endfunction
function post_randomize();
last_write_add = write_addr;
endfunction
endclass
Add a constraint in the sequence when randomising the sequence item
class someSequence extends uvm_sequence;
...stuff omitted...
task body();
seqItem item;
seqItem last_item;
last_item = null;
repeat (4728346) begin
item = new(); // or create to use factory
if (last_item) begin
item.randomize() with {
write_addr > last_item.write_addr
};
end else begin
item.randomize();
end
last_item = item;
// Send to driver or whatever
end
endtask
endclass
Number 2 is better in my opinion, because it doesn't bake the increasing address behaviour into the sequence item. Incrementing addresses is really a property of the sequence of items, not of any single item. I can now write some sequences that have increasing addresses, decreasing addresses, or any other pattern.
One solution to your problem of generating ascending (or descending) addresses is to keep a note of the last value generated and to use this as the lower bound in the constraint:
class seqItem;
randc logic [0:15] wfifo_addr;
static logic [0:15] last_wfifo_addr = '0;
constraint wArrdGivnRangCnstr {
this.wfifo_addr inside {[last_wfifo_addr:params_pkg::addrHighValue]};
}
function void post_randomize;
last_wfifo_addr = wfifo_addr;
if (last_wfifo_addr >= params_pkg::addrHighValue)
last_wfifo_addr= params_pkg::addrLowValue;
endfunction
endclass
http://www.edaplayground.com/x/3QxX
The post_randomize function is a built-in method that can be overrided. It is called implicitly after the (built-in) randomize method. There is also a built-in pre_randomize, which of course you can override, too.
One solution to your problem of changing randomisation modes, is to turn constraints on and off:
You can turn a constraint off using the implicit constraint_mode method:
s.wArrdGivnRangCnstr.constraint_mode(0);
and then turn it on again:
s.wArrdGivnRangCnstr.constraint_mode(1);
(where s is a reference to your seqItem class). You can't put constraints inside an if statement as your error message demonstrates.

Constraining an entire object in SystemVerilog

I'm trying to constrain an entire object (not just the fields of an object) based on some other object. Here is a stripped down version of my production code:
I have the following class:
class some_class;
bit[7:0] some_field;
bit[3:0] some_other_field;
// this function would do some complex procedural
// operations on the fields of the object
function void do_some_op();
bit[3:0] tmp = some_field[3:0];
some_field[3:0] = some_other_field;
some_other_field = some_field[7:4];
some_field[7:4] = tmp;
endfunction
function some_class some_function(bit some_param);
some_function = new this;
$display("foo"); // this print here to see that method is executed
if (some_param)
some_function.do_some_op();
endfunction
function void print();
$display("some_field = %x", some_field);
$display("some_other_field = %x", some_other_field);
endfunction
endclass // some_class
This class contains some integral fields. It also has a method that does some complex procedural on the fields of that class. In the example I've simplified it. I also have another class that returns a new object on which the operation has been performed.
I have another class that operates with some_class instances. As per Dave's input I have made it create the objects first (as randomize() does not create objects).
class some_shuffler;
rand bit params[];
rand some_class objects[];
constraint size_c {
params.size() == objects.size() - 1;
params.size() <= 10;
};
constraint shuffle_c {
// not allowed by standard
// foreach (params[i])
// objects[i+1].some_field == objects[i].some_function(params[i]);
foreach (params[i])
objects[i+1].some_field ==
objects[i].some_function(params[i]).some_field &&
objects[i+1].some_other_field ==
objects[i].some_function(params[i]).some_other_field;
};
function new();
objects = new[10]; // create more objects than needed
foreach (objects[i])
objects[i] = new();
// initialize first object
objects[0].some_field = 8'hA5;
endfunction // new
function void post_randomize();
foreach (objects[i]) begin
$display("objects[%0d]:", i);
objects[i].print();
$display("");
end
endfunction
endclass
This class has two arrays, one of operations performed and one of the intermediate states. There is an initial object. On this one, some_function is performed and it results in the next object.
This is how I wanted to test it:
module top;
import some_pkg::*;
initial begin
static some_shuffler shuffler = new();
bit rand_ok;
rand_ok = shuffler.randomize() with {
params.size() == 1;
};
assert (rand_ok);
end
endmodule
When trying to constrain the objects directly I immediately get a constraint violation. The simulator seems to try to make the 2 handles equal. This is anyway forbidden by the standard and I'm not doing it anymore (though a compile failure would have been nice). I've unraveled the constraints as suggested by Dave and Greg (I think doing some_function().some_field is non-standard, but it compiles in Questa).
Even now, the foo print does not appear on the command line (some_function() is not getting executed). What I see is that objects[1] contains the initial value (all 0s for both fields).
I can't just generate the list of params and then procedurally randomize the objects for each iteration, because I want to be able to constrain the last object to have a certain value - basically giving the constraint solver the start and the end points and let it figure out the way to get there.
Object vs. object constraints are not allowed in SystemVerilog because they are not integral types. See IEEE Std 1800-2012 § 18.3:
Constraints can be any SystemVerilog expression with variables and constants of integral type (e.g., bit, reg, logic, integer, enum, packed struct).
You can constrain the integral components of class object if the component is a rand (ex obj[1].value == obj[0].value+1;).
Functions are allowed in constraints, but there limitation. See IEEE Std 1800-2012 § 18.5.12 Functions in constraints for full details. Limitations include:
Functions cannot contain output or ref arguments
Functions should be automatic and leave no side effects
The functions arguments have an implicit priority (ex x<=F(y) infers solve y before x)
Circular dependencies will result in an error
Update:
Looks like the only thing truly being randomized is params. The values of some_field and some_other_fieldare calculations. So it makes more sense to move the loop for shuffling into thepost_randomize` function.
constraint size_c {
params.size() == objects.size() - 1;
params.size() <= 10;
};
function void postrand_shuffle();
foreach (params[i])
objects[i+1] = objects[i].some_function(params[i]);
endfunction
function void post_randomize();
postrand_shuffle();
// ... your other post_rand code...
endfunction
SystemVerilog's random constraint solver will work when there is at least one solution. However when the solution space is small and difficult to determine or a long chain, simulator performance drops. For these scenarios it is better move the one-to-one sequential calculations into post_randomize.
A couple of problems with your example code.
Objects must be constructed first before calling randomize(). If
you know the exact size before calling randomize (like in your
example), just new[n] the dynamic arrays constructing each object
element first, and remove the size constraints. If the size will be
random, you need an upper limit constraint on the size. construct the max
number of objects before calling randomize(), and after randomizing the array, the unused objects will be eliminated.
Constraint expressions must be integral. You can do objects[i+1].some_field == objects[i].some_field but the solver cannot manipulate class handles.
The return values of functions are treated as state variables. Move these to post_randomize

Load a record using dot notation and strings from an array in Modelica

Is there a way to load a record in Modelica, and manipulating part of the record directory with a string from an array?
I included the minimal working example below, in summary, I have two different records with parameter values named Cylinder and Board and I have an array containing their names. I would like to access one set of parameter values in a function, depending on the user input "ShapeNumber". So when the user is putting a 1, load the parameters from the Cylinder record, when the user puts a 2, load the parameters from the Board record. Ideally, I would like to add the string "Cylinder" or "Board" into the dot notation for loading the record, as indicated in the code below.
package Test_things
record General_parameters
parameter Real Length "Length in m";
end General_parameters;
record Cylinder_parameters
extends General_parameters;
parameter Real Diameter "Diameter in m";
end Cylinder_parameters;
record Board_parameters
extends General_parameters;
parameter Real Width "Width in m";
parameter Real Depth "Depth in m";
end Board_parameters;
record Parameter_values
constant Test_things.Cylinder_parameters Cylinder(Length=10, Diameter=0.5);
constant Test_things.Board_parameters Board(
Length=20,
Width=1,
Depth=0.01);
constant String[2] ShapesArray = {"Cylinder","Board"};
end Parameter_values;
function Length_tester
input Integer ShapeNumber; //Which shape from within the shape array is tested
output Boolean LongEnough;
Test_things.Parameter_values Values; //Gives Values.Cylinder, Values.Board, Values.ShapesArray
protected
String ShapeName;
Real Length;
algorithm
//Load correct shape
ShapeName := Values.ShapesArray[ShapeNumber];
// This is what I would like: Length := Values.{ShapeName}.Length;
// which results in one of the two lines of code below.
// Length := Values.Cylinder.Length;
// Length := Values.Board.Length;
// This works for now, but has to be adjusted whenever a new shape is used
if ShapeName == "Cylinder" then
Length := Values.Cylinder.Length;
elseif ShapeName == "Board" then
Length := Values.Board.Length;
end if;
if Length > 15 then
LongEnough := true;
else
LongEnough := false;
end if;
end Length_tester;
model Main
Boolean LongEnoughCylinder;
Boolean LongEnoughBoard;
equation
// Test Cylinder
LongEnoughCylinder = Length_tester(1);
// Test Board
LongEnoughBoard = Length_tester(2);
end Main;
end Test_things;
I found the work around with the if-statement, but when I add a new record, I have to update that if-statement in every function I have, rather than only updating the array with the names of the records.
Thank you for any help!
What you try to do is currently not possible, as Modelica does not support dynamic creation of class paths. All class paths must be hard coded.
Therefore your if/else workaround is a legit solution. To keep the maintainability low, you could create a function getLength with contains the if/else logic and all your other functions will use this one.
Below you can find the solution which I would use. Everything is made as generic as possible, so you can use an arbitrary number of shapes of any kind. To do so, there is the generic shape type Shapewith all possible shape parameters. Then there are the specialized shapes Board and Cylinder, which set not needed parameters on 0 in combination with the keyword final, so users can not modify them. This allows to create arrays of shapes, containing the basic shape type, as well as the specialized shapes. The testLength function will then be very simple, accepting an array of shapes. The downside of this solution is (for you as developer), that new shapes with new parameter require an update the basic shape type and the specialized shapes.
package Test_things
record Shape "Generic shape with all possible parameters"
import SI = Modelica.SIunits;
// Common parameters
parameter ShapeTypes shapeType "Kind of shape";
parameter SI.Length length;
// Board parameters
parameter SI.Length width;
parameter SI.Length depth;
// Cylinder parameters
parameter SI.Diameter diameter;
end Shape;
type ShapeTypes = enumeration(Cylinder, Board);
record Cylinder "Cylinder shape with unneeded parameters set on final 0"
extends Shape(final shapeType=ShapeTypes.Cylinder, final width=0, final depth=0);
end Cylinder;
record Board "Board shape with unneeded parameters set on final 0"
extends Shape(final shapeType=ShapeTypes.Board, final diameter=0);
end Board;
function testLength "Check if length of a specific shape in an array of shapes is bigger than 15"
input Integer shapeNumber "Shape of interest";
input Shape shapes[:] "Array of shapes";
output Boolean longEnough;
algorithm
longEnough := shapes[shapeNumber].length > 15;
end testLength;
model Main
constant Shape shapes[:] = {
Board(length=20, width=1, depth=0.01),
Cylinder(length=1, diameter=2)};
// note: this works in OpenModelica only with the prefix constant, parameter does not compile
// in Dymola it works with both
Boolean longEnoughCylinder;
Boolean longEnoughBoard;
equation
// Test Cylinder
longEnoughCylinder = testLength(1, shapes);
// Test Board
longEnoughBoard = testLength(2, shapes);
end Main;
end Test_things6;
Some notes on style:
all classes except of function should be written in upper case
all instances, parameters, etc. should be written in lower case
instead of writing the unit into the comment, use SI units