luyuan_0922的个人空间 https://blog.eetop.cn/106870 [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

slave sequence

已有 1799 次阅读| 2017-5-17 15:35 |个人分类:mentor_cookbook|系统分类:芯片设计

slave sequence, 基本上是作为 responder的。
由于slave sequence 不知道(hard to predict)什么时候需要respond,一般我们都做成无限循环的(lasting-long)的sequence,持续整个simulation,提供response服务。

mentor的解决办法以下两条:
• Using a single sequence item
• Using a sequence item for each slave phase (in the APB example used as an illustration there are two phases).

对于第一点,一般只用一种sequence item来代表 request和response。涉及response的域要rand,request的域不需要rand

class apb_slave_seq_item extends uvm_sequence_item;
`uvm_object_utils(apb_slave_seq_item)
//------------------------------------------
// Data Members (Outputs rand, inputs non-rand)
//------------------------------------------
logic[31:0] addr;
logic[31:0] wdata;
logic rw;
rand logic[31:0] rdata;
rand logic slv_err;
rand int delay;
//------------------------------------------
// Constraints
//------------------------------------------
constraint delay_bounds {
delay inside {[0:2]};
}
constraint error_dist {
slv_err dist {0 := 80, 1 := 20};
}
//------------------------------------------
// Methods
//------------------------------------------
extern function new(string name = "apb_slave_seq_item");
extern function void do_copy(uvm_object rhs);
extern function bit do_compare(uvm_object rhs, uvm_comparer comparer);
extern function string convert2string();
extern function void do_print(uvm_printer printer);
extern function void do_record(uvm_recorder recorder);
endclass:apb_slave_seq_item



Sequence and Driver
slave sequence一般接收request,然后response,要模仿这个行为,可能需要多个sequence item。(it needs to use multiple objects of the slave sequence item to accomplish the different phases.)特别是牵扯到一些总线协议的slave sequence。一个phase 接受 control info(request),一个phase 进行respond (data)


task apb_slave_sequence::body;
apb_slave_agent_config m_cfg = apb_slave_agent_config::get_config(m_sequencer);
apb_slave_seq_item req;
apb_slave_seq_item rsp;
wait (m_cfg.APB.PRESETn); //这里用了config中的virtual interface handle
forever begin
req = apb_slave_seq_item::type_id::create("req");
rsp = apb_slave_seq_item::type_id::create("rsp");
// Slave request:
start_item(req);           // 还没有理解为啥slave sequence 要有request
finish_item(req);
// Slave response:
if (req.rw) begin
memory[req.addr] = req.wdata;
end
//这里应该是rsp phase
start_item(rsp);
rsp.copy(req);
assert (rsp.randomize() with {if(!rsp.rw) rsp.rdata == memory[rsp.addr];});
finish_item(rsp);
end
endtask:body

于此对应的driver code:

task apb_slave_driver::run_phase(uvm_phase phase);
apb_slave_seq_item req;
apb_slave_seq_item rsp;
forever
begin
if (!APB.PRESETn) begin
APB.PREADY = 1'b0;
APB.PSLVERR = 1'b0;
@(posedge APB.PCLK);
end
else begin
// Setup Phase
seq_item_port.get_next_item(req);
// Setup phase activity
seq_item_port.item_done();
// Access Phase
seq_item_port.get_next_item(rsp);
// Access phase activity
seq_item_port.item_done();
end
end
endtask: run_phase

Multiple Sequence Items Implementation:
有的时候slave sequence 需要用到多种item(应该是协议相关的)。
slave sequence 的 request item里面的field 应该不能有random,而response sequence 的 item 类 里面除了有rand以外,还应该有和request item的一些field,用来做一些决定,例如读/写:

request item
class apb_slave_setup_item extends apb_sequence_item;
`uvm_object_utils(apb_slave_setup_item)
//------------------------------------------
// Data Members (Outputs rand, inputs non-rand)
//------------------------------------------
logic[31:0] addr;
logic[31:0] wdata;
logic rw;
endclass

//response item
class apb_slave_access_item extends apb_sequence_item;
`uvm_object_utils(apb_slave_setup_item)
//------------------------------------------
// Data Members (Outputs rand, inputs non-rand)
//------------------------------------------
rand logic rw;     //common field with request item
rand logic[31:0] rdata;
rand logic slv_err;
rand int delay;
constraint delay_bounds {
delay inside {[0:2]};
}
constraint error_dist {
slv_err dist {0 := 80, 1 := 20};
}
endclass

Sequence and Driver
于此对应的driver code 和之前的那个很接近,唯一不太一样的地方在于多种sequence item 应该都基于一个item base 来的,这样的话无论是driver还是sequencer的定义都是parameterized,并且参数应该是基类,这样的话不同的sequence item 可以在sequence和 driver 中传递。

class apb_slave_sequence extends uvm_sequence #(apb_sequence_item);
class apb_slave_sequencer extends uvm_sequencer #(apb_sequence_item);
class apb_slave_driver extends uvm_driver #(apb_sequence_item, apb_sequence_item);

下面是driver 的code:
task apb_slave_driver::run_phase(uvm_phase phase);
apb_sequence_item item;
apb_slave_setup_item req;
apb_slave_access_item rsp;
forever
begin
if (!APB.PRESETn) begin     //config 中的virtual sequence
...
end
else begin
seq_item_port.get_next_item(item);
if ($cast(req, item))              //必须用到cast
begin
....
end
else
`uvm_error("CASTFAIL", "The received sequence item is not a request seq_item");
seq_item_port.item_done();
// Access Phase
seq_item_port.get_next_item(item);
if ($cast(rsp, item))              //再次cast
begin
....
end
else
`uvm_error("CASTFAIL", "The received sequence item is not a response seq_item");
seq_item_port.item_done();
end
end
endtask: run_phase



Stimulus/Signal Wait

经常在sequence 会有这样的需求,就是一直等到一个signal toggle或者为某个电平,然后再发下面的item/seq,mentor的解决方案是把这些等待做成config里面的method(你没有看错),省的再做个什么agent/monitor component去做这件事情,烦。

在mentor的这本书里面提到了用get_config 去在sequence 里面那agent 的cfg,我没有试,觉得很叼。

additions to the configuration object

• 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






点赞

全部作者的其他最新日志

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 0

    周排名
  • 0

    月排名
  • 0

    总排名
  • 0

    关注
  • 4

    粉丝
  • 1

    好友
  • 1

    获赞
  • 11

    评论
  • 1002

    访问数
关闭

站长推荐 上一条 /1 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-3-19 16:17 , Processed in 0.014583 second(s), 8 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部