UVM layering sequence
Introduction to layering sequence
- If we are building more complex protocol bus transmission, such as PCIe, USB3.0, etc., a single transmission level will be less friendly to future incentive multiplexing and upper layer control. For this deeper data transmission, in practice, both VIP and self-developed environments tend to simulate the protocol level through sequence communities at several abstract levels.
- Through the hierarchical sequence, transaction transformations from high level of abstraction to low level of abstraction, such as transaction layer, transport layer and physical layer, can be constructed respectively. This hierarchical sequence construction method is called layering sequence. For example, when performing register level access operations, it needs to be transformed through transport layer, and finally mapped to specific bus transmission.
- We will explain the core of layer sequence through a simple example code at one end and highlight the idea of sequence level conversion.
layering sequence example
typedef enum{CLKON,CLKOFF,RESET,WRREG,RDREG} phy_cmd_t; typedef enum{FREG_LOW_TRANS,FREG_MED_TRANS,FREG_HIGH_TRANS}layer_cmd_t; class bus_trans extends uvm_sequence_item; rand phy_cmd_t cmd; rand int addr; rand int data; constraint cstr{ soft addr=='h0; soft data=='h0; }; ... endclass class packet_seq extends uvm_sequence; rand int len; rand int addr; rand int data[]; rand phy_cmd_t cmd; constraint cstr{ soft len inside {[30:50]}; soft addr[31:26]=='hFF00; data.size()==len; }; ... task body(); bus_trans req; foreach(data[i]) `uvm_do_with{req,{cmd==local::cmd; addr==local::addr; data==local::data[i];}} endtask endclass class layer_trans extends uvm_sequence_item; rand layer_cmd_t layer_cmd; rand int pkt_len; rand int pkt_idle; constraint cstr{ soft pkt_len inside{[10:20]}; layer_cmd == FREQ_LOW_TRANS -> pkt_idle inside {[300:400]}; layer_cmd == FREQ_MED_TRANS -> pkt_idle inside {[100:200]}; layer_cmd == FREQ_HIGH_TRANS -> pkt_idle inside {[20:40]}; }; ... endclass class adapter_sequence extends uvm_sequence; `uvm_object_utils(adapter_seq) `uvm_declare_p_sequencer(phy_master_sequencer) ... task body(); layer_trans trans; packet_seq pkt; forever begin p_sequencer.up_sqr.get_next_item(req); void'($cast(trans,req)); repeat(trans.pkt_len)begin `uvm_do(pkt) delay(trans.pkt_idle); end p_sequencer.up_sqr.item_done(); end endtask virtual task delay(int delay); ... endtask endclass class top_seq extends uvm_sequence; ... task body(); layer_trans trans; `uvm_do_with(trans,{layer_cmd == FREQ_LOW_TRANS;}) `uvm_do_with(trans,{layer_cmd == FREQ_HIGH_TRANS;}) endtask endclass class layering_sequencer extends uvm_sequencer; ... endclass class phy_master_sequencer extends uvm_sequencer; layering_sequencer up_sqr; ... endclasss class phy_master_driver extends uvm_driver; ... task run_phase(uvm_phase phase); REQ tmp; bus_trans req; forever begin seq_item_port.get_next_item(tmp); void'($cast(req,tmp)); `uvm_info("DRV",$sformatf("got a item \n %s",req.sprint()),UVM_LOW) seq_item_port.item_done(); end endtask endclass class phy_master_agent extends uvm_agent; phy_master_driver drv; phy_master_sequencer sqr; ... function void build_phase(uvm_phase phase); sqr=reg_master_sequencer::type_id::create("sqr",this); drv=reg_master_driver::type_id::create("drv",this); endfunction function void connect_phase(uvm_phase phase); drv.seq_item_port.connect(sqr.seq_item_export); endfunction endclass class test1 extends uvm_test; layering_sequencer layer_sqr; phy_master_agent phy_agt; ... function void build_phae(uvm_phase phase); layer_sqr=layering_sequencer::type_id::create("layer_sqr",this); phy_agt=phy_master_agent::type_id::create("phy_agt",this); endfunction function void connect_phase(uvm_phase phase); phy_agt.sqr.up_sqr=layer_sqr; endfunction task run_phase(uvm_phase phase); top_seq seq; adapter_seq adapter; phase.raise_objection(phase); seq=new(); adapter=new(); fork adapter.start(phy_agt.sqr); join_none seq.start(layer_sqr); phase.drop_objection(phase); endtask endclass
Not a driver, you can call get_next_item? You can, too, because get_ next_ The item method actually belongs to sqr, so why can the driver call this method directly through port? This is because port is connected to sqr's import.
We can get some methods on how to implement the sequence layer protocol conversion:
- No matter how many abstract transaction class definitions there are, there should be a corresponding sequencer as the routing channel of the transaction. For example, layer_sequencer and phy_master_sequencer as layer_trans and bus_trans channel.
- In each abstract level sequence, there needs to be a corresponding conversion method to obtain the high-level transaction from the high-level sequencer, then convert it into a low-level transaction, and finally send it through the low-level sequencer. For example, adapter_seq is responsible for retrieving from layer_sequencer get layer_trans, and then convert it to PHY_ master_ The corresponding sequence or transaction on the sequencer side, and finally remove it from PHY_ master_ The sequencer is sent out.
- These adaptation sequences should run on the lower level sequencer side. As a "perpetual" sequence, they should always be ready for service, obtain transactions from the high-level sequencer, and send them out from the lower level sequencer side through conversion. For example, in test1 above, the adapter sequence is attached to the low-level sequence through adapter.start(phy_agt.sqr), ready to convert the transaction and send it.
- As for how many levels of transaction item classes need to be defined, the above example is only to illustrate the general method of layer sequence. For the actual level definition and transaction item definition, we need to deal with specific problems.
layering sequence parsing
We can see the sequencer corresponding to each sequence category, as well as the methods for sending and converting sequence item s. After conversion, the high-level transaction content can be implemented to the low-level protocol agent and physical interface.
The example code does not give the processing of the read loop, that is, the communication from the physical interface through the physical agent and finally to the laying sequencer. In practice, we can use monitor to transform the abstract level to return item.
You can also collect response transmission through monitors at different levels, and finally realize it by returning item through monitor transformation abstraction level.
As for which feedback loop to choose, it is related to the implementation method of the underlying agent feedback loop, that is, if the original method returns response through the driver side, we suggest continuing the conversion from low-level transaction to high-level transaction on the feedback chain. If the original method returns response through the monitor side, Then we also suggest creating a corresponding high-level monitor and drawing abstract level transformation.
Focus on the author
- Readme
The author is a graduate student majoring in digital design at China University of science and technology. His level is limited. If there are mistakes, please correct them and want to make progress with you. - experience
He has won the national scholarship, "Higher Education Society Cup" mathematical modeling national second prize - Update in succession:
1. Follow up content of system verilog related to UVM verification;
2. Some basic module designs related to verilog digital design, such as FIFO, UART, I2C, etc.
3. Research guarantee and competition experience, etc - WeChat official account
Welcome to the official account of "the daily practice of digital IC Xiao Bai". We look forward to fighting with you to travel around the digital IC world.