Events, fields and constraints
Events, fields and constraints are members of the structured types actor, scenario, struct and modifier. The constructs described in the following sections are also members of structured types:
Events
Signify a point in time.
Struct, actor, or scenario member
event <event-name> [(<param>+) | is <qualified-event> ]
<event-name>- (Required) Is a name unique in the enclosing construct.
<param>+-
(Optional) Is a comma-separated list of one or more fields in the form:
<field-name>: <type-name> [= <default_value>]Note
If you specify a parameter list, you cannot specify a qualified event.
<qualified-event>- (Optional) See Qualified event for a description.
Events are transient objects that represent a point in time and can trigger actions defined in scenarios. You can define an event within a struct, but more typically within an actor or scenario.
Scenarios can emit events. Events are used to:
- Cause scenarios waiting for that event to proceed.
- Assign a sampled value to a field when that event occurs.
- Collect coverage data when that event occurs.
Note
Coverage and performance metrics are not collected if the event triggering collection is not emitted. For example, if a scenario fails, coverage and performance metrics defined on the scenario's @end event are not collected.
For a more detailed description of events with examples, see Defining events, including:
Qualified event
A qualified event defines a trigger for time-based actions, such as sample (see <sample-exp> in the Fields and variables section), procedural code execution (using on), or a new event.
<event-condition>
or:
@<event-path> [as <param-name>] [if <event-condition>]
<event-condition>-
(Optional) Is a Boolean expression. The expression may contain temporal operators.
<event-path>-
(Required) Is the pathname of a defined event.
If <event-path> is not specified, the <event-condition> is tested at top.clk (every return from the simulator). If <event-condition> is not specified, true is assumed.
The <param-name> clause creates a pseudo-variable (an event object variable) with the specified name in the current scope—the current scenario, for example. This notation is allowed only for events with parameters. You can use this variable to access the values of the event parameters, possibly collecting coverage over their values. See the examples below.
extend top.main:
do drive1: sut.car.drive(duration: 10s) with:
speed(50kph, at:end)
# Sampling occurs at the event drive1.start
# Qualified event: @drive1.start
var speed_at_start := sample(sut.car.state.speed, @drive1.start)
# log_info() is called at the start of the 'drive1' scenario if the SUT's speed is over 20kph
# Qualified event: @drive1.start if sut.car.state.speed > 20kph
on @drive1.start if sut.car.state.speed > 20kph:
logger.log_info("Speed is over 20kph at the start of the drive")
# The implicit event is top.clk, therefore the condition will be checked on every simulation cycle
# Qualified event: sut.car.state.speed > 100kph
on sut.car.state.speed > 100kph:
sut_error(over_speed, "SUT speed exceeded 100kph")
# log_info() is called every time the SUT accelerates beyond 30kph in the first 5 seconds
# Qualified event: rise(sut.car.state.speed > 30kph) and top.time < 5sec
on rise(sut.car.state.speed > 30kph) and top.time < 5sec:
logger.log_info("SUT speed crossed 30kph before t=5sec")
# Using 'as' to capture event arguments
# Qualified event: @sut.car.collision as c
on @sut.car.collision as c:
logger.log_info("SUT collided with: $(c.other_object)")
See complete example.
Fields and variables
Declare a field to contain data
Struct, actor, or scenario member
[var | const] <field-name> : [<type>] [ = <default-value> | <sample-exp> | new [<type>]] [<with-block>]
Notes
-
If <type> is not provided, then [= <default-value> | <sample-exp> | new <type>] must be provided.
-
Only var fields can accept <sample-exp>.
-
Only var and const fields can accept new.
-
const fields with struct type must be initialized with the new keyword.
var-
(Optional) The var keyword specifies that no value should be generated for this field before the run executes. Instead, a value is assigned during the run. You can also assign a default value to the variable in the declaration. It retains that value during the run unless it is assigned a new one. If you do not assign a default value, these values are assigned:
- For int: 0
- For uint: 0
- For float: 0.0
- For physical: 0.0
- For bool: false
- For string: empty string
- For enum: first member
- For struct: null
- For list: an empty list
Note: Passing an argument to a var field is not allowed and will result in a compilation error.
const- (Optional) The const keyword specifies a non-generated field that cannot be changed at runtime. A const field can be used in contexts that require a constant value that is known at compile time. For more details, see const fields.
new-
(Optional) The new keyword allocates space for and initializes a variable of type actor or struct. Each field in the new object is initialized with its default value:
- For int: 0
- For uint: 0
- For float: 0.0
- For physical: 0.0
- For bool: false
- For string: empty string
- For enum: first member
- For struct: null
- For list: an empty list
However, if a field in the object was declared with var and initialized, then it gets the initialized value.
<field-name>- (Required) Is a unique name within the enclosing struct, actor or scenario.
<type>- (Required) Is required unless a default value or sample expression is assigned, allowing the type to be derived from the specified value or expression. <type> is any data type or a list of any of these. Use list of <type> for lists.
<default-value>- (Optional) Both generated fields and variables can be assigned a default value. This default value is handled the same manner as a default constraint: the default value is retained unless it is overridden by another assignment.
<sample-exp>-
(Optional) Has the following form:
sample( <exp>, <qualified-event> [, <default-value>] )<exp>: An expression that evaluates to the field whose value you want to sample.
<default-value>: The initial value you want to assign to the expression.
<qualified-event>: See Qualified event for a description.
<with-block>-
(Optional) Is in the form:
orwith: <member>+orwith: <member1> [; <member2>;...]with(<field>:<value> [, field>:<value>,...)See with block syntax for examples.
<member>-
(Optional) Is either:
-
A coverage definition in the form cover(<field-name>), where <field-name> is a scalar field, or cover(<field-name1>.<field-name2>), where <field-name1> is a struct or actor field and <field-name2> is the field that you want to cover.
-
A constraint in the form
keep(<constraint-type> <constraint-exp>)
where <constraint-type> is either soft or default. default is allowed only for fields declared within scenarios.
Within the constraint expression, use the implicit variable it to refer to the field if it is scalar, or [it.]<field-name> if it is a struct or actor field and <field-name> is the field that you want to constrain.
Note
If the field you are declaring is of type struct or actor, and you want to constrain one or more of its fields to a single value or range of values, you can pass the fields as parameters using the following syntax:
with(<field-name>: [<constraint-type>] <value>, ...)For example:
field1: some_struct with(x: default 1m, y:2):Is the same as:
field1: some_struct with: keep(default it.x == 1m) keep(it.y == 2) -
You can define fields in any structured type—structs, actors, scenarios or modifiers—to specify the attributes or characteristics of that type. During generation, before executing the test, Foretify selects a value from the range of legal values for each field. You can constrain the range of legal values for a field to suit the purposes of a particular test.
Variables are similar to fields, except that Foretify does not select a value for them during generation. Instead, you can assign a default value for a variable and then use a method to assign a value to it during the execution of a test. Foretify support three types of methods:
- Expression methods simply assign the value of any legal OSC2 expression to the variable.
- Native methods are written in OSC2 and can perform various actions such as manipulating list, certain mathematical calculations and so on.
- External methods can access more complex functions written in an external language such as C++.
For example field declarations, see Defining fields and variables, including:
- Scalar fields
- List fields
- String fields
- Fields in actor or struct
- Fields in scenario
- Fields with sampling
- Fields without explicit type
- Variable declaration with new
const fields
A const field holds a value that is set at compile time and does not change throughout the run.
Declaring a const field
The complete field declaration syntax is described in Fields and variables.
const fields are specified using the const keyword:
const my_const : int = 5
See complete example.
For scalar field types, the default value must be a constant. const fields of struct type must be initialized with the new keyword:
const my_const_struct : my_struct = new
See complete example.
Using a const field
const fields can be accessed like any other fields. Additionally, you can reference const fields in contexts that require a constant value that is known at compile time, for example, in the options of a cover declaration, as shown in the following example.
extend sut.vehicle_cut_in_and_slow:
# Declaring const fields
const speed_cover_range_kph := [20..70]
const speed_cover_every_kph := 10
# Using const fields
cover(sut_speed_at_end, expression: sut.car.state.speed, range: speed_cover_range_kph, every: speed_cover_every_kph, unit: kph)
cover(civ_speed_at_end, expression: cut_in_vehicle.state.speed, range: speed_cover_range_kph, every: speed_cover_every_kph, unit: kph)
See complete example.
Changing a const field’s value
You can change the value of a const field using a set declaration, which is processed at compile-time. The field’s value is determined by the last set declaration, according to load order.
See the full syntax of the set declaration.
extend sut.vehicle_cut_in_and_slow:
set speed_cover_range_kph = [10..80]
See complete example.
You cannot change the field’s value at runtime, for example, by setting the value using ‘on’.
extend sut.vehicle_cut_in_and_slow:
on @vehicle_cut_in_at_essence.start:
speed_cover_range_kph = [10..80] # error
This example results in the following error:
[ERROR] Assignment to a const field 'speed_cover_range_kph' in procedural code
You can change the value of a sub-object’s const field, as long as all the steps in the path are const fields.
| OSC2 code: const fields in sub-objects | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 | |
In this example, line 11 causes the following error because general_var is a var and not a const:
[ERROR] Cannot set const field 'threshold' from non-const context 'general_var.specific.threshold'
Usage of const in inheritance
You can declare a const field in a type and change its value in a subtype. This prevents it from being used in a constant context in any type other than the subtype, its offspring, and types that contradict the subtype.
# Declare a const field in a scenario
scenario top.base_scenario:
flag1: bool
flag2: bool
const speed_range_kph := [20..70]
# Set the field's value in a sub-scenario
scenario top.child_scenario inherits base_scenario(flag1 == true):
flag3: bool
set speed_range_kph = [10..80]
cover(c2, expression: sut.car.state.speed, range: speed_range_kph, unit: kph) #ok
# Reference the field in a sub-scenario of the sub-scenario
scenario top.grandchild_scenario inherits child_scenario(flag3 == true):
cover(c3, expression: sut.car.state.speed, range: speed_range_kph, unit: kph) # usage in a subtype is also ok
# Reference the field in a scenario that contradicts the sub-scenario
scenario top.mutex_child inherits base_scenario(flag1 == false):
cover(c4, expression: sut.car.state.speed, range: speed_range_kph, unit: kph) # speed_range_kph will resolve to [20..70]
# Cannot reference the field in the base scenario, because the value cannot be determined at compile time
extend top.base_scenario:
cover(c1, expression: sut.car.state.speed, range: speed_range_kph, unit: kph) # error
# Cannot reference the field in a different sub-scenario that does not contradict child_scenario, because the value cannot be determined at compile time
scenario top.non_contradictory_child inherits base_scenario(flag2 == true):
cover(c5, expression: sut.car.state.speed, range: speed_range_kph, unit: kph) # error
See complete example.
Constraints
Define a generation constraint
Struct, actor, or scenario member
Note
keep() is also allowed in the with block of field declarations and scenario invocations.
keep([<constraint-strength>] <constraint-exp> )
<constraint-strength>- (Optional) Is either soft or default. default is allowed only in scenario fields. If neither soft nor default is specified, the constraint type is hard. See below for a description of these constraint types.
<constraint-exp>-
(Required) Is an expression that includes at least one Boolean operator and one generatable item. It returns either true or false when evaluated at runtime.
Any expression that returns a Boolean value can be used in a constraint, including methods and the following operators:
The part of the planning process that creates data structures and assigns values to fields is called generation. This process follows the specifications you provide in type declarations, in field declarations, and in keep statements. Those specifications are called constraints.
The degree to which generated values for a field are random depends on the constraints that are specified. A field’s values can be either:
-
Fully random — without explicit constraints, for example:
OSC2 code: fully randomextend top.main: tolerance: intSee complete example.
-
Fully directed — with constraints that specify a single value, for example:
OSC2 code: fully directedkeep(my_speed == 50kph) # my_speed is set to 50 kphSee complete example.
-
Constrained random — with constraints that specify a range of possible values, for example:
OSC2 code: constrained randomSee complete example.keep(my_speed in [30..80]kph) # my_speed is restricted to a range
For an explanation of various constraint types with examples, see Defining constraints, including:
- Simple Boolean constraints
- Compound Boolean constraints
- List constraints
- Relative strength of constraints
- Soft constraints and default constraints
- Remove default constraints
- Soft constraints and distribution methods
for <item> in <list>
Constrain or modify an item in a list
Struct, actor, or scenario member
Note
for is also allowed in the with block of field declarations and scenario invocations.
for <item-name> in <list>:
<member>+
<item-name>- (Required) Is an identifier.
<list-path>- (Required) Is a path expression that points to a list field. The path cannot contain method calls.
<member>- (Required) Is a list of one or more keep() constraints or scenario modifiers, listed on separate lines as a block.
Use this construct to constrain or modify items in a list.
scenario top.my_scenario:
list1: list of int with:
for num in it: # in a field declaration
keep (num > 10)
list2: list of int
for num in list2: # as a scenario member
keep(num > 20)
extend top.main:
do my_scenario() with:
for num in it.list2: # in a scenario invocation
keep (num < 50)
See complete example.
set <field> = <value>
Assign a value to a field
set <field> = <value>
<field>- (Required) Is the pathname to a field.
<value>- (Required) Is an appropriate value for the field.
Multiple set definitions are allowed to the same field, and the last loaded set assignment wins.
The value of some_field is 23 when only file1.osc is loaded, and 45 when file2.osc (and the imported file1.osc) are loaded.
# file1.osc
extend top:
var some_field: int
set some_field = 23
# file2.osc
import "file1.osc"
extend top:
set some_field = 45