Skip to content

Hello World

Bill Moore edited this page Nov 11, 2024 · 9 revisions

"Hello, World!"

The "Hello, World!" example in $BATHTUB_VIP_DIR/examples/getting_started/ is rightfully a tiny example of a Bathtub-empowered testbench. Still, it's a useful introduction to the anatomy of the creature.

See the Getting Started page for instructions on how to run this example.

hello_world.feature

The first file is $BATHTUB_VIP_DIR/examples/getting_started/hello_world.feature:

# hello_world.feature
Feature: Hello, World!
    Scenario: Print a simple message
        Given a Bathtub simulation
        When I print 'Hello, World!'
        Then the test should pass

BDD begins with informal discovery of the intended behavior of an upcoming code increment. The next step is to capture that behavior in a structured, executable format. Bathtub uses the Gherkin language for formulating behavior. hello_world.feature is a Gherkin file which captures the behavior of our test. Such files are generally known as feature files, and hence the convention is to use the .feature suffix.

Gherkin files are written in plain natural language--English in this case--and hopefully are literally self-explanatory. Even the most casual reader can discern that the expected behavior of this Bathtub simulation (whatever that means) is to print "Hello, World!". Bathtub will read this text file and execute it as a test.

Let's break it down, line-by-line.

# hello_world.feature

The first line is a comment, and simply gives the name of the file. A comment line starts with # and takes up the whole line. There are no inline comments. Comments can go anywhere in the file.

Feature: Hello, World!

The second line introduces the feature, and gives it a name, "Hello, World!" Feature lines begin with the Gherkin keyword, "Feature:", with a colon. A feature file can have only one feature.

    Scenario: Print a simple message

The third line introduces the first and only scenario, and gives it a name: "Print a simple message." Scenario lines begin with the Gherkin keyword, "Scenario:, with a colon. A scenario is a concrete example of the behavior of the feature. A feature can have any number of scenarios. Some features are complex and require many examples to illustrate them.

It's conventional to indent Gherkin files to make them easier to read, but it's not required. Leading and trailing whitespace is ignored.

        Given a Bathtub simulation

The fourth, fifth, and sixth lines are the steps that make up the scenario. Steps are simple sequential statements that break a scenario into discrete chunks. Gherkin steps famously start with the keywords "Given", "When", or "Then", without colons. There are other keywords as well: "And", "But", and "*", which can be viewed as a bullet point or wildcard. Choose the keyword that makes your steps and scenarios the most readable. The convention is to use "Given" for setup or preconditions, "When" for the triggering action of this scenario, and "Then" for the expected outcome.

This "Given" step tells us the scenario requires a Bathtub simulation.

        When I print 'Hello, World!'

This "When" step tells us that we are printing "Hello, World."

        Then the test should pass

This "Then" step tells us very little, simply that this test should succeed. In a real scenario, "Then" steps would be hard, verifiable checks. If the "Then" fails, the test should fail. Our "Hello, World!" example is not self-checking. We'll have to check the log file manually.

hello_world.sv

The second file is $BATHTUB_VIP_DIR/examples/getting_started/hello_world.sv:

// hello_world.sv

`timescale 1s/1ms
`include "uvm_macros.svh"
`include "bathtub_macros.sv"

program hello_world();

    import uvm_pkg::*;
    
    class echo_step extends uvm_sequence implements bathtub_pkg::step_definition_interface;
        `Given(".*")

        `uvm_object_utils(echo_step)

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

        virtual task body();
            get_step_attributes().print_attributes(UVM_NONE);
        endtask : body
    endclass : echo_step


    class bathtub_test extends uvm_test;
        bathtub_pkg::bathtub bathtub;

        `uvm_component_utils(bathtub_test)

        function new(string name, uvm_component parent);
            super.new(name, parent);
            bathtub = new();
        endfunction : new
        
        virtual task run_phase(uvm_phase phase);
            bathtub.run_test(phase);
        endtask : run_phase
    endclass : bathtub_test
    
    initial run_test("bathtub_test");
    
endprogram : hello_world

It represents an incredibly small UVM testbench. So small, in fact, that there is no DUT.

Program hello_world

