Sunday, January 8, 2017

Implementing Reusable UVM Sequences

In many designs, it is not uncommon for blocks and/or registers to be reused multiple times. Implementation of Register Abstraction Layer (RAL) sequences for replicated design blocks and/or registers greatly reduces time spent coding and automatically permeates environment bug fixes. Let's take a look how such flows can be implemented.

Example RAL structure 

Before getting into the heart of things, an example RAL structure referenced throughout this post is shown in the code snippet below:

class ral_my_reg extends uvm_reg;
  rand uvm_reg_field FIELD1;
  rand uvm_reg_field FIELD2;
.
.
endclass : ral_my_reg
//-----------------------------------------//
class ral_my_block extends uvm_reg_block;
  rand ral_my_reg REG1;
  rand ral_my_reg REG2;
.
.
endclass : ral_my_block
//-----------------------------------------//
class ral_my_sys extends uvm_reg_block;
  rand ral_my_block BLOCK1;
  rand ral_my_block BLOCK2;
.
.
endclass : ral_my_sys

Different Sequence Implementations for Each Block

Let's start at the beginning: how would sequences be implemented without any code reuse in mind? Each sequence would hold the same functional code using different RAL pointers to differentiate different block/register access.

class block1_sequence extends uvm_sequence #(sequence_item);

task body();
  ral.BLOCK1.REG1.write( .status(status), .value(data) );
  ral.BLOCK1.REG2.read(  .status(status), .value(data) );
endtask: body

endclass: block1_sequence
//-----------------------------------------//
class block2_sequence extends uvm_sequence #(sequence_item);

task body();
  ral.BLOCK2.REG1.write( .status(status), .value(data) );
  ral.BLOCK2.REG2.read(  .status(status), .value(data) );
endtask: body

endclass: block2_sequence
//------------------------------------------------------------------//
class block_access_test extends uvm_test;
  
  // boilerplate code excluded

task main_phase(uvm_phase phase);
  block1_sequence block1_seq;
  block2_sequence block2_seq;
  
  super.main_phase(phase);
  phase.raise_objection(this);
  
  block1_seq = block1_sequence::type_id::create("block1_seq");
  block1_seq.start(null);
  
  block2_seq = block2_sequence::type_id::create("block2_seq");
  block2_seq.start(null);
  
  // add more blocks as needed
  
  phase.drop_objection(this);
  
endtask: main_phase

endclass: block_access_test

This example shows only 2 blocks but can easily represent N blocks.

Sequence Reuse for reused Design Blocks

When coding a sequence for reuse between replicated blocks, a RAL block pointer is used for the functional code where the decision of which block is under test is decided by the sequence caller. In the example below, the test creates and holds the sequence and decides which block to verify by passing the appropriate pointer before execution.

class my_block_sequence extends uvm_sequence #(sequence_item);
  ral_my_block myblk;

task body();  
  if(myblk == null) `uvm_fatal(get_type_name(), $sformatf("pointer myblk is null!") )
  
  myblk.REG1.write( .status(status), .value(data) );
  myblk.REG2.read(  .status(status), .value(data) );
endtask: body

endclass: my_block_sequence
//------------------------------------------------------------------//
class block_access_test extends uvm_test;

task main_phase(uvm_phase phase);
  my_block_sequence my_block_seq;
  
  super.main_phase(phase);
  phase.raise_objection(this);
  
  my_block_seq = my_block_sequence::type_id::create("my_block_seq");
  
  my_block_seq.myblk = ral.BLOCK1;
  my_block_seq.start(null);
  
  my_block_seq.myblk = ral.BLOCK2;
  my_block_seq.start(null);

  phase.drop_objection(this);
  
endtask: main_phase

endclass: block_access_test


The test code could be further simplified by creating a queue of the block pointers and executing the code using a foreach statement.

task main_phase(uvm_phase phase);
  ral_my_block my_blocks[$] = '{ral.BLOCK1, ral.BLOCK2};
  my_block_sequence my_block_seq;
  
  super.main_phase(phase);
  phase.raise_objection(this);
  
  my_block_seq = my_block_sequence::type_id::create("my_block_seq");
  
  foreach(my_blocks[i]) begin
    my_block_seq.myblk = my_blocks[i];
    my_block_seq.start(null);
  end
  
  phase.drop_objection(this);
  
endtask: main_phase

Sequence Reuse for Different Design Blocks

It is possible that multiple non-replicated blocks in a design have the same functionality implemented. Since the block types differ, a specific block type pointer can no longer be used and a parent pointer will take it's place. One needs to remember that members defined in the derived class cannot be directly accessed using the parent pointer. For this purpose, UVM RAL classes have built-in methods allowing retrieval of member pointers in derived classes using strings.

class my_block_sequence extends uvm_sequence #(sequence_item);
  uvm_reg_block myblk;

task body();  
  if(myblk == null) `uvm_fatal(get_type_name(), $sformatf("pointer myblk is null!") )
  
  myblk.get_reg_by_name("REG1").write( .status(status), .value(data) );
  myblk.get_reg_by_name("REG2").read(  .status(status), .value(data) );
