看到壇子里不少朋友,對于基本數(shù)字電路存在這樣那樣的疑惑,本人決定開貼,介紹數(shù)字電路最常見的模塊單元,希望給初學者帶來幫助,也歡迎大佬們前來拍磚。如果想要做數(shù)字設計,下面這些電路是一定會碰到的,也是所有大型IP,SOC設計必不可少的基礎,主要包括異步信號的同步處理,同步FIFO,異步FIFO,時鐘無縫切換,信號濾波debounce等等,后面會根據(jù)大家反饋情況再介紹新電路。
首先介紹異步信號的跨時鐘域同步問題。一般分為單bit的控制信號同步,以及多bit的數(shù)據(jù)信號同步。多bit的信號同步會使用異步FIFO完成,而單bit的信號同步,又是時鐘無縫切換電路以及異步FIFO電路的設計基礎,這里先介紹單bit信號同步處理。
clka域下的信號signal_a,向異步的clkb域傳遞時,會產(chǎn)生亞穩(wěn)態(tài)問題。所有的亞穩(wěn)態(tài),歸根結底就是setup/hold時間不滿足導致。在同一個時鐘域下的信號,綜合以及布線工具可以在data路徑或者clock路徑上插入buffer使得每一個DFF的setup/hold時間都滿足;但是當signal_a在clkb域下使用時,由于clka與clkb異步,它們的相位關系不確定,那么在clkb的時鐘沿到來時,無法確定signal_a此時是否處于穩(wěn)定無變化狀態(tài),也即setup/hold時間無法確定,從而產(chǎn)生亞穩(wěn)態(tài)。這種異步信號在前后端流程里面是無法做時序分析的,也就是靜態(tài)時序分析里常說的false_path。
消除亞穩(wěn)態(tài),就是采用多級DFF來采樣來自另一個時鐘域的信號,級數(shù)越多,同步過來的信號越穩(wěn)定。對于頻率很高的設計,建議至少用三級DFF,而兩級DFF同步則是所有異步信號處理的最基本要求。
單bit的信號跨時鐘域同步,又分成電平信號同步以及脈沖信號同步。電平信號,就是說clka下的信號signal_a在clkb看來,是一個很寬的信號,會保持多個clkb的時鐘周期,一定能被clkb采到。這種情況,只需要使用clkb用至少兩級DFF連續(xù)抓signal_a即可,特別需要強調的是,此時signal_a必須是clka下的寄存器信號,如果signal_a是clka下的組合邏輯信號,一定要先在clka下用DFF抓一拍,再使用兩級DFF向clkb傳遞。這是因為clka下的組合邏輯信號會有毛刺,在clka下使用時會由setup/hold時間保證毛刺不會被clka采到,但由于異步相位不確定,組合邏輯的毛刺卻極有可能被clkb采到。電平信號的同步處理,一般用于知道確定的時鐘頻率大小關系或者極慢時鐘下的信號向極快時鐘域傳遞時使用,簡單處理如下:
always @ (posedge clkb or negedge rst_n)begin
if (!rst_n) begin
levl_b_d1 《= #DLY 1‘b0;
levl_b_d2 《= #DLY 1’b0;
levl_b_d3 《= #DLY 1‘b0;
end
else begin
levl_b_d1 《= #DLY levl_a_in;
levl_b_d2 《= #DLY levl_b_d1;
levl_b_d3 《= #DLY levl_b_d2;
endend
assign puls_b_pos = levl_b_d2 & (~levl_b_d3);
assign puls_b_neg = levl_b_d3 & (~levl_b_d2);
assign levl_b_out = levl_b_d2;
上面三個輸出分別是經(jīng)過同步之后,clkb下可以使用的0變1脈沖信號,1變0脈沖信號以及電平信號。再次強調:levl_a_in必須是clka的DFF信號!下面是更常見的,clka下的脈沖信號,同步到clkb時鐘域下,它對于clka與clkb的時鐘頻率關系沒有任何限制,快到慢,慢到快都沒問題。其主要原理就是先把脈沖信號在clka下展寬,變成電平信號,再向clkb傳遞,當確認clkb已經(jīng)“看見”信號同步過去之后,再清掉clka下的電平信號。脈沖信號同步處理電路,有兩個地方使用了上面的電平信號同步處理原則,請仔細揣摩原因。詳細見下面的RTL,其中省略了信號定義聲明:
module sync_pulse (
// input
rst_n, // system reset
clka, // clockA
clkb, // clockB
puls_a_in, // pulse input from clka
// output
puls_b_out, // pulse output in clkb
levl_b_out // level output in clkb
);
parameter DLY = 1; //
always @ (posedge clka or negedge rst_n)
begin
if (rst_n == 1’b0)
signal_a 《= # DLY 1‘b0 ;
else if (puls_a_in)
signal_a 《= # DLY 1’b1 ;
else if (signal_b1_a2)
signal_a 《= # DLY 1‘b0 ;
else ;
end
always @ (posedge clkb or negedge rst_n)
begin
if (rst_n == 1’b0)
signal_b 《= # DLY 1‘b0 ;
else
signal_b 《= # DLY signal_a ;
end
always @ (posedge clkb or negedge rst_n)
begin
if (rst_n == 1’b0) begin
signal_b_b1 《= # DLY 1‘b0 ;
signal_b_b2 《= # DLY 1’b0 ;
end
else begin
signal_b_b1 《= # DLY signal_b ;
signal_b_b2 《= # DLY signal_b_b1 ;
end
end
always @ (posedge clka or negedge rst_n)
begin
if (rst_n == 1‘b0) begin
signal_b1_a1 《= # DLY 1’b0 ;
signal_b1_a2 《= # DLY 1‘b0 ;
end
else begin
signal_b1_a1 《= # DLY signal_b_b1 ;
signal_b1_a2 《= # DLY signal_b1_a1 ;
end
end
assign puls_b_out = signal_b_b1 & (~signal_b_b2) ;
assign levl_b_out = signal_b_b1 ;
endmodule
下一篇講時鐘切換電路。
留下一個思考題:clka下的同一個寄存器信號signal_a,電平寬度對clkb而言足夠長,如果同時調用兩個相同的電平同步模塊向clkb時鐘傳遞,分別得到levl_b1和levl_b2,那么在clkb時鐘域下看到的lev_b1和levl_b2信號是否一樣?
這個問題是實際設計中一不小心就會犯錯的,如果能夠想明白正確回答這個問題,異步信號的理解就可以過關了。
(帖子鏈接:http://bbs.eetop.cn/thread-605514-1-1.html)
時鐘切換分成兩種方式,普通切換和去毛刺無縫切換。
普通切換,就是不關心切出的時鐘是否存在毛刺,這種方式電路成本小。如果時鐘切換時,使用此時鐘的模塊電路處于非工作狀態(tài),或者模塊內(nèi)電路被全局復位信號reset住的,即使切出毛刺也不會導致DFF誤觸發(fā),這樣的模塊可以選擇用此種切換方式。
寫法很簡單 assign clk_o = sel_clkb ? clkb : clka ,當sel_clkb為1時選擇clkb,否則選擇clka。不過在實際設計中,建議直接調用庫里的MUX單元set_dont_touch,不要采用這里的assign寫法,因為這種寫法最后綜合得到的可能不是MUX而是復雜組合邏輯,給前后端流程的時鐘約束和分析帶來不便。
無縫切換,就是切換時無毛刺時鐘平穩(wěn)過渡。在時鐘切換中,只要出現(xiàn)比clka或者clkb頻率更高的窄脈沖,不論是窄的高電平還是窄的低電平,都叫時鐘毛刺。工作在切換后時鐘clk_o下的電路模塊,綜合約束是在max{clka,clkb}頻率下的,也就是說設計最后signoff的時候,只保證電路可以穩(wěn)定工作的最高頻率是max{clka,clkb},如果切換中出現(xiàn)更高頻的時鐘毛刺,電路可能出現(xiàn)無法預知的結果而出錯。無縫切換,一般用在處于工作狀態(tài)的模塊需要調頻或者切換時鐘源,比如內(nèi)部系統(tǒng)總線,CPU等。你剛用手機打完游戲后馬上關屏聽音樂,這兩種場景中,CPU在滿足性能前提下為了控制功耗,其工作頻率會動態(tài)地從很高調至較低,此時就可能是在CPU一直處于工作狀態(tài)下,通過無縫切換時鐘源頭實現(xiàn)的。
在無縫切換電路中,切換信號sel_clkb可以是任意時鐘域下的信號,包括但不限于clka或者clkb域,但是sel_clkb必須是一個DFF輸出信號;clka與clkb的頻率大小相位關系可以任意。無縫切換需要解決兩個問題,一是異步切換信號的跨時鐘域同步問題,這里需要使用《Verilog基本電路設計之一》里的同步電路原理消除亞穩(wěn)態(tài);二是同步好了的切換信號與時鐘信號如何做邏輯,才能實現(xiàn)無毛刺。
下面寫出無縫切換電路的主體部分,忽略了內(nèi)部信號的定義聲明等。
module clk_switch (
rst_n,
clka,
clkb,
sel_clkb,
clk_o
);
always @ (posedge clka or negedge rst_n)
begin
if (!rst_n) begin
sel_clka_d0 《= 1’b0;
sel_clka_d1 《= 1‘b0;
end
else begin
sel_clka_d0 《= (~sel_clkb) & (~sel_clkb_dly3) ;
sel_clka_d1 《= sel_clka_d0 ;
end
end
// part2
//always @ (posedge clka_n or negedge rst_n)
always @ (posedge clka or negedge rst_n)
begin
if (!rst_n) begin
sel_clka_dly1 《= 1’b0;
sel_clka_dly2 《= 1‘b0;
sel_clka_dly3 《= 1’b0;
end
else begin
sel_clka_dly1 《= sel_clka_d1;
sel_clka_dly2 《= sel_clka_dly1 ;
sel_clka_dly3 《= sel_clka_dly2 ;
end
end
// part3
//always @ (posedge clkb_n or negedge rst_n)
always @ (posedge clkb or negedge rst_n)
begin
if (!rst_n) begin
sel_clkb_d0 《= 1‘b0;
sel_clkb_d1 《= 1’b0;
end
else begin
sel_clkb_d0 《= sel_clkb & (~sel_clka_dly3) ;
sel_clkb_d1 《= sel_clkb_d0 ;
end
end
// part4
//always @ (posedge clkb_n or negedge rst_n)
always @ (posedge clkb or negedge rst_n)
begin
if (!rst_n) begin
sel_clkb_dly1 《= 1‘b0;
sel_clkb_dly2 《= 1’b0;
sel_clkb_dly3 《= 1‘b0;
end
else begin
sel_clkb_dly1 《= sel_clkb_d1 ;
sel_clkb_dly2 《= sel_clkb_dly1 ;
sel_clkb_dly3 《= sel_clkb_dly2 ;
end
end
// part5
clk_gate_xxx clk_gate_a ( .CP(clka), .EN(sel_clka_dly3), .Q(clka_g) .TE(1’b0) );
clk_gate_xxx clk_gate_b ( .CP(clkb), .EN(sel_clkb_dly3), .Q(clkb_g) .TE(1‘b0) );
//assign clka_g = clka & sel_clka_dly3 ;
//assign clkb_g = clkb & sel_clkb_dly3 ;
assign clk_o = clka_g | clkb_g ;
endmodule
上面是我認為比較合理的無縫切換電路,其他切換方式跟這個會有些許出入,但基本大同小異原理是一樣的。有幾點說明:
1、拋開注釋掉的電路不看,由于part5部分直接調用庫里的clock gating cell,使得整個切換電路全部只需要用到時鐘上升沿,無需額外定義反向時鐘,精簡了DC綜合的時鐘約束;直接調用gating cell的 另一個好處是,前后端工具會自動檢查gating cell的CP信號與EN信號的setup/hold時間,使得gating后的Q時鐘輸出無毛刺尖峰。TE端可以根據(jù)實際需要接上scan測試模式信號。如果使用part5部分的gating cell實現(xiàn),前面的part1,2,3,4全部替換成注釋掉的反相時鐘也是沒有問題。
2、part2和part4部分,具體需要多少級DFF,甚至完全不要也是可以的,這就回到了《Verilog基本電路設計之一》里討論的到底多少級DFF消除亞穩(wěn)態(tài)才算合理的問題。時鐘頻率很低可能無所謂,如果時鐘頻率達到GHz,這部分建議至少保留三級DFF,因為三級DFF延時也僅僅只有3ns的時間裕度。沒必要為了省這么幾個DFF降低電路可靠性,在復雜IP以及大型SOC系統(tǒng)中,你會發(fā)現(xiàn)多幾十個DFF,面積上可以忽略,系統(tǒng)可靠性和穩(wěn)定性才是首要的。
3、如果part5部分希望使用注釋掉的兩行“與”邏輯實現(xiàn)時鐘gating,此時part1與part3使用正相或者反相時鐘都可以,但是必須把part2和part4部分改為注釋掉的反相時鐘實現(xiàn),目的是初步從RTL設計上避免“與”邏輯的毛刺,同時還需要后端配合,因為很多后端工具對時鐘“與”邏輯的clock gating check未必會檢查。用clk下降沿拍出的en信號,再跟clk做與邏輯得到的門控時鐘,在RTL仿真階段看到的一定不會有毛刺,但是布線完成后,如果clk相對en后移,那與邏輯得到的門控時鐘就有毛刺了。這就是用與邏輯做門控的缺點,由于后端工具可能不會去檢查這個與門的時序關系而導致出錯。但直接調用庫里的gating cell,工具天然就會去檢查這個時序,免去人工確認的后顧之憂。
最后,請大家仔細看看sel_clka_d0 《= (~sel_clkb) & (~sel_clkb_dly3) 和sel_clkb_d0 《= sel_clkb & (~sel_clka_dly3) 這兩處邏輯,按理說,sel_clkb跟sel_clka_dly3以及sel_clkb_dly3之間相互都是異步的,而按照異步信號同步處理原則,兩個不同時鐘域下的信號是不允許直接做組合邏輯的,為什么這里可以這樣使用?
Verilog基本電路設計之三:異步FIFO
(帖子鏈接:http://bbs.eetop.cn/thread-605632-1-1.html)
FIFO用于為匹配讀寫速度而設置的數(shù)據(jù)緩沖buffer,當讀寫時鐘異步時,就是異步FIFO。多bit的數(shù)據(jù)信號,并不是直接從寫時鐘域同步到讀時鐘域的,而是讀寫時鐘域分別派遣了一個信使,去通知對方時鐘域,當前本方所處的讀寫情況,來判斷還能不能寫以及可不可以讀,這兩個信使就是讀寫指針。
在《Verilog基本電路設計之一》里已討論過,即使單bit的異步信號,通過兩個相同的同步電路,達到clkb域時都可能“長”的不是一個模樣,更加不用說多bit的異步信號同時傳遞到clkb域會變成什么五花八門的模樣了。這里讀寫指針不是單bit信號,它們?nèi)绾蜗驅Ψ綍r鐘域去同步呢?格雷碼!它的特點是每次只有一個bit發(fā)生變化,這樣就把多bit信號同步轉變?yōu)榱藛蝏it信號同步,這也是為什么多bit的格雷碼信號,可以類似于單bit信號那樣,直接使用兩級DFF去同步的根本原因。
下面給出異步FIFO的主體部分,同樣,省略了信號聲明定義。
module asyn_fifo (
// input
af_wclk , // async-FIFO clear in write clock
af_rclk , // async-FIFO clear in read clock
rst_n, // system reset
af_wr_en, // async-FIFO write enable
af_rd_en, // async-FIFO read enable
af_dati, // async-FIFO data in
//output
af_full , // Async-FIFO full flag
af_empty, // Async-FIFO empty flag
af_dato // Async-FIFO data out
);
//------------------------- data input --------------------------
assign nxt_wptr_wclk = (af_wr_en && !af_full) ? (wptr_wclk + 1’b1) : wptr_wclk ;
assign nxt_wptr_gray = (nxt_wptr_wclk 》》 1) ^ nxt_wptr_wclk ;
always @ (posedge af_wclk or negedge rst_n)
begin
if (rst_n == 1‘b0) begin
wptr_wclk 《= # DLY 5’b0 ;
wptr_gray 《= # DLY 5‘b0 ;
end
else begin
wptr_wclk 《= # DLY nxt_wptr_wclk ;
wptr_gray 《= # DLY nxt_wptr_gray ;
end
end
reg [31:0] ram[15:0] ; //
always @ (posedge af_wclk)
begin
if (af_wr_en == 1’b1)
ram[wptr_wclk[3:0]] 《= # DLY af_dati ;
else ;
end
//------------------------ data output ---------------------------
assign nxt_rptr_rclk = (af_rd_en && !af_empty) ? (rptr_rclk + 1‘b1) : rptr_rclk ;
assign nxt_rptr_gray = (nxt_rptr_rclk 》》 1) ^ nxt_rptr_rclk ;
always @ (posedge af_rclk or negedge rst_n)
begin
if (rst_n == 1’b0) begin
rptr_rclk 《= # DLY 5‘b0 ;
rptr_gray 《= # DLY 5’b0 ;
end
else begin
rptr_rclk 《= # DLY nxt_rptr_rclk ;
rptr_gray 《= # DLY nxt_rptr_gray ;
end
end
assign af_dato = ram[rptr_rclk[3:0]] ;
// sync read pointer
always @ (posedge af_wclk or negedge rst_n)
begin
if (rst_n == 1‘b0) begin
rptr_sp1 《= # DLY 5’b0 ;
rptr_sp2 《= # DLY 5‘b0 ;
end
else begin
rptr_sp1 《= # DLY rptr_gray ;
rptr_sp2 《= # DLY rptr_sp1 ;
end
end
// sync write pointer
always @ (posedge af_rclk or negedge rst_n)
begin
if (rst_n == 1’b0) begin
wptr_sp1 《= # DLY 5‘b0 ;
wptr_sp2 《= # DLY 5’b0 ;
end
else begin
wptr_sp1 《= # DLY wptr_gray ;
wptr_sp2 《= # DLY wptr_sp1 ;
end
end
assign af_full = (wptr_gray == {~rptr_sp2[4],~rptr_sp2[3],rptr_sp2[2:0]}) ;
assign af_empty = (rptr_gray == wptr_sp2) ;
assign wptr_bin[4] = wptr_sp2[4] ;
assign wptr_bin[3] = (^wptr_sp2[4:3]) ;
assign wptr_bin[2] = (^wptr_sp2[4:2]) ;
assign wptr_bin[1] = (^wptr_sp2[4:1]) ;
assign wptr_bin[0] = (^wptr_sp2[4:0]) ;
assign rptr_bin[4] = rptr_sp2[4] ;
assign rptr_bin[3] = (^rptr_sp2[4:3]) ;
assign rptr_bin[2] = (^rptr_sp2[4:2]) ;
assign rptr_bin[1] = (^rptr_sp2[4:1]) ;
assign rptr_bin[0] = (^rptr_sp2[4:0]) ;
assign af_wlevel = wptr_wclk - rptr_bin ;
assign af_rlevel = wptr_bin - rptr_rclk ;
assign af_half_full = (af_rlevel 》= 5‘h7) ;
endmodule
上面給出的是深度16,寬度32的示例,大家可以使用parameter參數(shù)化定義深度和寬度,方便不同需求下的調用。除了空滿信號標志,也可以根據(jù)需要做出半空半滿之類信號。上面需要注意的一點就是,格雷碼必須在本時鐘域下DFF輸出,再往另一個時鐘域同步。同步FIFO呢,就不用有格雷碼轉換,設計更加簡單,就不專門開貼描述了。
Verilog基本電路設計之四:去抖濾波
(帖子鏈接:http://bbs.eetop.cn/thread-605729-1-1.html)
debounce電路,就是常說的去抖濾波,主要用在芯片的PAD輸入信號,或者模擬電路輸出給數(shù)字電路的信號上。
parameter BIT_NUM = 4 ;
reg [BIT_NUM-1 : 0] signal_deb ; //
always @ (posedge clk or negedge rst_n)
begin
if (rst_n == 1’b0)
signal_deb 《= {BIT_NUM{1‘b0}} ;
else
signal_deb 《= # DLY {signal_deb[BIT_NUM-2:0],signal_i} ;
end
always @ (posedge clk or negedge rst_n)
begin
if (rst_n == 1’b0)
signal_o 《= 1‘b1 ;
else if (signal_deb[3:1]==3’b111)
signal_o 《= # DLY 1‘b1 ;
else if (signal_deb[3:1]==3’b000)
signal_o 《= # DLY 1‘b0 ;
else ;
end
上面的電路,第一個always,還兼顧了去亞穩(wěn)態(tài)作用。它可以濾掉的寬度是兩個clk的cycle,對于大于兩個cycle而小于三個cycle的信號,有些可以濾掉,有些不能濾掉,這與signal_i相對clk的相位有關。
根據(jù)希望濾除的寬度相關,換算到clk下是多少個cycle數(shù),從而決定使用多少級DFF。如果希望濾除的寬度相對cycle數(shù)而言較大,可以先在clk下做一個計數(shù)器,產(chǎn)生固定間隔的脈沖,再在脈沖信號有效時使用多級DFF去抓signal_i;或者直接將clk分頻后再使用。
編輯:黃飛
評論