Skip to content

Step Definition Macros

Bill Moore edited this page Sep 20, 2024 · 15 revisions

Step Definition Macros

User step definition classes can and should use the Bathtub step definition macros. There are two types of macros: step definition interface implementation macros, and step parameter macros.

Step Definition Interface Implementation Macros

Step definition classes must directly or indirectly subclass uvm_sequence_base, and they must directly or indirectly implement interface class step_definition_interface. The step definition interface implementation macros automatically implement all the required methods from the interface class. They are:

  • `Given()
  • `When()
  • `Then()
  • `virtual_step_definition()

Put one of these macros immediately after the step definition class declaration. For example:

class user_step_definition extends uvm_pkg::uvm_sequence implements bathtub_pkg::step_definition_interface;
	`When("the step definition prints 'Hello, World!'")

	`uvm_object_utils(user_step_definition)

	function new (string name="user_step_definition");
		super.new(name);
	endfunction

	virtual task body();
		`uvm_info("WHEN", "Hello, World!", UVM_NONE)
	endtask
endclass

Choose `Given(), `When(), or `Then() for concrete step definition classes. Use `virtual_step_definition() for virtual--i.e., abstract--step definition base classes. You can also use `virtual_step_definition() for concrete classes that you do not want to store in the resource database or match with step text. A concrete class that subclasses a virtual base class should use one of the concrete macros, effectively overriding the virtual macro.

Here is an example of an inheritance hierarchy of different types of classes:

// Regular sequence base class; not a step definition
virtual class base_sequence extends uvm_sequence#();
    `uvm_object_utils(base_sequence)

    function new (string name="base_sequence");
        super.new(name);
    endfunction

    virtual task body();
        `uvm_info("BODY", "This is useful.", UVM_NONE)
    endtask
endclass

// Virtual step definition base class implements the step definition interface
virtual class virtual_base_step_definition extends base_vseq implements bathtub_pkg::step_definition_interface;
    `virtual_step_definition("Base class for step definitions")

    `uvm_object_utils(virtual_base_step_definition)

    function new (string name="virtual_base_step_definition");
        super.new(name);
    endfunction
endclass

// Concrete step definition class inherits from the superclasses
class concrete_step_definition extends virtual_base_step_definition;
    `Given("a concrete step definition that does something useful")

    `uvm_object_utils(concrete_step_definition)

    function new (string name="concrete_step_definition");
        super.new(name);
    endfunction
endclass

The macros' string argument becomes the step definition's expression. It is a static attribute of the step definition. When Bathtub runs the feature file steps, it looks for a step definition whose expression matches the Gherkin step text. The expression could be a POSIX regular expression surrounded by slashes ("/"), or a SystemVerilog format string with conversion specifiers like %d and %s. If the expression has neither regular expression syntax nor SystemVerilog conversion specifiers, then the expression must match the step text exactly. The step keyword "Given," "When," "Then", "And," "But," or "*" is not part of the expression matching. For purposes of matching, tt doesn't matter which keyword is used in the step definition, or which keyword is used in the Gherkin file; the keyword is ignored.

The conversion specifiers in the expression are a subset of those accepted by the SystemVerilog $sscanf() function format string parameter.

Conversion Specifiers Description
%b, %o, %d, %h, %x Integer numbers in a variety of bases.
%f, %e, %g Real numbers.
%s, %c Strings and single characters, respectively.

Strings and single characters cannot contain whitespace because $sscanf() treats its input strings as whitespace-delimited tokens.

Step Parameter Macros

Step definitions can be parameterized so that a single definition can match step text strings with a variety of values. Parameters are defined by SystemVerilog conversion specifiers in the expression. Step definitions can and should use the step parameter macros to extract parameter values from the Gherkin step text.

For the examples in the reference section below, assume a Gherkin feature file that has the following steps:

Then the device 1 SLOW clock should be running at 32.768e3 Hz
And the device 2 FAST clock should be running at 11.2896e6 Hz

