路科IC验证--SVlab2中的细节
仿真器使用VCS
tb4代码如下:

`timescale 1ns/1ps interface chnl_intf(input clk, input rstn); logic [31:0] ch_data; logic ch_valid; logic ch_ready; logic [ 5:0] ch_margin; clocking drv_ck @(posedge clk); default input #1ns output #1ns; output ch_data, ch_valid; input ch_ready, ch_margin; endclocking endinterface package chnl_pkg; class chnl_trans; int data; int id; int num; endclass: chnl_trans class chnl_initiator; local string name; local int idle_cycles; local virtual chnl_intf intf; function new(string name = "chnl_initiator"); this.name = name; this.idle_cycles = 1; endfunction function void set_idle_cycles(int n); this.idle_cycles = n; endfunction function void set_name(string s); this.name = s; endfunction function void set_interface(virtual chnl_intf intf); if(intf == null) $error("interface handle is NULL, please check if target interface has been intantiated"); else this.intf = intf; endfunction task chnl_write(input chnl_trans t); @(posedge intf.clk); // USER TODO 1.1 // Please use the clocking drv_ck of chnl_intf to drive data intf.drv_ck.ch_valid <= 1; intf.drv_ck.ch_data <= t.data; @(negedge intf.clk); wait(intf.ch_ready === 'b1); $display("%t channel initiator [%s] sent data %x", $time, name, t.data); // USER TODO 1.2 // Apply variable idle_cycles and decide how many idle cycles to be // inserted between two sequential data repeat(this.idle_cycles) chnl_idle(); endtask task chnl_idle(); @(posedge intf.clk); // USER TODO 1.1 // Please use the clocking drv_ck of chnl_intf to drive data intf.drv_ck.ch_valid <= 0; intf.drv_ck.ch_data <= 0; endtask endclass: chnl_initiator class chnl_generator; chnl_trans trans[$]; int num; int id; function new(int n); this.id = n; this.num = 0; endfunction function chnl_trans get_trans(); chnl_trans t = new(); t.data = 'h00C0_0000 + (this.id<<16) + this.num; t.id = this.id; t.num = this.num; this.num++; this.trans.push_back(t); return t; endfunction endclass: chnl_generator class chnl_agent; chnl_generator gen; chnl_initiator init; local int ntrans; virtual chnl_intf vif; function new(string name = "chnl_agent", int id = 0, int ntrans = 1); this.gen = new(id); this.init = new(name); this.ntrans = ntrans; endfunction function void set_ntrans(int n); this.ntrans = n; endfunction function void set_interface(virtual chnl_intf vif); this.vif = vif; init.set_interface(vif); endfunction task run(); repeat(this.ntrans) this.init.chnl_write(this.gen.get_trans()); this.init.chnl_idle(); // set idle after all data sent out endtask endclass: chnl_agent class chnl_root_test; chnl_agent agent[3]; protected string name; function new(int ntrans = 100, string name = "chnl_root_test"); foreach(agent[i]) begin this.agent[i] = new($sformatf("chnl_agent%0d",i), i, ntrans); end this.name = name; $display("%s instantiate objects", this.name); endfunction task run(); $display("%s started testing DUT", this.name); fork agent[0].run(); agent[1].run(); agent[2].run(); join $display("%s waiting DUT transfering all of data", this.name); fork wait(agent[0].vif.ch_margin == 'h20); wait(agent[1].vif.ch_margin == 'h20); wait(agent[2].vif.ch_margin == 'h20); join $display("%s: 3 channel fifos have transferred all data", this.name); $display("%s finished testing DUT", this.name); endtask function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif); agent[0].set_interface(ch0_vif); agent[1].set_interface(ch1_vif); agent[2].set_interface(ch2_vif); endfunction endclass // each channel send data with idle_cycles inside [1:3] // each channel send out 200 data // then to finish the test class chnl_basic_test extends chnl_root_test; function new(int ntrans = 200, string name = "chnl_basic_test"); super.new(ntrans, name); foreach(agent[i]) begin this.agent[i].init.set_idle_cycles($urandom_range(1, 3)); end $display("%s configured objects", this.name); endfunction endclass: chnl_basic_test // USER TODO 4.2 // Refer to chnl_basic_test, and extend another 2 tests // chnl_burst_test, chnl_fifo_full_test // each channel send data with idle_cycles == 0 // each channel send out 500 data // then to finish the test class chnl_burst_test extends chnl_root_test; //USER TODO function new(int ntrans = 500, string name = "chnl_burst_test"); super.new(ntrans, name); foreach(agent[i]) begin this.agent[i].init.set_idle_cycles(0); end $display("%s configured objects", this.name); endfunction endclass: chnl_burst_test // USER TODO 4.2 // The test should be immediately finished when all of channels // have been reached fifo full state, but not all reaching // fifo full at the same time class chnl_fifo_full_test extends chnl_root_test; // USER TODO function new(int ntrans = 1_000_000, string name = "chnl_fifo_full_test"); super.new(ntrans, name); foreach(agent[i]) begin this.agent[i].init.set_idle_cycles(0); end $display("%s configured objects", this.name); endfunction task run(); $display("%s started testing DUT", this.name); fork: fork_all_run agent[0].run(); agent[1].run(); agent[2].run(); join_none $display("%s: 3 agents running now", this.name); $display("%s: waiting 3 channel fifos to be full", this.name); fork wait(agent[0].vif.ch_margin == 0); wait(agent[1].vif.ch_margin == 0); wait(agent[2].vif.ch_margin == 0); join $display("%s: 3 channel fifos have reached full", this.name); $display("%s: stop 3 agents running", this.name); disable fork_all_run; $display("%s: set and ensure all agents' initiator are idle state", this.name); fork agent[0].init.chnl_idle(); agent[1].init.chnl_idle(); agent[2].init.chnl_idle(); join $display("%s waiting DUT transfering all of data", this.name); fork wait(agent[0].vif.ch_margin == 'h20); wait(agent[1].vif.ch_margin == 'h20); wait(agent[2].vif.ch_margin == 'h20); join $display("%s: 3 channel fifos have transferred all data", this.name); $display("%s finished testing DUT", this.name); endtask endclass: chnl_fifo_full_test endpackage: chnl_pkg module tb4_ref; logic clk; logic rstn; logic [31:0] mcdt_data; logic mcdt_val; logic [ 1:0] mcdt_id; mcdt dut( .clk_i (clk ) ,.rstn_i (rstn ) ,.ch0_data_i (chnl0_if.ch_data ) ,.ch0_valid_i (chnl0_if.ch_valid ) ,.ch0_ready_o (chnl0_if.ch_ready ) ,.ch0_margin_o(chnl0_if.ch_margin ) ,.ch1_data_i (chnl1_if.ch_data ) ,.ch1_valid_i (chnl1_if.ch_valid ) ,.ch1_ready_o (chnl1_if.ch_ready ) ,.ch1_margin_o(chnl1_if.ch_margin ) ,.ch2_data_i (chnl2_if.ch_data ) ,.ch2_valid_i (chnl2_if.ch_valid ) ,.ch2_ready_o (chnl2_if.ch_ready ) ,.ch2_margin_o(chnl2_if.ch_margin ) ,.mcdt_data_o (mcdt_data ) ,.mcdt_val_o (mcdt_val ) ,.mcdt_id_o (mcdt_id ) ); // clock generation initial begin clk <= 0; forever begin #5 clk <= !clk; end end // reset trigger initial begin #10 rstn <= 0; repeat(10) @(posedge clk); rstn <= 1; end // USER TODO 4.1 // import defined class from chnl_pkg import chnl_pkg::*; chnl_intf chnl0_if(.*); chnl_intf chnl1_if(.*); chnl_intf chnl2_if(.*); chnl_basic_test basic_test; chnl_burst_test burst_test; chnl_fifo_full_test fifo_full_test; initial begin basic_test = new(); burst_test = new(); fifo_full_test = new(); // USER TODO 4.4 // assign the interface handle to each chnl_initiator objects basic_test.set_interface(chnl0_if, chnl1_if, chnl2_if); burst_test.set_interface(chnl0_if, chnl1_if, chnl2_if); fifo_full_test.set_interface(chnl0_if, chnl1_if, chnl2_if); // USER TODO 4.5 // START TESTs basic_test.run(); burst_test.run(); fifo_full_test.run(); $display("*****************all of tests have been finished********************"); $finish(); end endmodule
interface chnl_intf(input clk, input rstn); logic [31:0] ch_data; logic ch_valid; logic ch_ready; logic [ 5:0] ch_margin; clocking drv_ck @(posedge clk); default input #1ns output #1ns; output ch_data, ch_valid; input ch_ready, ch_margin; endclocking endinterface
【问题】:接口chnl_intf的clocking驱动或采样具体细节是怎么实现的?
【解答与分析】
可以通过时钟块来解决采样和数据驱动的竞争问题,如以上代码,在clk的上升沿前1ns时,对输入信号进行采样,在clk的上升沿后1ns时,对输出信号做驱动。
时钟块drv_ck中的信号是clocking采样以后的结果,会比原始clk时钟信号多一个延时。比如接口中定义的ch_data信号会比时钟块中定义的输出信号ch_data会更早发生。
注意:只要做采样,一定是采样clk信号对应左边的值。
fork wait(agent[0].vif.ch_margin == 0); wait(agent[1].vif.ch_margin == 0); wait(agent[2].vif.ch_margin == 0); join
【问题】:为什么要wait接口中的ch_margin信号,接口时钟块中的ch_margin信号不行吗?
【解答与分析】
wait语句表明使用的是组合逻辑,有时钟参与的是时序逻辑。
上一问题的解答提到,时钟块drv_ck中的信号是clocking采样以后的结果,会比原始clk时钟信号多一个延时。
因此:直接使用接口中的信号能更早得到margin为0的信息,在代码的实现角度来说更好,当然使用时钟块drv_ck中的ch_margin信号也没问题。
task automatic chnl_write(input logic[31:0] data); @(posedge intf.clk); // USER TODO 1.1 // Please use the clocking drv_ck of chnl_intf to drive data intf.drv_ck.ch_valid <= 1; intf.drv_ck.ch_data <= data; @(negedge intf.clk); wait(intf.ch_ready === 'b1); $display("%t channel initiator [%s] sent data %x", $time, name, data); // USER TODO 1.2 // Apply variable idle_cycles and decide how many idle cycles to be // inserted between two sequential data repeat(idle_cycles) chnl_idle(); endtask
【问题】如何理解chnl_write任务里面的时序逻辑采样和组合逻辑采样?
【解答与分析】
在设计中实现的是组合逻辑,在验证中要恰当的模拟出一种组合逻辑。而组合逻辑是在一拍里边,它能做出一些相应的反应。
组合逻辑的上升沿一拍:
因此在一拍内采样的信号都可以模拟组合逻辑,如取红线位置(时钟的下降沿)。
时序逻辑的上升沿:
因此:
1. intf.drv_ck.ch_valid <= 1;intf.drv_ck.ch_data <= data; 是按照时钟块采样得到稳定的值。采样稳定用clocking块。
2. @(negedge intf.clk); wait(intf.ch_ready === 'b1); 通过clk模拟组合逻辑的一拍的过程,得到接口中ready信号为1的过程。不能用clocking块模拟组合逻辑,因为clocking块表示的是一个采样的过程,采样的过程一定是采样的clk的上一拍,所以clocking块无论如何都不能模拟出组合逻辑的效果。
class chnl_generator;//tb3的代码 chnl_trans trans[$]; int num; int id; chnl_trans t; function new(int n); this.id = n; this.num = 0; endfunction function chnl_trans get_trans(); t = new(); t.data = 'h00C0_0000 + (this.id<<16) + this.num; t.id = this.id; t.num = this.num; this.num++; this.trans.push_back(t); return t; endfunction endclass
【问题】上面代码中t=new()放在get_trans( )函数里和放在new()函数里有什么区别?
【解答与分析】
t=new()若放在new( )函数里,则每次例化的实例只有一个,因为几个new几个实例。因此tb3后面的代码中repeat都是对同一个实例进行修改,即100个句柄指向同一个实例,而t=new( )若放在get_trans( )函数里,则100个句柄分别指向100个实例,并放入到队列中。
在tb3中两种放法直接运行都没问题,但是t=new()若放在new( )函数里会有一个潜在的问题,即不利于调试,在同一时刻只能看到一个实例中的数据。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?