Skip to content

alu_division

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

ALU Division

Introduction

ALU Division is a small example of a complete Bathtub environment. It depicts a simple arithmetic logic unit (ALU) that can do integer division. This example is included in the DVCon U.S. 2024 paper, “Gherkin Implementation in SystemVerilog Brings Agile Behavior-Driven Development to UVM.” The paper contains complete code for:

  • The ALU Division Gherkin feature file
  • Two step definitions
  • The Bathtub UVM test

This sample environment contains just enough testbench to validate that the code samples in the paper are functional. Still, this example may be instructive.

To summarize, this example:

  • Provides
    • A complete, executable Bathtub test
      • Gherkin feature file
        • Two scenarios
        • Steps with integer and string parameters
      • Functional RTL
        • A Verilog interface
        • A DUT module
      • Functional UVM testbench
        • Top module
        • UVM environment class
        • UVM virtual sequencer class
        • UVM virtual sequence base class
        • UVM bathtub test class
      • Step definitions for all feature file steps
        • Parameters
        • Checks
    • Elaboration on the DVCon paper
    • An EDA Playground
      • Simulator command line
      • Log file
  • Omits
    • Many Gherkin language features
    • Realistic design RTL
    • Many UVM testbench features
    • Advanced step definition features

EDA Playground

This complete example is available as an EDA Playground at bathtub_test_paper. It is open freely for viewing, but user registration is required to run it.

Most the files from EDA Playground have been downloaded and committed to the GitHub repository here: https://github.com/williaml33moore/bathtub/tree/main/examples/alu_division. EDA Playground downloads the entire playground--input and output files--in a single directory called results. The entire results directory (minus a few unnecessary files) is committed to the repository as-is as illustration. We do not recommend storing everything in a single directory like this on an actual ASIC project.

Gherkin Feature File

The Bathtub flow begins with a Gherkin feature file. Presumably teammates gather to discuss the behavior of a new feature and formulate their findings in a plain text file like this. The ALU Division feature file is alu_division.feature:

# This Gherkin feature file's name is alu_division.feature

Feature: Arithmetic Logic Unit division operations

    The arithmetic logic unit performs integer division.

    Scenario: In integer division, the remainder is discarded
        Given operand A is 15 and operand B is 4
        When the ALU performs the division operation
        Then the result should be 3
        And the DIV_BY_ZERO flag should be clear

    Scenario: Attempting to divide by zero results in an error
        Given operand A is 10 and operand B is 0
        When the ALU performs the division operation
        Then the DIV_BY_ZERO flag should be raised

It's a functional, but very small sample that illustrates only the basics of the Gherkin language.

Comments begin with #. This comment helpfully demonstrates the convention that feature filenames end with the suffix “.feature.”

The Feature: line provides a title for the high-level feature. A feature file can have only one Feature: keyword.

The block following Feature: is free-form text for documentation purposes. It can span multiple lines.

A Scenario: is a concrete example of a discrete behavior, comprised of steps. The text is a description for documentation purposes.

The Given step is declarative and sets up this division’s operands.

The When step indicates the procedural operation being tested.

The Then step asserts the expected outcome. It is traditional to use the auxiliary verb “should” in Gherkin Then steps (“should be clear”) but it carries the same meaning as stronger verbs like “must” or “shall” in that it is considered an error if the assertion fails.

And is an alias for the preceding keyword that makes the scenario more readable than repeating the keyword Then. Gherkin has additional syntactic sugar step keywords But and *; the asterisk allows the user to create bullet lists of steps. Scenarios may have any number of Given, When, Then, And, But, and * steps in any combination.

The second scenario is an error case. Note that some lines are common between the two scenarios, differing only in literal values, illustrating that steps can be parameterized and reused across behaviors.

Leading and trailing whitespace is insignificant, but the prevailing convention is to indent keyword lines consistently.

Gherkin syntax consists solely of the keywords at the beginning of each line. The text following each keyword is arbitrary, not subject to any syntactic or grammatical requirements.

Design RTL

The feature file describes what our RTL should do. The RTL file is design.sv. This is very simple Verilog code for a combinational arithmetic logic unit that can add, subtract, multiply, and do integer division on two 16-bit integers. It's not intended to be realistic or synthesizable; it only needs to satisfy our sample feature file. The file contains a Verilog interface and a module.