endtask: body

endclass: my_block_sequence

Register Reuse for Identical Functionality

Just like sequences, registers can and are replicated with identical functionality in design blocks. What coding reuse options are available for identical registers/fields? As with the sequences let's look at a number of examples showing how one would code without reuse and then with reuse in mind.

class my_block_sequence extends uvm_sequence #(sequence_item);
  ral_my_block myblk;

task body();  
  if(myblk == null) `uvm_fatal(get_type_name(), $sformatf("pointer myblk is null!") )
  
  myblk.REG1.write( .status(status), .value(data) );
  myblk.REG1.mirror( .status(status) );
  
  if( myblk.REG1.FIELD1.get() !== 1 ) `uvm_error(get_type_name(), $sformatf("value of FIELD1 expected 1, received value %0h", myblk.REG1.FIELD1.get()) )
  else                                `uvm_info( get_type_name(), $sformatf("value of FIELD1 expected 1, received value %0h", myblk.REG1.FIELD1.get()), UVM_NONE )

  myblk.REG2.write( .status(status), .value(data) );
  myblk.REG2.mirror( .status(status) );
  
  if( myblk.REG2.FIELD1.get() !== 1 ) `uvm_error(get_type_name(), $sformatf("value of FIELD1 expected 1, received value %0h", myblk.REG2.FIELD1.get()) )
  else                                `uvm_info( get_type_name(), $sformatf("value of FIELD1 expected 1, received value %0h", myblk.REG2.FIELD1.get()), UVM_NONE )
  
endtask: body
endclass: my_block_sequence


So what would be the first step to making the code more generic? By referencing the previous section of this post on block reuse, register accesses can be executed using a parent pointer of uvm_reg. Derived class members are then retrieved via the built in UVM string search methods.

class my_block_sequence extends uvm_sequence #(sequence_item);
  ral_my_block myblk;
//---------//
task check_ral_my_reg_functionality(uvm_reg current_reg);
  if(current_reg == null) `uvm_fatal(get_type_name(), $sformatf("pointer current_reg is null!") )
  
  current_reg.write( .status(status), .value(data) );
  current_reg.mirror( .status(status) );
  
  if( current_reg.get_field_by_name("FIELD1").get() !== 1 ) `uvm_error(get_type_name(), $sformatf("value of FIELD1 expected 1, received value %0h", current_reg.get_field_by_name("FIELD1").get()) )
  else                                                      `uvm_info( get_type_name(), $sformatf("value of FIELD1 expected 1, received value %0h", current_reg.get_field_by_name("FIELD1").get()), UVM_NONE )
  
endtask: check_ral_my_reg_functionality
//---------//
task body();  
  if(myblk == null) `uvm_fatal(get_type_name(), $sformatf("pointer myblk is null!") )
  
  check_ral_my_reg_functionality( .current_reg(myblk.REG1) );
  check_ral_my_reg_functionality( .current_reg(myblk.REG2) );
  
endtask: body
endclass: my_block_sequence

The previous code snippet is already looking pretty good for reuse. But, with a few more tweaks, a single functional implementation is feasible without any prior knowledge of either the register or field types/names.

class my_block_sequence extends uvm_sequence #(sequence_item);
  ral_my_block myblk;
//---------//
task check_ral_my_reg_functionality(uvm_reg current_reg, string field_to_verify);
  if(current_reg == null) `uvm_fatal(get_type_name(), $sformatf("pointer current_reg is null!") )
  
  current_reg.write( .status(status), .value(data) );
  current_reg.mirror( .status(status) );
  
  if( current_reg.get_field_by_name(field_to_verify).get() !== 1 ) `uvm_error(get_type_name(), $sformatf("value of %s expected 1, received value %0h", field_to_verify, current_reg.get_field_by_name(field_to_verify).get()) )
  else                                                             `uvm_info( get_type_name(), $sformatf("value of %s expected 1, received value %0h", field_to_verify, current_reg.get_field_by_name(field_to_verify).get()), UVM_NONE )
  
endtask: check_ral_my_reg_functionality
//---------//
task body();  
  if(myblk == null) `uvm_fatal(get_type_name(), $sformatf("pointer myblk is null!") )
  
  check_ral_my_reg_functionality( .current_reg(myblk.REG1), .field_to_verify("FIELD1") );
  check_ral_my_reg_functionality( .current_reg(myblk.REG2), .field_to_verify("FIELD1") );
  
endtask: body
endclass: my_block_sequence


No comments:

Post a Comment