These steps clearly should be parameterized with an integer for the device number, a string for the clock name, and a real for the frequency. To that end, assume the user step definition has the following expression:

`Then("the device %d %s clock should be running at %f Hz")

User step definitions receive their runtime attributes, including step text, after they are created and before they start running. Therefore, do not use the step parameter macros in the constructor; that is too soon. The step definition sequence is executed by a call to uvm_sequence_base::start(), so use these parameter macros in the sequence's pre_start() or body() tasks. (pre_body() also works, but only if the bathtub object's call_pre_post variable is set to 1.)

Declare variables of suitable types to hold the argument values. Here is a complete step definition that extracts the argument values in pre_start(), and uses them in body().

class check_device_clock_frequency extends base_step_definition;
    `Then("the device %d %s clock should be running at %f Hz")

    // Local variables for the parameters extracted from the step text
    protected int device_number;
    protected string clock_name;
    protected real clock_frequency;

    `uvm_object_utils_begin(check_device_clock_frequency)
    // Optional field macros for the variables
    `uvm_field_int(device_number, UVM_ALL_ON)
    `uvm_field_string(clock_name, UVM_ALL_ON)
    `uvm_field_real(clock_frequency, UVM_ALL_ON)
    `uvm_object_utils_end

    function new (string name="check_device_clock_frequency");
        super.new(name);
    endfunction
    
    virtual task pre_start();
        // Extract the arguments in the order they appear in the expression
        `step_parameter_get_args_begin()
        device_number = `step_parameter_get_next_arg_as(int)
        clock_name = `step_parameter_get_next_arg_as(string)
        clock_frequency = `step_parameter_get_next_arg_as(real)
        `step_parameter_get_args_end
    endtask

    virtual task body();
        assert (super.device_clock_frequency_matches(device_number, clock_name, clock_frequency))
            `uvm_info("OK", sprint(), UVM_MEDIUM)
        else
            `uvm_error("MISMATCH", sprint())
    endtask
endclass

Macro Reference

Name Description
`define Given Sets the keyword and expression, and implements required interface methods.
`define When Sets the keyword and expression, and implements required interface methods.
`define Then Sets the keyword and expression, and implements required interface methods.
`define virtual_step_definition Implements placeholders for the required interface methods suitable for abstract base classes.
`define step_parameter_get_args_begin Begins a block to get arguments from the step text.
`define step_parameter_get_arg_as Gets the value of the argument at the given index and converts it to the given type.
`define step_parameter_get_next_arg_as Gets the value of the next argument and converts it to the given type.
`define step_parameter_num_args Returns the number of arguments in the format string.
`define step_parameter_get_arg_object Gets the argument object at the given index.
`define step_parameter_get_next_arg_object Gets the next argument object.
`define step_parameter_get_args_end Ends a block that gets arguments from the step text.

`define Given

`define Given(e)

Sets the keyword and expression, and implements required interface methods.

Sets the keyword for this step definition. This is the keyword returned by step_definition_interface::get_step_definition_keyword(). Sets the expression for this step definition to the string e. This is the expression returned by step_definition_interface::get_step_definition_keyword(). Expands into implementations of all the methods and supporting variables required by step_definition_interface. Registers this class as a step definition in the UVM resource database, so Bathtub can match it to Gherkin step text.

Every concrete step definition class needs to have a `Given(), `When(), or `Then() macro immediately after the class declaration. The choice of keyword--Given, When, or Then--makes no functional difference. Choose whichever keyword expresses the semantics of the step.

The expression e can be a SystemVerilog $sscanf()-style format string with conversion specifiers like %d or %s, or it could be a Posix regular expression surrounded by slashes ("/").

Examples:

class check_device_clock_frequency extends base_step_definition;
    // SystemVerilog format string
    `Then("the device %d %s clock should be running at %f Hz")
...
class check_device_clock_frequency extends base_step_definition;
    // Regular expression
    `Then("/the device \d+ \S+ clock should be running at \S+ Hz/")

`define When

`define When(e)

Sets the keyword and expression, and implements required interface methods.

