UVM Tutorial for Candy Lovers – 9. Register Abstraction

Last Updated on November 6, 2016

This post will explain how to use the UVM Register Abstraction Layer (RAL) to generate register transactions. The figure below shows the verification platform used for this post. Among other things, the jelly_bean_reg_block, the jelly_bean_reg_adapter, and the jelly_bean_reg_predictor are the classes used for the register abstraction.

Verification Platform

The figure below shows the diagram of the RAL-related classes. The standard UVM classes are shown in pink, while the jelly-bean classes are shown in light blue. The diagram looks busy, but bear in mind that I will explain each jelly-bean class one by one.

Diagram of the Jelly-Bean-Register Related Classes
Diagram of the Jelly-Bean-Register Related Classes

Register Definitions

In the previous posts, the DUT had no accessible registers. We are going to add the registers that hold jelly-bean recipe information and its taste. We will also add a command input port to the DUT so that we can write a jelly-bean recipe to the register and read its taste. The figure below shows the register definition of the DUT.

DUT Registers

The source code of the DUT (jelly_bean_taster) is shown below. When the command input is WRITE, the values of flavor, color, sugar_free, and sour input ports are written to the RECIPE register (line 22 to 25). When the command input is READ, the TASTE register is read out and the taste output is driven accordingly (line 27).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module jelly_bean_taster( jelly_bean_if.slave_mp jb_slave_if );
   import jelly_bean_pkg::*;
 
   reg [2:0] flavor;
   reg [1:0] color;
   reg       sugar_free;
   reg       sour;
   reg [1:0] command;
   reg [1:0] taste;
 
   initial begin
      flavor     = 0;
      color      = 0;
      sugar_free = 0;
      sour       = 0;
      command    = 0;
      taste      = 0;
   end
 
   always @ ( posedge jb_slave_if.clk ) begin
      if ( jb_slave_if.command == jelly_bean_types::WRITE ) begin
         flavor     < = jb_slave_if.flavor;         color      <= jb_slave_if.color;         sugar_free <= jb_slave_if.sugar_free;         sour       <= jb_slave_if.sour;      end else if ( jb_slave_if.command == jelly_bean_types::READ ) begin
         jb_slave_if.taste <= taste;      end
   end
 
   always @ ( posedge jb_slave_if.clk ) begin
      if ( jb_slave_if.flavor == jelly_bean_types::CHOCOLATE &&
           jb_slave_if.sour ) begin
         taste <= jelly_bean_types::YUCKY;
      end else if ( jb_slave_if.flavor != jelly_bean_types::NO_FLAVOR ) begin
         taste <= jelly_bean_types::YUMMY;
      end
   end
endmodule: jelly_bean_taster

Register Model

The model of the RECIPE register is defined by extending the uvm_reg class. Each field of the register is defined as a uvm_reg_field (line 4 to 7). The fields are configured in the build function. Note that the name, build, is used for convenience. Do not confuse it with the build_phase of the uvm_component because the uvm_reg is not a uvm_component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class jelly_bean_recipe_reg extends uvm_reg;
   `uvm_object_utils( jelly_bean_recipe_reg )
 
   rand uvm_reg_field flavor;   rand uvm_reg_field color;   rand uvm_reg_field sugar_free;   rand uvm_reg_field sour; 
   constraint flavor_color_con {
      flavor.value != jelly_bean_types::NO_FLAVOR;
      flavor.value == jelly_bean_types::APPLE
                   -> color.value != jelly_bean_types::BLUE;
      flavor.value == jelly_bean_types::BLUEBERRY
                   -> color.value == jelly_bean_types::BLUE;
      flavor.value < = jelly_bean_types::CHOCOLATE;
   }
 
   function new( string name = "jelly_bean_recipe_reg" );
      super.new( .name( name ), .n_bits( 7 ), .has_coverage( UVM_NO_COVERAGE ) );
   endfunction: new
 
   virtual function void build();
      flavor = uvm_reg_field::type_id::create( "flavor" );
      flavor.configure( .parent                 ( this ),
                        .size                   ( 3    ),
                        .lsb_pos                ( 0    ),
                        .access                 ( "WO" ),
                        .volatile               ( 0    ),
                        .reset                  ( 0    ),
                        .has_reset              ( 1    ),
                        .is_rand                ( 1    ),
                        .individually_accessible( 0    ) );
 
      color = uvm_reg_field::type_id::create( "color" );
      color.configure( .parent                 ( this ),
                       .size                   ( 2    ),
                       .lsb_pos                ( 3    ),
                       .access                 ( "WO" ),
                       .volatile               ( 0    ),
                       .reset                  ( 0    ),
                       .has_reset              ( 1    ),
                       .is_rand                ( 1    ),
                       .individually_accessible( 0    ) );
 
      sugar_free = uvm_reg_field::type_id::create( "sugar_free" );
      sugar_free.configure( .parent                 ( this ),
                            .size                   ( 1    ),
                            .lsb_pos                ( 5    ),
                            .access                 ( "WO" ),
                            .volatile               ( 0    ),
                            .reset                  ( 0    ),
                            .has_reset              ( 1    ),
                            .is_rand                ( 1    ),
                            .individually_accessible( 0    ) );
 
      sour = uvm_reg_field::type_id::create( "sour" );
      sour.configure( .parent                 ( this ),
                      .size                   ( 1    ),
                      .lsb_pos                ( 6    ),
                      .access                 ( "WO" ),
                      .volatile               ( 0    ),
                      .reset                  ( 0    ),
                      .has_reset              ( 1    ),
                      .is_rand                ( 1    ),
                      .individually_accessible( 0    ) );
   endfunction: build
endclass: jelly_bean_recipe_reg

The model of the TASTE register is similarly defined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class jelly_bean_taste_reg extends uvm_reg;
   `uvm_object_utils( jelly_bean_taste_reg )
 
   rand uvm_reg_field taste; 
   function new( string name = "jelly_bean_taste_reg" );
      super.new( .name( name ), .n_bits( 2 ), .has_coverage( UVM_NO_COVERAGE ) );
   endfunction: new
 
   virtual function void build();
      taste = uvm_reg_field::type_id::create("taste");
      taste.configure( .parent                 ( this ),
                       .size                   ( 2    ),
                       .lsb_pos                ( 0    ),
                       .access                 ( "RO" ),
                       .volatile               ( 1    ),
                       .reset                  ( 0    ),
                       .has_reset              ( 1    ),
                       .is_rand                ( 1    ),
                       .individually_accessible( 1    ) );
   endfunction: build
endclass: jelly_bean_taste_reg

Register Block

The jelly_bean_reg_block contains the two registers created above and defines a register map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class jelly_bean_reg_block extends uvm_reg_block;
   `uvm_object_utils( jelly_bean_reg_block )
 
   rand jelly_bean_recipe_reg jb_recipe_reg;
   rand jelly_bean_taste_reg  jb_taste_reg;
   uvm_reg_map                reg_map;
 
   function new( string name = "jelly_bean_reg_block" );
      super.new( .name( name ), .has_coverage( UVM_NO_COVERAGE ) );
   endfunction: new
 
   virtual function void build();
      jb_recipe_reg = jelly_bean_recipe_reg::type_id::create( "jb_recipe_reg" );      jb_recipe_reg.configure( .blk_parent( this ) );      jb_recipe_reg.build(); 
      jb_taste_reg = jelly_bean_taste_reg::type_id::create( "jb_taste_reg" );      jb_taste_reg.configure( .blk_parent( this ) );      jb_taste_reg.build(); 
      reg_map = create_map( .name( "reg_map" ), .base_addr( 8'h00 ),                            .n_bytes( 1 ), .endian( UVM_LITTLE_ENDIAN ) );      reg_map.add_reg( .rg( jb_recipe_reg ), .offset( 8'h00 ), .rights( "WO" ) );      reg_map.add_reg( .rg( jb_taste_reg  ), .offset( 8'h01 ), .rights( "RO" ) );      lock_model(); // finalize the address mapping
   endfunction: build
 
endclass: jelly_bean_reg_block

Register Adapter

The jelly_bean_reg_adapter class provides two functions to convert between a uvm_reg_bus_op and a jelly_bean_transaction. The reg2bus function converts a uvm_reg_bus_op into a jelly_bean_transaction, whereas the bus2reg function converts a jelly_bean_transaction back to a uvm_reg_bus_op.

The uvm_reg_adapter is a uvm_object, not a uvm_component.

Advanced Topic: Even though a uvm_sequence is a uvm_sequence_item, you cannot let the reg2bus() function create a uvm_sequence and return it. This is because when a register is read/written, the uvm_reg_map calls uvm_sequence_base::start_item() passing the object returned by the reg2bus(), but the start_item() does not expect a uvm_sequence. This will cause a fatal error. For more details, please see Register Access Methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class jelly_bean_reg_adapter extends uvm_reg_adapter;
   `uvm_object_utils( jelly_bean_reg_adapter )
 
   function new( string name = "" );
      super.new( name );
      supports_byte_enable = 0;
      provides_responses   = 0;
   endfunction: new
 
   virtual function uvm_sequence_item reg2bus( const ref uvm_reg_bus_op rw );
      jelly_bean_transaction jb_tx
        = jelly_bean_transaction::type_id::create("jb_tx");
 
      if ( rw.kind == UVM_READ )       jb_tx.command = jelly_bean_types::READ;      else if ( rw.kind == UVM_WRITE ) jb_tx.command = jelly_bean_types::WRITE;      else                             jb_tx.command = jelly_bean_types::NO_OP;      if ( rw.kind == UVM_WRITE )        { jb_tx.sour, jb_tx.sugar_free, jb_tx.color, jb_tx.flavor } = rw.data;      return jb_tx;   endfunction: reg2bus
 
   virtual function void bus2reg( uvm_sequence_item bus_item,
                                  ref uvm_reg_bus_op rw );
      jelly_bean_transaction jb_tx;
 
      if ( ! $cast( jb_tx, bus_item ) ) begin
         `uvm_fatal( get_name(),
                     "bus_item is not of the jelly_bean_transaction type." )
         return;
      end
 
      rw.kind = ( jb_tx.command == jelly_bean_types::READ ) ? UVM_READ : UVM_WRITE;      if ( jb_tx.command == jelly_bean_types::READ )        rw.data = jb_tx.taste;      else if ( jb_tx.command == jelly_bean_types::WRITE )        rw.data = { jb_tx.sour, jb_tx.sugar_free, jb_tx.color, jb_tx.flavor };      rw.status = UVM_IS_OK;   endfunction: bus2reg
 
endclass: jelly_bean_reg_adapter

Register Predictor

The register predictor updates the values of the register model based on observed bus transactions. As the jelly-bean register predictor is not involved with any kind of extended features, the uvm_reg_predictor is used as is.

typedef uvm_reg_predictor#( jelly_bean_transaction ) jelly_bean_reg_predictor;

Agent

The jelly_bean_agent instantiates the jelly_bean_reg_adapter (line 35).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class jelly_bean_agent extends uvm_agent;
   `uvm_component_utils( jelly_bean_agent )
 
   uvm_analysis_port#( jelly_bean_transaction ) jb_ap;
 
   jelly_bean_agent_config jb_agent_cfg;
   jelly_bean_sequencer    jb_seqr;
   jelly_bean_driver       jb_drvr;
   jelly_bean_monitor      jb_mon;
   jelly_bean_reg_adapter  jb_reg_adapter;
 
   function new( string name, uvm_component parent );
      super.new( name, parent );
   endfunction: new
 
   function void build_phase( uvm_phase phase );
      super.build_phase( phase );
 
      if ( ! uvm_config_db#( jelly_bean_agent_config )::get( .cntxt( this ),
                                                             .inst_name ( "" ),
                                                             .field_name( "jb_agent_cfg" ),
                                                             .value( jb_agent_cfg ))) begin
         `uvm_error( "jelly_bean_agent", "jb_agent_cfg not found" )
      end
 
      jb_ap = new( .name( "jb_ap" ), .parent( this ) );
      if ( jb_agent_cfg.active == UVM_ACTIVE ) begin
         jb_seqr = jelly_bean_sequencer::type_id::create( .name( "jb_seqr" ),
                                                          .parent( this ) );
         jb_drvr = jelly_bean_driver::type_id::create( .name( "jb_drvr" ),
                                                       .parent( this ) );
      end
      jb_mon = jelly_bean_monitor::type_id::create( .name( "jb_mon" ),
                                                    .parent( this ) );
      jb_reg_adapter = jelly_bean_reg_adapter::type_id::create( .name( "jb_reg_adapter" ) );   endfunction: build_phase
 
   function void connect_phase( uvm_phase phase );
      super.connect_phase( phase );
 
      jb_mon.jb_if = jb_agent_cfg.jb_if;
      if ( jb_agent_cfg.active == UVM_ACTIVE ) begin
         jb_drvr.seq_item_port.connect( jb_seqr.seq_item_export );
         jb_drvr.jb_if = jb_agent_cfg.jb_if;
      end
      jb_mon.jb_ap.connect( jb_ap );
   endfunction: connect_phase
endclass: jelly_bean_agent

Environment Configuration

The jelly_bean_env_config has a handle to the jelly_bean_reg_block so that the jelly_bean_env can access the register model.

1
2
3
4
5
6
7
8
9
10
11
12
13
class jelly_bean_env_config extends uvm_object;
   `uvm_object_utils( jelly_bean_env_config )
 
   bit has_jb_agent = 1;
   bit has_jb_sb    = 1;
 
   jelly_bean_agent_config jb_agent_cfg;
   jelly_bean_reg_block    jb_reg_block; 
   function new( string name = "" );
      super.new( name );
   endfunction: new