Interface

Here is the interface:

interface alu_if ();

  logic[15:0] operand_a;
  logic[15:0] operand_b;
  logic[3:0] operation;
  logic[31:0] result;
  logic[31:0] status;

    modport dut (
    input operand_a,
    input operand_b,
    input operation,
    output result,
    output status
    );

    modport driver (
    output operand_a,
    output operand_b,
    output operation,
    input result,
    input status
    );

    modport monitor (
    input operand_a,
    input operand_b,
    input operation,
    input result,
    input status
    );
endinterface : alu_if

This interface has modports for the DUT, the testbench driver, and a testbench monitor. This example doesn't have a monitor, but the modport is included for symmetry.

The DUT's interface is:

Port Direction Type Description
operand_a input logic[15:0] operand A
operand_b input logic[15:0] operand B
operation input logic[3:0] opcode (0 = add, 1 = subtract, 2 = multiply, 3 = divide)
result output logic[31:0] result of operation, i.e., A op B = result
status output logic[31:0] status flags following operation (bit[0] = OKAY, bit[1] = DIV_BY_ZERO error, bit[2] = invalid opcode error)

Module

Here is the module:

module alu (alu_if i);

  localparam logic[3:0]
  OP_ADD = 0,
  OP_SUBTRACT = 1,
  OP_MULTIPLY = 2,
  OP_DIVIDE = 3;
  
  localparam int unsigned
    STATUS_OKAY = 0,
    STATUS_DIV_BY_ZERO = 1,
    STATUS_OP_ERR = 2;
  
  always_comb begin
    i.dut.result = 0;
    i.dut.status = 0;
    case (i.dut.operation)
      OP_ADD : begin
        i.dut.result = i.dut.operand_a + i.dut.operand_b;
        i.dut.status[STATUS_OKAY] = 1;
      end
      
      OP_SUBTRACT : begin
        i.dut.result = i.dut.operand_a - i.dut.operand_b;
        i.dut.status[STATUS_OKAY] = 1;
      end
      
      OP_MULTIPLY : begin
        i.dut.result = i.dut.operand_a * i.dut.operand_b;
        i.dut.status[STATUS_OKAY] = 1;
      end
      
      OP_DIVIDE : begin
        if (i.dut.operand_b != 0) begin
          i.dut.result = i.dut.operand_a / i.dut.operand_b;
          i.dut.status[STATUS_OKAY] = 1;
        end
        else begin
          i.dut.result = 0;
          i.dut.status[STATUS_DIV_BY_ZERO] = 1;
        end
      end
      
      default : begin

        i.dut.result = -1;
        i.dut.status[STATUS_OP_ERR] = 1;
      end
        
    endcase
  end
endmodule : alu

It's simple combinational logic that continuously outputs an arithmetic result and status based on the operand and opcode inputs.

Testbench

Now that we have a DUT and its interface, we can build a testbench.

The testbench is in testbench.sv. This file consists of:

  • A testbench top module
  • A UVM environment class
  • A UVM virtual sequencer class
  • A UVM virtual sequence base class, compatible with the virtual sequencer
  • A UVM test class

Following the familiar refrain, everything is as minimal as possible with the sole goal of validating the examples in the paper. This is not representative of best practices for a working verification environment.

Testbench Top Module

module top();

  import uvm_pkg::*;
  
  typedef enum {ADD=0, SUBTRACT=1, MULTIPLY=2, DIVIDE=3} op_type;
  typedef enum {OKAY=0, DIV_BY_ZERO=1, OP_ERR=2} status_type;

  alu_if vif();
  alu dut(vif);

  initial begin
    $timeformat(0, 3, "s", 20);
    uvm_config_db#(virtual alu_if)::set(uvm_coreservice_t::get().get_root(), "top", "vif", vif);
    run_test();
  end

...