See `Given().

`define Then

`define Then(e)

Sets the keyword and expression, and implements required interface methods.

See `Given().

`define virtual_step_definition

`define virtual_step_definition(e)

Implements placeholders for the required interface methods suitable for abstract base classes.

Use this macro for abstract base classes that implement the step_definition_interface. The implemented methods are minimal placeholders. A child class can easily extend such an abstract base class, but the child must re-implement the methods with its own `Given(), `When(), or `Then() macro. This macro does not register the class in the UVM resorce database, so it is not available for matching with Gherkin step text. The string argument e is included for parity with `Given(), `When(), and `Then(), but it is unused and ignored.

`define step_parameter_get_args_begin

`define step_parameter_get_args_begin(f=<expression>)

Begins a block to get arguments from the step text.

Create a begin-end macro "sandwich" to extract argument values from the Gherkin step text, using a SystemVerilog format string as the template. The begin-end block form a context in which the remaining step parameter macros can operate. The step parameter macros can only be used inside a step definition class that implements step_definition_interface.

`step_parameter_get_args_begin()
device_number = `step_parameter_get_next_arg_as(int)
clock_name = `step_parameter_get_next_arg_as(string)
clock_frequency = `step_parameter_get_next_arg_as(real)
`step_parameter_get_args_end

The macro's string argument f is the format string. It must be a SystemVerilog format string suitable for use with $sscanf(). If f is omitted, the macro uses the step definition's static expression by default. Override f if you want to use a expression different from the class' static expression. For example, if the class expression is a POSIX regular expression, you must call the macro with a SystemVerilog format string with conversion specifiers for the parameters.

class check_device_clock_frequency extends base_step_definition;
    // Regular expression
    `Then("/the device \d+ \S+ clock should be running at \S+ Hz/")
    ...
    virtual task pre_start();
        // An equivalent SystemVerilog format string
        `step_parameter_get_args_begin("the device %d %s clock should be running at %f Hz")
        device_number = `step_parameter_get_next_arg_as(int)
        clock_name = `step_parameter_get_next_arg_as(string)
        clock_frequency = `step_parameter_get_next_arg_as(real)
        `step_parameter_get_args_end
    endtask

The begin macro must always have parentheses, even if f is omitted. The parameter macros won't work in the constructor. Use them in the body() or pre_start() task, or in methods called by those tasks.

`define step_parameter_get_arg_as

`define step_parameter_get_arg_as(i, t)

Gets the value of the argument at the given index and converts it to the given type.

Arguments are numbered according to their position in the format string, beginning with 0. This macro retrieves the parameter at the given index, integer i. Arguments in the Gherkin step text are strings. Use the second macro argument to convert the string to a value of type t, where t can be bareword int, real, or string. t should be compatible with whatever local variable or expression you use it with.

class check_device_clock_frequency extends base_step_definition;
    // Argument order is int, string, real
    `Then("the device %d %s clock should be running at %f Hz")

     protected int device_number;
     protected string clock_name;
     protected real clock_frequency;
    ...
    virtual task pre_start();
        `step_parameter_get_args_begin()
        // Get the arguments in reverse order
        clock_frequency = `step_parameter_get_arg_as(2, real)
        clock_name = `step_parameter_get_arg_as(1, string)
        device_number = `step_parameter_get_arg_as(0, int)
        `step_parameter_get_args_end
    endtask

`define step_parameter_get_next_arg_as

`define step_parameter_get_next_arg_as(t)

Gets the value of the next argument and converts it to the given type.

The macro keeps an internal counter that starts at 0, then increments each time the macro is called. The macro thus returns each argument in order. Repeated macro calls will eventually get every argument.

Arguments in the Gherkin step text are strings. Use the macro argument to convert the string to a value of type t, where t can be bareword int, real, or string. t should be compatible with whatever local variable or expression you use it with.

class check_device_clock_frequency extends base_step_definition;
    // Argument order is int, string, real
    `Then("the device %d %s clock should be running at %f Hz")

    protected int device_number;
    protected string clock_name;
    protected real clock_frequency;
    ...
    virtual task pre_start();
        // Extract the arguments in the order they appear in the expression
        `step_parameter_get_args_begin()
        device_number = `step_parameter_get_next_arg_as(int)
        clock_name = `step_parameter_get_next_arg_as(string)
        clock_frequency = `step_parameter_get_next_arg_as(real)
        `step_parameter_get_args_end
    endtask

`define step_parameter_num_args