The code is contained in a SystemVerilog program, hello_world, which is playing the role of the testbench top module, which would normally instantiate the UVM test and DUT. Our program has no DUT, but it does define a UVM test, bathtub_test.

At the bottom of the file, hello_world calls the global UVM function run_test() and gives it a hard-coded test name, "bathtub_test", described below. In this small example, we can hard-code the test name because there is only one test. A typical UVM testbench contains many tests, and the user selects one with the command-line plusarg +UVM_TESTNAME=<test name>. A Bathtub-enabled testbench works the same way. The expectation is that you have one Bathtub test which is simply one among many "normal" tests, and you select that Bathtub test with plusarg +UVM_TESTNAME=bathtub_test.

Note that all the Bathtub functionality in hello_world is contained inside bathtub_test. We could easily have a second test called normal_test with no mention of Bathtub, and run either one at will.

    class normal_test extends uvm_test;

        `uvm_component_utils(normal_test)

        function new(string name, uvm_component parent);
            super.new(name, parent);
        endfunction : new
        
        virtual task run_phase(uvm_phase phase);
            `uvm_info("normal", "Hello, World!", UVM_NONE)
        endtask : run_phase
    endclass : normal_test 

One testbench can support both your normal and your Bathtub tests.  

Test Class bathtub_test

Typically a UVM test instantiates a UVM environment, connects it to the DUT, and launches sequences on it. A Bathtub test is very similar, but instead of launching sequences, it instantiates a Bathtub object and "runs" that. The Bathtub object reads and parses the feature file and uses that to run sequences on the DUT.

Our Bathtub test very simply instantiates a Bathtub object in the constructor new(), then runs it in run_phase() with a call to bathtub.run_test().

A Bathtub test is meant to fit in with all your other normal tests. Our bathtub_test class directly extends uvm_test, but if you have an abstract or concrete test base class that all your other tests extend, and which provides useful shared members and methods, your Bathtub test could extend that base class as well. This class diagram shows a family of tests, two normal and one Bathtub test, that descend from a common user base class.

---
title: Family of Tests
---
classDiagram
    class uvm_test {
    }
    class language_base_test {
        -language
        +set_language()
        +get_language()
    }
    class english_test {
    }
    class french_test {
    }
    class bathtub_test {
        #bathtub
    }

    language_base_test --|> uvm_test
    english_test --|> language_base_test
    french_test --|> language_base_test
    bathtub_test --|> language_base_test
Loading

Step Definition Sequence Class echo_step

When Bathtub executes the feature file, it needs to know what to do for each Given-When-Then step. You must provide a UVM sequence class that covers every step. These sequence classes are known as step definitions, because they define the action for each Gherkin step. There doesn't have to be a one-to-one correspondence between Gherkin steps and step definitions; in can be one-to-many in that one step definition can cover multiple steps. That's what we have done here. We define one step definition, class echo_step, which covers all the steps.

A Bathtub step definition must satisfy three criteria:

  1. It must be a UVM sequence, i.e., be a direct or indirect descendent of uvm_sequence_base.
  2. It must implement interface class bathtub_pkg::step_definition_interface.
  3. It must provide implementations for all the virtual methods in step_definition_interface. The easiest way to do that is to contain a Bathtub step definition macro, `Given(), `When(), or `Then().

As you can see, our echo_step meets all three requirements. The `Given() macro's argument is a POSIX regular expression which indicates which Gherkin steps it will cover. Since the regular expression, ".*", matches every string, this one step definition will be called for every step.

Like all UVM sequences, step definitions have body() tasks that perform the step's actions. Step definition body() tasks can start sub-sequences or sequence items on sequencers, check results with immediate assertions, or perform pretty much any procedural operations. When Bathtub launches your step definition, Bathtub configures it with a number of attributes, such as the original step text from the feature file. Since our echo_step step definition matches every step, all the body task does is call get_step_attribute().print_attributes() to print all the attributes to the log file. This is effectively our "Hello, World!"

For simplicity, we put our top "hello_world" program, UVM test, and step definition all in one file. Naturally, the best practice for a UVM test environment is to partition modules and classes into as many files as makes sense.

Run the Simulation

These are the commands to run our simulation.

# Xcelium
xrun -uvm -f $BATHTUB_VIP_DIR/src/bathtub_vip.f hello_world.sv +bathtub_features=hello_world.feature