`include "alu_step_definition.svh"

endmodule : top

The top module of the simulation instantiates the DUT module and its interface, and passes the interface to the module.

The initial block stores the interface virtually in the UVM configuration database using uvm_root as context. Then it calls run_test().

The file which declares our step definitions, alu_step_definition.svh, is included near the bottom of the module. Adding these step definitions is the only change to the top module required for Bathtub. Step definitions are UVM sequences, so you should include or read them the same way you include or read your other sequences.

UVM Environment Class

Module top defines a UVM environment class for the ALU, extending base class uvm_env:

  class alu_env extends uvm_env;
    `uvm_component_utils(alu_env)

    alu_sequencer alu_vseqr;
    virtual alu_if vif;

    function new(string name, uvm_component parent);
      super.new(name, parent);
    endfunction : new

    virtual function void build_phase(uvm_phase phase);
      alu_vseqr = alu_sequencer::type_id::create("alu_vseqr", this);
    endfunction : build_phase

    virtual function void connect_phase(uvm_phase phase);
      bit ok;

      ok = uvm_config_db#(virtual alu_if)::get(uvm_coreservice_t::get().get_root(), "top", "vif", vif);
      assert (ok);
      assert_vif_not_null : assert (vif != null);
      alu_vseqr.vif = vif;
    endfunction : connect_phase

  endclass : alu_env

The build phase instantiates our virtual sequencer, alu_vseqr, described in the following section.

The connect phase retrieves the virtual interface from the UVM configuration database and passes it to the virtual sequencer.

In the interest of simplicity, this environment has no additional customary components like UVM agents or scoreboards.

The environment has no dependencies on Bathtub. This environment could be used unchanged from a "normal" testbench without Bathtub.

UVM Virtual Sequencer

Our UVM virtual sequencer class is as follows:

  class alu_sequencer extends uvm_sequencer#(uvm_sequence_item);
    `uvm_component_utils(alu_sequencer)
    virtual alu_if vif;

    function new (string name="alu_sequencer", uvm_component parent) ;
      super.new(name, parent);
    endfunction : new
  endclass

Our minimal testbench does not have agents, sub-sequencers, or sequence items, so the sequencer is quite small. All it needs is the DUT's virtual interface, which it gets from the environment.

Like the environment, the sequencer does not require any changes for Bathtub.

UVM Virtual Sequence Base Class

We need virtual sequences to run on our virtual sequencer. Following standard object-oriented programming practices, we declare a base class that all our sequences can extend. The base class contains common setup and convenient API methods our sequences can use to access the DUT.

  class alu_base_vsequence extends uvm_sequence#(uvm_sequence_item);
    `uvm_object_utils(alu_base_vsequence)
    `uvm_declare_p_sequencer(alu_sequencer)
    
    function new(string name="alu_base_vsequence");
      super.new(name);
      set_automatic_phase_objection(1);
    endfunction : new
    
    task set_operand_A(bit[15:0] operand_A);
      `uvm_info(get_name(), "set_operand_A", UVM_MEDIUM)
      p_sequencer.vif.driver.operand_a = operand_A;
    endtask : set_operand_A
    
    task set_operand_B(bit[15:0] operand_B);
      `uvm_info(get_name(), "set_operand_B", UVM_MEDIUM)
      p_sequencer.vif.driver.operand_b = operand_B;
    endtask : set_operand_B
    
    task do_operation(bit[3:0] operation);
      `uvm_info(get_name(), "set_operation", UVM_MEDIUM)
      p_sequencer.vif.driver.operation = operation;
    endtask : do_operation
    
    task get_result(output bit[31:0] result);
      `uvm_info(get_name(), "get_result", UVM_MEDIUM)
      result = p_sequencer.vif.driver.result;
    endtask : get_result
    
    task get_div_by_zero_flag(output bit div_by_zero_flag);
      bit[31:0] status;
      `uvm_info(get_name(), "get_div_by_zero_flag", UVM_MEDIUM)
      status = p_sequencer.vif.driver.status;
      div_by_zero_flag = status[DIV_BY_ZERO];
    endtask : get_div_by_zero_flag
    
  endclass : alu_base_vsequence