endclass: jelly_bean_env_config

Environment

The jelly_bean_env instantiates the jelly_bean_agent and the jelly_bean_reg_predictor (line 28 to 31), then connects register-related objects:

Firstly, in the connect_phase(), the set_sequencer() function associates the jelly-bean sequencer and the register adapter with the register map (line 45 and 46). The set_sequencer() must be called before starting the sequence based on a uvm_reg_sequence. A register block may have more than one register map.

Secondly, the register map and the register adapter are associated with the register predictor (line 49 and 50). The register predictor will use the register map and the register adapter to convert a jelly_bean_transaction back to a register operation.

Lastly, the register predictor is connected to the agent to subscribe the jelly_bean_transactions from the agent (line 51).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class jelly_bean_env extends uvm_env;
   `uvm_component_utils( jelly_bean_env )
 
   jelly_bean_env_config    jb_env_cfg;
   jelly_bean_agent         jb_agent;
   jelly_bean_fc_subscriber jb_fc_sub;
   jelly_bean_scoreboard    jb_sb;
   jelly_bean_reg_predictor jb_reg_predictor;
 
   function new( string name, uvm_component parent );
      super.new( name, parent );
   endfunction: new
 
   function void build_phase( uvm_phase phase );
      super.build_phase( phase );
      if ( ! uvm_config_db#( jelly_bean_env_config )::get
           ( .cntxt( this ),
             .inst_name( "" ),
             .field_name( "jb_env_cfg" ),
             .value( jb_env_cfg ) ) ) begin
         `uvm_fatal( get_name(), "jb_env_cfg not found" )
      end
 
      uvm_config_db#( jelly_bean_agent_config )::set( .cntxt( this ),
                                                      .inst_name( "jb_agent*" ),
                                                      .field_name( "jb_agent_cfg" ),
                                                      .value( jb_env_cfg.jb_agent_cfg ) );
      jb_agent = jelly_bean_agent::type_id::create( .name( "jb_agent" ),                                                    .parent( this ) );      jb_reg_predictor = jelly_bean_reg_predictor::type_id::create( .name( "jb_reg_predictor" ),                                                                    .parent( this ) );      if ( jb_env_cfg.has_jb_sb ) begin
         jb_sb = jelly_bean_scoreboard::type_id::create( .name( "jb_sb" ),
                                                         .parent( this ) );
      end
      jb_fc_sub = jelly_bean_fc_subscriber::type_id::create( .name( "jb_fc_sub" ),
                                                             .parent( this ) );
    endfunction: build_phase
 
   function void connect_phase( uvm_phase phase );
      super.connect_phase( phase );
      jb_agent.jb_ap.connect( jb_fc_sub.analysis_export );
      jb_agent.jb_ap.connect( jb_sb.jb_analysis_export );
      if ( jb_env_cfg.jb_reg_block.get_parent() == null ) begin // if the top-level env
         jb_env_cfg.jb_reg_block.reg_map.set_sequencer( .sequencer( jb_agent.jb_seqr ),                                                        .adapter( jb_agent.jb_reg_adapter ) );      end
      jb_env_cfg.jb_reg_block.reg_map.set_auto_predict( .on( 0 ) );
      jb_reg_predictor.map     = jb_env_cfg.jb_reg_block.reg_map;      jb_reg_predictor.adapter = jb_agent.jb_reg_adapter;      jb_agent.jb_ap.connect( jb_reg_predictor.bus_in );   endfunction: connect_phase
 
endclass: jelly_bean_env

Base Test

The base test instantiates a jelly_bean_reg_block (line 16 and 17) and stores its handle in the jelly_bean_env_config (line 19 and 20).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class jelly_bean_base_test extends uvm_test;
   `uvm_component_utils( jelly_bean_base_test )
 
   jelly_bean_env          jb_env;
   jelly_bean_env_config   jb_env_cfg;
   jelly_bean_agent_config jb_agent_cfg;
   jelly_bean_reg_block    jb_reg_block;
 
   function new( string name, uvm_component parent );
      super.new( name, parent );
   endfunction: new
 
   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
 
      jb_reg_block = jelly_bean_reg_block::type_id::create( "jb_reg_block" );      jb_reg_block.build(); 
      jb_env_cfg = jelly_bean_env_config::type_id::create( "jb_env_cfg" );      jb_env_cfg.jb_reg_block = jb_reg_block; 
      jb_agent_cfg = jelly_bean_agent_config::type_id::create( "jb_agent_cfg" );
 
      if ( ! uvm_config_db#( virtual jelly_bean_if )::get( .cntxt( this ),
                                                           .inst_name( "" ),
                                                           .field_name( "jb_if" ),
                                                           .value( jb_agent_cfg.jb_if ))) begin
         `uvm_error( "jelly_bean_test", "jb_if not found" )
      end
 
      jb_env_cfg.jb_agent_cfg = jb_agent_cfg;
 
      uvm_config_db#(jelly_bean_env_config)::set( .cntxt( null ),
                                                  .inst_name( "*" ),
                                                  .field_name( "jb_env_cfg" ),
                                                  .value( jb_env_cfg ) );
      jb_env = jelly_bean_env::type_id::create( .name( "jb_env" ),
                                                .parent( this ) );
   endfunction: build_phase
 
   virtual function void start_of_simulation_phase( uvm_phase phase );
      super.start_of_simulation_phase( phase );
      uvm_top.print_topology();
   endfunction: start_of_simulation_phase
 
endclass: jelly_bean_base_test

Sequence without Register Abstraction

We have created all the components by now and we are ready to create a register sequence. But before doing that, let’s create a “regular” sequence without using the register abstraction for comparison. The jelly_bean_sequence is a sequence to generate a sour-green-apple jelly bean. The body of the sequence creates a jelly_bean_transaction, which will be used by the driver to do pin wiggling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class jelly_bean_sequence extends uvm_sequence#( jelly_bean_transaction );
   `uvm_object_utils( jelly_bean_sequence )
 
   function new( string name = "" );
      super.new( name );
   endfunction: new
 
   task body();
      jelly_bean_transaction jb_tx;
      jb_tx = jelly_bean_transaction::type_id::create( .name( "jb_tx" ),
                                                       .contxt( get_full_name()));
      start_item( jb_tx );
      jb_tx.flavor     = jelly_bean_types::APPLE;      jb_tx.color      = jelly_bean_types::GREEN;      jb_tx.sugar_free = 0;      jb_tx.sour       = 1;      finish_item(jb_tx);
   endtask: body
endclass: jelly_bean_sequence

Sequence Using Register Abstraction

The jelly_bean_reg_sequence is another sequence to generate a sour-green-apple jelly bean, but using the register abstraction. This sequence is extended from the uvm_reg_sequence class so that we can use the convenience functions such as write_reg() and read_reg(). The body of the sequence writes a recipe (line 23) to the RECIPE register, then reads back its taste from the TASTE register (line 24). Note that we do not create a jelly_bean_transaction in the sequence. The register adapter will convert the register operations into the corresponding jelly_bean_transactions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class jelly_bean_reg_sequence extends uvm_reg_sequence;
   `uvm_object_utils( jelly_bean_reg_sequence )
 
   function new( string name = "" );
      super.new( name );
   endfunction: new
 
   virtual task body();
      jelly_bean_reg_block       jb_reg_block;
      jelly_bean_types::flavor_e flavor;
      jelly_bean_types::color_e  color;
      bit                        sugar_free;
      bit                        sour;
      uvm_status_e               status;
      uvm_reg_data_t             value;
 
      $cast( jb_reg_block, model );
      flavor     = jelly_bean_types::APPLE;
      color      = jelly_bean_types::GREEN;
      sugar_free = 0;
      sour       = 1;
 
      write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor } );      read_reg ( jb_reg_block.jb_taste_reg,  status, value );   endtask: body
 
