Skip to content

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

Purpose

Signify a point in time.

Category

Struct, actor, or scenario member

Syntax

event <event-name> [(<param>+) | is <qualified-event> ]

Syntax parameters

<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.

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

Purpose

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.

Syntax

<event-condition>

or:

@<event-path> [as <param-name>] [if <event-condition>]

Parameters

<event-condition>

(Optional) Is a Boolean expression. The expression may contain temporal operators.

<event-path>

(Required) Is the pathname of a defined event.

Description

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.

Examples

OSC2 code: examples of qualified events
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

Purpose

Declare a field to contain data

Category

Struct, actor, or scenario member

Syntax

[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.

Syntax parameters

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:

with:
    <member>+
or
with: <member1> [; <member2>;...]
or
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)

Description

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:

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:

OSC2 code: declaring a const field
    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:

OSC2 code: initialize const structs with new
    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.

OSC2 code: declaring a const field and using it in a cover declaration
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.

OSC2 code: changing value of const field using set
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’.

OSC2 code: cannot change a const field's value at runtime
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
struct specific_config:
  const threshold: int = 10

struct general_config:
  const specific: specific_config = new

extend top.main:
  const general : general_config = new
  set general.specific.threshold = 20 #ok
  var general_var : general_config = new
  set general_var.specific.threshold = 20 # error

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.

OSC2 code: usage of const in inheritance
# 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

Purpose

Define a generation constraint

Category

Struct, actor, or scenario member

Note

keep() is also allowed in the with block of field declarations and scenario invocations.

Syntax

keep([<constraint-strength>] <constraint-exp> )

Syntax parameters

<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:

Description

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 random
    extend top.main:
    tolerance: int
    

    See complete example.

  • Fully directed — with constraints that specify a single value, for example:

    OSC2 code: fully directed
        keep(my_speed == 50kph) # my_speed is set to 50 kph
    

    See complete example.

  • Constrained random — with constraints that specify a range of possible values, for example:

    OSC2 code: constrained random
        keep(my_speed in [30..80]kph) # my_speed is restricted to a range
    
    See complete example.

For an explanation of various constraint types with examples, see Defining constraints, including:

for <item> in <list>

Purpose

Constrain or modify an item in a list

Category

Struct, actor, or scenario member

Note

for is also allowed in the with block of field declarations and scenario invocations.

Syntax

for <item-name> in <list>:
  <member>+

Syntax parameters

<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.

Description

Use this construct to constrain or modify items in a list.

Example

OSC2 code: for item in list example
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>

Purpose

Assign a value to a field

Syntax

set <field> = <value>

Syntax parameters

<field>
(Required) Is the pathname to a field.
<value>
(Required) Is an appropriate value for the field.

Description

Multiple set definitions are allowed to the same field, and the last loaded set assignment wins.

Example

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.

OSC2 code: set field example 1
# file1.osc

extend top:
    var some_field: int
    set some_field = 23
OSC2 code: set field example 2
# file2.osc

import "file1.osc"
extend top:
   set some_field = 45