Skip to content

Type declarations

actor

Purpose

Declare an active object with activities (behaviors)

Category

Type declaration

Syntax

actor <actor-name>[inherits <base-actor-type>[(<condition>)]] [:
    <member>+]

Syntax parameters

<actor-name>
(Required) Must be different from any other defined actor, type, or struct name because the namespace for these constructs is global.
<base-actor-type>
(Optional) The name of a previously defined actor type. The new type inherits all members of the previously defined type as well as its associated scenarios.
<condition>
(Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you add members to an actor only if the field has the value specified.
<member>+

(Optional) Is a list of one or more of the following:

  • Constraint declaration
  • Coverage declaration
  • Event declaration
  • External method declaration
  • Field declaration
  • Scenario declaration

Each member must be on a separate line and indented consistently from actor. See the Example scenario declaration in the examples below for a detailed explanation of how to declare a scenario inside an actor.

Description

Like structs, actors are compound data structures that you create to store various types of related data. In contrast to structs, actors also are associated with scenarios. Thus, an actor is a collection of both related data and declared activity.

You can use the following extension mechanisms with actors:

  • extend
  • Unconditional inheritance
  • Conditional inheritance

Note

See Type inheritance to learn about inheritance mechanisms.

Example actor declaration

OSC2 code: actor declaration
enum veh_category: [car, truck, motorcycle]
enum track_kind: [single_track, multi_track]

actor my_vehicle:
  category: veh_category
  emergency_vehicle: bool
  track_kind: track_kind

See complete example

Example unconditional inheritance

OSC2 code: unconditional inheritance
enum rv_category: [motorhome, campervan, caravan, fifth_wheel_trailer, popup_camper, truck_camper]

actor recreational_vehicle:
    category: rv_category
    has_kitchen: bool

actor truck_camper inherits recreational_vehicle:
    keep(category==truck_camper)
    keep(has_kitchen==false)

See complete example

Example conditional inheritance

OSC2 code: conditional inheritance
actor my_truck inherits my_vehicle(category == truck):
    num_of_trailers: uint with: keep(it in [0..2])

See complete example

Example scenario declaration

You can declare a scenario inside an actor. The following declarations are equivalent:

OSC2 code: scenario declaration example 1
# Declaration 1
actor x:
  b: bool

  scenario y:
    do top.something()

See complete example

OSC2 code: scenario declaration example 2
# Declaration 2
actor x:
  b: bool

scenario x.y:
  do top.something()

See complete example

You can also extend a scenario of a parent actor:

OSC2 code: extend a scenario of a parent actor
actor x:
  b: bool
  scenario y:
    do something: top.something(speed: default 20kph)

actor z inherits x (b == true):
  extend y:
    keep(something.speed == 30kph)

See complete example

However, you cannot declare a scenario inside an extend declaration:

actor x
  b: bool

extend x:
  scenario y: # Not allowed
    do something()

action

Purpose

Define a basic activity of an actor

Category

Type declaration

Syntax

action <name> [inherits <base-action-type>(<condition>)] [:
    <member>+]

Syntax parameters

<name>
(Required) Is in the form <actor-name>.<action-name>. The action name must be unique among that actor’s actions, but it can be the same as the action of a different actor. Parentheses are not allowed in action declarations.
<base-action-type>
(Optional) Is the name of an action previously defined with <actor-name>. The new type inherits the behavior of the previously defined type.
<condition>

(Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you modify the behavior of the action only if the field has the value specified.

Note

Unconditional inheritance is not allowed for actions.

<member>+

(Optional) Is a list of one or more of the following:

  • Constraint declaration
  • Coverage declaration
  • Event declaration
  • External method declaration
  • Field declaration
  • Modifier invocation
  • Behavior specification, modification or monitoring constructs

Notes

  • A do member that describes the behavior of the action is required.
  • Each member must be on a separate line and indented consistently from action.
  • Every action has a predefined duration field of type time.

Description

Actions are similar to scenarios in that they can have the same members, including optional behavior specifications.

They differ from scenarios in that they should be treated as atomic behavior descriptions from the outside: Their internal structure, if any, is to be considered opaque from the point of view of the user.

Example

OSC2 code: action/scenario difference
actor flying_object inherits physical_object:
    weight: mass
    keep(weight < 10kg)

# action flying_object.fly:
scenario flying_object.fly:
    pos: global_position

See complete example.

enum

Purpose

Define a scalar type with named values

Category

Type declaration

Syntax

enum <type-name> : [<member>*]

Syntax parameters

<type-name>
(Required) Must be different from any other defined actor, struct or type name because the namespace for these constructs is global.
<member>*

(Optional) Is a comma-separated list, enclosed in square brackets and placed on one or more lines, of zero or more enumerations in the form:

<enum-name> [=<exp>]

Each <enum-name> must be unique within the type.

<exp> evaluates to a constant value. If no <exp> is specified, the first name in the list is assigned a value of 0, the next 1 and so on.

Description

You can declare a type without defining any values using the following syntax:

OSC2 code: empty enum declaration
enum <type-name> : []

Enumerated types can be extended. You cannot use inheritance, either unconditional or conditional, with enumerated types.

If multiple enumeration types use the same literal, then the literal is overloaded — which literal is evaluated to depends on the type requirements of the context it is used in. If this ambiguity cannot be resolved uniquely, an error is issued.

In order to avoid the potential for such errors, or to be specific which enumeration type is needed, you can either use the casting operator or reference a literal value prefixed with the enumeration type and a separating ! character. See examples 2 and 3 below.

Example 1

In this example, the enum type declarations define the types of vehicles in an OSC2 environment and the driving style.

OSC2 code: enum declaration
enum car_type: [sedan = 1, truck = 2, bus = 3]

# by default, aggressive = 0, normal = 1
enum my_driving_style: [aggressive, normal, timid]

See complete example.

Example 2

Here is an example of how to resolve ambiguity using the type casting operator as(). Is a the field a or the enum value a? The compiler picks the field.

OSC2 code: ambiguous constraint with enum
enum my_enum: [a, b]

actor my_actor:
    a: int
    x: my_enum with:
        keep(it == a)

See complete example.

If the intention is to specify the enumerated type value, use the type casting as() operator. This forces the compiler to treat it an an enum value:

OSC2 code: resolving ambiguity with 'as()'
enum my_enum: [a, b]

actor my_actor:
    a: int
    x: my_enum with:
        keep(it == self.a.as(my_enum))

See complete example.

Example 3

Here are examples of using explicit literals in fields and in expressions.

Extending rgb_color by adding black, overloads black with cmyk_color!black.

OSC2 code: explicit enum literals
extend rgb_color: [black]

extend top.main:
  # Example enumeration fields
  my_cmyk_color: cmyk_color = black          # Resolved to cmyk_color!black
  my_new_rgb_color: rgb_color = black        # Resolved to rgb_color!black

  # Resolution in expressions
  # field1: bool = (black == black)            # Could be either type -> error issued
  field2: bool = (rgb_color!black == rgb_color!black)  # True

See complete example.

extend

Purpose

Add to an existing type or subtype of an enumerated or structured type

Category

Type declaration

Syntax

extend <type-name> :
    <member>+
Syntax parameters

<type-name>
(Required) Is the name of a previously defined enumerated type, struct, actor, scenario or modifier.
<member>+

(Required) Is a list of one or more new members of the type.

For enumerated types, the list is comma-separated, enclosed in square brackets, and can be placed on one line.

For compound types, each member must be placed on a separate line and indented consistently from extend.

Description

At compile time, extend modifies the type and thus all instances of the type for the test in which it is included. extend allows you to encapsulate attributes or behaviors as an aspect of an object. It also allows you to modify built-in actors, structs and scenarios.

Example extend enumerated type

Because two values are already defined for the driving_style type (aggressive=0, normal=1), erratic has the value of 2, unless explicitly assigned a different value.

OSC2 code: extend enum
extend driving_style: [erratic]

See complete example.

Example extend struct

This example extends the struct named storm_data with a field for wind velocity. This extension applies to all instances of storm_data.

OSC2 code: extend struct
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

struct storm_data:
    snow_depth: length

extend storm_data:
    var wind_velocity: speed

extend test_config:
    set map = "$FTX_PACKAGES/maps/hooder.xodr"

extend top.main:
    do sut.car.drive(duration: 5s)

Example extend scenario: replace previous behavior

A scenario can have one and only one do block. However, additional do blocks can be defined in scenario extensions. In that case, the do block in the last loaded extension takes precedence over previously loaded ones and is executed.

In this example, the previously defined behavior of scenario3 is replaced by an invocation of scenario2.

OSC2 code: extend scenario
scenario top.scenario1:
  flag: bool

  do log("Scenario 1 is executing")

scenario top.scenario2:

  do log("Scenario 2 is executing")

scenario top.scenario3:

  do log("Scenario 3 is executing")

extend top.scenario3:

  do scenario2()

extend top.main:

  do serial:
    scenario1()
    scenario3()

Here are the results of executing this example:

Foretify output: extended scenario
Foretify> run
Checking license...

Running the test ...
Loading configuration ... done.
[0.000] [MAIN] Executing plan for top.all@1 (top__all)
[0.000] [MAIN] Scenario 1 is executing
[0.000] [MAIN] Scenario 2 is executing
[0.020] [MAIN] Run finished
[0.020] [MAIN] Ending the run
[0.020] [SIMULATOR] stop_run()
Run completed

See complete example.

Example extend scenario: extend previous behavior

In this example, the previous behavior of scenario3 is executed first, using previous_do(). Then new behavior is added. You can of course define new behavior first and then call previous_do() or you can use parallel to execute new behavior in parallel with the previous behavior.

OSC2 code: extend previous behavior
scenario top.scenario1:
  flag: bool

  do log("Scenario 1 is executing")

extend top.scenario1:

  do serial:
    previous_do()
    log("Extending scenario 1 behavior")

extend top.main:

  do serial:
    scenario1()

Here are the results of executing this example:

Foretify output: extended scenario
Foretify> run
Checking license...

Running the test ...
Loading configuration ... done.
[0.000] [MAIN] Executing plan for top.all@1 (top__all)
[0.000] [MAIN] Scenario 1 is executing
[0.000] [MAIN] Extending scenario 1 behavior
[0.020] [MAIN] Run finished
[0.020] [MAIN] Ending the run
[0.020] [SIMULATOR] stop_run()
Run completed

See complete example.

Example extend modifier

In the following example, the modifier sut.curving_road() is extended to constrain the curve to be to the right. Below is the original modifier declaration:

OSC2 code: modifier
modifier sut.curving_road:
    gr: road_curvature with:
        keep(it.min_radius == 6meter)
        keep(it.max_radius == 11meter)

extend sut.curving_road:
    keep(gr.direction == right_curve)

See complete example.

global parameter

Purpose

Declare a parameter that is accessible anywhere

Category

Declaration

Syntax

global <field-name> : <type> [<with-block>]

Syntax parameters

<field-name>
(Required) Is a unique name in the global scope.

Description

A global parameter can be accessed within any scenario or actor declaration in the same file, imported files, or importing files. This is in contrast to parameter declarations contained within structured type declarations that are only accessible within these structured type declarations.

Any local fields defined within structured type declarations with the same name as a global parameter shadow the global declaration within that structured type declaration. In other words, within that structured type declaration any references to that field name reference the local field, not the global parameter.

Example 1

This example shows that setting a default value for a global parameter is not yet supported. However, you can constrain the parameter with a hard or soft constraint when you declare it.

You cannot constrain a global parameter from a dynamic object. The value of a global parameter is set during generation and cannot be changed during the run.

OSC2 code: constrain global parameter
# global count2: uint = 0                   # default constraint not supported yet
global count: uint with: keep(soft it == 0) # soft constraint allowed

struct keep_count:
#    keep(soft count== 1) # constraining global parameter from dynamic struct not allowed

See complete example.

Example 2

The following example shows that when a field is defined locally with the same name as a global parameter, a constraint on an entity with that name refers to the local field, not the global parameter.

OSC2 code: constraint on local/global fields
global count: uint with: keep(it == 0)

extend top.main:
    count: uint
    keep(count == 2)

See complete example.

modifier

Purpose

Declare a modifier for scenarios

Category

Declaration

Syntax

[global] modifier <name> [of <scenario-name>] [:
    <member>+]

Syntax parameters

global
(Optional) Causes an instance of the modifier to be created for every instance of the actor, and persist throughout the test. Note that global cannot be used with of <scenario>.
<name>
(Required) Is in the form [<actor-name>].<modifier-name>. The <modifier-name> must be unique among that actor's scenarios and scenario modifiers, but it can be the same as the scenario or scenario modifier of a different actor. To make the modifier visible globally, use top as the actor name or use the global parameter. If <actor_name> is not provided, top is used.
<scenario-name>
(Optional) Is in the form <scenario-name>. Specifying the actor name is not allowed because the scenario must be associated with the same actor as the modifier. If you specify the name of a scenario, the modifier can be invoked only by that scenario. It cannot be invoked by other scenarios, even those associated with the same actor. Note that of <scenario> cannot be used with the global parameter.
<member>+

(Optional) Is a list of one or more of the following:

  • Constraint declaration
  • Coverage declaration
  • Event declaration
  • External method declaration
  • Field declaration
  • Modifier invocation
  • on directive

Notes

  • Scenario modifiers cannot include scenario invocations or do.
  • Each member must be on a separate line and indented consistently from modifier.

Description

Scenario modifiers constrain or modify the behavior of a scenario; they do not define the primary behavior of a scenario. Modifiers can be extended.

For global modifiers, @start is emitted at the start of the test and @end is emitted at the end of the test, regardless of whether the test succeeded or failed.

You can use the following extension mechanisms with modifiers:

  • extend
  • Conditional inheritance
  • Unconditional inheritance
  • The in modifier

When you create a modifier using the optional global parameter, an instance of the global modifier is created for each instance of the actor. Global modifiers allow composition and encapsulation of modifiers, ensuring that their behavior occurs for each instance.

As an example, assume you have a regular (non-global) modifier that checks that no objects or actors are too close to the actor. Suppose you have another regular modifier that collects coverage of items in the vicinity of that specific actor. You can then write a global modifier that instantiates both modifiers and you get both behaviors for each such actor.

When you create a global modifier for an actor, an implicit field is added to the actor: : . The actor parameter can be used in expressions inside the modifier to access its actor, just like regular modifiers.

Global modifiers have an enabled field. If enabled is constrained to false, all event-based constructs in the modifier are disabled, for example, cover and sample statements. Global modifiers cannot be invoked. They cannot inherit from other modifiers, and cannot be inherited from.

Example 1

This example declares a modifier that can be invoked only by the vehicle.to_side_on_curve() scenario.

OSC2 code: modifier declaration for a scenario
modifier vehicle.object_in_road of to_side_on_curve:
    tc: traffic_cone

See complete example.

Example 2

This example declares a modifier that defines a curving road of the sut actor.

OSC2 code: modifier declaration for an actor
modifier sut.curving_road:
    gr: road_curvature 
    keep(gr.min_radius == 6meter)
    keep(gr.max_radius == 11meter)

See complete example.

Example 3

This example declares a global modifier for the vehicle actor and shows how to use actor to reference the actor instance. Then, under top.main, it shows how to disable the modifier for a specific actor instance.

OSC2 code: global modifier for the vehicle actor
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

extend issue_kind: [high_speed_at_end]

# Declaring a global modifier
global modifier vehicle.end_speed_info:
    # Use 'actor' to reference the actor instance
    cover(name: speed_at_end, expression: actor.state.speed, event: end, unit: kph)

    # Simple check
    on @end if actor.state.speed > 50kph:
        other_error(high_speed_at_end, "speed too high at end")

# Disabling a global modifier for a specific actor:
extend top.main:
    vehicle_1: vehicle
    vehicle_2: vehicle

    # Access the modifier field under the actor's end_speed_info
    keep(vehicle_2.end_speed_info.enabled == false)

    do parallel:
        sut.car.drive() with: speed([30..60]kph)
        vehicle_1.drive() with: speed([55..70]kph)
        vehicle_2.drive() with: speed([25..50]kph)
    with:
        duration([3..7]s)

scenario

Purpose

Declare an ordered set of behaviors by an actor

Category

Type declaration

Syntax

scenario <name>[inherits <base-scenario-type>(<condition>)] [:
    <member>+]

Syntax parameters

<name>
(Required) Is in the form [<actor-name>.]<scenario-name>. The scenario name must be unique among that actor’s scenarios, but it can be the same as the scenario of a different actor. Parentheses are not allowed in scenario declarations.
<base-scenario-type>
(Optional) Is the name of a scenario previously defined with <actor-name>. The new type inherits the behavior of the previously defined type.
<condition>

(Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you modify the behavior of the scenario only if the field has the value specified.

Note

Unconditional inheritance is not allowed for scenarios.

<member>+

Is a list of one or more of the following:

  • Constraint declaration
  • Coverage declaration
  • Event declaration
  • External method declaration
  • Field declaration
  • Modifier invocation
  • Behavior specification, modification or monitoring constructs

Notes

  • A do member that describes the behavior of the scenario is required.
  • Each member must be on a separate line and indented consistently from scenario.
  • Every scenario has a predefined duration field of type time.

Description

To define the behavior of a scenario, you must use the do member to invoke an operator scenario, such as serial, a library scenario, such as vehicle.drive(), or a user-defined scenario.

The events @start, @end, and @fail are defined for every scenario. They are emitted whenever a scenario instance starts, ends, or fails, respectively. In scenarios that invoke other scenarios, each scenario may emit its own @start, @end and @fail event.

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.

Note that coverage or other metrics are impacted if they are emitted on these events. In fact any logic that is defined on these events will not take place if the events are not emitted.

You can control the behavior of a scenario and collect data about its execution by declaring data fields and other members in the scenario itself, in its related actor or structs, or in the test file. For example, the vehicle actor has a field of type speed that allows you to use the speed() scenario modifier to control the speed of a car.

When you declare or extend a scenario, you must associate it with a specific actor by prefixing the scenario name with the actor name in the form actor-instance-name.scenario-name.

Note

Invoking the generic form of the scenario (actor.scenario) is not allowed.

You can use the following extension mechanisms with scenarios:

  • extend
  • Conditional inheritance
  • the in modifier

Example scenario declaration

OSC2 code: scenario declaration
# A two-phase scenario
scenario traffic.two_phases:   # Scenario name
    # Define the truck with specific attributes
    truck1: vehicle with:
        keep(it.color == green)
        keep(it.vehicle_category == box_truck)

    # Define the behavior
    do serial:
        phase1: truck1.drive() with:
            speed(speed: 0kph, at: start)
            speed(speed: 10kph, at: end)
        phase2: truck1.drive() with:
            speed(speed: [10..15]kph)

See complete example.

Example scenario inheritance

OSC2 code: scenario inheritance
enum my_vehicle_category: [car, truck, motorcycle]
enum my_emergency_vehicle_kind: [fire, police, ambulance]
enum light_kind: [parking, low_beam, high_beam, emergency]

actor my_vehicle:
    category: my_vehicle_category
    emergency_vehicle: bool

See complete example. See Example scenarios for more examples of how to create and reuse scenarios.

struct

Purpose

Define a compound data structure

Category

Type declaration

Syntax

struct <type-name>[inherits <base-struct-type>[(<condition>)]] [:
    <member>+]
Syntax parameters

<type-name>
(Required) Must be different from any other defined type, struct or actor name because the namespace for these constructs is global.
<base-struct-type>
(Optional) Is the name of a previously defined struct. The new type inherits all members of the previously defined type.
<condition>
(Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you add members to a struct only if the field has the value specified.
<member>+

(Optional) Is a list of one or more of the following:

  • Constraint declaration
  • Coverage declaration
  • Event declaration
  • External method declaration
  • Field declaration

Each member must be on a separate line and indented consistently from struct.

Description

Structs are compound data structures that you can create to store related data of various types. For example, the AV library has a struct called car_collision that stores data about the vehicles involved in a collision.

You can use the following extension mechanisms with actors:

  • extend
  • Unconditional inheritance
  • Conditional inheritance

Example struct declaration

This example defines a struct named my_car_status with variables called time and current_speed.

OSC2 code: struct declaration
struct my_car_status:
    var time: time
    var current_speed: speed

See complete example.

Example struct unconditional inheritance

This example creates a new struct type my_collision_data from the base type collision_data and adds a field to store the type of the other car involved in the collision.

OSC2 code: struct unconditional inheritance
struct my_collision_data inherits collision_data:
    var other_vehicle_category: vehicle_category

See complete example.

Example struct conditional inheritance

This example creates a new struct type snow_storm_data from the base type storm_data and adds a field for snow_depth.

OSC2 code: struct conditional inheritance
enum storm_type: [rain, ice, snow]

struct storm_data:
    storm: storm_type
    wind: speed

struct snow_storm_data inherits storm_data(storm == snow):
    snow_depth: length

See complete example.

type

Purpose

Define a physical type

Category

Type declaration

Syntax

type <name> 'is' <SI-base-exponent-list>

Syntax parameters

<name>
(Required) The name of the new type you are declaring.
<SI-base-exponent-list>

(Required) Is in the form:

SI(<SI-unit-name> : <int> [, <SI-unit-name> : <int>]*)
<SI-unit-name>

(Required) Is an SI base unit or derived unit name. The base unit names are:

  • Length - meter (m)
  • Time - second (s)
  • Amount of substance - mole (mole)
  • Electric current - ampere (A)
  • Temperature - kelvin (K)
  • Luminous intensity - candela (cd)
  • Mass - kilogram (kg)

lm and deg are examples of a derived unit name.

<int>
(Required) A literal of type integer.

Description

Physical types represent physical quantities. A physical type must define the following:

  • The basis for representing a physical quantity such as time.
  • A base unit that is used to measure that quantity.

The base unit must be defined with a unit type declaration.

See Numeric literals for a list of the predefined types and their units.

Example

OSC2 code: Foretify physical types
type length is SI(m: 1)
type time is SI(s: 1)
type speed is SI(m: 1, s: -1)
type acceleration is SI(m: 1, s: -2)
type jerk is SI(m: 1, s: -3)
type angle is SI(deg: 1)
type angular_rate is SI(deg: 1, s: -1)
type angular_acceleration is SI(deg: 1, s: -2)
type mass is SI(kg: 1)
type temperature is SI(K: 1)
type pressure is SI(kg: 1, m: -1, s: -2)
type luminous_flux is SI(lm: 1)
type illuminance is SI(lm: 1, m: -2)

unit

Purpose

Define a unit for a physical type

Category

Type declaration

Syntax

unit <unit-name> is <physical-type>(factor: <float> [, offset: <float>])
Syntax parameters

<unit-name>
(Required) The name of the new unit you are declaring.
<physical-type>
(Required) The name of a defined physical type, either an SI base type or a derived type.
<float>
(Required) A floating point value.

Description

Unit names comprise a separate global namespace. All unit names must be unique within that namespace, although the definitions can be shared: m and meter share the same definition, for example.

See Numeric literals for a list of the predefined types and their units.

Example

OSC2 code: Foretify units per physical type
type speed is SI(m: 1, s: -1)

unit meter_per_second    is speed(factor: 1.0)
unit mps                 is speed(factor: 1.0)
unit kilometer_per_hour  is speed(factor: 0.277777778)
unit kmph                is speed(factor: 0.277777778)
unit kph                 is speed(factor: 0.277777778)
unit mile_per_hour       is speed(factor: 0.447038889)
unit mph                 is speed(factor: 0.447038889)
unit miph                is speed(factor: 0.447038889)

type temperature is SI(K: 1)

unit K          is temperature(factor: 1.0)
unit kelvin     is temperature(factor: 1.0)
unit celsius    is temperature(factor: 1.0, offset: 273.15)
unit c          is temperature(factor: 1.0, offset: 273.15)
unit fahrenheit is temperature(factor: 0.555555556, offset: 255.372222222)
unit f          is temperature(factor: 0.555555556, offset: 255.372222222)

Tip

You can configure how units are displayed. See Display unit options for details.

watcher type

Purpose

A watcher type is a type of an object that has a time life span and emits intervals. An interval is data that captures some behavior over a period of time. Intervals can be viewed in Foretify and Foretify Manager. See Value and interval traces for details.

The watcher can hold any struct member that a modifier can hold (including modifier invocations) and has a specific API for creating and handling intervals.

Category

Watcher

Syntax

watcher modifier <watcher-name>[(<watcher-data>)] [: <member>+]

Syntax parameters

<watcher-name>

(Required) The name of this watcher type.

<watcher-data>

(Optional) The type of the data that an instance of this watcher emits. The data type has to be derived from any_watcher_data type. The watcher will have a field called data of the declared <watcher-data> type.

The watcher will emit instances of that type (see start_interval and end_interval in the Watcher API section).

<member>+

(Optional) A sequence of modifier members.

Description

The watcher can hold any struct member that a modifier can hold (including modifier invocations) and has a specific API for creating, handling, and emitting intervals (of type <watcher-data>).

For more details, see Defining watchers and checkers.

The watcher API

Watchers provide the following API:

i_start

An event that is emitted when a new interval starts.

i_end

An event that is emitted when an interval ends.

i_clock

An event that is emitted every system cycle between i_start and i_end.

start_interval(data: <watcher-data> = null)

This is a predefined method that initializes a new interval with a watcher data struct. It assigns data to the watcher’s data field and emits the event i_start of the watcher. If data is not provided, an empty instance of will be created. The value for the data parameter should be created using the new_data() method. For more information see, Example 3.

new_data()

This returns a new instance of the watcher’s data struct with the type watcher-data.

end_interval()

Predefined method that ends the current interval. The method performs the following actions:

  • Emits the event i_end.

  • Samples the interval data coverage.

  • Emits the event top.any_interval_ended.

  • Nullifies the data field.

Example 1: watcher type for exceeded speed limit

The below example defines a watcher type that creates intervals when the speed of a vehicle exceeds the speed limit.

OSC2 code: watcher type for exceeded speed limit
# define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit
watcher modifier speed_above:
    v: vehicle
    limit: speed
    on @top.w_clk:
        if(data == null):
            if(v.state.speed > limit):
                start_interval()
        else:
            if(v.state.speed <= limit):
                end_interval()

See complete example.

Example 2: watcher type for maximal speed

The below example defines a watcher type that creates intervals that hold the maximal speed when the speed of a vehicle exceeds the speed limit.

OSC2 code: watcher type for maximal speed
# Define watcher data that records a speed
struct speed_watcher_data inherits any_watcher_data:
    var speed: speed
    record(speed, unit: kph)

# Define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit,
# and records the maximal exceeding speed
watcher modifier speed_above(speed_watcher_data):
    v: vehicle
    limit: speed

    on @top.clk:
        if(data == null):
            if(v.state.speed > limit):
                start_interval()
        else:
            if(v.state.speed <= limit):
                end_interval()
    on @i_clock:
        if(data.speed < v.state.speed): data.speed = v.state.speed

See complete example.

Example 3: providing initial watcher data to start_interval()

The below example defines a watcher type that creates intervals when the vehicle exceeds the speed limit, and records the vehicle’s acceleration at the start of the interval.

OSC2 code: watcher type for initial watcher data
# Define watcher data that records acceleration
struct speed_watcher_data inherits any_watcher_data:
    var acceleration_when_exceeded: acceleration
    record(acceleration_when_exceeded, unit: mpsps)

# Define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit,
# and records the acceleration at the start of the interval
watcher modifier speed_above(speed_watcher_data):
    v: vehicle
    limit: speed

    on @top.clk:
        if(data == null):
            if(v.state.speed > limit):
                var d := new_data()
                d.acceleration_when_exceeded = v.state.road_acceleration.lon
                start_interval(d)
        else:
            if(v.state.speed <= limit):
                end_interval()

See complete example.

any_watcher_data

any_watcher_data is a predefined struct used for describing intervals. This is the base type of all intervals emitted by watchers and checkers. The watcher will have a field called data of the declared <watcher-data> type.

Attributes

start_time

start_time: time

Specifies the simulation time when the interval started.

end_time

end_time: time

Specifies the simulation time when the interval ended.

end_status

end_status: watcher_data_status

Specifies the status of the interval. Can have one of the following values:

  • normal - Normal end of the interval.
  • context_ended - The interval did not end, but its encapsulating scenario/modifier has ended.

issue

issue: issue_struct

Holds the checker issue when this data is used for a checker declaration.

Coverage

If you want to preserve interval data for later analysis, you can add a cover or record declaration for a specific field to the interval data struct. When using a cover or record inside an interval data struct, the following limitations apply:

  • The ‘expression’ may only be the name of a field in the interval data struct.
  • The data is always sampled at the end of the interval. The ‘event’ option is not applicable.

Example: watcher data type that records speed

The following example defines a watcher data type that records a given speed for each interval.

OSC2 code: watcher data type that records speed
# Define watcher data that records a speed
struct speed_watcher_data inherits any_watcher_data:
    speed: speed
    record(speed, unit: kph)

See complete example.

Compound types

list type

Purpose

Define an ordered collection of elements of the same type

Category

Type declaration

Syntax

list of <type>

Syntax parameters

<type>
(Required) The type of the elements in the list.

Description

You can specify a <list of type> in field or variable declarations, in expressions or in any construct where you can specify a <type>.

You can constrain the size of a list of generatable items and also apply constraints to its members.

You can access, modify and manipulate any list by defining a native method and using the list methods that are valid in that context.

Example 1

This example declares a list of three items of type speed and constrains the value of each item to a range.

OSC2 code: a list of speed
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

extend top.main:
    slow_vehicle: vehicle
    mid_vehicle: vehicle
    fast_vehicle: vehicle
    speeds: list of speed with:
        keep(speeds.size() == 3)
        keep(speeds[0] in [20..35]kph)
        keep(speeds[1] in [40..55]kph)
        keep(speeds[2] in [60..75]kph)

    do parallel:
       slow_vehicle.drive() with: speed(speeds[0])
       mid_vehicle.drive() with: speed(speeds[1])
       fast_vehicle.drive() with: speed(speeds[2])

Example 2

This example declares a generated list and uses a native method to sort the list and return the value of the first item in the list (the item with the lowest value).

OSC2 code: using a native method to modify a list
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

extend top.main:
    my_ints: list of int

    def get_lowest(ints: list of int) -> int is:
        return ints.sort(it)[0]

    do serial:
       sut.car.drive() with: duration([4..7]s)
       call logger.log_info("Lowest integer is $(get_lowest(my_ints))")

range type

Purpose

Define an ordered pair of numeric values that specify a range starting from the lower value and ending at the upper value

Category

Type declaration

Syntax

range of <type>

Syntax parameters

<type>
(Required) The name of the type of the lower and upper values. The type can be one of the following: int, uint, float and physical types.

Description

You can set fields of range types using literal ranges or single values of the corresponding type. You can use an equality constraint or the in operator to constrain the value of such a field.

Example 1

This example exhibits the different ways to constrain a range type field.

OSC2 code: constraining range type fields
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

struct ranges:
    int_range: range of int
    # constrain the range to a specific range
    keep(int_range == [5..10])

    speed_range: range of speed
    # constrain the range to be within a specific range
    keep(speed_range in [20..50]kph)

    length_range: range of length
    # constrain the range to a specific value
    keep(length_range == 50m)

extend top.main:
    ranges: ranges

    in sut_drive with: speed(ranges.speed_range)
    do  sut_drive: sut.car.drive() with: duration([3..7]s)