endclass: jelly_bean_reg_sequence

Register Test

The jelly_bean_reg_test creates a jelly_bean_reg_sequence we have just created and starts the sequence (line 12 to 15).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class jelly_bean_reg_test extends jelly_bean_base_test;
   `uvm_component_utils( jelly_bean_reg_test )
 
   function new( string name, uvm_component parent );
      super.new( name, parent );
   endfunction: new
 
   task main_phase( uvm_phase phase );
      jelly_bean_reg_sequence jb_reg_seq;
 
      phase.raise_objection( .obj( this ) );
      jb_reg_seq = jelly_bean_reg_sequence::type_id::create( .name( "jb_reg_seq" ),                                                             .contxt( get_full_name()));      jb_reg_seq.model = jb_reg_block;      jb_reg_seq.start( .sequencer( jb_env.jb_agent.jb_seqr ) ); 
      #100ns;
      phase.drop_objection( .obj( this ) );
 
   endtask: main_phase
endclass: jelly_bean_reg_test

Simulation

Let’s look at a simulation result. The simulation successfully generated a sour-green apple and read back its taste from the DUT.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UVM_INFO jb3.sv(727) @ 30: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
---------------------------------------------------------
Name          Type                         Size  Value
---------------------------------------------------------
jb_tx         jelly_bean_transaction       -     @7929
  flavor      jelly_bean_types::flavor_e   3     APPLE  color       jelly_bean_types::color_e    2     GREEN  sugar_free  integral                     1     'h0  sour        integral                     1     'h1  command     jelly_bean_types::command_e  2     WRITE  taste       jelly_bean_types::taste_e    2     NO_TASTE
---------------------------------------------------------
 
UVM_INFO jb3.sv(727) @ 60: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
----------------------------------------------------------
Name          Type                         Size  Value
----------------------------------------------------------
jb_tx         jelly_bean_transaction       -     @7928
  flavor      jelly_bean_types::flavor_e   3     NO_FLAVOR
  color       jelly_bean_types::color_e    2     NO_COLOR
  sugar_free  integral                     1     'h0
  sour        integral                     1     'h0
  command     jelly_bean_types::command_e  2     READ  taste       jelly_bean_types::taste_e    2     YUMMY----------------------------------------------------------

I hope this tutorial helped you to understand the UVM Register Abstraction.

Get source code

EDA Playground

You can view and run the code on EDA Playground.

116 thoughts on “UVM Tutorial for Candy Lovers – 9. Register Abstraction”

  1. I am interested in how you create your diagrams: are the block diagrams from visio or some other tool? Also, the class hierarchy looks like the dot-language. Am I correct? If so, is this automatically generated (e.g. from within NaturalDocs?).

      1. Is the arrow direction from jelly_bean_recipie_reg to uvm_reg_map correct?
        Shouldn’t it be vice versa, meaning that uvm_reg_map depends on jelly_bean_recipie_reg?

  2. Hi,

    I Am Anand, Trying to model UVM RAL Model for my IP by following your example.

    I am following your blog, It was very useful for me to understand & trying to mimic same in my environment, Here i am facing following problem:
    * When i am setting the sequencer & adapter, like
    env_cfg0.reg_block.reg_map.set_sequencer( .sequencer(apb_env0.master.sequencer), .adapter(adapter0) )
    says, null pointer at this line.

    I am having a virtual sequence to control multiple interfaces in my env.

    Could you help me out to fix the above issue.

    Let me know you if require further details to understand the issue better.
    Thanks in advance…

    Regards,
    Anand

    1. Hi Anand,

      I am glad that my blog was useful.
      Regarding the null pointer problem, I am not sure which object is null by just looking at the line. Have you instantiated the apb_env0.master.sequencer and the adapter0? If you could paste more lines and/or the error message, I might be able to figure out.

  3. Hi Shimizu,

    I could figure out problem in the above line, As i was not calling the “reg_block.build()”, it was reporting null pointer error.

    Now i am deviating problem statement to uvm_reg_sequence level, where i am casting ‘model’ with ‘reg_block’, i am getting null pointer access.

    error msg:
    ncsim: *E,TRNULLID: NULL pointer dereference.
              File:my_reg_seq_lib.sv, line = 21, pos = 12
             Scope: worklib.$unit_0x37a3bb58::my_reg_seq@14758_4.body
    
    =====================source code====================================
    //---------------------my_reg_seq.sv--------------------------------------------
    class my_reg_seq extends uvm_reg_sequence; 
    
      `uvm_object_utils(my_reg_seq)
    
      c3a_reg_block  reg_block;
      uvm_status_e   status;
      uvm_reg_data_t value;
      extern function new(string name = "my_reg_seq");
    
      virtual task body();
        $cast(reg_block, model); // line 21
        reg_block.intr_en_reg.write(status, 'h10);
        reg_block.intr_en_reg.read(status, value);
      endtask: body
    endclass: my_reg_seq
    
    //------------------virtual_seq.sv-----------------------------------------------
    class my_virtual_seq extends uvm_sequence;
      `uvm_object_utils(my_virtual_seq)
      c3a_reg_block reg_block;
      // RAL Seq.
      c3a_reg_seq reg_seq0;
      // my ip Sequences
      my_traffic_gen_seq      traffic_gen_seq;
    
      // virtual seq. body definition
      virtual task body();
        `uvm_do_on_with(reg_seq0, p_sequencer.apb_mst_seqr, {reg_seq0.model == reg_block;} )
        `uvm_do_on(traffic_gen_seq, p_sequencer.c3a_seqr)
      endtask : body
    endclass : my_virtual_seq
    
    //-----------------base_test.sv------------------------------------------------
    class my_base_test extends uvm_test;
      `uvm_component_utils(my_base_test)
      my_top_env top_env0;
      my_top_env_config top_config0;
      c3a_reg_block reg_block;
      virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        reg_block = c3a_reg_block::type_id::create("reg_block", this);
        reg_block.build();
    
        top_config0 = my_top_env_config::type_id::create("top_config0", this);
        top_config0.reg_block = reg_block;
        uvm_config_db #(my_top_env_config)::set(
        				             .cntxt( this ),
    				             .inst_name( "*" ),
    				             .field_name( "top_cfg0" ),
    				             .value( top_config0 ) 
    				             );
        top_env0 = my_top_env::type_id::create(
        					    .name( "top_env0" ),
    					    .parent( this ) 
    					    );
      endfunction : build_phase
    endclass : my_base_test
    
    //-----------------base_test.sv------------------------------------------------
    class my_ral_test extends my_base_test;
      `uvm_component_utils(my_ral_test)
      my_virtual_seq v_reg_seq0;
    
      virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        v_reg_seq0 = my_virtual_seq::type_id::create("v_reg_seq0", this);
        v_reg_seq0.reg_block = reg_block;
      endfunction : build_phase
    
      // Start the virtual sequence on virtual sequencer
      task run_phase(uvm_phase phase);
        v_reg_seq0.start(
    		   .sequencer( top_env0.virtual_seqr )
    		   );
      endtask : run_phase
    endclass : my_ral_test
    =========================================================
    

    Thanks and Regards,
    Anand

      1. Hey Kiesuke,

        I had few questions:

        1)Why is the DUT coding (especially for read) is different on this page with respect to the same example on GitHub?

        2)Also I want to ask, for read, the value that is returned has to be given by driver? But in real scenarios this doesn’t seem practical.

        Thank you,

        Regards,
        Muneeb Ulla Shariff

          1. Good catch. I changed the code in GitHub so that the DUT always outputs the taste without a READ command.
          2. The driver captures read values (taste) from the jelly_bean_if. It does not drive the taste.
  4. No that was not the problem Manjunath, I forgot to call the build() method of reg_block in the env.
    so what happened was when i am trying to access the registers inside the reg_seq to perform write/read operations, i was getting null pointer access.

    Hope you got the problem statement…

    Thanks,
    Anand Kola

  5. Hi,
    I need help in getting the functional coverage for register model. I generated Register model package and covergroups for each register using the Register Assistant tool from Mentor Graphics. Small code snippet for single register covergroup as shown below:

    --------------------------
    Class register0_reg extends uvm_reg;
    `uvm_object_utils(register0_reg);
    
    rand uvm_reg_field port;
    rand uvm_reg_field ack;
    rand uvm_reg_field response;
    
    Covergroup c1;
    port: coverpoint port.value[4:0];
    ack : coverpoint ack.value[5:0];
    response: coverpoint response.value[1:0];
    endgroup
    
    function new(string name=" ");
    super.new(name,build_coverage(UVM_CVR_FIELD_VALS));
    add_coverage(build_coverage(UVM_CVR_FIELD_VALS));
    if(has_coverage(UVM_CVR_FIELD_VALS)
    c1=new();
    endfunction
    
    virtual function void sample_values();
    super.sample_values();
    if(get_coverage(UVM_CVR_FIELD_VALS))
    c1.sample();
    endfunction
    
    virtual function void build();
    port = uvm_reg_field::type_id::create("port");
    ack = uvm_reg_field::type_id::create("ack");
    response = uvm_reg_field::type_id::create("response");
    
    port.configure();
    ack.configure();
    response.configure();
    endfunction
    
    endclass
    ------------------------------------
    

    In the base test class, I have written the following code:

    function void proj_test_base::build_phase(uvm_phase build);
    
    // Register model
    
    uvm_reg::include_coverage("*", UVM_CVR_FIELD_VALS);
    proj_rm = proj_reg_block::type_id::create("proj_rm");
    proj_rm.build();
    endfunction
    

    In the makefile, I used the following command:

    vsim -c tb_mem -coverage -do "run -a; coverage save cov.ucdb; vcover report -html cov.ucdb ;q"
    

    But, Questasim tool was not able to generate any UCDB files related to coverage.
    Is there any code should be written to get the Register coverage? Please help me…

    Thanks,
    Sivaji

  6. I tried by calling proj_rm.sample() from my testbench, but no file generated related to coverage.
    Please suggest me, if there is any option.

  7. Hi,

    I have multiple bus interfaces in the testbench, i control sequences through common virtual sequencer. Now i need to incorporate RAL at this bus virtual sequencer level. How to do that ? ie. using a single generic adaptor for all interfaces.

    1. Hi Gayathri,

      I assume your DUT has two bus interfaces (bus_A and bus_B) and you can access the registers in the DUT through the bus_A and the bus_B. If that is the case, I would suggest:

      1. Create a register block (I call it reg_block) as usual.
      2. Create two register maps (reg_map_A and reg_map_B) in the register block using create_map(). The reg_map_A defines the address map of the registers using the bus_A, while the reg_map_B defines the address map of the registers using the bus_B.
      3. Create two register adapters (reg_adapter_A and reg_adapter_B). The reg_adapter_A converts a register operation into a bus_A transaction, while the reg_adapter_B converts a register operation into a bus_B transaction.
      4. Associate the register adapters to the register maps using set_sequencer() so that the reg_map_A will use the reg_adapter_A and the reg_map_B will use the reg_adapter_B.
      5. In your virtual sequence, specify which register map to use. For example, if you want to read reg_X through bus_A, do reg_block.reg_X.read(status, value, .map(reg_block.reg_map_A)).

      Let me know if you need further clarification.

      1. Hi Keisuke,

        In the above case what happens if both drivers initiate a write transaction to same register with different address map, Which transaction will get a priority? Is testbanch has to be smart enough to do arbitration, If so then in which component/object we need code this arbitration logic?

  8. Hi Keisuke,

    If I have to write more than 2 registers in the reg2bus or bus2reg methods under same condition, like in adapter class,

    virtual function void bus2reg( uvm_sequence_item bus_item,
                                      ref uvm_reg_bus_op rw );
          jelly_bean_transaction jb_tx;
    ...
    ...
     
          rw.kind = ( jb_tx.command == jelly_bean_types::READ ) ? UVM_READ : UVM_WRITE;
          if ( jb_tx.command == jelly_bean_types::READ )
            rw.data = jb_tx.taste;
    --->  //* WANT TO WRITE ANOTHER REGISTER HERE *//
    
          else if ( jb_tx.command == jelly_bean_types::WRITE )
            rw.data = { jb_tx.sour, jb_tx.sugar_free, jb_tx.color, jb_tx.flavor };
          rw.status = UVM_IS_OK;
       endfunction: bus2reg
    

    In that case, how do we manage with rw.data value for two registers ?

    How can we define a bunch of registers that adopt the similar properties and are to be written under same condition. Eg: During reset phase.

    Thank You..!
    S

    1. As far as I know, you cannot associate a bus_item to more than one rw in the bus2reg. If all you need is to set some values to multiple registers, you could do:

      uvm_reg regs[$];
      uvm_status_e status;
      
      your_reg_block.get_registers( regs );
      foreach ( regs[i] ) regs[i].set( some_value );
      your_reg_block.update( status );
      

      As all your registers are the descendants of uvm_reg, you can set the some_value without knowing the actual type of the registers (the beauty of polymorphism). Let me know if I did not answer your question.

  9. Hi,
    I was wondering that the reg_adapter may be better not to included in agent, instead it may be included in reg model. the adapter looks more close to reg model. Am I right?

    1. The idea behind the reg adapter is to convert a generic uvm_reg_bus_op to/from a specific bus transaction. For example, the jelly_bean_reg_adapter converts a uvm_reg_bus_op to/from a jelly_bean_transaction, but you might want to create another adapter for like gummy_bear_transaction later. Since the adapter is dependent of underlying bus transaction, I put it in the agent.

  10. I am using Register model in my project, now I have to create an adaptor class. But the problem is to convert the bus_sequence_item into reg_item, I have to see what is value already set in the registers according to which the conversion have to be done. I can’t take handle of configuration in apaptor class because its an object. What is the possible solution for this.

      1. Thank you for your time Keisuke.
        Passing handle will work, but I was thinking if there is something similar to uvm_config_db :: get which is for components and not objects, to make it(adapter) more independent.
        My concern is that in one of the registers there are fields which tells how the data is distributed on the data line, it can be either serial or staggered and other such things. Now when the further transactions take place, I have to convert the data and store in register accordingly.

        1. Although the uvm_reg_adapter is a uvm_object, we can still use the uvm_config_db. For example, we could create a new configuration setting in a test like:

          uvm_config_db#( jelly_bean_env_config )::set( .cntxt     ( null ),
                                                        .inst_name ( "jb_reg_adapter" ),
                                                        .field_name( "jb_env_cfg" ),
                                                        .value     (  jb_env_cfg  ) );
          

          Then, in the jelly_bean_reg_adapter, we could get the value by:

          uvm_config_db#( jelly_bean_env_config )::get( .cntxt     ( null ),
                                                        .inst_name ( get_name() ),
                                                        .field_name( "jb_env_cfg" ),
                                                        .value     (  jb_env_cfg  ) );
          

          Note that since the uvm_reg_adapter is not a uvm_component, it does not have a hierarchical name. Thus, we used null as the context. The get can find jb_env_cfg as long as the inst_name matches.

  11. Hi Keisuke,

    I was wondering why in reg2apb() function of uvm_reg_adapter we have arguments with const ref uvm_reg_bus_op, what is the significance of using const ref there !! Similarly in bus2reg() why only ref is enough .. Plase explain

    Best Regards,
    Vittal

    1. pure virtual function uvm_sequence_item reg2bus( const ref uvm_reg_bus_op rw )
      

      Since the uvm_reg_bus_op is a structure, not a class handle, each member of the structure will be copied (pass by value) when it is passed as an argument to a function. To avoid the expensive copy, ref (pass by reference) is used, and to protect the rw from being modified by the reg2bus, const is also used.

      pure virtual function void bus2reg( uvm_sequence_item bus_item, ref uvm_reg_bus_op rw )
      

      Unless ref is used, the caller of bus2reg won’t see the change of rw because pass by value is the default mechanism for passing arguments to functions. You cannot use const here because you need to modify the rw.

  12. Hi,
    Thanks for your useful tutorial. I am new in UVM. I want to know the meaning and importance of ” $cast( jb_reg_block, model ) ” this statement.what is this “model”? .

    Regards,
    Kousik

    1. The model is a property of uvm_reg_sequence class (the base class of the jelly_bean_reg_sequence). The model holds the register block abstraction the uvm_reg_sequence executes on. We used our jb_reg_block as the model (please see the line 14 of jelly_bean_reg_test). Since the type of model is uvm_reg_block, we needed the $cast( jb_reg_block, model ) to access the properties specific to the jb_reg_block, such as jb_recipe_reg and jb_taste_reg.

  13. Hi Keisuke,
    Great Article.
    I have one problem regarding Explicit Prediction method.
    I have created RAL Model for my UART Protocol and I made necessary connections for that.
    Everything is working fine.
    But the problem is, when is use explicit prediction method, the mirror value is not being updated.
    In Auto prediction, Its working Fine.
    For Explicit prediction, do I need to add some extra code to update the mirror value of registers.
    Please help me, I am struggling with this problem.

    1. Check the connection between your analysis port and the bus_in of your predictor. Does your monitor call write to the analysis port? Also check whether the bus2reg function of your adapter is called.

  14. Hi Keisuke,

    First of all, thank you for this page that makes order in all these uvm objects and how they work together.

    In my verification environment, the reads and writes of registers are done using a UART agent. The problem is that for each write or read of a register I need to generate multiple uart_trans items, because each register access consists of an entire message (header, command, address and data, and checksum).

    What is the way to associate the reg_adaptor with such a mechanism? will I need then a virtual agent that will process the UART transactions in a transaction layer?

    Thanks

    1. I am glad to hear that my blog was useful. For your UART, I would create a class that groups together the uart_trans like:

      class uart_trans_group extends uvm_sequence_item;
      // …
         uart_trans trans[];
      // …
      endclass

      The trans array will hold UART header, command, address, data, etc. Then, I would define the reg2bus function that converts a register read/write into this class and fills the trans array.

      For the driver side, I would create a layered sequencer; one to process the uart_trans_group and the other to process the uart_trans in the group. The downstream sequencer runs a sequence that breaks down a uart_trans_group into multiple uart_trans.

      Similarly for the predictor side, I would create a layered monitor; one to monitor UART signals and the other to group the uart_trans. There might be a better solution. I welcome any suggestions.
      UART reg adapter

    1. You can set rw.status = UVM_HAS_X in the bus2reg function of your reg adapter if the bus data contains X. But as far as I know, the RAL treats UVM_HAS_X as UVM_IS_OK, so there is no difference whether you use UVM_HAS_X or not.

      1. Hi Keisuke,

        Do you consider this is a bug in uvm? Why not fix uvm source code to handle x detection in register checking?

        BTW, thanks for the great work!

  15. Hi Keisuke,
    Can you explain a little about how the address of a register is get passed to a driver in a memory based system?
    I mean in the reg_map which I snipped as following, since really_bean_taster is not address based configuration system, the addresses’ of the two register don’t used in your example, but for a complex real application, how the address of the registers’ is transferred/passed to a driver?

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    reg_map.add_reg( .rg( jb_recipe_reg ), .offset( 8’h00 ), .rights( “WO” ) );
    reg_map.add_reg( .rg( jb_taste_reg ), .offset( 8’h01 ), .rights( “RO” ) );
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    Thanks,
    Rui Huang

    1. To make the address availble on the bus, define the address in the transaction:

      1
      2
      3
      4
      
      class jelly_bean_transaction extends uvm_sequence_item;
        rand bit[7:0] addr;  // ...
      endclass: jelly_bean_transaction

      Then, assign the addr in the functions of the reg adapter:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      class jelly_bean_reg_adapter extends uvm_reg_adapter;
        virtual function uvm_sequence_item reg2bus( const ref uvm_reg_bus_op rw );
          // ...
          jb_tx.addr = rw.addr;  endfunction: reg2bus
       
        virtual function void bus2reg( uvm_sequence_item bus_item, ref uvm_reg_bus_op rw );
          // ...
          rw.addr = jb_tx.addr;  endfunction: bus2reg
      endclass: jelly_bean_reg_adapter

      Based on the base-address (8'h00) and the offset (8'h00 or 8'h01) defined in the jelly_bean_reg_block, you will see either 8'h00 (jb_recipe_reg) or 8'h01 (jb_taste_reg) on the address bus.

  16. Hi Keisuke,
    I have been following you tutorial for some time now. Thanks for all the effort you have put into this tutorial. It is very beneficial for people like me.
    I just started going through this tutorial. I have a few questions.
    1) I am not able to understand how register modelling is helpful. I see that here the register values in register block follow the value in the design’s register. I am not able to figure out where the comparison for correctness is done.
    2) Also I see in the tutorial source you have provided tutorial_9.sv “https://github.com/cluelogic/uvm-tutorial-for-candy-lovers/blob/master/src/tutorial_9.sv” , you have coded a scoreboard component. In the class diagram it is missing. Is it required here?
    3) Under what circumstances do we use a register model?
    4) The reg predictor class, is it for predicting expected register values?

    Regards,
    Gautam

      1. Register data comparison is usually done outside of the RAL (you read a register and compare it against the expected value).
      2. The diagram shows the classes related to the RAL only.
      3. Any verifications that access registers. Tests become more reusable because the user does not need to know the address, field location, or underlying bus that accesses the registers.
      4. The reg predictor itself does not predict the register value. It takes a bus transaction, converts it to a register item, and updates the register model.
  17. How to write register sequences for UVM Register Model?
    What would be the desired and mirrored values w.r.t. read & write access methods?

  18. Hi Mr.Keisuke Shimizu, I have basic doubt. In RAL based sequence of READ (i.e Auto predict mode, not explicit prediction ) how the read data got updated into the Register models ??? Thanks in advance for your graphical answer :).

      1. Hey Kiesuke,

        Your posts are amazing. They are very informative and very easy to follow.

        I have a question with regards to auto predict mode:

        Since, we are not monitoring the bus to see what value the DUT gives on read, which value does the do_predict use to update the mirror value?

        Thank you,

        Regards,
        Muneeb Ulla Shariff

  19. Hi,
    I am trying to make use of the RAL generated coverage at the full chip level. From monitor I am collecting data, and using base address and offset I am getting register name(get_reg_by_offset). I am facing issue in the next step, if I try to use write method of the register I am getting error saying sequencer not set, so I tried using sample method of the register in couple of ways that is also not worked. Could you please help me to resolve this issue.
    thanks
    Sudhish

  20. Hi Keisuke Shimizu,

    First of all thank you for this wonderful blog, helped a lot in understanding and developing a RAL model.

    I have a simple memory interface, in which

    * Write & Read takes one clk to update-to and read-out from memory.
    * o_read_data is the memory output with o_data_vld (and its working as expected for the given address)

    * Whereas on the RAL side, the o_read_data is always having the value zero, though I am sampling its value when risign_edge of o_data_vld,
    hence
    I always get the data mismatch UVM_ERROR for predictor-mirror. and also for .read operation I will have the read_value zero.

       
    -----------------------------------------------------------------
                          -- Transaction --  
    -----------------------------------------------------------------
      class reg_access_type;
        typedef enum bit[1:0] {NO_OPERATION,READ,WRITE} access_types;
      endclass : reg_access_type
        
      class reg_access_response_type;
        typedef enum bit[1:0] {NO_OPERATION,READ,WRITE} access_types;
      endclass : reg_access_response_type
        
      class my_transaction  extends uvm_sequence_item;
        randc bit [07:0]     wr_addr;
        randc bit [15:0]     wr_data;
        randc bit [07:0]     rd_addr;
        randc bit [15:0]     rd_data;
        randc bit              rd_en;
        randc bit              wr_en;
        rand  reg_access_type                ::access_types   command; 
                 reg_access_response_type::access_types   otp_command; 
    
    		`uvm_object_utils_begin(my_transaction)
    			`uvm_field_int (wr_addr, UVM_ALL_ON)
    			`uvm_field_int (wr_data, UVM_ALL_ON)
    			`uvm_field_int (rd_addr, UVM_ALL_ON)
    			`uvm_field_int (rd_data, UVM_ALL_ON)
                           `uvm_field_enum(reg_access_type                 ::access_types, command, UVM_ALL_ON)
                           `uvm_field_enum(reg_access_response_type::access_types, otp_command, UVM_ALL_ON)
    		`uvm_object_utils_end
    		
        function new(string name ="");
          super.new(name);
        endfunction:new
      endclass : my_transaction    
       
    -----------------------------------------------------------------
                            -- Monitor --  
    -----------------------------------------------------------------
      task run_phase(uvm_phase phase);
      forever
      begin 
        @(posedge v_if.o_data_vld);
        begin 
          `uvm_info(get_type_name(),"at rising_edge of o_data_vld",UVM_DEBUG)
          my_txn.otp_command     = reg_access_response_type::READ;
          my_txn.rd_data         = v_if.o_rd_data;
          AP_mon.write(my_txn);
          `uvm_info(get_type_name(),"sampled o_rd_data",UVM_DEBUG)
        end 
      end 
      endtask : run_phase
       
    -----------------------------------------------------------------
              -- Here is the code of bus2reg conversion --  
    -----------------------------------------------------------------
    		virtual function void bus2reg( uvm_sequence_item bus_item, ref uvm_reg_bus_op rw );
    			if(!$cast(my_txn, bus_item))
    			begin 
    				`uvm_fatal("my_transaction","Provided bus_item is not of the correct type")
    				return;
    			end 
    			//INFORM THE UVM_KIND WITH USER READ/WRITE TYPE 
    			rw.kind =(my_txn.otp_command == reg_access_response_type::READ) ? UVM_READ : UVM_WRITE;
    			if(my_txn.otp_command == reg_access_response_type::READ)
                            begin 
    				rw.data = my_txn.rd_data;
    				rw.addr = my_txn.rd_addr;
                            end 
    			else if(my_txn.otp_command == reg_access_response_type::WRITE)
                            begin 
    				rw.data = my_txn.wr_data;
    				rw.addr = my_txn.wr_addr;
    			end 	
    			rw.status = UVM_IS_OK; 
    		endfunction: bus2reg
    	endclass:reg_adapter
    
    -----------------------------------------------------------------
              -- Here is the code of reg2bus conversion --  
    -----------------------------------------------------------------
    
    		virtual function uvm_sequence_item reg2bus( const ref uvm_reg_bus_op rw );
    		        my_transaction       my_txn;
    			my_txn  =  my_transaction::type_id::create("my_txn");
    
    			if(rw.kind == UVM_READ)
    				my_txn.command = reg_access_type::READ;
    			else if(rw.kind == UVM_WRITE)
    				my_txn.command = reg_access_type::WRITE;
    			else 
    				my_txn.command = reg_access_type::NO_OPERATION;
    				
    			//FOR WRITE, 
    			if(rw.kind == UVM_WRITE)
    				my_txn.wr_data = rw.data;
    				my_txn.wr_addr = rw.addr;
            
    			if(rw.kind == UVM_READ)
    				my_txn.rd_addr = rw.addr;
            
    			//RETURN THE USER TRANSACTION 
    			return my_txn;
    		endfunction : reg2bus	
    

    Please help me out in resolving this issue.

    Thank you,
    Tejas T V

    1. Did you see the correct o_rd_data value in your monitor? It seems that the o_rd_data has not changed @ ( posedge v_if.o_data_vld ). Can you try something like this to avoid the race?

      // monitor
      forever begin
        @( posedge v_if.clk ) ;
        if ( v_if.o_data_vld ) begin
          my_txn.otp_command = reg_access_response_type::READ;
          my_txn.rd_data = v_if.o_rd_data;
          AP_mon.write( my_txn );
        end
      end
  21. Hi Keisuke,

    I have tried with your suggestion, but still I see a problem here, RAL is comparing the data before the valid data is seen in the monitor.

    ----------------[test]--------------------
      class my_reg_test extends my_test_base; 
        `uvm_component_utils(my_reg_test)
        
        //REGISTER MODEL 
        uvm_reg_map    reg_map;
        my_reg_block   reg_block;
        string         reg_name;
        bit[15:0]      reg_data;
        bit[15:0]      read_data;
        bit[15:0]      rst_data;
        uvm_status_e   op_status;
        
        function new(string name, uvm_component parent);
          super.new(name,parent);
        endfunction : new
        
        task run_phase(uvm_phase phase);
          seq  = my_sequence ::type_id::create("seq ");
          rst   = assert_rst   ::type_id::create("rst");
          phase.raise_objection(this,"test starts now");
          
          h_env.h_env_cnfg.h_reg_block.STATUS_CONTROL_REG.RAJ.set(3'h7);
          h_env.h_env_cnfg.h_reg_block.STATUS_CONTROL_REG.update(op_status, .path(UVM_FRONTDOOR), .map(reg_map), .parent(null));
          #20 //wait for 2 clks
          h_env.h_env_cnfg.h_reg_block.STATUS_CONTROL_REG.RAJ.predict(3'h7);
          h_env.h_env_cnfg.h_reg_block.STATUS_CONTROL_REG.RAJ.mirror(op_status, .check(UVM_CHECK), .path(UVM_FRONTDOOR), .map(reg_map), .parent(null));
          phase.drop_objection(this,"test ends now");
        endtask : run_phase
          
      endclass : my_reg_test
    
    -----------------[ monitor ]-------------------
      task run_phase(uvm_phase phase);
      @(posedge v_if.clk);
        begin 
        @(posedge v_if.o_data_vld);
          begin 
            `uvm_info(get_type_name(), $sformatf("o_data_vld :: %0d, o_rd_data :: %0d",v_if.o_data_vld,v_if.o_rd_data),UVM_LOW)
            my_txn.rd_addr     = v_if.i_rd_addr;
            AP_mon.write(my_txn);
            `uvm_info(get_type_name(),"sampled vld data",UVM_LOW)
          end 
        end 
     endtask : run_phase
    
    -----------------[ log ]-------------------
    # UVM_INFO @ 0: reporter [RNTST] Running test my_reg_test...
    # UVM_ERROR verilog_src/uvm-1.0p1/src/reg/uvm_reg.svh(2892) @ 255: reporter [RegModel] Register "STATUS_CONTROL_REG" value read from DUT (0x0000000000000000) does not match mirrored value (0x0000000000000007)
    # UVM_INFO verilog_src/uvm-1.0p1/src/reg/uvm_reg.svh(2902) @ 255: reporter [RegMem] field RAJ mismatch read=3'h0 mirrored=3'h7 slice [2:0]
    # UVM_INFO ../tb/agent/monitor.svh(64) @ 265: uvm_test_top.h_env.h_agt .h_mntr  [monitor] o_data_vld :: 1, o_rd_data :: 7
    # UVM_INFO ../tb/agent/monitor.svh(67) @ 265: uvm_test_top.h_env.h_agt .h_mntr  [monitor] sampled vld data
    # UVM_INFO verilog_src/uvm-1.0p1/src/base/uvm_objection.svh(1116) @ 1255: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
    
    1. It seems that the mirror() returns too soon, even before the monitor sees the transaction on the bus. Do you use the auto-predict mode? If so, check if your driver returns the read value to the sequencer correctly.

    2. Do not use @(posedge v_if.o_data_vld)
      …. in monitor..

         fork
            forever begin
                 @(posedge v_if.clk);   // wait for a clock edge
                 if (v_if.o_data_vld) begin   // was the valid asserted this clock?
                                                                    // grab the data and make a txn to AP_mon and display.... etc
                 end
            end
          join
      

      (if you want this task to end, you can quit out of the forever loop once you see your txn..but I assume your monitor would just run till the phase was terminated.)

      Note that this will show up at the “next clock edge” to when the valid is present. This is when any “hardware” might sample it anyway, so that should be reasonable.
      If you were to go to gate level, the timing of the valid and data relative to clock might be such that it would not become valid immediately after the rising edge of the clock.
      I recommend you look into clocking blocks as part of your interface as well. And use the clocking vars and clocking events instead of the interface wires if you ever plan on this working with gate level delays in your RTL (DUT).

      By way of explanation,
      your code:

         @(posedge v.if.clk); // wait for a clock edge (even X to 1'b1 would do at time 0 if it happened to start at a 1'b1)
            begin   // decoration really...
               @(posedge v_if.o_data_vld);  // ok, now that clock is good, next edge on valid (many clocks later?)....
                  begin  // again why?
                       //  do your stuff...
      

      Please don’t take this as a criticism. my “comments” are just information, not putdowns.

      I’m an architect and RTL designer, not really much of a validator but I am learning .. thanks,

  22. Hi Keisuke Shimizu,
    I am trying to implement RAL model UVM env by taking your example as reference. But I am getting following error.
    Can you please look into it and help me.

    UVM_WARNING /lin_tools/synopsys/vcs_mx_J-2014.12-sp3/etc/uvm/reg/uvm_reg_sequence.svh(137) @ 0: uvm_test_top.env.sagent.ssp_seqr@@s_seq [REG_XLATE_NO_SEQR] Executing RegModel translation sequence on sequencer uvm_test_top.env.sagent.ssp_seqr’ does not have an upstream sequencer defined. Execution of register items available only via direct calls to ‘do_rw_access’

    Thanks,
    Jay

    1. Hi Keisuke

      I solved the above mentioned issue. It was mistake. My sequence was extended from uvm_reg_sequence and I was calling “super.body” in actual body method of sequence. I removed it. It works fine. Thanks for your blog. Great Help

      I am not aware of internal details of uvm_reg_sequence. If possible can you explain why body method is present in uvm_reg_sequence, if it is required, Why can’t I use super.body.

      Thanks in Advance.

      1. The uvm_reg_sequence can be used as two ways. One way is as the base class of a user-defined register sequence as you did. The other way is as a register-to-bus-transaction translator. If the upstream sequencer (reg_seqr) is specified, the body method of the uvm_reg_sequence gets a uvm_reg_item from the reg_seqr and converts it to a jelly_bean_transaction (see the figure below).
        uvm_reg_sequence
        The reason you must not call super.body() in a user-defined sequence is that the translation logic mentioned above will execute otherwise.

        1. Hi,

          Though this is not a reply, but I could not find a switch to post a question.
          It is related to the user defined uvm_reg_sequence.
          Consider a case where a register field is written followed by another write to same register but to a different field.
          Now, two things can be done:
          1. If these are back to back accesses, they can be clubbed to a single write on bus interface. This intelligence has to be built in user extended uvm_reg_sequence?
          2. If these two writes are not back to back, so user_reg_sequence (extended from uvm_reg_sequence) has to do read-modify-write. Is that correct? Or, is it taken care by some magic in uvm?

          1. You can control how the fields are written. For example, if you want to combine the two writes to one, you could do:

            uvm_status_e status;
             
            your_reg.field1.set( value1 );
            your_reg.field2.set( value2 );
            your_reg.update( status );

            Or, if you want two separate writes, you could do:

            uvm_status_e status;
             
            your_reg.field1.write( status, value1 );
            your_reg.field2.write( status, value2 );

            In the latter case, if the target bus supports byte-enable and the field is the only field in a byte lane, then only the field is written. Otherwise, the entire register is written using the mirrored values of the other fields. So, you don’t have to use Read-Modify-Write if you think mirrored values are good enough. I hope I answered your questions.

  23. Hi Simizu san,
    I would like to know how can i define a register which has Read Only access type for BUS_A and WRitable access type for BUS_B. Is it possible to create a map that overwrites the default access type configured in the register?. How can this be done?. is it with the add_reg function to the register map?.

    I came to this problem with the register assistant from Mentor. This tool generates the systemverilog registermodel classes automatically for you from excel tables where the registers are described. However, this tool doesn´t allow you to define different access type for individual register maps. The register maps can only have different address offsets but not different access types. Makes this sense or is it a tool limitation?. Any idea?

    Thanks in advance

    1. You should be able to set different accessibility when you do add_reg() as you mentioned. For example, you could do:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
      class jelly_bean_recipe_reg extends uvm_reg;
        virtual function void build();
          flavor = uvm_reg_field::type_id::create( "flavor" );
          flavor.configure( this, 3, 0, "RW", 0, 0, 1, 1, 0 ); // set "RW" to the field    // ...
      endclass: jelly_bean_recipe_reg
       
      class jelly_bean_reg_block extends uvm_reg_block;
        rand jelly_bean_recipe_reg jb_recipe_reg;
        uvm_reg_map reg_map_a; // register map for BUS_A
        uvm_reg_map reg_map_b; // register map for BUS_B
       
        virtual function void build();
          jb_recipe_reg = jelly_bean_recipe_reg::type_id::create( "jb_recipe_reg" );
          // ...
          reg_map_a.add_reg( jb_recipe_reg, 8'h00, .rights( "RO" ) ); // read-only    reg_map_b.add_reg( jb_recipe_reg, 8'h00, .rights( "RW" ) ); // writable    // ...
      endclass: jelly_bean_reg_block

      The register model restricts the accessibility based on the rights argument of the add_reg() on top of the field access policy.

      Unfortunately, I don’t have access to the tool you mentioned, so I don’t know if this is a tool limitation or not.

  24. Hi Keisuke,

    No I am not using auto-predict mode.

    -------------------[ env ]----------------------------
    typedef uvm_reg_predictor#( my_transaction ) reg_predictor;
    
      import agent_pkg::*;
      
      class my_env extends uvm_env;
        `uvm_component_utils(my_env)
        
        env_config   	  h_env_cnfg;
        agent  		         h_agt;
         
        //REG PREDICTOR
        reg_predictor       h_predictor;
    
        //NEW
        function new(string name,uvm_component parent);
          super.new(name,parent);
        endfunction : new
        
        function void build_phase(uvm_phase phase);
          super.build_phase(phase);
          
          //GET THE ENV-CONFIGURATION HANDLE FOR CONFIG_DB
          if(!uvm_config_db#( env_config )::get
            (.cntxt(this),.inst_name(""),.field_name("h_env_cnfg"),.value(h_env_cnfg)))
          begin
            `uvm_fatal( get_name(), "h_env_cnfg not found" )
          end
          //ASSIGN THE AGENT-CONFIG FROM ENV-CONFIGURATION
          uvm_config_db#( agent_config )::set
            (.cntxt( this ),.inst_name("h_agt*"),.field_name("h_agt_cnfg"),.value(h_env_cnfg.h_agt_cnfg));
            h_agt        = agent         ::type_id::create("h_agt ",this);
            h_predictor  = reg_predictor ::type_id::create("h_predictor",this);
    	`uvm_info(get_full_name()," AGENT CREATION DUE TO SWITCH", UVM_DEBUG);
          `uvm_info(get_type_name(),"build",UVM_DEBUG);
        endfunction : build_phase
    
        function void connect_phase(uvm_phase phase);
          super.connect_phase(phase);
          
          //
          if(h_env_cnfg.h_reg_block.get_parent() == null)
          begin 
            h_env_cnfg.h_reg_block.reg_map.set_sequencer(.sequencer( h_agt.h_seqr ),.adapter( h_agt.h_adapter));
          end 
    
          h_env_cnfg.h_reg_block.reg_map.set_auto_predict(.on(0));
          h_predictor.map     = h_env_cnfg.h_reg_block.reg_map; 
          h_predictor.adapter = h_agt.h_adapter; 
          h_agt.AP_in_agt.connect(h_predictor.bus_in);  
          
          `uvm_info(get_type_name(),"connect",UVM_DEBUG);
        endfunction : connect_phase
        
      endclass : my_en
    
      1. I am using get_next_item and item_done and I have not made use of response port.(is this causing early execution of bus2reg ?).

        and my provides_responses is always zero(set during the construction of adapter class, and not modifying there after)

        --------------------[ adapter ]------------------------
            //NEW
            function new(string name = "reg_adapter");
              super.new();
              supports_byte_enable = 0;
              provides_responses   = 0;
            endfunction: new
        
        ------------------------[ driver ]------------------------
        
            task run_phase(uvm_phase phase);
              forever 
              begin
                `uvm_info(get_type_name()," **** Requesting for an item(txn ) **** ",UVM_DEBUG);
                 seq_item_port.get_next_item(txn);
                 `uvm_info(get_type_name()," **** Received an item(txn ) **** ",UVM_DEBUG);
                  @(v_if.cb);
                  if(txn.command == reg_access_type::READ)
                  begin 
                    v_if.cb.i_rd_en     <= 1'b1;
                    v_if.cb.i_rd_addr   <= txn.rd_addr;
                    @(v_if.cb);
                    v_if.cb.i_rd_en     <= 1'b0;
                  end 
                  else if(txn.command == reg_access_type::WRITE)
                  begin 
                    v_if.cb.i_wr_en     <= 1'b1;
                    v_if.cb.i_wr_addr   <= txn.wr_addr;
                    v_if.cb.i_wr_data   <= txn.wr_data;
                    @(v_if.cb);
                    v_if.cb.i_wr_en     <= 1'b0;
                  end  
                  `uvm_info(get_type_name()," **** Driven the inputs (txn ) **** ",UVM_DEBUG);
                  seq_item_port.item_done(txn); 
                  `uvm_info(get_type_name(),"  **** Informing the sequence, I am done() **** ",UVM_DEBUG);
                end 
            endtask : run_phase
        
          1. Can you please explain why driver has to set the txn.rd_data ? as it is DUT’s output and it shall be monitored by a monitor ?

          2. Thank you, this resolved my problem.
            but please clarify the below doubts,
            1. If the mirror function compares the returned transaction from driver, why we connect the monitor ap to predictor’s bus_in implementation port ?
            2. Shouldn’t be driver independent of slave’s response ? why not monitor tells the predictor the valid transaction ?

            1. The monitor is used to capture all transactions on the bus, not just user-initiated register accesses, to keep the register model up to date.
            2. I am afraid I fully understand your question, but the monitor cannot tell which transactions correspond to user-initiated register accesses.
          3. Hi
            I am Sasha and I am having the same issue as Tejas T V reported while ago. The mirror() task starts comparison before the bus monitor collects the transaction on the bus. As result, I get this error – “model.reg_xxx” value read from DUT (0x0000000000000000) does not match mirrored value (0x00000000f0031060). However, I clearly see the correct data was read back and captured by the monitor. Also, the mirrored value has been updated with the correct data, but somehow this two processes are not sync’ed.
            My driver use get_next_item()/item_done() for handshaking with the sequencer. Also, in my register adapter I do not use provides_responses.
            What should I change in order to make the mirror() pass?
            Thank you

  25. Hi,
    First, Thanks for yours blog.
    I try used uvm_reg_block that contains uvm_reg_block in my code.
    after the crate model, I can see that each register allocated in 4 address(offset), and when I using write_mem I getting 4 transactions.
    I can’t understand what wrong with my code.
    Thanks.

  26. Hi Keisuke,

    How do I write/read to a register in my test using the fields defined in the register model. In some instances, some of the fields get shrunk/expanded or in some rare occasion moved around within the register during development. How do I make my code immune to these type of changes?

    Thanks,

    Del

    1. In the jelly_bean_reg_sequence, we wrote to the jb_recipe_reg like this:

      write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor } );

      Suppose the location of the sour field and the sugar_free field are swapped due to the specification change. As you guessed, we have to rewrite the code to reflect the change.

      // sugar_free and sour are swapped
      write_reg( jb_reg_block.jb_recipe_reg, status, { sugar_free, sour, color, flavor } );

      To make the code immune from the change, we could write to the each register field instead of writing to the entire register:

      uvm_status_e status;
      jb_reg_block.jb_recipe_reg.sour      .write( status, sour       );
      jb_reg_block.jb_recipe_reg.sugar_free.write( status, sugar_free );
      jb_reg_block.jb_recipe_reg.color     .write( status, color      );
      jb_reg_block.jb_recipe_reg.flavor    .write( status, flavor     );

      However, this will generate four front-door register writes. Better approach would be using the set and update:

      uvm_status_e status;
      jb_reg_block.jb_recipe_reg.sour      .set( status, sour       );
      jb_reg_block.jb_recipe_reg.sugar_free.set( status, sugar_free );
      jb_reg_block.jb_recipe_reg.color     .set( status, color      );
      jb_reg_block.jb_recipe_reg.flavor    .set( status, flavor     );
      jb_reg_block.jb_recipe_reg.update( status );

      This will generate one register write at most. Similarly, we could read the jb_taste_reg without worrying about the location of the taste field like this:

      uvm_status_e   status;
      uvm_reg_data_t value;
      jb_reg_block.jb_taste_reg.read( status, value );
       
      // You need to know the location of the taste field ([1:0]).
      $display( "taste=%d", value[1:0] );
       
      // Using the register field, the location becomes transparent to the user.
      $display( "taste=%d", jb_reg_block.jb_taste_reg.taste.get() ); // or get_mirrored_value()
      1. Hi Keisuke,

        Thank you for the blog. Even I had the same issue as Tejas T V & Sasha. but got it resolved as I returned the rd value from the driver.

        1. But my question is, If the mirror value compares the data against the data returned from driver with the RTL output, then what is the necessary of establishing the connection with predictor’s bus_in imp port with the monitor’s ap?

        2. Is it the only fix that we have to return the data from driver ? Cannot we send data to mirror method from the monitor as the bus transactions are captured at monitor?

        Thanks in advance,
        Sheela B Patil

          1. The monitor captures all transactions on the bus, not just user-initiated read/writes. For example, the monitor captures the transactions initiated by a bus bridge to keep the register model up to date.
          2. The monitor does not know which transactions are initiated by the mirror methods.
  27. Hello Keisuke,

    Please help in knowing the error while executing your code in questasim-10.2b. The error is:

    ** Error: (vlog-8386) jelly_bean_sequence.sv(13): An enum variable ‘jb_tx.color’ of type ‘color_e’ may only be assigned the same enum typed variable or one of its values. Variable ‘GREEN’ of type ‘color_e’ is not valid.

    Please guide.

    Thanks and Regards
    Sunil

      1. Hi Keisuke,
        I have some doubts in RAL model ,
        1. Why we need randomize the class in reg block. rand Class_Name C_N_handle. If we are doing every field as ‘rand’ why again we need to declare rand.
        2. what is the exact functionality of support_byte_enable and provide_response?
        3. what is the use of lock_model?

          1. If you have the rand type-modifier to the registers in the register block, you can randomize the entire register block like jb_reg_block.randomize(). Otherwise, you need to randomize the registers one by one.
            • If the target bus supports byte enable, set supports_byte_enable to 1. Otherwise, set it to 0.
            • If the target driver provides separate responses using get(req) and put(resp) for example, then set provides_responses to 1. If the target driver does not provide separate responses using get_next_item(req) and item_done() for example, then set it to 0.
          2. The lock_model() checks the address overlapping and builds the address maps.
  28. Hi Dear Keisuke,

    Thank you very much for all your post with so many details.

    I try to use the serial bus like i2c to program the registers at the beginning of the simulation. I have some questions about it. would you please take a look at it?

    1. I have finished the regmodel. But I am not sure if a reg sequence is good enough, or we need to have a i2c sequence still. ( As my understanding, virtual sequence will start the reg sequence and pass the transaction all the way down to DUT interface if I do the connection correctly).

    2. Since the bus is serial, we need to separate the transaction from regmodel into bit and drive to i2c interafce. . Which way do you think is better for the i2c driver? In some example , it use seq_item_port. get_next_item and item_done(rsp) to do the conversation. Do you think it s a good way to do it? Do you have any example for it?

    3. In the DUT( jalley_bean_taster.v) I saw these code as follows, Should it be part of the jb_slave_if driver? I am a little bit confused about it.

        always @ ( posedge jb_slave_if.clk ) begin
          if ( jb_slave_if.command == jelly_bean_types::WRITE ) begin
             flavor     < = jb_slave_if.flavor;
             color      <= jb_slave_if.color;
             sugar_free <= jb_slave_if.sugar_free;
             sour       <= jb_slave_if.sour;
          end else if ( jb_slave_if.command == jelly_bean_types::READ ) begin
             jb_slave_if.taste <= taste;
          end
       end
     
       always @ ( posedge jb_slave_if.clk ) begin
          if ( jb_slave_if.flavor == jelly_bean_types::CHOCOLATE &&
               jb_slave_if.sour ) begin
             taste <= jelly_bean_types::YUCKY;
          end else if ( jb_slave_if.flavor != jelly_bean_types::NO_FLAVOR ) begin
             taste <= jelly_bean_types::YUMMY;
          end
       end
    

    Thanks you very much for your time and help.

      1. I assume you already have the I2C transaction class (i2c_transaction) that contains I2C address and data information, and the I2C driver that converts an I2C transaction into SDA/SCL wiggling. If so, all you need is a register adapter that converts register operation from/to an i2c_transaction. Once you let your reg_map know the adapter by calling set_sequencer(), you should be able to do your_reg.write(status, value), etc. in your sequence.
      2. I would use get_next_item and item_done as you mentioned.
      3. The code you mentioned represents the DUT. It’s not a verification component.

      i2c
      Let me know if you need further clarification.

      1. Hi Keisuke,

        Thank you very much for your replay. The diagram is great !!

        Yes, I write my own SCCB driver( i2c like interface). I still have some questions about the reg model to interface.

        1. “you should be able to do your_reg.write(status, value), etc. in your sequence.”

        You mention above that I am able to do reg.write in my sequence. Do you mean test sequence? As my understanding now, I do not need to write an SCCB sequence. I could use the virtual sequence in the test cases to program register. Please correct me if i am wrong. For read I have more questions.

        Write:
        virtual sequence—>reg_sequence —(uvm_reg_bus_op, write)—>SCCB_adapter.reg2bus—(SCCB_trans)—>SCCB_sequencer—>SCCB_driver—>sda/scl.

        Read:
        virtual sequence—>reg_sequence —(uvm_reg_bus_op. read)—>SCCB_adapter.reg2bus—(SCCB_trans)—>SCCB_sequencer—>SCCB_driver—>sda/scl—>SCCB_driver get read data—> save it to SCCB_trans.

        After read operation, driver will get data from sda/scl and pack it into SCCB_trans, do you think it is necessary to use a tlm_fifo to hold the data? ( As my understading, tlm_fifo is better). I am not sure where should we use the SCCB monitor to grep the SCCB_trans.

        And how do we compare the value with the value in the regmodel.

        2. Do we need to call reg2bus and bus2reg anywhere in the code? ( I do not find it). Or once we we do reg.write and reg.read in the reg_sequence, it will be called automatically. I understand the usage of reg2bus, what why do we need bus2reg? In the read flow above, I do not find the place to use bus2reg.

        3. Some of the Read/Write are not single read and write. But the access operations are only UVM_READ and UVM_WRITE( I search the Reference manual). Does that mean the adapter do not handle complicated R/W. Those control will located in the SCCB driver.

        Best,

          1. I don’t think you need a uvm_tlm_fifo. In your sequence, call your_reg.read(status, value), then compare the value against your expected value. Regarding the monitor location, please see the read() section of Register Access Methods.
          2. You don’t need to call reg2bus or bus2reg. Your register map calls bus2reg after your sequence has completed a register read transaction on the bus. Then the converted value is used to update the register model.
          3. The adapter should do simple field-to-field conversion between the uvm_reg_bus_op and the SCCB_trans. Serialization and deserialization should be done by the driver.
          1. Thanks Keisuke for your detailed explanation. I will try to implement it and let you know if it work.

            Thank you very much.

            Best

          2. Hi Keisuke,

            I try to use virtual sequence and virtual sequencer to control the test. Would you please take a look at the questions as follows,

            1. I wonder if I connect the m_reg_seq to p_sequencer.m_sccb_sqr will work? I try to control the RAL sequence though virtual sequence and virtual sequencer in the test.

            2. Since I do m_vseq.start(m_env.m_vsqr); Do I need to do anything else to instantiate the vseq?
            Such as connecting m_vseq.m_sccb_sqr to env.m_vsqr.m_sccb_sqr?? I think i already connect them in the env so that it should be OK.

            3. p_sequencer seems not quite easy to connect. Do you think just use virtual sequence will be better?

            // Inside the virtual sequence class:
            class my_vsequence extends uvm_sequence;
            
                    `uvm_declare_p_sequencer(my_vsequencer)
            ....
            task body();
                    m_clk_seq = clk_base_sequence::type_id::create("m_clk_seq"); 
                    m_rst_seq = rst_base_sequence::type_id::create("m_rst_seq"); 
                    m_reg_seq = reg_base_sequence::type_id::create("m_reg_seq"); 
             
                    fork
                        m_clk_seq.start(p_sequencer.m_clk_sqr);
                        m_rst_seq.start(p_sequencer.m_rst_sqr);
                        m_reg_seq.start(p_sequencer.m_sccb_sqr);       // 
                   join
            endtask
            
            endclass
            
            // Inside the env class:
            class my_env extends uvm_env;
            
                my_vsequencer  m_vsqr;
            
               ...
               
               function void connect_phase(uvm_phase phase);
                    super.connect_phase(phase);
                    m_vsqr.m_clk_sqr = m_clk_agt.m_sqr;
                    m_vsqr.m_rst_sqr = m_rst_agt.m_sqr;
                    m_vsqr.m_sccb_sqr = m_sccb_agt.m_sqr;
            
                    m_env_cfg.m_reg_block.reg_map.set_sequencer(.sequencer(m_sccb_agt.m_sqr),
                                                                .adatpter (m_sccb_agt.m_sccb_adapter));
                    m_env_cfg.m_reg_block.reg_map.set_auto_prediect(0);
                
               endfunction: connect_phase
            endclass
            
            
            // Inside the test class:
            class my_test extends uvm_test;
            ....
            
            my_env m_env;
            
            m_env = my_env::type_id::create("m_env");
            
            task run;
            ...  
               m_vseq = my_vsequence::type_id::create("m_vseq");
             
              //...
              m_vseq.start(m_env.m_vsqr);
              //...
            endtask: run
  29. Hi Keisuke,
    Thanks a lot for this topic, but I have some quastion. What I should do if I have 2 interaction register fields in 2 registers. For example reg A.field1(RO), but always have value from reg B.field1(RW) . How I can implement this using RAL.

  30. Hi Keisuke,

    I am not clear how we can read the reset value for the RAL registers.
    I’ve tried your example “http://cluelogic.com/2012/10/uvm-tutorial-for-candy-lovers-register-abstraction/” inside the EDA playground and modified it a little bit
    https://www.edaplayground.com/x/XT2

    Modifications:
    1. inside ral.svh, changed the reset values to be different than 0 for “jelly_bean_recipe_reg”.

          flavor.configure( .parent                 ( this ), 
                           ....
                           .reset                  ( 5    ), 
          color.configure( .parent                 ( this ), 
                           ....
                          .reset                  ( 2    ), 
    

    – same for “sugar_free” and “sour” fields;

    2. inside sequences.svh file, at jelly_bean_reg_sequence class, commented the write_reg and added a print message for the read value

          //write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor } );
          read_reg( jb_reg_block.jb_taste_reg, status, value );
         $display("after read jb_reg_block.jb_taste_reg=%x", value);
    

    Run the test again, but the read value is not as expected:
    after read jb_reg_block.jb_taste_reg=0000000000000000

    I was expected to have the default values for each field returned by the read_reg function.
    Am I wrong?

    Can you please advise, why is this behavior OR how can we do to read and check the default reset value for a register (from the uvm-reg-model) to be compared against the DUT one?

    Thank you.
    Adi

    1. The reset argument of the configure function sets the reset value of the register model. It has nothing to do with the DUT. On the other hand, the read_reg task actually reads the register from the DUT. Since the reset value of the jb_taste_reg in the DUT is 0, you get 0.

      One way to check the default value of a register is:

      your_reg_field.configure( ... .reset( reset_value ) );
       
      // Reset the mirrored value of the model with the reset_value
       
      your_reg_field.reset(); // the mirrored value of the model becomes reset_value
       
      // Read the register from the DUT and check the read value against the mirrored value.
      // Make sure you set the "check" argument to be UVM_CHECK to enable the check.
       
      your_reg_field.mirror( status, .check( UVM_CHECK ) );

      For more information, please see this article.

  31. Hi Keisuke-san,

    Please explain the purpose of line 44 of jelle_bean_env:
    if ( jb_env_cfg.jb_reg_block.get_parent() == null ) begin // if the top-level env

    It seems to me that since jb_reg_block has a parent (jelle_bean_base_test created it), this if clause would be evaluated to be false, and the set_sequencer in the next line won’t be executed.

    Thanks,

    1. The if (jb_env_cfg.jb_reg_block.get_parent() == null) checks if the jb_reg_block has a parent register block. Don’t confuse this with uvm_component::get_parent(), which returns a parent component. This code means if the jb_reg_block is the top-level register block (true in this case), then use the sequencer and the adapter in the jb_agent in this jelly_bean_env. Otherwise (the jb_reg_block has a parent register block), an upper environment (or a test) that has the access to the parent register block should call this function.

  32. Hi Shimizu-san,

    I have some doubt in uvm_reg_predictor.

    In my environment, I have connected predictor bus_in port to output analysis port of monitor. I have also implement reg_adapter bus2reg function and connect adapter to predictor. I’m using passive prediction (https://verificationacademy.com/cookbook/registers/integrating). The mirror value of uvm_reg should be updated as long as there’s transaction sent from monitor. However I did not see that happen. When I check the source code for uvm_reg_predictor, it seems like it failed in get_reg_by_offset() function so that it did not get uvm_reg object. Have you met similar issue? If so, what’s your solution? Thanks.

    1. Make sure you use the top address map in your predictor if you have a hierarchical address map. Also make sure you use the address with respect to the top address map in your bus2reg function.

  33. Hi Keisuke,

    I have some problem on modeling a large data bus (e.g. 128-bit) with word-enable bus access,
    here is the example:
    I use the +define+UVM_REG_DATA_WIDTH=128 for vlog compilation paired with RALF generated reg model:

    register ctl {
       bytes 4;
       // other fields definition
    ...
    }
    ...
    block rf00 {
       bytes 16;
       endian big;
       register ctl             @'h0;
       register sts             @'h8;
       register inten           @'hc;
    }
    ...
    system sys {
       bytes 152;
       block rf00               @'h40100000;
       block rf30               @'h40100030;
       block rf40               @'h40100040;
    ...
    }
    

    the sequence part, I choose uvm_reg_sequemce paired with write_reg() or write_mem() calls

    ...
    write_reg( regmodel.rf00.ctl, status, 32'h21414 );
    ...
     write_mem(regmodel.rxffmem.RxFIFO, status, i*16, ram_data);
    ...
    

    no matter what type of access I tried, the default byte_en always 0xffff, it means my uvm_reg_adapter has no get any chance to distinguish if I do want to do
    just separated words, like 0xff00, 0x0f0f, 0xff0f, 0x0f00 … etc., in one bus transaction.

    do you have any advise on modeling such thing in easy way?
    thanks for any help from you in advance.

    1. The byte_en of uvm_reg_bus_op is properly set only if you use the read or write task of uvm_reg_field. The write_reg and write_mem tasks always write the entire bus. You could model your rf00 as a uvm_reg instead of a uvm_reg_block, and model your ctl register as a uvm_reg_field. Then do regmodel.rf00.ctl.write( status, 32'h21414 ) for a partial write. Make sure you set the supports_byte_enable property of the uvm_reg_adapter.

  34. Hi.

    A have a question related to DUT lookup tables.
    I am using UVM register package to test the registers and LUTs for my DUT.
    I’ve model the LUTs as uvm_mem inside my RAL.
    I can write them/read them (issuing transactions on AMBA AHB bus), but I am not clear how to check them.

    For uvm_reg, there is a mirror mechanism (predictor) that helps with checking, but not for uvm_mem (from what I know).

    Do you have a suggestion on how to deal with checking of the LUTs (or what is the best practice here)?

    Thank you.
    Adi

    1. As you mentioned, there is no built-in mechanism to compare the data in memory. You could have an associative array to store the expected data. Then use the peek task of the uvm_mem to read the memory using a back-door access and compare.

  35. virtual task body();
          jelly_bean_reg_block       jb_reg_block;
          jelly_bean_types::flavor_e flavor;
          jelly_bean_types::color_e  color;
          bit                        sugar_free;
          bit                        sour;
          uvm_status_e               status;
          uvm_reg_data_t             value;
     
          $cast( jb_reg_block, model );
          flavor     = jelly_bean_types::APPLE;
          color      = jelly_bean_types::GREEN;
          sugar_free = 0;
          sour       = 1;
     
          write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor } );
          read_reg ( jb_reg_block.jb_taste_reg,  status, value );
       endtask: body
    

    what is the necessity of casting statement? Can you pease explain?

    1. The type of model is uvm_reg_block, while the type of jb_reg_block is jelly_bean_reg_block, which extends uvm_reg_block. $cast is required to assign a superclass handle to a variable of a subclass type.

Leave a Reply to Keisuke Shimizu Cancel reply

Your email address will not be published. Required fields are marked *