Category: UVM
UVM Related topics
UVM Callback Tutorial
UVM Callback Tutorial
UVM RAL Example DMA
UVM Register Model Example
In this section will see an example that shows one of the ways to access DUT registers with the UVM RAL Model.
Let’s consider a DMA design which consists of registers in it and reg_interface is used to access the registers.
Below is the block diagram of DMA.
Below are the DMA registers,
- INTR
- CTRL
- IO ADDR
- MEM ADDR
Address of each register and register field description is given below,
Reg Name | Address | Field Description | ||||||
INTR | h400 | 31:16 | 15:0 | |||||
MASK | STATUS | |||||||
CTRL | h404 | 31:10 | 9 | 8:1 | 0 | |||
RESVD | IO_MEM | W_COUNT | START_DMA | |||||
IO_ADDR | h408 | 31:0 | ||||||
ADDRESS | ||||||||
MEM_ADDR | h40C | 31:0 | ||||||
ADDRESS | ||||||||
Below is the testbench block diagram,
The testbench components are,
- Environment
- DMA Agent
- Driver
- Monitor
- Sequencer and Sequences
- RAL Model
- DMA Reg package
- Adapter
- DMA Agent
UVM Tetsbench prior knowledge is required before going through this section. For detailed steps on writing UVM Testbench refer to UVM Testbench Architecture.
UVM RAL Model creation involves the below steps,
- Writing register classes
- Writing register package
- Instantiation of register classes in register package
- Writing Adapter class
- Integrating register package and adapter in environment
- Accessing registers with RAL
Writing register classes
Writing reg class for INTR
Below is the register filed details of INTR reg.
Reg Name | Address | Field Description | ||||||
INTR | h400 | 31:16 | 15:0 | |||||
MASK | STATUS | |||||||
- Register class is written by extending the uvm_reg class
class intr extends uvm_reg; `uvm_object_utils(intr) //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "intr"); super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width endfunction endclass
- Declare the fields with type uvm_reg_field
//--------------------------------------- // fields instance //--------------------------------------- rand uvm_reg_field status; rand uvm_reg_field mask;
- Create the fields
function void build; // Create bitfield status = uvm_reg_field::type_id::create("status"); mask = uvm_reg_field::type_id::create("mask"); endfunction
- Configure the fields with access type, bit width and etc,
// Create bitfield status = uvm_reg_field::type_id::create("status"); // Configure status.configure(.parent(this), .size(16), .lsb_pos(0), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); mask = uvm_reg_field::type_id::create("mask"); mask.configure(.parent(this), .size(16), .lsb_pos(16), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0));
- Complete register class
class intr extends uvm_reg; `uvm_object_utils(intr) //--------------------------------------- // fields instance //--------------------------------------- rand uvm_reg_field status; rand uvm_reg_field mask; //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "intr"); super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width endfunction //--------------------------------------- // build_phase - // 1. Create the fields // 2. Configure the fields //--------------------------------------- function void build; // Create bitfield status = uvm_reg_field::type_id::create("status"); // Configure status.configure(.parent(this), .size(16), .lsb_pos(0), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); // Below line is equivalen to above one // status.configure(this, 32, 0, "RW", 0, 0, 1, 1, 0); // reg, bitwidth, lsb, access, volatile, reselVal, hasReset, isRand, fieldAccess mask = uvm_reg_field::type_id::create("mask"); mask.configure(.parent(this), .size(16), .lsb_pos(16), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); endfunction endclass
Writing reg class for CTRL
This register has multiple fields, each field has to be declared, created and configured.
class ctrl extends uvm_reg; `uvm_object_utils(ctrl) //--------------------------------------- // fields instance //--------------------------------------- rand uvm_reg_field start_dma; rand uvm_reg_field w_count; rand uvm_reg_field io_mem; rand uvm_reg_field reserved; //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "ctrl"); super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width endfunction //--------------------------------------- // build_phase - // 1. Create the fields // 2. Configure the fields //--------------------------------------- function void build; start_dma = uvm_reg_field::type_id::create("start_dma"); start_dma.configure(.parent(this), .size(1), .lsb_pos(0), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); w_count = uvm_reg_field::type_id::create("w_count"); w_count.configure(.parent(this), .size(8), .lsb_pos(1), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); io_mem = uvm_reg_field::type_id::create("io_mem"); io_mem.configure(.parent(this), .size(1), .lsb_pos(9), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); reserved = uvm_reg_field::type_id::create("reserved"); reserved.configure(.parent(this), .size(22), .lsb_pos(10), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); endfunction endclass
Writing reg class for IO_Addr
class io_addr extends uvm_reg; `uvm_object_utils(io_addr) //--------------------------------------- // fields instance //--------------------------------------- rand uvm_reg_field addr; //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "io_addr"); super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width endfunction //--------------------------------------- // build_phase - // 1. Create the fields // 2. Configure the fields //--------------------------------------- function void build; // Create bitfield addr = uvm_reg_field::type_id::create("addr"); // Configure addr.configure(.parent(this), .size(32), .lsb_pos(0), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); endfunction endclass
Writing reg class for Mem_Addr
class mem_addr extends uvm_reg; `uvm_object_utils(mem_addr) //--------------------------------------- // fields instance //--------------------------------------- rand uvm_reg_field addr; //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "mem_addr"); super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width endfunction //--------------------------------------- // build_phase - // 1. Create the fields // 2. Configure the fields //--------------------------------------- function void build; // Create bitfield addr = uvm_reg_field::type_id::create("addr"); // Configure addr.configure(.parent(this), .size(32), .lsb_pos(0), .access("RW"), .volatile(0), .reset(0), .has_reset(1), .is_rand(1), .individually_accessible(0)); endfunction endclass
Writing register package
register package is written by extending the uvm_reg_block
class dma_reg_model extends uvm_reg_block; `uvm_object_utils(dma_reg_model) //--------------------------------------- // Constructor //--------------------------------------- function new (string name = ""); super.new(name, build_coverage(UVM_NO_COVERAGE)); endfunction endclass
Instantiation of register classes in register package
- Instantiating the register classes
//--------------------------------------- // register instances //--------------------------------------- rand intr reg_intr; rand ctrl reg_ctrl; rand io_addr reg_io_addr; rand mem_addr reg_mem_addr;
- Creating, building and configuring the register instances
reg_intr = intr::type_id::create("reg_intr"); reg_intr.build(); reg_intr.configure(this); reg_ctrl = ctrl::type_id::create("reg_ctrl"); reg_ctrl.build(); reg_ctrl.configure(this); reg_io_addr = io_addr::type_id::create("reg_io_addr"); reg_io_addr.build(); reg_io_addr.configure(this); reg_mem_addr = mem_addr::type_id::create("reg_mem_addr"); reg_mem_addr.build(); reg_mem_addr.configure(this);
- Creating the memory map
default_map = create_map("my_map", 0, 4, UVM_LITTLE_ENDIAN);
- Adding registers to memory map
default_map.add_reg(reg_intr , 'h0, "RW"); // reg, offset, access default_map.add_reg(reg_ctrl , 'h4, "RW"); default_map.add_reg(reg_io_addr , 'h8, "RW"); default_map.add_reg(reg_mem_addr, 'hC, "RW");
- Complete register package class
class dma_reg_model extends uvm_reg_block; `uvm_object_utils(dma_reg_model) //--------------------------------------- // register instances //--------------------------------------- rand intr reg_intr; rand ctrl reg_ctrl; rand io_addr reg_io_addr; rand mem_addr reg_mem_addr; //--------------------------------------- // Constructor //--------------------------------------- function new (string name = ""); super.new(name, build_coverage(UVM_NO_COVERAGE)); endfunction //--------------------------------------- // Build Phase //--------------------------------------- function void build; //--------------------------------------- //reg creation //--------------------------------------- reg_intr = intr::type_id::create("reg_intr"); reg_intr.build(); reg_intr.configure(this); reg_ctrl = ctrl::type_id::create("reg_ctrl"); reg_ctrl.build(); reg_ctrl.configure(this); reg_io_addr = io_addr::type_id::create("reg_io_addr"); reg_io_addr.build(); reg_io_addr.configure(this); reg_mem_addr = mem_addr::type_id::create("reg_mem_addr"); reg_mem_addr.build(); reg_mem_addr.configure(this); //--------------------------------------- //Memory map creation and reg map to it //--------------------------------------- default_map = create_map("my_map", 0, 4, UVM_LITTLE_ENDIAN); default_map.add_reg(reg_intr , 'h0, "RW"); // reg, offset, access default_map.add_reg(reg_ctrl , 'h4, "RW"); default_map.add_reg(reg_io_addr , 'h8, "RW"); default_map.add_reg(reg_mem_addr, 'hC, "RW"); lock_model(); endfunction endclass
Writing Adapter Class
- An adapter class is written by extending the uvm_reg_adapter
class dma_adapter extends uvm_reg_adapter; `uvm_object_utils (dma_adapter) //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "dma_adapter"); super.new (name); endfunction endclass
- Writing reg2bus method
//--------------------------------------- // reg2bus method //--------------------------------------- function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw); dma_seq_item tx; tx = dma_seq_item::type_id::create("tx"); tx.wr_en = (rw.kind == UVM_WRITE); tx.addr = rw.addr; if (tx.wr_en) tx.wdata = rw.data; if (!tx.wr_en) tx.rdata = rw.data; return tx; endfunction
- Writing bus2reg method
//--------------------------------------- // bus2reg method //--------------------------------------- function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw); dma_seq_item tx; assert( $cast(tx, bus_item) ) else `uvm_fatal("", "A bad thing has just happened in my_adapter") rw.kind = tx.wr_en ? UVM_WRITE : UVM_READ; rw.addr = tx.addr; rw.data = tx.rdata; rw.status = UVM_IS_OK; endfunction
- Complete adapter class
class dma_adapter extends uvm_reg_adapter; `uvm_object_utils (dma_adapter) //--------------------------------------- // Constructor //--------------------------------------- function new (string name = "dma_adapter"); super.new (name); endfunction //--------------------------------------- // reg2bus method //--------------------------------------- function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw); dma_seq_item tx; tx = dma_seq_item::type_id::create("tx"); tx.wr_en = (rw.kind == UVM_WRITE); tx.addr = rw.addr; if (tx.wr_en) tx.wdata = rw.data; if (!tx.wr_en) tx.rdata = rw.data; return tx; endfunction //--------------------------------------- // bus2reg method //--------------------------------------- function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw); dma_seq_item tx; assert( $cast(tx, bus_item) ) else `uvm_fatal("", "A bad thing has just happened in my_adapter") rw.kind = tx.wr_en ? UVM_WRITE : UVM_READ; rw.addr = tx.addr; rw.data = tx.rdata; rw.status = UVM_IS_OK; endfunction endclass
Integrating register package and adapter in environment
This involves below steps,
- Instantiating the regmodel and adapter
dma_reg_model regmodel; dma_adapter m_adapter;
- Creating the register model and adapter
regmodel = dma_reg_model::type_id::create("regmodel", this); regmodel.build(); m_adapter = dma_adapter::type_id::create("m_adapter",, get_full_name());
- Connecting the adapter sequencer with agent sequencer
regmodel.default_map.set_sequencer( .sequencer(dma_agnt.sequencer), .adapter(m_adapter) );
- Setting the base address to regmodel address map
regmodel.default_map.set_base_addr('h400);
- Complete env code
class dma_model_env extends uvm_env; //--------------------------------------- // agent and scoreboard instance //--------------------------------------- dma_agent dma_agnt; //--------------------------------------- // Reg Model and Adapter instance //--------------------------------------- dma_reg_model regmodel; dma_adapter m_adapter; `uvm_component_utils(dma_model_env) //--------------------------------------- // constructor //--------------------------------------- function new(string name, uvm_component parent); super.new(name, parent); endfunction : new //--------------------------------------- // build_phase - create the components //--------------------------------------- function void build_phase(uvm_phase phase); super.build_phase(phase); dma_agnt = dma_agent::type_id::create("dma_agnt", this); //--------------------------------------- // Register model and adapter creation //--------------------------------------- regmodel = dma_reg_model::type_id::create("regmodel", this); regmodel.build(); m_adapter = dma_adapter::type_id::create("m_adapter",, get_full_name()); endfunction : build_phase //--------------------------------------- // connect_phase - connecting regmodel sequencer and adapter with // mem agent sequencer and adapter //--------------------------------------- function void connect_phase(uvm_phase phase); regmodel.default_map.set_sequencer( .sequencer(dma_agnt.sequencer), .adapter(m_adapter) ); regmodel.default_map.set_base_addr('h400); endfunction : connect_phase endclass : dma_model_env
Accessing registers with RAL
Write to the register
- Write to the register is performed by calling the register write method
- Data to be written to the registers is passed as an argument to the write method
Syntax:
reg_model.rag_name.write(status, write_data)
Read from the register
- Read from the register is performed by calling the register read method
- The read method returns the Data
Syntax:
reg_model.rag_name.read(status, read_data)
Access to the registers,
//Write to the Registers regmodel.reg_intr.write(status, 32'h1234_1234); regmodel.reg_ctrl.write(status, 32'h1234_5678); regmodel.reg_io_addr.write(status, 32'h1234_9ABC); regmodel.reg_mem_addr.write(status, 32'h1234_DEF0); //Read from the register regmodel.reg_intr.read(status, rdata); regmodel.reg_ctrl.read(status, rdata); regmodel.reg_io_addr.read(status, rdata); regmodel.reg_mem_addr.read(status, rdata);
Complete Sequence Code
class dma_reg_seq extends uvm_sequence; `uvm_object_utils(dma_reg_seq) dma_reg_model regmodel; //--------------------------------------- // Constructor //--------------------------------------- function new (string name = ""); super.new(name); endfunction //--------------------------------------- // Sequence body //--------------------------------------- task body; uvm_status_e status; uvm_reg_data_t incoming; bit [31:0] rdata; if (starting_phase != null) starting_phase.raise_objection(this); //Write to the Registers regmodel.reg_intr.write(status, 32'h1234_1234); regmodel.reg_ctrl.write(status, 32'h1234_5678); regmodel.reg_io_addr.write(status, 32'h1234_9ABC); regmodel.reg_mem_addr.write(status, 32'h1234_DEF0); //Read from the registers regmodel.reg_intr.read(status, rdata); regmodel.reg_ctrl.read(status, rdata); regmodel.reg_io_addr.read(status, rdata); regmodel.reg_mem_addr.read(status, rdata); if (starting_phase != null) starting_phase.drop_objection(this); endtask endclass
Test case
register sequence is declared, created and started from the test case.
class dma_reg_test extends dma_model_base_test; `uvm_component_utils(dma_reg_test) //--------------------------------------- // sequence instance //--------------------------------------- dma_reg_seq reg_seq; //--------------------------------------- // constructor //--------------------------------------- function new(string name = "dma_reg_test",uvm_component parent=null); super.new(name,parent); endfunction : new //--------------------------------------- // build_phase //--------------------------------------- virtual function void build_phase(uvm_phase phase); super.build_phase(phase); // Create the sequence reg_seq = dma_reg_seq::type_id::create("reg_seq"); endfunction : build_phase //--------------------------------------- // run_phase - starting the test //--------------------------------------- task run_phase(uvm_phase phase); phase.raise_objection(this); if ( !reg_seq.randomize() ) `uvm_error("", "Randomize failed") //Setting sequence in reg_seq reg_seq.regmodel = env.regmodel; reg_seq.starting_phase = phase; reg_seq.start(env.dma_agnt.sequencer); phase.drop_objection(this); //set a drain-time for the environment if desired phase.phase_done.set_drain_time(this, 50); endtask : run_phase endclass : dma_reg_test
Simulator Output
UVM_INFO @ 0: reporter [RNTST] Running test dma_reg_test... UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_spell_chkr.svh(123) @ 0: ---------------------------------------------------------------- Name Type Size Value ---------------------------------------------------------------- uvm_test_top dma_reg_test - @1912 env dma_model_env - @1980 dma_agnt dma_agent - @2031 driver dma_driver - @2299 rsp_port uvm_analysis_port - @2371 seq_item_port uvm_seq_item_pull_port - @2334 monitor dma_monitor - @2189 item_collected_port uvm_analysis_port - @2266 sequencer dma_sequencer - @2402 rsp_export uvm_analysis_export - @2447 seq_item_export uvm_seq_item_pull_imp - @2857 arbitration_queue array 0 - lock_queue array 0 - num_last_reqs integral 32 'd1 num_last_rsps integral 32 'd1 ---------------------------------------------------------------- Design WR addr 400 Data 12341234 Design WR addr 404 Data 12345678 Design WR addr 408 Data 12349abc Design WR addr 40c Data 1234def0 Design RD addr 400 Data 12341234 Design RD addr 404 Data 12345678 Design RD addr 408 Data 12349abc Design RD addr 40c Data 1234def0 UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 245: reporter [TEST_DONE] UVM_INFO dma_test.sv(58) @ 245: uvm_test_top [dma_reg_test] --------------------------------------- UVM_INFO dma_test.sv(59) @ 245: uvm_test_top [dma_reg_test] ---- TEST PASS ---- UVM_INFO dma_test.sv(60) @ 245: uvm_test_top [dma_reg_test] --------------------------------------- UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 245: reporter [UVM/REPORT/SERVER] --- UVM Report Summary --- ** Report counts by severity UVM_INFO : 7 UVM_WARNING : 0 UVM_ERROR : 0 UVM_FATAL : 0 ** Report counts by id [RNTST] 1 [TEST_DONE] 1 [UVM/CONFIGDB/SPELLCHK] 1 [UVM/RELNOTES] 1 [dma_reg_test] 3
Write and Read Waveform:
Write Waveform:
Read Waveform:
UVM RAL Usage Model
UVM RAL Usage Model
The previous section described the UVM RAL base classes. This section explains using UVM RAL base classes with reference to the design elements. i.e mapping UVM RAL elements with design elements.
Let’s consider the design architecture of the CPU. In this section, In order to understand the use of each UVM RAL element, first, we will map each RAL element with the CPU design elements.
- The architecture of the CPU shows that it consists of multiple modules
- uvm_reg_block shall be used to represent each design module
- each module consists of many registers
- uvm_reg_file shall group the module registers
- each module consists of registers
- uvm_reg shall be used to implement the design register
- each register consists of one or multiple fields
- uvm_reg_field type shall be used for field implementation
Below block diagram is the Direct Memory Access (DMA) design block containing four registers, which have two, three, one and one fields respectively, an internal memory, and external memory.
Below block diagram shows the UVM RAL element equivalent to the design element.
RAL Building blocks
- Register block
- Register file
- Register
- Register Filed
Each is explained in detail below,
Register Block
The reg block is written by extending the uvm_reg_block.
A block corresponds to a design component/hierarchy with its own interface(s), registers, register files, memories, and sub-blocks.
In simple words, a block can be referred to as a design module with its own interface(s), registers, register files, memories, and sub-blocks. And it will be having unique address decoding to route the access to it.
A register model is an instance of a register block, which may contain any number of registers, register files, memories, and other blocks.
Register File
The reg file is written by extending the uvm_reg_file.
The reg file shall be used to group the number of registers or register files. below block diagram shows the register file consists of reg_file_0, reg_file_1 and reg_file_2. and each register file consists of a set of registers. ( registers are shown only in reg_file_2, Assume that there are registers in reg_file_0 and reg_file_1 )
Register
The uvm register class is written by extending the uvm_reg.
A register represents a set of fields that are accessible as a single entity.
Each register contains any number of fields, which mirror the values of the corresponding elements in hardware.
Register Field
The register field is declared with the type uvm_reg_filed.
Fields represent a contiguous set of bits. All data values are modeled as fields. A field is contained within a single register but may have different access policies. access policies are explained in the next sections.
Complete Register Model block diagram
UVM RAL Methods
Register Access Methods
UVM RAL library classes have builtin methods implemented in it, these methods can be used for accessing the registers. These methods are referred to as Register Access Methods.
The register model has methods to read, write, update and mirror DUT registers and register field values, these methods are called API (application programming interface).
APIs can either use front door access or back door access to DUT registers and register fields.
- Front door access involves using the bus interface and it is associated with the timing
- Back door access uses simulator database access routines and this happens in zero simulation time
API methods
read and write
- read() returns and updates the value of the DUT register.
- write() writes and updates the value of the DUT register.
- both read and write can be used for front door access or back door access.
- In read or write value will be updated by the bus predictor on completion of the front door read or write cycle and automatically in back door read or write cycle.
peek and poke
- peek() reads the DUT register value using a backdoor
- poke() writes a value to DUT register using backdoor
set and get
- set() and get() writes and reads directly to the desired value.
- set and get methods operates on the register model desired value, not accesses to DUT register value. The desired value can be updated to the DUT using the update method.
update
- if there is a difference between desired value and mirrored value,update() will initiate a write to register.update method can be used after the set method.
mirror
- mirror() reads the updated DUT register values. The mirroring can be performed in the front door or back door( peek() ).
randomize
- randomize() randomizes register or field values with or without constraints.as per the requirement register values can be modified in post_randomize().after randomization update() can be used to update the DUT register values.
reset
reset() sets the register desired and mirrored value to the pre-defined reset value.
Method | Front door access | Back door access |
---|---|---|
read() | RF | RF |
write() | RF | RF |
peek() | RF | RF |
poke() | RF | RF |
set() | RF | RF |
get() | RF | RF |
update() | BR | BR |
mirror() | BR | BR |
randomize() | BRF | BRF |
reset() | BRF | BRF |
register access methods at a different level
* B – Block R – Register F – Field
UVM RAL Adapter
UVM Register Model Adapter
With the UVM Register model, we do design register access, i.e WRITE to the design register or READ from the design register by calling RAL methods. Finally, these transactions have to be placed to design bus, this will be done by RAL component Adapter.
The RAL adapter acts as a converter between the RAL model and Interface. It converts transactions of RAL methods to Interface/Bus transactions.
- The Adapter converts between register model read, write methods and the interface-specific transactions
- The transaction adapter is implemented by extending the uvm_reg_adapter class and implementing the reg2bus() and bus2reg() methods
UVM RAL reg2bus
- reg2bus method converts the RAL transactions to Interface (bus) transactions
UVM RAL bus2reg
- bus2reg method converts the Interface (bus) transactions to RAL transactions
class tb_env extends uvm_env; reg_model regmodel; uvm_reg_predictor#(ahb_trans) ahb2reg_predictor; reg2ahb_adapter reg_adapter; ahb_agent ahb; virtual function void build_phase(uvm_phase phase); ahb2reg_predictor = new(“ahb2reg_predictor”, this); endfunction virtual function void connect_phase(uvm_phase phase); if (regmodel.get_parent() == null) begin reg_adapter = reg2ahb_adapter::type_id::create(“reg_adapter”,,get_full_name()); ... ahb2reg_predictor.map = regmodel.AHB; ahb2reg_predictor.adapter = reg_adapter; ahb.monitor.ap.connect(ahb2reg_predictor.bus_in); end ... endfunction ... endclass
UVM RAL Predictor
UVM Register Model Predictor
We know that the UVM Register model maintains the latest design register values in it. but how Register model will get to know the latest values?
This will be done by the UVM RAL component Predictor.
UVM RAL Predictor predicts the register access done through the register model and updates the RAL Model registers. UVM RAL provides the base class uvm_reg_predictor. uvm_reg_predictor updates the register model based on observed transactions published by a monitor.
- Any register access done through the register model is predicted and updated inherently by the base classes (implicit prediction)
- Prediction of the register values in the model can also be done explicitly using an external predictor which also takes care of interface bus transactions not occurring through the register model (explicit prediction)
Implicit prediction
- Implicit prediction only requires the integration of the register model with one or more bus sequencers
- Updates to the mirror are predicted automatically (i.e., implicitly) by the register model after the completion of each read, write, peek, or poke operation
Explicit prediction
- Explicit prediction requires the register model to be integrated with both the bus sequencers and corresponding bus monitors
- In this mode, the implicit prediction is turned off and all updates to the mirror are predicted externally (i.e., explicitly) by a uvm_reg_predictor component, one for each bus interface
UVM REG EXAMPLE WITHOUT RAL
Register Access without RAL Model
In this section will see an example that shows one of the ways to access DUT registers without the UVM RAL Model.
Let’s consider a DMA design which consists of registers in it and reg_interface is used to access the registers.
Below is the block diagram of DMA.
Below are the DMA registers,
- INTR
- CTRL
- IO ADDR
- MEM ADDR
Address of each register and register field description is given below,
Reg Name | Address | Field Description | |||||
INTR | h400 | 31:16 | 15:0 | ||||
MASK | STATUS | ||||||
CTRL | h404 | 31:10 | 9 | 8:1 | 0 | ||
RESVD | IO_MEM | W_COUNT | START_DMA | ||||
IO_ADDR | h408 | 31:0 | |||||
ADDRESS | |||||||
MEM_ADDR | h40C | 31:0 | |||||
ADDRESS | |||||||
Below is the testbench block diagram,
The testbench component DMA agent is used to access the reg_interface.
DMA Agent consists of,
- Driver
- Monitor
- Sequencer
- Sequences (Write sequence and Read Sequence)
In testbench,
- Write to the register is done by calling the write sequence
- Read of the register is done by calling the read sequence
This testbench is UVM based testbench. For ease understanding, only the sequence and test case is explained in this section. For detailed steps on writing UVM Testbench refer to UVM Testbench Architecture.
Base Sequence
class dma_sequence extends uvm_sequence#(dma_seq_item); `uvm_object_utils(dma_sequence) //--------------------------------------- //Constructor //--------------------------------------- function new(string name = "dma_sequence"); super.new(name); endfunction `uvm_declare_p_sequencer(dma_sequencer) //--------------------------------------- // create, randomize and send the item to driver //--------------------------------------- virtual task body(); repeat(2) begin req = dma_seq_item::type_id::create("req"); wait_for_grant(); req.randomize(); send_request(req); wait_for_item_done(); end endtask endclass
Write Sequence
class write_sequence extends uvm_sequence#(dma_seq_item); bit [31:0] t_addr,t_data; `uvm_object_utils(write_sequence) //--------------------------------------- //Constructor //--------------------------------------- function new(string name = "write_sequence"); super.new(name); endfunction virtual task body(); `uvm_do_with(req,{req.wr_en==1;req.addr==t_addr;req.wdata==t_data;}) endtask endclass
Read Sequence
class read_sequence extends uvm_sequence#(dma_seq_item); bit [31:0] t_addr; `uvm_object_utils(read_sequence) //--------------------------------------- //Constructor //--------------------------------------- function new(string name = "read_sequence"); super.new(name); endfunction virtual task body(); `uvm_do_with(req,{req.wr_en==0;req.addr==t_addr;}) endtask endclass
Register Accessing:
Write or Read operation to any SFR is done by calling the write or read sequence respectively.
Write Operation:
//Write to register INTR wr_seq.t_addr = 32'h400; wr_seq.t_data = 32'hFFFF_0F0F; wr_seq.start(env.dma_agnt.sequencer); //Write to register CTRL wr_seq.t_addr = 32'h404; wr_seq.t_data = 32'h1234_5678; wr_seq.start(env.dma_agnt.sequencer); //Write to register IO_ADDR wr_seq.t_addr = 32'h408; wr_seq.t_data = 32'hABCD_EF12; wr_seq.start(env.dma_agnt.sequencer); //Write to register MEM_ADDR wr_seq.t_addr = 32'h40C; wr_seq.t_data = 32'h9731_2345; wr_seq.start(env.dma_agnt.sequencer);
Read Operation:
//Read from register INTR rd_seq.t_addr = 32'h400; rd_seq.start(env.dma_agnt.sequencer); //Read from register CTRL rd_seq.t_addr = 32'h404; rd_seq.start(env.dma_agnt.sequencer); //Read from register IO_ADDR rd_seq.t_addr = 32'h408; rd_seq.start(env.dma_agnt.sequencer); //Read from register MEM_ADDR rd_seq.t_addr = 32'h40C; rd_seq.start(env.dma_agnt.sequencer);
In the above code, Register Address is assigned for register access. for the complex designs, the number of registers will be more. readability and debug will be difficult. to overcome this we can use define for register address.
Register address defines
`define INTR_SFR_ADDR 32'h400 `define CTRL_SFR_ADDR 32'h404 `define IO_ADDR_SFR_ADDR 32'h408 `define MEM_ADDR_SFR_ADDR 32'h40C
Write and Read operation using defines
Write Operation:
wr_seq.t_addr = `INTR_SFR_ADDR; wr_seq.t_data = 32'hFFFF_0F0F; wr_seq.start(env.dma_agnt.sequencer); wr_seq.t_addr = `CTRL_SFR_ADDR; wr_seq.t_data = 32'h1234_5678; wr_seq.start(env.dma_agnt.sequencer); wr_seq.t_addr = `IO_ADDR_SFR_ADDR; wr_seq.t_data = 32'hABCD_EF12; wr_seq.start(env.dma_agnt.sequencer); wr_seq.t_addr = `MEM_ADDR_SFR_ADDR; wr_seq.t_data = 32'h9731_2345; wr_seq.start(env.dma_agnt.sequencer);
Read Operation:
rd_seq.t_addr = `INTR_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `CTRL_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `IO_ADDR_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `MEM_ADDR_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer);
Accessing registers from TestCase
class dma_reg_test extends uvm_test; `uvm_component_utils(dma_reg_test) //--------------------------------------- // env instance //--------------------------------------- dma_model_env env; //--------------------------------------- // sequence instance //--------------------------------------- write_sequence wr_seq; read_sequence rd_seq; //--------------------------------------- // constructor //--------------------------------------- function new(string name = "dma_reg_test",uvm_component parent=null); super.new(name,parent); endfunction : new //--------------------------------------- // build_phase //--------------------------------------- virtual function void build_phase(uvm_phase phase); super.build_phase(phase); // Create the env env = dma_model_env::type_id::create("env", this); // Create the sequence wr_seq = write_sequence::type_id::create("wr_seq"); rd_seq = read_sequence::type_id::create("rd_seq"); endfunction : build_phase //--------------------------------------- // end_of_elobaration phase //--------------------------------------- virtual function void end_of_elaboration(); //print's the topology print(); endfunction //--------------------------------------- // run_phase - starting the test //--------------------------------------- task run_phase(uvm_phase phase); phase.raise_objection(this); wr_seq.t_addr = `INTR_SFR_ADDR; wr_seq.t_data = 32'hFFFF_0F0F; wr_seq.start(env.dma_agnt.sequencer); wr_seq.t_addr = `CTRL_SFR_ADDR; wr_seq.t_data = 32'h1234_5678; wr_seq.start(env.dma_agnt.sequencer); wr_seq.t_addr = `IO_ADDR_SFR_ADDR; wr_seq.t_data = 32'hABCD_EF12; wr_seq.start(env.dma_agnt.sequencer); wr_seq.t_addr = `MEM_ADDR_SFR_ADDR; wr_seq.t_data = 32'h9731_2345; wr_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `INTR_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `CTRL_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `IO_ADDR_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); rd_seq.t_addr = `MEM_ADDR_SFR_ADDR; rd_seq.start(env.dma_agnt.sequencer); phase.drop_objection(this); endtask : run_phase endclass : dma_reg_test
Simulator Output
UVM_INFO @ 0: reporter [RNTST] Running test dma_reg_test... ---------------------------------------------------------------- Name Type Size Value ---------------------------------------------------------------- uvm_test_top dma_reg_test - @1881 env dma_model_env - @1949 dma_agnt dma_agent - @2017 driver dma_driver - @2128 rsp_port uvm_analysis_port - @2200 seq_item_port uvm_seq_item_pull_port - @2163 monitor dma_monitor - @2048 item_collected_port uvm_analysis_port - @2095 sequencer dma_sequencer - @2231 rsp_export uvm_analysis_export - @2276 seq_item_export uvm_seq_item_pull_imp - @2686 arbitration_queue array 0 - lock_queue array 0 - num_last_reqs integral 32 'd1 num_last_rsps integral 32 'd1 ---------------------------------------------------------------- UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 195: reporter [TEST_DONE] UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 195: reporter --- UVM Report Summary --- ** Report counts by severity UVM_INFO : 3 UVM_WARNING : 0 UVM_ERROR : 0 UVM_FATAL : 0 ** Report counts by id [RNTST] 1 [TEST_DONE] 1 [UVM/RELNOTES] 1
Write and Read Waveform:
Write Waveform:
Read Waveform:
Integrating RAL to Bus Agent
Integrating RAL to Agent
Once after the RAL implementation, RAL has to be connected with the Bus Agent. This section describes connecting RAL with the sequencer and monitor of the bus.
Integrating Bus Sequencers
All integration approaches require a register model to be configured with one or more bus sequencers.
The register model becomes a property of a uvm_reg_sequence subtype that executes
- Directly on a bus sequencer, if there is only one bus interface providing access to the DUT registers
- As a virtual sequence, if there are one or more bus interfaces providing access to the DUT registers;
- As a register sequence running
Integrating the Register Model with a Bus Monitor
By default, the register model updates its mirror copy of the register values implicitly. Every time a register is read or written through the register model, its mirror value is updated.
If other agents on the bus interface perform read and write transactions outside the context of the register model, the register model must learn of these bus operations to update its mirror accordingly.
- Integration is accomplished by first instantiating a uvm_reg_predictor component
- uvm_reg_predictor component is then connected to the bus monitor’s analysis port
class tb_env extends uvm_env; reg_model regmodel; uvm_reg_predictor#(ahb_trans) ahb2reg_predictor; reg2ahb_adapter reg_adapter; ahb_agent ahb; virtual function void build_phase(uvm_phase phase); ahb2reg_predictor = new(“ahb2reg_predictor”, this); endfunction virtual function void connect_phase(uvm_phase phase); if (regmodel.get_parent() == null) begin reg_adapter = reg2ahb_adapter::type_id::create(“reg_adapter”,,get_full_name()); ... ahb2reg_predictor.map = regmodel.AHB; ahb2reg_predictor.adapter = reg_adapter; ahb.monitor.ap.connect(ahb2reg_predictor.bus_in); end ... endfunction ... endclass
UVM RAL Base Classes
UVM RAL Model types, enums and utility classes
This section describes UVM RAL model types, enums and utility classes.
UVM RAL types
uvm_reg_data_t
2-state data value with `UVM_REG_DATA_WIDTH bits
uvm_reg_data_logic_t
4-state data value with `UVM_REG_DATA_WIDTH bits
uvm_reg_addr_t
2-state address value with `UVM_REG_ADDR_WIDTH bits
uvm_reg_addr_logic_t
4-state address value with `UVM_REG_ADDR_WIDTH bits
uvm_reg_byte_en_t
2-state byte_enable value with `UVM_REG_BYTENABLE_WIDTH bits
uvm_reg_cvr_t
Coverage model value set with `UVM_REG_CVR_WIDTH bits
uvm_hdl_path_slice
Slice of an HDL path
UVM RAL Enums
uvm_status_e
Return status for register operations
- UVM_IS_OK
- Operation completed successfully
- UVM_NOT_OK
- Operation completed with error
- UVM_HAS_X
- Operation completed successfully bit had unknown bits.
uvm_path_e
Path used for register operation
- UVM_FRONTDOOR
- Use the front door
- UVM_BACKDOOR
- Use the back door
- UVM_PREDICT
- Operation derived from observations by a bus monitor via the uvm_reg_predictor class
- UVM_DEFAULT_PATH
- Operation specified by the context
uvm_check_e
Read-only or read-and-check
- UVM_NO_CHECK
- Read only
- UVM_CHECK
- Read and check
uvm_endianness_e
Specifies byte ordering
- UVM_NO_ENDIAN
- Byte ordering not applicable
- UVM_LITTLE_ENDIAN
- Least-significant bytes first in consecutive addresses
- UVM_BIG_ENDIAN
- Most-significant bytes first in consecutive addresses
- UVM_LITTLE_FIFO
- Least-significant bytes first at the same address
- UVM_BIG_FIFO
- Most-significant bytes first at the same address
uvm_elem_kind_e
Type of element being read or written
- UVM_REG
- Register
- UVM_FIELD
- Field
- UVM_MEM
- Memory location
uvm_access_e
Type of operation begin performed
- UVM_READ
- Read operation
- UVM_WRITE
- Write operation
uvm_hier_e
Whether to provide the requested information from a hierarchical context.
- UVM_NO_HIER
- Provide info from the local context
- UVM_HIER
- Provide info based on the hierarchical context
uvm_predict_e
How the mirror is to be updated
- UVM_PREDICT_DIRECT
- Predicted value is as-is
- UVM_PREDICT_READ
- Predict based on the specified value having been read
- UVM_PREDICT_WRITE
- Predict based on the specified value having been written
uvm_coverage_model_e
Coverage models available or desired. Multiple models may be specified by bitwise
OR’ing individual model identifiers.
- UVM_NO_COVERAGE
- None
- UVM_CVR_REG_BITS
- Individual register bits
- UVM_CVR_ADDR_MAP
- Individual register and memory addresses
- UVM_CVR_FIELD_VALS
- Field values
- UVM_CVR_ALL
- All coverage models
uvm_reg_mem_tests_e
Select which pre-defined test sequence to execute. Multiple test sequences may be selected by bit-wise OR’ing their respective symbolic values.
- UVM_DO_REG_HW_RESET
- Run uvm_reg_hw_reset_seq
- UVM_DO_REG_BIT_BASH
- Run uvm_reg_bit_bash_seq
- UVM_DO_REG_ACCESS
- Run uvm_reg_access_seq
- UVM_DO_MEM_ACCESS
- Run uvm_mem_access_seq
- UVM_DO_SHARED_ACCESS
- Run uvm_reg_mem_shared_access_seq
- UVM_DO_MEM_WALK
- Run uvm_mem_walk_seq
- UVM_DO_ALL_REG_MEM_TESTS
- Run all of the above
Test sequences, when selected, are executed in the order in which they are specified
above.
❮ Previous Next ❯