# Questa
qrun -uvm -f $BATHTUB_VIP_DIR/src/bathtub_vip.f hello_world.sv +bathtub_features=hello_world.feature

# VCS
vcs -R -full64 +incdir+$UVM_HOME/src $UVM_HOME/src/uvm.sv $UVM_HOME/src/dpi/uvm_dpi.cc -CFLAGS -DVCS -sverilog -f $BATHTUB_VIP_DIR/src/bathtub_vip.f hello_world.sv +bathtub_features=hello_world.feature

The Xcelium and Questa commands are very similar, so let's examine them argument-by-argument.

xrun/qrun

These are the executables for Xcelium and Questa, respectively.

-uvm

Bathtub requires UVM, so provide the simulator option which enables it.

-f $BATHTUB_VIP_DIR/src/bathtub_vip.f

Bathtub includes a simulator options file, bathtub_vip.f, which contains Bathtub's include directories and source code packages. Put this argument before all your Bathtub user code which depends on Bathtub so that your code can compile.

hello_world.sv

Read our all-in-one UVM test environment source code files. If we had several source files, we would put them all here.

+bathtub_features=hello_world.feature

Bathtub provides several plusargs that control how it runs. This one tells Bathtub which feature file to read. This hello_world.feature file is in the same directory as the simulation, so all we need is the filename. If the file were in a different directory, we would need to provide a fully qualified absolute or relative hierarchical pathname.

Results

Since our simple test is not self-checking, we must examine the log file. By default Bathtub emits several messages containing the tag "bathtub" to the log file. Bathtub logs the name of the feature files it reads, and a dump of the parsed feature files in the form of a recursive UVM object tree. Then, when Bathtub starts running the feature file, it prints out each Gherkin line as it goes: Feature:, Scenario:, Given, When, Then. This is a nice benefit of Gherkin's granularity; a Bathtub test is self-documenting. You can always tell where it is and what it's doing.

For each step, our catch-all step definition dumps the step's attributes.

With UVM 1.2 and later, Bathtub takes advantage of the UVM message printing facility for dumping step attributes. Here's a typical excerpt from the log file. Yours may differ slightly.

UVM_INFO bathtub/src/bathtub_pkg/gherkin_document_runner/gherkin_document_runner.svh(605) @ 0: bathtub [runner] When 
I print 'Hello, World!'
UVM_INFO bathtub/src/bathtub_pkg/gherkin_document_runner/gherkin_document_runner.svh(189) @ 0: bathtub [bathtub_pkg::
gherkin_document_runner.start_step] When I print 'Hello, World!'
UVM_INFO bathtub/src/bathtub_pkg/step_nurture.svh(89) @ 0: reporter [step_attributes] 
 +--------------------------------------------------------------------------------------
 +Name               Type                                  Size  Value                  
 +--------------------------------------------------------------------------------------
 +element_container  uvm_report_message_element_container  -     @2331                  
 +  step             step                                  -     @2171                  
 +    keyword        string                                4     When                   
 +    text           string                                23    I print 'Hello, World!'
 +    argument       object                                -     <null>                 
 +--------------------------------------------------------------------------------------
 +

The log file from UVM 1.1 has much the same information, but simpler formatting:

# UVM_INFO bathtub/src/bathtub_pkg/gherkin_document_runner/gherkin_document_runner.svh(597) @ 0: bathtub [runner] When I print 'Hello, World!'
# UVM_INFO bathtub/src/bathtub_pkg/gherkin_document_runner/gherkin_document_runner.svh(181) @ 0: bathtub [bathtub_pkg.gherkin_document_runner.start_step] When I print 'Hello, World!'
# UVM_INFO bathtub/src/bathtub_pkg/step_nurture.svh(65) @ 0: reporter [step_attributes] 
# UVM_INFO bathtub/src/bathtub_pkg/step_nurture.svh(66) @ 0: reporter [step_attributes] runtime_keyword:When
# UVM_INFO bathtub/src/bathtub_pkg/step_nurture.svh(67) @ 0: reporter [step_attributes] text:I print 'Hello, World!'
# UVM_INFO bathtub/src/bathtub_pkg/step_nurture.svh(68) @ 0: reporter [step_attributes] argument:null