`define step_parameter_num_args

Returns the number of arguments in the format string.

The number of arguments may be useful in for-loops, or other forms of introspection.

class check_device_clock_frequency extends base_step_definition;
    `Then("the device %d %s clock should be running at %f Hz")

    // Array of step_parameter_arg objects
    protected bathtub_pkg::step_parameter_arg arg_values[3];
    ...
    virtual task pre_start();
        // Extract the arguments with a for-loop
        // `step_parameter_num_args returns 3
        `step_parameter_get_args_begin()
        for (int i = 0; i < `step_parameter_num_args; i++) begin
            arg_values[i] = `step_parameter_get_arg_object(i);
        end
        `step_parameter_get_args_end
    endtask

`define step_parameter_get_arg_object

`define step_parameter_get_arg_object(i)

Gets the argument object at the given index.

Arguments are numbered according to their position in the format string, beginning with 0. This macro retrieves the bathtub_pkg::step_parameter_arg argument object at the given index, integer i. A step_parameter_arg instance is a value object that encapsulates the argument text, type, and value.

class check_device_clock_frequency extends base_step_definition;
    // Argument order is int, string, real
    `Then("the device %d %s clock should be running at %f Hz")

    // Array of step_parameter_arg objects
    protected bathtub_pkg::step_parameter_arg arg_values[3];
    protected int device_number;
    protected string clock_name;
    protected real clock_frequency;
    ...
    virtual task pre_start();
        `step_parameter_get_args_begin()
        // Extract the array of argument objects
        `step_parameter_get_args_begin()
        for (int i = 0; i < `step_parameter_num_args; i++) begin
            arg_values[i] = `step_parameter_get_arg_object(i);
        end
        `step_parameter_get_args_end

        // Convert and assign argument object values to individual variables
        device_number = arg_values[0].as_int();
        clock_name = arg_values[1].as_string();
        clock_frequency = arg_values[2].as_real();
    endtask

`define step_parameter_get_next_arg_object

`define step_parameter_get_next_arg_object

Gets the next argument object.

The macro block keeps an internal counter that starts at 0, then increments each time this macro is called. The macro thus returns each bathtub_pkg::step_parameter_arg argument object in order. Repeated macro calls will eventually get every argument. A step_parameter_arg instance is a value object that encapsulates the argument text, type, and value.

class check_device_clock_frequency extends base_step_definition;
    // Argument order is int, string, real
    `Then("the device %d %s clock should be running at %f Hz")

    // Array of step_parameter_arg objects
    protected bathtub_pkg::step_parameter_arg arg_values[3];
    protected int device_number;
    protected string clock_name;
    protected real clock_frequency;
    ...
    virtual task pre_start();
        `step_parameter_get_args_begin()
        // Extract the array of argument objects
        `step_parameter_get_args_begin()
        for (int i = 0; i < `step_parameter_num_args; i++) begin
            arg_values[i] = `step_parameter_get_next_arg_object;
        end
        `step_parameter_get_args_end

        // Convert and assign argument object values to individual variables
        device_number = arg_values[0].as_int();
        clock_name = arg_values[1].as_string();
        clock_frequency = arg_values[2].as_real();
    endtask

`define step_parameter_get_args_end

`define step_parameter_get_args_end

Ends a block that gets arguments from the step text.

Use this macro to close a begin-end macro "sandwich" that begins with `step_parameter_get_args_begin. The begin-end block form a context in which the remaining step parameter macros can operate. The step parameter macros can only be used inside a step definition class that implements step_definition_interface.

Clone this wiki locally