We use the `uvm_declare_p_sequencer macro to declare a p_sequencer variable and assign it to the parent sequencer.

Instead of executing sequence items on a driver, our sequencer simply declares "setter" and "getter" accessor methods to peek and poke the signals of the virtual interface driver modport directly.

Method Description
set_operand_A() Drive operand_a
set_operand_B() Drive operand_b
do_operation() Drive operation
get_result() Read result
get_div_by_zero_flag() Read status bit[1]

Like our other components, this sequence base class is unchanged for Bathtub.

UVM Test Class

The last item in the file is the UVM test class. The test is similar to other tests, but it does require special modifications for Bathtub.

  class bathtub_test extends uvm_test;
    `uvm_component_utils(bathtub_test)
    alu_env my_alu_env; // uvm_env containing the virtual sequencer
    bathtub_pkg::bathtub bathtub;

    function new(string name = "bathtub_test", uvm_component parent = null);
      super.new(name, parent);
    endfunction : new

    virtual function void build_phase(uvm_phase phase);
      bathtub = bathtub_pkg::bathtub::type_id::create("bathtub");
      super.build_phase(phase);
      my_alu_env = alu_env::type_id::create("my_alu_env", this);
    endfunction : build_phase
    
    task run_phase(uvm_phase phase);
      bathtub.configure(my_alu_env.alu_vseqr); // Virtual sequencer
      bathtub.feature_files.push_back("alu_division.feature"); // Feature file
      phase.raise_objection(this);
      bathtub.run_test(phase); // Run Bathtub!
      phase.drop_objection(this);
    endtask : run_phase

  endclass : bathtub_test

We call our test class bathtub_test to signify that it runs Bathtub. Recall that you pass this test name to the simulator on the command line, i.e., +UVM_TESTNAME=bathtub_test. Our test extends raw base class uvm_test. If your project has a custom base class for tests, your Bathtub test could likely extend that same custom base class.

In the build phase, our test instantiates a bathtub_pkg::bathtub object and calls it bathtub. It also instantiates an ALU environment object.

Note that this test does not specify a default sequence to run. It "runs" bathtub instead.

In the run phase, we configure bathtub with a handle to the virtual sequencer instance inside the environment, my_alu_env.alu_vseqr. This way Bathtub is able to run sequences on our virtual sequencer.

Next, the run phase pushes our Gherkin feature filename alu_division.feature onto bathtub's queue of file names. In EDA Playground, all source files are in the same directory--the simulation directory--so the pathname is relative and correct. If the feature files were in a different directory, the filenames passed to bathtub would require correct absolute or relative pathnames. We hard-code the filename inside the test. Best practice would be to pass filenames into the test through some more flexible run-time means, such as a plusarg. Finally the run phase launches bathtub with the bathtub::run_test() task, guarded by an objection.

Step Definitions

We have a feature file, a testbench, and a test. Now we need step definitions to map the English steps in the feature file into executable SystemVerilog code. The step definitions are all in alu_step_definition.svh. Ours are all in one file, but they could be partitioned into separate files. Just make sure all the step definitions are compiled with the testbench, or else the feature file will fail.

Our feature file has seven steps:

Given operand A is 15 and operand B is 4
When the ALU performs the division operation
Then the result should be 3
And the DIV_BY_ZERO flag should be clear
Given operand A is 10 and operand B is 0
When the ALU performs the division operation
Then the DIV_BY_ZERO flag should be raised

If we factor out the integer constants and the DIV_BY_ZERO flag value strings, we can condense that down to four parameterized steps.

Given operand A is %d and operand B is %d
When the ALU performs the division operation
Then the result should be %d
Then the DIV_BY_ZERO flag should be %s

We replace any And and But keywords with the canonical keywords, in this case, Then.

This is not the only way to refactor the step strings. For example we could have replaced the word "division" with a "%s" placeholder in order to handle different operations. For the purposes of this example, we have gone with four steps, which means we need four step definitions.

The step definitions are all UVM virtual sequences, and they all extend our virtual sequence base class alu_base_vsequence from our testbench.sv file. Our step definitions must all implement interface class bathtub_pkg::step_definition_interface. That is what makes them eligible to be Bathtub step definitions, and not just normal sequences.

"Given operand A is %d and operand B is %d"

Here is the first step definition, which sets operands A and B to given integer values.

class set_operand_A_and_B_vseq extends alu_base_vsequence implements bathtub_pkg::step_definition_interface;
    `Given("operand A is %d and operand B is %d")
    int operand_A, operand_B;
   `uvm_object_utils(set_operand_A_and_B_vseq)
    function new (string name="set_operand_A_and_B_vseq");
        super.new(name);
    endfunction : new
    virtual task body();
        // Extract the parameters
        `step_parameter_get_args_begin()
        operand_A = `step_parameter_get_next_arg_as(int);
        operand_B = `step_parameter_get_next_arg_as(int);
        `step_parameter_get_args_end
        // Do the actual work using the API in the base sequence
        super.set_operand_A(operand_A); 
        super.set_operand_B(operand_B);
    endtask : body
