指定路徑延遲,目的是讓仿真的時(shí)序更加接近實(shí)際數(shù)字電路的時(shí)序。利用時(shí)序約束對(duì)數(shù)字設(shè)計(jì)進(jìn)行時(shí)序仿真,檢查設(shè)計(jì)是否存在違反(violation)時(shí)序約束的地方,并加以修改,也是數(shù)字設(shè)計(jì)中不可或缺的過(guò)程。
Verilog 提供了一些系統(tǒng)任務(wù),用于時(shí)序檢查。這些系統(tǒng)任務(wù)只能在 specify 塊中調(diào)用。下面就介紹 6 種常用的用于時(shí)序檢查的系統(tǒng)任務(wù):$setup, $hold, $recovery, $removal, $width 與 $period。
系統(tǒng)任務(wù) $setup 用來(lái)檢查設(shè)計(jì)中元件的建立時(shí)間約束條件,$hold 用來(lái)檢查保持時(shí)間約束條件。其用法格式如下:
$setup(data_event, ref_event, setup_limit);
data_event
?: 被檢查的信號(hào),判斷它是否違反約束
ref_event
?: 用于檢查的參考信號(hào),一般為時(shí)鐘信號(hào)的跳變沿
setup_limit
?: 設(shè)置的最小建立時(shí)間如果 ?T( ref_event - data_event) < setup_limit
?, 則會(huì)打印存在 ?violation
?的報(bào)告。
$hold(ref_event, data_event, hold_limit);
data_event
?: 被檢查的信號(hào),判斷它是否違反約束
ref_event
?: 用于檢查的參考信號(hào),一般為時(shí)鐘信號(hào)的跳變沿
hold_limit
?: 設(shè)置的最小保持時(shí)間如果 ?T( data_event - ref_event ) < hold_limit
?, 則會(huì)打印存在 ?violation
?的報(bào)告。
注意: ?$setup
? 和 ?$hold
? 輸入端口的位置是不同的。
Verilog 提供了同時(shí)檢查建立時(shí)間和保持時(shí)間的系統(tǒng)任務(wù):
$setuphold (ref_event, data_event, setup_limit, hold_limit);
下面完成一個(gè)數(shù)乘以 15 的操作,來(lái)說(shuō)明 ?$setup
? 和 ?$hold
? 的用法。
Verilog 中,一個(gè)變量乘以常數(shù)一般用移位相加的方法來(lái)完成,例如對(duì)變量 num 乘以 15 的操作可以表示為:
num x 15 = (num << 3) + (num << 2) + (num << 1) + num
此操作需要 3 個(gè)加法器。下面對(duì)加法器進(jìn)行建模,并指定路徑延遲。
全加器功能描述可參考《Verilog 教程》的 《Verilog 連續(xù)賦值》。
//單 bit 全加器,指定路徑延遲
module full_adder1(
input Ai, Bi, Ci,
output So, Co);
assign So = Ai ^ Bi ^ Ci ;
assign Co = (Ai & Bi) | (Ci & (Ai | Bi));
specify
(Ai, Bi, Ci *> So) = 1.1 ;
(Ai, Bi *> Co) = 1.3 ;
(Ci => Co) = 1.2 ;
endspecify
endmodule
//8bit 位寬加法器例化
module full_adder8(
input [7:0] a , //adder1
input [7:0] b , //adder2
input c , //input carry bit
output [7:0] so , //adding result
output co //output carry bit
);
wire [7:0] co_temp ;
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b1 : 1'b0),
.So (so[0]),
.Co (co_temp[0]));
genvar i ;
generate
for(i=1; i<=7; i=i+1) begin: adder_gen
full_adder1 u_adder(
.Ai (a[i]),
.Bi (b[i]),
.Ci (co_temp[i-1]),
.So (so[i]),
.Co (co_temp[i]));
end
endgenerate
assign co = co_temp[7] ;
endmodule
8bit 位寬的觸發(fā)器描述如下。觸發(fā)器中指定路徑延遲,并加入建立時(shí)間和保持時(shí)間的時(shí)序檢查。
建立時(shí)間設(shè)置為 2ns,保持時(shí)間設(shè)置為 3ns。
module D8(
input [7:0] d ,
input clk ,
output reg [7:0] q);
always @(posedge clk)
q <= d ;
specify
$setup(d, posedge clk, 2);
$hold(posedge clk, d, 3);
(d,clk *> q) = 0.3 ;
endspecify
endmodule
在 testbench 里完成乘以 15 的操作,并在一個(gè)周期內(nèi)輸出給下一級(jí)寄存器。
`timescale 1ns/1ns
module test ;
reg [3:0] a ;
reg [3:0] b ;
wire [3:0] so ;
wire co ;
parameter CYCLE_10NS = 10ns;
reg clk ;
initial begin
clk = 0 ;
# 111 ;
forever begin
#(CYCLE_10NS/2) clk = ~clk ;
end
end
//需要乘以 15 的數(shù)
reg [7:0] num = 0 ;
always @(posedge clk) begin
num[3:0] <= num[3:0] + 1 ;
end
// num * 8 + num * 4
wire [7:0] adder1 ;
full_adder8 u1_adder8(
.a (num<<2),
.b (num<<3),
.c (1'b0),
.so (adder1),
.co ());
//num * 2 + num
wire [7:0] adder2 ;
full_adder8 u2_adder8(
.a (num<<1),
.b (num),
.c (1'b0),
.so (adder2),
.co ());
//num x 15
wire [7:0] adder3 ;
full_adder8 u3_adder8(
.a (adder1),
.b (adder2),
.c (1'b0),
.so (adder3),
.co ());
//store the result
wire [7:0] res_mul15 ;
D8 data_store(
.d (adder3),
.clk (clk),
.q (res_mul15));
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
仿真報(bào)告中則出現(xiàn)了帶有 setup/hold violation 的打印信息,部分截圖如下。
截取出現(xiàn) violation 時(shí)間的波形圖,如下所示。
分析如下:
保持時(shí)間的時(shí)序優(yōu)化,在 RTL 層級(jí)描述上一般不好控制,這屬于后端設(shè)計(jì)工程師的工作范疇,這里不做討論。
本次主要簡(jiǎn)單探討建立時(shí)間不滿足約束條件時(shí)的優(yōu)化問(wèn)題。由上一節(jié)《Verilog 建立時(shí)間和保持時(shí)間》中可知建立時(shí)間約束表達(dá)式為:
Tcq + Tcomb + Tsu <= Tclk + Tskew (1)
Tcq
?: 寄存器 clock 端到 Q 端的延遲;
Tcomb
?: data path 中的組合邏輯延遲;
Tsu
?: 建立時(shí)間;
Tclk
?: 時(shí)鐘周期;
Tskew
?: 時(shí)鐘偏移。優(yōu)化此不等式可從以下幾個(gè)方面考慮:
從 RTL 層次進(jìn)行時(shí)序優(yōu)化時(shí),只能考慮方法(2)(3)。
例如,將上述仿真中的工作時(shí)鐘周期由 10ns 改為 20ns,則不會(huì)出現(xiàn) setup violation。
或者,調(diào)整邏輯,一個(gè)周期內(nèi)完成 3 次加法運(yùn)算,改為分散到兩個(gè)周期內(nèi)完成,中間增加一級(jí)寄存器進(jìn)行緩沖,來(lái)減少時(shí)序上的壓力。同時(shí),變量 num 的變化周期也應(yīng)該變?yōu)樵瓉?lái)的 2 倍時(shí)長(zhǎng)。
testbench 修改如下:
`timescale 1ns/1ns
`define LOGIC_BUF
module test ;
parameter CYCLE_10NS = 10ns;
reg clk ;
initial begin
clk = 0 ;
# 111 ;
forever begin
#(CYCLE_10NS/2) clk = ~clk ;
end
end
reg slow_flag = 0 ;
always @(posedge clk) begin
`ifdef LOGIC_BUF
slow_flag <= ~slow_flag ;
`else
slow_flag <= 1'b1 ;
`endif
end
reg [7:0] num = 0 ;
always @(posedge clk) begin
if(slow_flag)
num[3:0] <= num[3:0] + 1 ;
end
wire [7:0] adder1 ;
full_adder8 u1_adder8(
.a (num<<2),
.b (num<<3),
.c (1'b0),
.so (adder1),
.co ());
wire [7:0] adder2 ;
full_adder8 u2_adder8(
.a (num<<1),
.b (num),
.c (1'b0),
.so (adder2),
.co ());
//====== for better time=========
//adding buffer
wire [7:0] adder1_r, adder2_r ;
D8 adder1_buf(
.d (adder1),
.clk (clk),
.q (adder1_r));
D8 adder2_buf(
.d (adder2),
.clk (clk),
.q (adder2_r));
`ifdef LOGIC_BUF
wire [7:0] adder1_t = adder1_r ;
wire [7:0] adder2_t = adder2_r ;
`else
wire [7:0] adder1_t = adder1 ;
wire [7:0] adder2_t = adder2 ;
`endif
wire [7:0] adder3 ;
full_adder8 u3_adder8(
.a (adder1_t),
.b (adder2_t),
.c (1'b0),
.so (adder3),
.co ());
wire [7:0] res_mul15 ;
D8 data_store(
.d (adder3),
.clk (clk),
.q (res_mul15));
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
此時(shí)仿真報(bào)告中不再有 violation,仿真截圖如下。
由圖可知,信號(hào)提前到達(dá)并保持不變的時(shí)間可達(dá) 8.6 ns,完全滿足建立時(shí)間的時(shí)序要求。
此種方法的根本原理,是將信號(hào)多次變化的時(shí)序,分散在多個(gè)周期內(nèi),來(lái)滿足時(shí)序約束的要求。此外,流水線設(shè)計(jì),并行設(shè)計(jì)等都可以?xún)?yōu)化時(shí)序。
建立時(shí)間和保持時(shí)間的概念都是出現(xiàn)在同步電路的設(shè)計(jì)中。
對(duì)于異步復(fù)位的觸發(fā)器來(lái)說(shuō),異步復(fù)位信號(hào)也需要滿足 recovery time(恢復(fù)時(shí)間)和 removal time(去除時(shí)間),才能有效的復(fù)位和釋放復(fù)位,防止出現(xiàn)亞穩(wěn)態(tài)。
釋放復(fù)位時(shí),復(fù)位信號(hào)在時(shí)鐘有效沿來(lái)臨之前就需要提前一段時(shí)間恢復(fù)到非復(fù)位狀態(tài),這段時(shí)間為 recovery time。類(lèi)似于同步時(shí)鐘下觸發(fā)器的 setup time。
復(fù)位時(shí),復(fù)位信號(hào)在時(shí)鐘有效沿來(lái)臨之后,還需要在一段時(shí)間內(nèi)保持不變,這段時(shí)間為 removal time。類(lèi)似于同步時(shí)鐘下觸發(fā)器的 hold time。
recovery 與 removal time 示意圖如下所示。
系統(tǒng)任務(wù) ?$recovery
? 與 ?$removal
? 分別用于 recovery 和 removal time 的檢查,用法如下:
$recovery (ref_event, data_event, recovery_limit) ;
ref_event
?: 用于檢查的參考信號(hào),一般為清零或復(fù)位信號(hào)跳變沿;
data_event
?: 被檢查的信號(hào),一般為時(shí)鐘信號(hào)跳變沿。
recovery_limit
?:設(shè)置的最小 recovery time。當(dāng) ?ref_event (reset) < data_event (clock)
? 且 ?T(data_event - ref_event) < recovery_limit
? 時(shí),即復(fù)位信號(hào)在時(shí)鐘信號(hào)到來(lái)之前如果不滿足 ?recovery time
?,則報(bào)告中會(huì)打印 ?violation
?
$removal (ref_event, data_event, removal_limit) ;
ref_event
?: 用于檢查的參考信號(hào),一般為清零或復(fù)位信號(hào)跳變沿;
data_event
?: 被檢查的信號(hào),一般為時(shí)鐘信號(hào)跳變沿。
removal_limit
?:設(shè)置的最小 removal time。當(dāng) ?ref_event (reset) > data_event (clock)
? 且 ?T(ref_event - data_event) > removal_limit
? 時(shí),即復(fù)位信號(hào)在時(shí)鐘信號(hào)到來(lái)之后如果不滿足 ?removal time
?,則報(bào)告中會(huì)打印 ?violation
?。
Verilog 提供了同時(shí)檢查 revomal 和 recovery 的系統(tǒng)任務(wù):
$recrem (ref_event, data_event, recovery_limit, removal_limit);
有些數(shù)字設(shè)計(jì),例如 flash 存儲(chǔ)器,還需要對(duì)脈沖寬度或周期進(jìn)行檢查,為此 Verilog 分別提供了系統(tǒng)任務(wù) $width 和 $period。用法如下:
$width(ref_event, time_limit) ;
ref_event
?: 邊沿觸發(fā)事件
time_limit
?: 脈沖的最小寬度?$width
? 用于檢查邊沿觸發(fā)事件 ?ref_event
?到下一個(gè)反向跳變沿之間的時(shí)間,常用于脈沖寬度的檢查。如果兩次相反跳邊沿之間的時(shí)間小于 ?time_limit
?,則會(huì)報(bào)告 ?violation
?。
$period(ref_event, time_limit) ;
?$period
? 用于檢查邊沿觸發(fā)事件 ?ref_event
?到下一個(gè)同向跳變沿之間的時(shí)間,常用于時(shí)鐘周期的檢查。如果兩次同向跳邊沿之間的時(shí)間小于 ?time_limit
?,則報(bào)告中會(huì)打印 ?violation
?。
檢查信號(hào) CLK 寬度和周期的 specify 塊描述如下:
specify
$width(posedge CLK, 10);
$period(posedge CLK, 20);
endspecify
點(diǎn)擊這里下載源碼
更多建議: