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.
This example shows only 2 blocks but can easily represent N blocks.
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.
The test code could be further simplified by creating a queue of the block pointers and executing the code using a foreach statement.
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.
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 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