clocking block
- cb input sample 采样受 #(input skew)控制,如果#n step(n>0),采样的是前n个step的postpone区;如果#0,则是在本timestep的observed region进行采样。
- 在active区会判断@(event),如果有**@(edge)这样的事件,就会准备规划后面的cb内变量采样和驱动事件,这里只是一个触发**,并不真正采样或者驱动。如果是#0采样,那么规划在observed区结束时采样,如果#nstep则不会规划在observed区结束时采样。 active区可能有不只一次规划这个事件,如果是其他区域规划了active区有时钟事件,active区就会再次规划observed区的cb.in采样
- observed区如果有时钟事件且#0就先采样此时的值,如果没有时钟事件或者#nstep就不用采样。然后触发cb事件。让后边的区域继续处理。observed区可以被执行多次,如果active区有新的时钟事件,那么observed区就该被规划和执行与该新时钟事件相应的采样和 cb.trigger。
- 同步驱动规划在RE-NBA区,前提是本timestep有触发事件在active区被纪录,在observed区域被触发。
- 至此,如果输出的#(output_skew)为0,又可导致执行回到active区,并在active区触发新的时钟事件,然后按照前边的流程循环执行。如果active区没有了新的时钟事件了,那么在observed就不在执行新的采样。
在非時鐘沿去做cb賦值的時候,cb只會等到里程沿才會將值給到真正的signal。
在這種植情況下,如出現下面這種情況:
cb.a<=1;
@(cb);
cb.a<=2;
如果cb.a<=1 在沿1與沿2的中間,那么在沿2的時候,a的值將直接變為2
SOLUTION:
cb.a<=1;
@(cb);
if(cb.a!=1)
@(cb);
cb.a<=2
example1:
clocking cb @(negedge clk);
input v;
endclocking
always @(cb) $display(cb.v);
always @(negedge clk) $display(cb.v);
- cb在observed区被触发,然后循环执行@cb,看到的是经过#nstep升级的,或者是#0在本timestep升级的cb.v
- 该事件如果在#0情况下在active区执行,看到的是旧值;如果#nstep在active区执行,看到的是nstep前的postpone区值,也可叫新值。如果#0在REACTIVE区执行,看到的本timesep升级后的值。等等情况不太确定
example2:
clocking cb @(posedge clk);
input v;
endclocking
task tclk();
$display($time," pre-clk");
@(posedge clk);
$display($time," post-clk");
endtask
task tcb();
$display($time," pre-cb");
@(cb);
$display($time," post-cb");
endtask
initial begin
fork
begin:clk
@(posedge clk);//active
tclk();//next active
end
begin:cb
@(posedge clk);//current active
tcb();//current observed
end
join
end
result :
30 pre-clk
30 pre-cb
30 post-cb
40 post-clk
假设当前时刻是30,clock 周期是10,则其中clk 进程的两条语句执行的时间分别在30,40 ;
cb 进程的两条语句执行的时间都为30
clocking block 写法实例:
clocking bus @(posedge clock1);
default input #10ns output #2ns;
input data, ready, enable = top.mem1.enable;
output negedge ack;
input #1step addr;
endclocking
对于同一信号,经过cb比没经过cb的晚1T(与input,output无关)。比如说vif.cb.a会比vif.a delay 1T。
在验证环境中,我们通常的做法:
driver中利用cb来drive signals; 比如: vif.cb.a <= 1’b1
连接DUT时却用的是不加cb的信号;比如: .frame_in(vif.a)
monitor中用加cb的信号sample; 比如: txn.a = vif.cb.a
waveform:
___
vif.a _______| |_________(DUT的输入信号)
___
vif.cb.a _____________| |_________(monitor sample的信号)
1 2 3
在时钟沿采到的是沿前的值,所以monitor采到的值比DUT的值delay 1T。
上述情况是针对drive DUT input和sample DUT output;当在环境描述signal level behavior时,cb使用状况有差别。
fork
forever begin//进程1
@(posedge line_ck or negedge rst_b);
cur_vld = (rate_cnt == 0);
end
forever begin//进程2
@(posedge line_ck or negedge rst_b);
if(cur_vld == 1'b1)
......
end
join
上面描述了两个并行进程,进程1 drive cur_vld ,进程2 sample cur_vld.
上面这种写法会产生竞争冒险,因为在同一时间既drive又sample.
此时我们想到了clocking block,它可以规避这件事。
interface vif;
parameter setup_time = 0.05;
parameter hold_time = 0.05;
logic cur_vld;
clocking line_cb @(posedge line_ck );
default input #setup_time output #hold_time;
inout cur_vld;
endclocking
endinterface
fork
forever begin//进程1
@(line_cb or negedge rst_b);
vif.line_cb.cur_vld <= (rate_cnt == 0);//驱动时加cb
end
forever begin//进程2
@(line_cb or negedge rst_b);
if(vif.line_cb.cur_vld === 1'b1) //采样时加cb
......
end
join
上面的写法可看出: 当自己驱动自己采样时 drive signals 时用cb, sample时也用cb.
__
vif.cur_vld _______| |_________(DUT的输入信号)
__
vif.cb.cur_vld _____________| |_________(monitor sample的信号)
1 2 3
采样时加不加cb都会在位置2处采到,debug看waveform时看不加cb的信号即可。前提是格式要统一,不要混用 ,即
forever begin//进程2
@(line_cb or negedge rst_b);//等cb
if(vif.line_cb.cur_vld === 1'b1) //采样时加cb
......
end
forever begin//进程2
@(posedge line_ck or negedge rst_b);//等posedge clk
if(vif.cur_vld === 1'b1) //采样时不加cb
......
end