endclass : set_operand_A_and_B_vseq

The macro `Given() associates the parameterized template string with this step definition. The hard-working macro implements all the methods required by step_definition_interface. Note that the word "Given" is not repeated inside the template string. This underscores the fact that Given is a Bathtub keyword and is separate from the rest of the step string. Plus, this way the macro line reads naturally and grammatically, similar to the original step string.

We declare two local integers to hold our two operands, operand_A and operand_B.

The `uvm_object_utils macro and constructor are the "usual stuff" required when declaring UVM classes.

We override the sequence body() task to implement the step's behavior. First we extract the actual parameters from the step string with the `step_parameter_* macros.

Code Description
`step_parameter_get_args_begin() Begin the `step_parameter_* "sandwich"
operand_A = `step_parameter_get_next_arg_as(int); Extract the first argument from the step string using the parameterized `Given string as a template
operand_B = `step_parameter_get_next_arg_as(int); Extract the next argument
`step_parameter_get_args_end End the `step_paramter_* "sandwich"

The arguments to the `step_parameter_get_next_arg_as() macros--in this case int--must match the type declaration of the operand_A and operand_B variables so the values get converted correctly.

Lastly we call the set_operand_A() and set_operand_B() functions from our base class ("super"), passing them our two operands. (The "super" is not required; it's included here just to emphasize where those methods came from.)

"When the ALU performs the division operation"

The next step definition is a When step, so it performs the triggering action for this behavior. It has no parameters so it's considerably simpler than the previous one.

class do_division_operation_vseq extends alu_base_vsequence implements bathtub_pkg::step_definition_interface;
  `When("the ALU performs the division operation")
  int operation;
  `uvm_object_utils(do_division_operation_vseq)
  function new (string name="do_division_operation_vseq");
    super.new(name);
  endfunction : new
  virtual task body();
    operation = DIVIDE;
    super.do_operation(operation);
  endtask : body
endclass : do_division_operation_vseq

We simply call the sequence base class method do_operation() with the constant enum, DIVIDE.

"Then the result should be %d"

As a Then step, this step definition should contain the equivalent of an assertion. If the expected check fails, then this step should indicate an error.

class check_result_vseq extends alu_base_vsequence implements bathtub_pkg::step_definition_interface;
  `Then("the result should be %d")
    int expected_result;
  	int actual_result;
  `uvm_object_utils(check_result_vseq)
    function new (string name="check_DIV_BY_ZERO_flag_vseq");
        super.new(name);
    endfunction : new
    virtual task body();
        // Extract the parameter
        `step_parameter_get_args_begin()
      expected_result = `step_parameter_get_next_arg_as(int);
        `step_parameter_get_args_end
      super.get_result(actual_result);
      check_result : assert (expected_result === actual_result) else
        `uvm_error("MISMATCH", $sformatf("expected %0d; actual %0d", expected_result, actual_result)) 
    endtask : body
endclass : check_result_vseq

The parameter in the step string holds the expected value for result. We declare a local int variable for the expected result, and another for the actual result.

Inside body() we use the macros to extract expected_result from the step string. Then we use the base class method get_result() to read the actual result from the DUT.

Lastly we use a labeled immediate assertion ("check_result : assert") to compare expected_result to actual_result and call `uvm_error() if there's a mismatch. We include ample details in the error message to assist with debug. There are other styles of coding a check like this, for example with an if-then statement, or the Verilog $error() system task, or perhaps with a custom macro or function call. Regardless of style, the important thing is that the test should fail if any Then checks fail.

"Then the DIV_BY_ZERO flag should be %s"

This is another check, like the previous step. This time we check the value of the DIV_BY_ZERO flag. However, to make things interesting, we add some flexibility so that this step definition will respond to all the following alternative phrasings:

  • Then the DIV_BY_ZERO flag should be raised
  • Then the DIV_BY_ZERO flag should be asserted
  • Then the DIV_BY_ZERO flag should be clear
  • Then the DIV_BY_ZERO flag should be deasserted

The point is that Gherkin feature files are written in natural language, and a best practice in BDD is to be reasonably accommodating in the feature file so as to avoid over-constraining the language that can be used. We imagine a scenario where sometimes the feature file formulation team wants to use "raised" and "clear," and sometimes wants to use "asserted" and "deasserted." Our step definition can handle all those cases.

class check_DIV_BY_ZERO_flag_vseq extends alu_base_vsequence implements bathtub_pkg::step_definition_interface;
    `Then("the DIV_BY_ZERO flag should be %s")
    string flag_arg;
    bit expected_flag;
    bit actual_flag;
  `uvm_object_utils(check_DIV_BY_ZERO_flag_vseq)
    function new (string name="check_DIV_BY_ZERO_flag_vseq");
        super.new(name);
    endfunction : new
    virtual task body();
        // Extract the parameter
        `step_parameter_get_args_begin()
        flag_arg = `step_parameter_get_next_arg_as(string);
        `step_parameter_get_args_end
        case (flag_arg) // convert the string to a bit value
            "raised", "asserted" : expected_flag = 1;
            "clear", "deasserted" : expected_flag = 0;
            default: `uvm_error("UNEXPECTED ARG", flag_arg)
        endcase
        super.get_div_by_zero_flag(actual_flag);
        check_DIV_BY_ZERO_flag : assert (expected_flag === actual_flag) else
            `uvm_error("MISMATCH", $sformatf("expected %b; actual %b", expected_flag, actual_flag)) 
    endtask : body
endclass : check_DIV_BY_ZERO_flag_vseq

We declare a local string variable flag_arg to hold the actual word from the step string: "raised," "clear," "asserted," or "deasserted." We also declare two one-bit variables, expected_flag and actual_flag, for the expected and actual bit values from the DUT. Since the flag_arg string represents our expected value, we need to map the flag_arg string to a bit value for expected_flag. We use a case statement. If flag_arg is "raised" or "asserted," the expected flag value is 1. If flag_arg is "clear" or "deasserted," the expected flag value is 0. Otherwise we call `uvm_error() and complain about the unexpected arg word.

We read the actual value with the base class method get_div_by_zero_flag(), then compare it to the expected value with another labeled immediate assertion, check_DIV_BY_ZERO_flag.

Bathtub Source

To simulate with Bathtub, naturally we need the Bathtub source code. EDA Playground stores the entire simulation in one flat directory, so it contains Bathtub right alongside the DUT and testbench. In an actual project, it would be sensible to organize files in separate directories.

The Bathtub files in the directory are:

File Description
gherkin_pkg.sv Package containing classes for Gherkin AST
bathtub_pkg.sv Package containing Bathtub parser, runner, printer, and utilities
bathtub_macros.sv Bathtub macros for use in step definitions
files.f List of files for simulator to read, e.g., -f files.f

Run Command

The EDA Playground runs with Cadence Xcelium 20.09. The run script is run.sh, and it contains the following run command:

xrun -Q -unbuffered -f files.f +define+EDAPG -timescale 1ns/1ns -sysv -access +rw +UVM_TESTNAME=bathtub_test +UVM_VERBOSITY=UVM_HIGH -uvmnocdnsextra -uvmhome $UVM_HOME $UVM_HOME/src/uvm_macros.svh design.sv testbench.sv

The key arguments are:

Argument Description
-f files.f Bathtub source files
+define+EDAPG Currently required as a workaround for an issue specific to EDA Playground
+UVM_TESTNAME=bathtub_test Run our Bathtub test
+UVM_VERBOSITY=UVM_HIGH Choose any verbosity level; Bathtub uses UVM_MEDIUM and UVM_HIGH messages
design.sv Our design RTL file
testbench.sv Our testbench file

The rest of the arguments are specific to Xcelium. They include required UVM arguments.

Log File

The log file is committed to the repo as xrun.log.

Here are some highlights, as produced with +UVM_HIGH verbosity.

Step Definitions

Bathtub prints static information as each step definition class is registered at time zero, e.g.:

UVM_INFO bathtub_pkg.sv(921) @ 0: reporter [bathtub_pkg::step_nature.register_step] 
-----------------------------------------------------------------------------------------------------------------------------
Name                Type            Size  Value                                                                              
-----------------------------------------------------------------------------------------------------------------------------
static_step_object  step_nature     -     @1980                                                                              
  keyword           step_keyword_t  32    Given                                                                              
  expression        string          35    operand A is %d and operand B is %d                                                
  regexp            string          83    /^operand A is (([-+]?[0-9_]+)|[xXzZ?]) and operand B is (([-+]?[0-9_]+)|[xXzZ?])$/
  step_obj_name     string          24    set_operand_A_and_B_vseq                                                           
-----------------------------------------------------------------------------------------------------------------------------

Gherkin Parser

The Gherkin parser prints information as it reads the feature files and processes each line and builds its internal representation of the data structure, e.g.:

UVM_INFO bathtub_pkg.sv(2312) @ 0.000s: reporter [bathtub_pkg::gherkin_parser.parse_step.unmblk1] gherkin_parser::parse_step enter
-----------------------------------------------------------------------------------------------------------------
 +Name               Type                                  Size  Value                                             
 +-----------------------------------------------------------------------------------------------------------------
 +element_container  uvm_report_message_element_container  -     @2957                                             
 +  file_name        string                                20    alu_division.feature                              
 +  line_number      integral                              32    'd8                                               
 +  eof              integral                              1     'b0                                               
 +  text             string                                50            Given operand A is 15 and operand B is 4

 +-----------------------------------------------------------------------------------------------------------------
 +

When the parsing is done, Bathtub recreates and dumps the entire feature file into the log, e.g.:

# language: 
Feature: Arithmetic Logic Unit division operations
  The arithmetic logic unit performs integer division.
   
  Scenario: In integer division, the remainder is discarded
     
    Given operand A is 15 and operand B is 4
    When the ALU performs the division operation
    Then the result should be 3
    And the DIV_BY_ZERO flag should be clear
 
  Scenario: Attempting to divide by zero results in an error
     
    Given operand A is 10 and operand B is 0
    When the ALU performs the division operation
    Then the DIV_BY_ZERO flag should be raised
 

Gherkin Runner

After the Bathtub parser parses the Gherkin file, the Bathtub runner executes it.

The runner prints each step as it runs, e.g.:

UVM_INFO bathtub_pkg.sv(3116) @ 0.000s: reporter [runner] Given operand A is 15 and operand B is 4

Then it reports the matching step definition, e.g.:

UVM_INFO bathtub_pkg.sv(2882) @ 0.000s: reporter [runner] Executing sequence set_operand_A_and_B_vseq (set_operand_A_and_B_vseq)

The runner prints step arguments as they are extracted from the step string, e.g.:

UVM_INFO bathtub_pkg.sv(767) @ 0.000s: reporter [bathtub_pkg::step_parameters.scan_step_params.unmblk1] 
--------------------------------------------------------------------------------------------------
 +Name               Type                                  Size  Value                              
 +--------------------------------------------------------------------------------------------------
 +element_container  uvm_report_message_element_container  -     @3171                              
 +  step_text        string                                34    operand A is 15 and operand B is 4 
 +  scanf_format     string                                35    operand A is %d and operand B is %d
 +--------------------------------------------------------------------------------------------------
 +
UVM_INFO bathtub_pkg.sv(838) @ 0.000s: reporter [bathtub_pkg::step_parameters.scan_step_params.unmblk3] 
------------------------------------------------
Name          Type                Size  Value   
------------------------------------------------
anonymous     step_parameter_arg  -     @3151   
  arg_type    arg_type_t          32    INT     
  int_arg     integral            32    'hf     
------------------------------------------------

UVM_INFO bathtub_pkg.sv(838) @ 0.000s: reporter [bathtub_pkg::step_parameters.scan_step_params.unmblk3] 
------------------------------------------------
Name          Type                Size  Value   
------------------------------------------------
anonymous     step_parameter_arg  -     @3192   
  arg_type    arg_type_t          32    INT     
  int_arg     integral            32    'h4     
------------------------------------------------

If there were any errors from the Then steps or any other source, they would be indicated in the log file. The UVM Report Summary in our log shows that there were no UVM errors:

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :  121
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
Clone this wiki locally