• wait_for_clock()
• wait_for_reset()
• wait_for_interrupt()
• interrupt_cleared()
config代码大概是这样子的:
class bus_agent_config extends uvm_object;
`uvm_object_utils(bus_agent_config)
virtual bus_if BUS; // This is the virtual interface with the signals to wait on
function new(string name = "bus_agent_config");
super.new(name);
endfunction
//
// Task: wait_for_reset
//
// This method waits for the end of reset.
task wait_for_reset;
@( posedge BUS.resetn );
endtask
//
// Task: wait_for_clock
//
// This method waits for n clock cycles.
task wait_for_clock( int n = 1 );
repeat( n ) begin
@( posedge BUS.clk );
end
endtask
//
// Task: wait_for_error
//
task wait_for_error;
@(posedge error);
endtask: wait_for_error
//
// Task: wait_for_no_error
//
task wait_for_no_error;
@(negedge error);
endtask: wait_for_no_error
endclass: bus_agent_config
sequence 代码大概是这样子的:
class bus_seq extends uvm_sequence #(bus_seq_item);
`uvm_object_utils(bus_seq)
bus_seq_item req;
bus_agent_config m_cfg;
rand int limit = 40; // Controls the number of iterations
function new(string name = "bus_seq");
super.new(name);
endfunction
task body;
int i = 5;
req = bus_seq_item::type_id::create("req");
// Get the configuration object
if(!uvm_config_db #(bus_agent_config)::get(null, get_full_name(), "config", m_cfg)) begin
`uvm_error("BODY", "bus_agent_config not found")
end
repeat(limit)
begin
start_item(req);
// The address is constrained to be within the address of the GPIO function
// within the DUT, The result will be a request item for a read or a write
if(!req.randomize() with {addr inside {[32'h0100_0000:32'h0100_001C]};}) begin
`uvm_error("body", "randomization failed for req")
end
finish_item(req);
// Wait for interface clocks:
m_cfg.wait_for_clock(i);
i++;
// The req handle points to the object that the driver has updated with response data
uvm_report_info("seq_body", req.convert2string());
end
endtask: body
endclass: bus_seq
另外这个方法还可以用到coverage 采集过程中:
//
// A coverage monitor that should ignore coverage collected during an error condition:
//
class transfer_link_coverage_monitor extends uvm_subscriber #(trans_link_item);
`uvm_component_utils(transfer_link_coverage_monitor)
T pkt;
// Error monitor bit
bit no_error;
// Configuration:
bus_agent_config m_cfg;
covergroup tlcm_1;
HDR: coverpoint pkt.hdr;
SPREAD: coverpoint pkt.payload {
bins spread[] {[0:1023], [1024:8095], [8096:$]}
}
cross HDR, SPREAD;
endgroup: tlcm_1
function new(string name = "transfer_link_coverage_monitor", uvm_component_parent = null);
super.new(name, parent);
tlcm_1 = new;
endfunction
// The purpose of the run method is to monitor the state of the error
// line
task run_phase( uvm_phase phase );
no_error = 0;
// Get the configuration
if(!uvm_config_db #(bus_agent_config)::get(this, "", "bus_agent_config", m_cfg)) begin
`uvm_error("run_phase", "Configuration error: unable to find bus_agent_config")
end
m_cfg.wait_for_reset; // Nothing valid until reset is over
no_error = 1;
forever begin
m_cfg.wait_for_error; // Blocks until an error occurs
no_error = 0;
m_cfg.wait_for_no_error; // Blocks until the error is removed
end
endtask: run_phase
function write(T t);
pkt = t;
if(no_error == 1) begin // Don't sample if there is an error
tlcm_1.sample();
end
endfunction: write
endclass: transfer_link_coverage_monitor
也是同样的在monitor中 get那个 从上面传下来的 agent_cfg,然后就可以用里面的virtual interface了。
Stimulus/Interrupts
一般牵扯到interrupt 的sequence可以分为这么几种:• Exclusive execution of an ISR sequence
• Prioritised execution of an ISR sequence or sequences
• Hardware triggered sequences
Hardware Interrupt Detection:
对于这种,直接就用cfg中的virtual interface里面做的wait function就可以了
Exclusive ISR Sequence
对于这种就是一旦有一个sequence 去服务interrupt ,其他的 sequence 就不能动了(这里我们就要用grab和ungrab去锁/解锁 sequencer)
这里用的ISR sequence 一般是fork join 的几个sequence的形式。
//
// Sequence runs a bus intensive sequence on one thread
// which is interrupted by one of four interrupts
//
class int_test_seq extends uvm_sequence #(bus_seq_item);
`uvm_object_utils(int_test_seq)
function new (string name = "int_test_seq");
super.new(name);
endfunction
task body;
set_ints setup_ints; // Main activity on the bus interface
isr ISR; // Interrupt service routine
int_config i_cfg; // Config containing wait_for_IRQx tasks
setup_ints = set_ints::type_id::create("setup_ints");
ISR = isr::type_id::create("ISR");
i_cfg = int_config::get_config(m_sequencer); // Get the config
// Forked process - two levels of forking
fork
setup_ints.start(m_sequencer); // Main bus activity,这个应该是和interrupt无关的其他sequence
begin
forever begin
fork // Waiting for one or more of 4 interrupts
i_cfg.wait_for_IRQ0();
i_cfg.wait_for_IRQ1();
i_cfg.wait_for_IRQ2();
i_cfg.wait_for_IRQ3();
join_any
disable fork;
ISR.start(m_sequencer); // Start the ISR
end
end
join_any // At the end of the main bus activity sequence
disable fork;
endtask: body
endclass: int_test_seq
此类情况的ISR sequence里面第一行就是grab,最后一行就是ungrab。哪怕早一点就可能让setup_ins 抢去机会。
以下就是ISR 的sequence:
class isr extends uvm_sequence #(bus_seq_item);
`uvm_object_utils(isr)
function new (string name = "isr");
super.new(name);
endfunction
rand logic[31:0] addr;
rand logic[31:0] write_data;
rand bit read_not_write;
rand int delay;
bit error;
logic[31:0] read_data;
task body;
bus_seq_item req;
m_sequencer.grab(this); // Grab => Immediate exclusive access to sequencer
req = bus_seq_item::type_id::create("req");
// Read from the GPO register to determine the cause of the interrupt
assert (req.randomize() with {addr == 32'h0100_0000; read_not_write == 1;});
start_item(req);
finish_item(req);
// Test the bits and reset if active
//
// Note that the order of the tests implements a priority structure
//
req.read_not_write = 0;
if(req.read_data[0] == 1) begin
`uvm_info("ISR:BODY", "IRQ0 detected", UVM_LOW)
req.write_data[0] = 0;
start_item(req);
finish_item(req);
`uvm_info("ISR:BODY", "IRQ0 cleared", UVM_LOW)
end
if(req.read_data[1] == 1) begin
`uvm_info("ISR:BODY", "IRQ1 detected", UVM_LOW)
req.write_data[1] = 0;
start_item(req);
finish_item(req);
`uvm_info("ISR:BODY", "IRQ1 cleared", UVM_LOW)
end
if(req.read_data[2] == 1) begin
`uvm_info("ISR:BODY", "IRQ2 detected", UVM_LOW)
req.write_data[2] = 0;
start_item(req);
finish_item(req);
`uvm_info("ISR:BODY", "IRQ2 cleared", UVM_LOW)
end
if(req.read_data[3] == 1) begin
`uvm_info("ISR:BODY", "IRQ3 detected", UVM_LOW)
req.write_data[3] = 0;
start_item(req);
finish_item(req);
`uvm_info("ISR:BODY", "IRQ3 cleared", UVM_LOW)
end
start_item(req); // Take the interrupt line low
finish_item(req);
m_sequencer.ungrab(this); // Ungrab the sequencer, let other sequences in
endtask: body
endclass: isr
Prioritised ISR Sequence
有这样一类interrupt,就是interrupt等级高的触发的时候可以把正在服务的ISR sequence 踢掉,然后优先解决等级高的interrupt,mentor在这个问题上没有直接解决,应该是做了个类似的解决方案,连他自己都承认这样不能百分之百的模拟这类情况。
whatever processing is happening in an ISR will still continue even if a higher priority ISR "interrupts" it, which means that sequence_items from the first lower priority ISR could potentially get through to the driver.
mentor的解决方案是 A less disruptive approach to implementing interrupt handling using sequences is to use sequence prioritisation.所以我们必须设置sequencer的arbiter。
the sequencer arbitration mode needs to be set to SEQ_ARB_STRICT_FIFO,
SEQ_ARB_STRICT_RAND or STRICT_ARB_WEIGHTED.
class int_test_seq extends uvm_sequence #(bus_seq_item);
`uvm_object_utils(int_test_seq)
function new (string name = "int_test_seq");
super.new(name);
endfunction
task body;
set_ints setup_ints; // Main sequence running on the bus
isr ISR0, ISR1, ISR2, ISR3; // Interrupt service routines
int_config i_cfg;
setup_ints = set_ints::type_id::create("setup_ints");
// ISR0 is the highest priority
ISR0 = isr::type_id::create("ISR0");
ISR0.id = "ISR0";
ISR0.i = 0;
// ISR1 is medium priority
ISR1 = isr::type_id::create("ISR1");
ISR1.id = "ISR1";
ISR1.i = 1;
// ISR2 is medium priority
ISR2 = isr::type_id::create("ISR2");
ISR2.id = "ISR2";
ISR2.i = 2;
// ISR3 is lowest priority
ISR3 = isr::type_id::create("ISR3");
ISR3.id = "ISR3";
ISR3.i = 3;
i_cfg = int_config::get_config(m_sequencer);
// Set up sequencer to use priority based on FIFO order
m_sequencer.set_arbitration(SEQ_ARB_STRICT_FIFO);
// A main thread, plus one for each interrupt ISR
fork
setup_ints.start(m_sequencer);
forever begin // Highest priority
i_cfg.wait_for_IRQ0();
ISR0.isr_no++;
ISR0.start(m_sequencer, this, HIGH);
end
forever begin // Medium priority
i_cfg.wait_for_IRQ1();
ISR1.isr_no++;
ISR1.start(m_sequencer, this, MED);
end
forever begin // Medium priority
i_cfg.wait_for_IRQ2();
ISR2.isr_no++;
ISR2.start(m_sequencer, this, MED);
end
forever begin // Lowest priority
i_cfg.wait_for_IRQ3();
ISR3.isr_no++;
ISR3.start(m_sequencer, this, LOW);
end
join_any
disable fork;
endtask: body
endclass: int_test_seq
Sequences/Stopping
Once started, sequences should not be stopped.这个是mentor 说的,一旦sequence开始就不能停下。但是有让他停下来的办法,但是我觉得不能用,当没有完成item_done()的时候,可能会造成uvm_fatal error.
• <sequence>.kill()
• <sequencer>.stop_sequences()
Sequences/Layering
特别有些协议有很多层,例如pcie之类的,这样对应的,我们的testbench也要有变化。
mentor 给了一些原则:
In order to do this we construct a layering component. A layering component:
• Must include a child sequencer for each non leaf level in the layering.
• Must create, connect and start a translator sequence for each non leaf level in the layering.
• Must have a handle to the leaf level protocol agent. This protocol agent may be a child of the layering or external to it.
• May include a reconstruction monitor for each non leaf level in the layering.
• Should create and connect external analysis ports for each monitor contained within the layering
• Will usually have a configuration object associated with it that contains the configuration objects of all the components contained within it.
child sequencers:
一层中的sequencer往往是和protocol相关的。child sequencer在一层中需要被例化。(原因我没读懂If the higher level protocol has been modeled as a protocol UVC, then the layering should instantiate an instance of the sequencer used by the agent for that protocol so that sequences can be targeted either at the bus agent or the layering.)
可以参见mentor 的cookbook 本章的图(图我上传不了)
code 大概如此:
class ABC_layering extends uvm_subscriber #( C_item );
`uvm_component_utils( ABC_layering )
...
A_sequencer a_sequencer;
B_sequencer b_sequencer;
...
C_agent c_agent;
function new(string name, uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
a_sequencer = A_sequencer::type_id::create("a_sequencer",this);
b_sequencer = B_sequencer::type_id::create("b_sequencer",this);
endfunction
...
endclass
Translator Sequences
把upstream的 sequence 翻译成downlevel的 sequence 需要在低层次的sequence 里面建一个upstream sequence 的sequencer,这样可以直接使用 sqr.get_next_item(),并且在upstream 的 sqr.get_next_item()和 sqr.item_done()之间进行转化。
class BtoC_seq extends uvm_sequence #(C_item);
`uvm_object_utils(BtoC_seq);
function new(string name="");
super.new(name);
endfunction
uvm_sequencer #(B_item) up_sequencer;
virtual task body();
B_item b;
C_item c;
int i;
forever begin
up_sequencer.get_next_item(b); //这个是sequencer.get_next_item() 跟以往 //seq_item_port.get_next_item()不一样
foreach (b.fb[i]) begin
c = C_item::type_id::create();
start_item(c);
c.fc = b.fb[i]; //进行赋值、转化
finish_item(c);
end
up_sequencer.item_done();
end
endtask
endclass
在test中(我猜)进行 sequencer的连接,部分代码如下:
virtual task run_phase(uvm_phase phase);
AtoB_seq a2b_seq;
BtoC_seq b2c_seq;
a2b_seq = AtoB_seq::type_id::create("a2b_seq");
b2c_seq = BtoC_seq::type_id::create("b2c_seq");
// connect translation sequences to their respective upstream sequencers
a2b_seq.up_sequencer = a_sequencer;
b2c_seq.up_sequencer = b_sequencer;
// start the translation sequences
fork
a2b_seq.start(b_sequencer);
b2c_seq.start(c_agent.c_sequencer);
join_none
endtask
The Protocol Agent
layering 必须要有一个叶子层协议agent 的 handle。
代码如下:
class ABC_layering extends uvm_component;
`uvm_component_utils( ABC_layering )
...
A_sequencer a_sequencer;
B_sequencer b_sequencer;
...
C_agent c_agent;
function new(string name, uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
a_sequencer = A_sequencer::type_id::create("a_sequencer",this);
b_sequencer = B_sequencer::type_id::create("b_sequencer",this);
c_agent = C_agent::type_id::create("c_sequencer",this);
endfunction
...
endclass