Register Access without RAL Model
Table of Contents
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: