一、关于DFF的构成
我们将 DFF内部 分成 两个主要部分 LAT1(包括前4个nand2),LAT2(包括后4个nand2)。LAT1前两个NAND2我们成为LAT1的门,
LAT2前两个NAND2我们成为LAT2的门。
以上升沿触发为例,进一步分析D触发器在上升沿捕获数据,并维持锁存的过程。
当D端为0,CLK为0时,此时第一级的SR锁存器输出为0,第二级SR锁存器处于保持状态(图1)
若继续保持D端为0,CLK变为1时,第一级D锁存器处于保持状态,第二级的SR锁存器将上一次的D值传递到Q端输出(图2)
若在D端数值发生改变为1,且CLK仍然为1,第一级的D锁存器仍处于保持状态,不会由于D端的变化而改变,
更不会影响最后Q端的输出。(图3)
若D端继续保持为1,CLK转换为0,此时第一级D锁存器的输出为D端的数据,Q端输出仍为保持状态;(图4)
关于setup time的理解
当信号传递比较慢时,可能发生setup违例。
从LAT1的角度看,clk0时,LAT1一直可以接收上一个数据的变化,clk变1的瞬间,LAT1的门关闭,这段clk posedge附近的时间就是setup time。clk1时,不再接收数据的变化。这样,(1)LAT1关门之前,完成变化,这是我们期望的,(2)LAT1关门之后,一定不能进入LAT1,这也是我们期望的,(3)信号变化遇上LAT1正好关门,则属于不确定,也许进入了 LAT1,也许没有进入LAT1,这是我们希望避免的。
setup 问题,是前一级寄存器上一个周期的稳定值,经过组合逻辑,能否在当前周期稳定地进入LAT1。
关于hold time的理解
当信号传递特别快时,可能发生hold违例。
从LAT1的角度看,clk变1的瞬间,LAT1有可能接收前一级当拍数据的变化,LAT1的门关闭前,前一级的当拍数据已经进入到本级寄存器,这段clk posedge附近的时间也是hold time。这样,(1)LAT1关门之前,完成了不期望的变化,这是我们希望避免的,(2)LAT1关门之后,一定不能进入LAT1,这也是我们期望的,(3)当拍信号变化遇上LAT1正好关门,则属于不确定,也许进入了 LAT1,也许没有进入LAT1,这也是我们希望避免的。
hold,在同一时钟周期的时钟上升沿,前一级寄存器q端特别快地到达了D端,后一级寄存器的LAT1的关闭前,锁存了前一级当前周期的值,最终LAT2的状态不正确。
二、关于DFF setup hold的仿真
1)结构为 dff1–>dff2,
从 dff2检查相对于dff1的setup,hold。称为第一组。通过 rtl的延时写法,仿真可以报告vio,但是波形没有x态。
为了演示的清晰,增加一组 prim_dff1 --> prim_dff2,激励相同。称为第二组。通过原语的写法,仿真可以报告vio,波形也有x态。
第一组,是为了看清楚DFF结构,第二组是为了看清楚violation。
// nand_1 -- nand_2
// nand_3 -- nand_4
primitive violated_dff (q, v, clk, d, xxx);
output q;
reg q;
input v, clk, d, xxx;
table
* ? ? ? : ? : x;
? (x1) 0 0 : ? : 0;
? (x1) 1 0 : ? : 1;
? (x1) 0 1 : 0 : 0;
? (x1) 1 1 : 1 : 1;
? (x1) ? x : ? : -;
? (bx) 0 ? : 0 : -;
? (bx) 1 ? : 1 : -;
? (x0) b ? : ? : -;
? (x0) ? x : ? : -;
? (01) 0 ? : ? : 0;
? (01) 1 ? : ? : 1;
? (10) ? ? : ? : -;
? b * ? : ? : -;
? ? ? * : ? : -;
endtable
endprimitive
module d_lat(clk,s,r,q,qn);
input clk, s, r;
output q, qn;
nand #2 u_nand_1(nand_1_o, s,clk);
nand #2 u_nand_2(nand_2_o,nand_1_o, nand_4_o);
nand #2 u_nand_3(nand_3_o,r, clk);
nand #2 u_nand_4(nand_4_o,nand_2_o, nand_3_o);
buf #1 u_buf_q ( q,nand_2_o);
buf #1 u_buf_qn(qn,nand_4_o);
endmodule
module mydff(clk,d,q,qn);
input clk,d;
reg notifier;
output q,qn;
not #1 u_clk_r(clk_r,clk);
not #1 u_d_r(d_r,d);
d_lat u_lat1(clk_r,d,d_r,q1,qn1);
d_lat u_lat2(clk,q1,qn1,q,qn);
specify
specparam tsetup = 1.5, thold = 1.5;
$setuphold(posedge clk, posedge d,tsetup,thold,notifier);
$setuphold(posedge clk, negedge d,tsetup,thold,notifier);
endspecify
endmodule
module prim_dff(clk,d,q,qn);
output q,qn;
input d, clk;
reg notifier;
wire delayed_D, delayed_CK;
// Function
wire int_q, unstable;
violated_dff (int_q, notifier, delayed_CK, delayed_D, unstable);
buf u_buf (q, int_q);
not #1 u_not_qn (qn,q);
specify
(posedge clk => (q+:d)) = 3;
$setuphold (posedge clk, posedge d, 1.5, 1.5, notifier,,, delayed_CK, delayed_D);
$setuphold (posedge clk, negedge d, 1.5, 1.5, notifier,,, delayed_CK, delayed_D);
endspecify
endmodule
module tb_top;
bit clk;
logic din;
logic dq1_dly, dq1p_dly;
logic dq1, dqn1, dq1p, dqn1p;
integer dly1,dly2;
initial begin
clk = 1'b0;
forever
#(10) clk = ~clk;
end
initial begin
din = 1'b0;
forever begin
@(posedge clk);
dly1 = {$random}%20;
dly2 = {$random}%20;
//$display("dly1 is %0d", dly1);
fork
begin #(dly1) din = ~din; end
begin #(dly2) dq1_dly = dq1; end
begin #(dly2) dq1p_dly = dq1p; end
join
end
end
initial begin
$fsdbDumpvars;
$fsdbDumpon;
#100us $finish;
end
mydff u_dff1(clk,din,dq1,dqn1);
mydff u_dff2(clk,dq1_dly,dq2,dqn2);
prim_dff u_p_dff1(clk,din,dq1p,dqn1p);
prim_dff u_p_dff2(clk,dq1p_dly,dq2p,dqn2p);
endmodule
2)setup/hold的检查,是针对谁呢?
dff1/dff2都是clk/d之间检查,但是从clock cycle的角度看,则是:
setup 检查 前一个寄存器的前一个时钟,到本寄存器的本时钟周期。(前后周期)
hold检查前一个寄存器的本时钟周期,到本寄存器的本时钟周期。(同一周期)
先分析 dff setup time违例
假设 nand2 delay 2ns buf/ inverter delay 1ns(为了方便演示,假设很大,实际上很小)setup = 1.5ns, hold = 1.5ns
每个竖线处,都是一个DFF1 setup违例。
上图出DFF2的setup 违例,d–>clk 1ns < 1.5ns,因为 clk->q 3ns,所以x出现在4ns之后。
其次,分析 dff hold time 违例
由于输入相同,在双线处,dff1和prim_dff1都发生setuo violation(1ns < 1.5 hold time),只是dff1仅打印报告,没有x态。