【深入淺出玩轉FPGA】學習筆記_設計技巧


一、基本語法

(一)可綜合的Verilog語法子集

硬件設計的精髓是力求用最簡單的語句描述最復雜的硬件。

常用的RTL語法結構:

模塊聲明:module……endmodule。

端口聲明:input,output,inout。

信號類型:wire,reg(最常用);tri,integer(一般用在測試腳本里)。

參數定義:parameter。

運算操作符:大多數邏輯操作符、移位操作符、算術操作符都是可綜合的(===與!==是不可綜合的)。

比較判斷:if……else,case(casex,casez)……default……endcase。

連續賦值:assign,問號表達式(?:)。

always模塊:敏感列表可以是電平、沿信號posedge/negedge。

begin……end。

任務定義:task……endtask。

循環語句:for

賦值信號:=和<=

(二)if……else與case語句分析

if……else和case語句實現的結構到底是怎樣還是要看開發工具,具體問題具體分析,不能片面強調誰好誰壞。

具體邏輯具體分析,如果你要表達的是同一個邏輯問題,那么if……else和case只不過是形式上的不同。綜合工具的優化能力足夠強的話,就能看穿這個形式上的不同,實現邏輯上的相同。

(三)for語句

for語句可綜合,但是在RTL級的代碼中基本不用。一方面因為for語句的使用很占用硬件資源,另一方面是因為在設計中往往采用時序邏輯設計,用到for循環的地方不多。

例:在一個時鍾周期內計算13路脈沖信號為高電平的個數

module test(clk,rst_n,data,num);
input clk;
input rst_n;
input [12:0] data;
output [15:0] num;

reg [3:0] i;
reg [15:0] num;

always @(posedge clk)begin
    if(!rst_n)begin
        num <= 0;
        end
    else begin
        for(i=0;i<13;i=i+1)
            if(data[i]) num <= num + 1;
        end
end

endmodule

發現每個時鍾周期for循環只執行一次 num<=num+1。

因為always語句中使用非阻塞賦值時,是在always結束后才把值賦給左邊的寄存器。

修改成阻塞賦值可解決問題。

for語句綜合的效率不高,在對速度要求不高的前提下,還是寧願用多個時鍾周期去實現也不用for語句。同時時序邏輯里多用非阻塞賦值語句,修改之后的代碼風格其實是不可取的。

硬件語言不能像C語言一樣片面的追求代碼的簡捷。

(四)inout用法

需要考慮到inout端口同時作為輸入輸出口的沖突問題。

驅動源 0 1 X Z
0 0 X X 0
1 X 1 X 1
X X X X X
Z 0 1 X Z

當inout端口作為輸入口使用時,一定要把它置為高阻態。

inout io_data;                                              //inout口
reg out_data;                                               //需要輸出的數據
reg io_link;                                                //inout口方向控制
assign io_data = io_link ? out_data:1'bz;                   //關鍵

(五)探討4輸入LUT

大多數FPGA是基於4輸入LUT的結構。

例1:4輸入與

input clk;
input a,b,c,d;
output reg dout;
always @(posedge clk)
    dout <= a & b & c & d;

綜合后:用了FPGA內部一個4輸入的LUT和一個觸發器

 

例2:5輸入與

input clk;
input a,b,c,d,e;
output reg dout;
always @(posedge clk)
    dout <= a & b & c & d & e;

綜合后:用了FPGA內部兩個4輸入的LUT和一個觸發器

 

還有兩個閑置的輸入能否利用起來?

 例3:

input clk;
input a,b,c,d,e;
input f,g;
output reg dout;
output reg fout;
always @(posedge clk)begin
    dout <= a & b & c & d & e;
    fout <= f | g;
end

綜合后:用了FPGA內部三個4輸入的LUT和兩個觸發器

 

 因此,在一個組合邏輯中沒有用完的LUT是無法被其他邏輯復用的。

二、狀態機設計

 (一)狀態機的基本概念

通過不同的狀態遷移來完成一些特定的順序邏輯,即分多個時間完成一個任務。

構成狀態機的基本要素是狀態機的輸入、輸出和狀態。

Moore型狀態機的狀態變化僅和當前狀態有關,而與輸入條件無關;Mealy型狀態機的狀態變化不僅和當前狀態有關,還取決於當前的輸入條件。

(二)三種不同的狀態機寫法

1、兩段式狀態機

reg [3:0] cstate;
reg [3:0] nstate;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) cstate <= IDLE;
    else cstate <= nstate;
end

always @(cstate or wr_req or rd_req) begin
    case(cstate)
        IDLE:   if(wr_req)begin
                    nstate = WR_S1;
                    cmd = 3'b011;
                    end
                else if(rd_req) begin
                    nstate = RD_S1;
                    cmd = 3'b011;
                    end
                else begin
                    nstate = IDLE;
                    cmd = 3'b111;
                    end
        WR_S1:  begin
                    nstate = WR_S2;
                    cmd = 3'b101;
                end
        WR_S2:  begin 
                    nstate = IDLE;
                    cmd = 3'b111;
                end
        RD_S1:  if(wr_req) begin 
                    nstate = WR_S2;
                    cmd = 3'b101;
                    end
                else begin
                    nstate = RD_S2;
                    cmd = 3'b110;
                    end
        RD_S2:  if(wr_req) begin 
                    nstate = WR_S1;
                    cmd = 3'b011;
                    end
                else begin 
                    nstate = IDLE;
                    cmd = 3'b111;
                    end
        default:nstate = IDLE;
    endcase
end

2、三段式狀態機

reg [3:0] cstate;
reg [3:0] nstate;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) cstate <= IDLE;
    else cstate <= nstate;
end

always @(cstate or wr_req or rd_req) begin
    case(cstate)
        IDLE:   if(wr_req) nstate = WR_S1;
                else if(rd_req) nstate = RD_S1;
                else nstate = IDLE;
        WR_S1:  nstate = WR_S2;
        WR_S2:  nstate = IDLE;
        RD_S1:  if(wr_req) nstate = WR_S2;
                else nstate = RD_S2;
        RD_S2:  if(wr_req) nstate = WR_S1;
                else nstate = IDLE;
        default:nstate = IDLE;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    case(nstate)
        IDLE:   if(wr_req) cmd <= 3'b011;
                else if(rd_req) cmd <= 3'b011;
                else cmd <= 3'b111;
        WR_S1:  cmd <= 3'b101;
        WR_S2:  cmd <= 3'b111;
        RD_S1:  if(wr_req) cmd <= 3'b101;
                else cmd <= 3'b110;
        RD_S2:  if(wr_req) cmd <= 3'b011;
                else cmd <= 3'b111;
        default:
    endcase
end

 二段式把時序邏輯和組合邏輯分開來,時序邏輯里進行當前狀態和下一狀態的切換,組合邏輯里實現各個輸入、輸出以及狀態判斷。易維護,但組合邏輯輸出容易產生毛刺。

三段式較為推薦,時序邏輯的輸出解決了兩段式寫法中組合邏輯的毛刺問題,但較之兩段式,三段式資源消耗多一些;另外,三段式從輸入到輸出會比兩段式延時一個時鍾周期。

三、復位設計

(一)異步復位與同步復位

1、異步復位

異步復位,指復位信號和系統時鍾信號的觸發可以在任何時刻,二者相互獨立。

always @(posedge clk or negedge rst_n)
    if(!rst_n) b <= 1'b0;
    else b <= a;

綜合后,可知寄存器存在一個異步的清零端(CLR),在異步復位的設計中,這個端口一般接低電平有效的復位信號rst_n,即使設計中是高電平復位,實際綜合后也會把異步復位信號反向后接到這個CLR端。

 

 2、同步復位

always @(posedge clk)
    if(!rst_n) b <= 1'b0;
    else b <= a;

綜合后,可知同步復位沒有用到寄存器的CLR端口,綜合出來的實際電路只是把復位信號rst_n作為輸入邏輯的使能信號。這必然會額外增加FPGA內部的資源消耗。

 

 

總結:兩種方式各有優缺點。FPGA的寄存器有支持異步復位專用的端口,采用異步復位無需增加器件的額外資源,但是異步復位也存在隱患,即異步時鍾域的亞穩態問題存在於異步復位信號和系統時鍾信號之間。同步復位在時鍾信號clk的上升沿觸發時進行系統是否復位的判斷,這降低了亞穩態出現的概率(但不能完全避免),缺點就是增加了器件資源,無法利用已有的復位端口CLR。

3、異步復位存在隱患

always @(posedge clk or negedge rst_n)
    if(!rst_n) b <= 1'b0;
    else b <= a;
always @(posedge clk or negedge rst_n)
    if(!rst_n) c <= 1'b0;
    else c <= b;

綜合出來電路圖:

 

問題在於:不能確定復位信號rst_n會在什么時候結束。

如果復位信號結束於b_reg0和c_reg0的{latch edge - setup time,latch edge + hold time}時間之外,一切正常。

但如果復位信號的撤銷(由低電平變為高電平)出現在clk鎖存信號的建立時間或者保持時間內,此時clk檢測到rst_n的狀態就會是一個亞穩態(不確定是0還是1)。b_reg0和c_reg0如果出現一個復位一個跳出復位,那么就會造成系統工作不同步的問題。

(二)復位與亞穩態

 亞穩態對一個寄存器的影響相對小一些,但是對於諸如總線式的寄存器受到的影響就很大了。

(三)異步復位、同步釋放

該電路由兩個同一時鍾沿觸發的層疊寄存器組成,該時鍾必須和目標寄存器是一個時鍾域。

input clk;                                      //系統時鍾信號
input rst_n;                                    //輸入復位信號,低有效
output rst_nr2;                                 //異步復位、同步釋放輸出
reg rst_nr1,rst_nr2;                            

//兩級層疊復位產生,低電平復位
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) rst_nr1 <= 1'b0;
    else rst_nr1 <= 1'b1;                       //給一個確定的值1'b1,不會出現亞穩態
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) rst_nr2 <= 1'b0;
    else rst_nr2 <= rst_nr1;
end

實現的電路圖:

 

既解決了同步復位的資源消耗問題,又解決了異步復位的亞穩態問題。

根本思想:異步信號同步化

應用到上述例子,可以得到新的電路圖:

 

 這就得到了穩定復位信號。

 (四)PLL配置后的復位設計

在系統復位后、PLL時鍾輸出前,即系統工作時鍾不確定的情況下,應怎么考慮這個復位的問題呢?

可以這樣解決:先用FPGA的外部輸入時鍾clk將FPGA的輸入復位信號rst_n做異步復位、同步釋放處理,然后這個復位信號輸入PLL,同時clk也輸入PLL。設計初衷是在PLL輸出時鍾有效前,系統的其他部分都保持復位狀態。PLL的輸出locked信號在PLL有效輸出之前一直是低電平,PLL輸出穩定有效之后才會拉高該信號,所以這里就把FPGA外部輸入復位信號rst_n和這個locked信號相與作為整個系統的復位信號。同時,這個復位信號也需要讓合適的PLL輸出時鍾異步復位、同步釋放處理一下。總的來說,為了達到可靠穩定的復位信號,該設計中對復位信號進行了兩次處理,分別在PLL輸出前和PLL輸出后。

 

 

module sys_ctrl(
    clk,rst_n,sys_rst_n,
    clk_25m,clk_100m
);

input clk;                                                  //FPGA輸入時鍾信號25MHz
input rst_n;                                                //系統復位信號

output sys_rst_n;                                           //系統復位信號,低有效

output clk_25m;                                             //PLL輸出25MHz時鍾頻率
output clk_100m;                                            //PLL輸出100MHz時鍾頻率
wire locked;                                                //PLL輸出有效標志位,高表示PLL輸出有效

//——————————————————————————————————————————————————————————————
//PLL復位信號產生,高有效
//異步復位,同步釋放
wire pll_rst;                                               //PLL復位信號,高有效

reg rst_r1,rst_r2;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) rst_r1 <= 1'b1;
    else rst_r1 <= 1'b0;                       

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) rst_r2 <= 1'b1;
    else rst_r2 <= rst_r1;
end

assign pll_rst = rst_r2

//——————————————————————————————————————————————————————————————
//系統復位信號產生,低有效
//異步復位,同步釋放
wire sys_rst_n;                                             //系統復位信號,低有效
wire sysrst_nr0;

reg sysrst_nr1,sysrst_nr2;

assign sysrst_nr0 = rst_n & locked;                         //系統復位直到PLL有效輸出

always @(posedge clk_100m or negedge sysrst_nr0) begin
    if(!sysrst_nr0) sysrst_nr1 <= 1'b0;
    else sysrst_nr1 <= 1'b1;                       

always @(posedge clk_100m or negedge sysrst_nr0) begin
    if(!sysrst_nr0) sysrst_nr2 <= 1'b0;
    else sysrst_nr2 <= sysrst_nr1;
end

assign sys_rst_n = sysrst_nr2;

//——————————————————————————————————————————————————————————————
//例化PLL產生模塊
PLL_ctrl            uut_PLL_ctrl(                           
                        .areset(pll_rst),                   //PLL復位信號,高電平復位
                        .inclk(clk),                        //PLL輸入時鍾,25MHz
                        .c0(clk_25m),                       //PLL輸出25MHz時鍾頻率
                        .c1(clk_100m),                      //PLL輸出100MHz時鍾頻率
                        .locked(locked)                     //PLL輸出有效標志位,高電平表示PLL輸出有效     
                    );

endmodule

 四、FPGA重要設計思想及工程應用

(一)速度和面積互換原則

速度:整個工程穩定運行所能達到的最高時鍾頻率;

面積:通過一個工程運行所消耗的觸發器(FF)、查找表(LUT)數量或者等效門數量來衡量。

從系統設計的角度闡釋速度和面積的互換原則:

 

 這很好的利用了FPGA的並行性。

(二)乒乓操作及串/並轉換設計

1、乒乓操作:主要用於數據流處理

 

數據緩沖模塊可以是任何存儲模塊,常用的有雙口RAM、SRAM、SDRAM、FIFO等。

第一個緩沖周期:輸入的數據流緩存到“數據緩沖1”模塊。

第二個緩沖周期:“輸入數據選擇控制”模塊將輸入的數據流緩存到“數據緩沖2”模塊的同時,“輸出數據選擇控制”模塊將“數據緩沖1”模塊第一個周期緩存的數據流送到“后續處理”模塊。

第三個緩沖周期:“輸入數據選擇控制”模塊切換使輸入的數據流緩存到“數據緩沖1”模塊,同時,“輸出數據選擇控制”模塊切換使“數據緩沖2”模塊緩存的第二個周期的數據送到“后續處理”模塊。

如此不斷循環。

乒乓操作可以實現數據的無縫緩沖與處理。

2、串/並轉換:高速數據流處理

根據數據的順序與數量的要求,串並轉換可以選用寄存器、雙口RAM、SRAM、SDRAM、FIFO等實現。對於數量比較小的設計可以采用移位寄存器完成串並轉換。

 

 移位一般需要時鍾做同步的,即n個時鍾采樣到的串行數據需要在n個時鍾周期后以並行的方式輸出,這是最基本的串入並出的設計思想。

例1:串轉並

使用寄存器cnt,每來一個數cnt即加1,同時以cnt作為索引放入對應位置的寄存器中,當加到符合要求的數值后就將寄存器中的數值輸出。

module serial_parallel(
    input clk,
    input rst_n,
    input data_i,
    output reg [7:0] data_o
);

reg [2:0] cnt;

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
      data_o <= 8'b0;
      cnt <= 3'b0;
    end
    else begin
        data_o[7 - cnt] <= data_i;                          //高位先賦值
        cnt <= cnt + 1'b1;
    end  
end

endmodule

另外一種思路是使用移位寄存器,即每存儲一個數據后即進行移位,將空的部分准備好存儲下一個數據。

module serial_parallel(
    input clk,
    input rst_n,
    input en,
    input data_i,
    output reg [7:0] data_o
);

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
      data_o <= 8'b0;
    end
    else if(en == 1'b1) begin
        data_o <= {data_o[6:0],data_i}; 
    end  
    else data_o <=data_o;
end

endmodule

例2:並轉串

module parallel_serial(
    input clk,
    input rst_n,
    input en,
    input [7:0] data_i,
    output data_o
);

reg [7:0] data_buf;

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        data_o <=1'b0;
        data_buf <= 8'b0;
    end
    else if (en == 1'b1)
        data_buf <= data_i;
    else
        data_buf <= data_buf << 1;
end

assign data_o = data_buf[7];

endmodule

(三)流水線設計

可提高系統頻率,常用於高速信號處理領域。

若某個設計可以分為若干步驟進行處理,而且整個數據處理過程是單向的,即沒有反饋運算或者迭代運算、前一個步驟的輸出即是下一個步驟的輸入,就可以采用流水線設計方法來提高系統的工作頻率。

 

典型的流水線設計是將原本一個時鍾周期完成的較大的組合邏輯通過合理的切割后分為由多個時鍾周期完成。因此,該部分邏輯運行的時鍾頻率會有明顯的提升,尤其當它是一條關鍵路徑,采用流水線設計后整個系統的性能都會得到提升。具體流水線實現可參見下圖:

 

 (四)邏輯復制與模塊復用

1、邏輯復制

邏輯復制是一種通過增加面積來改善時序條件的優化手段,最主要的應用是調整信號的扇出。

如果某個信號需要驅動的后級邏輯信號很多,即扇出非常大,那么為了增加這個信號的驅動能力,就必須插入很多級的Buffer,也就在一定程度上增加了這個信號的路徑延時。

這種情況下就可以復制生成這個信號的邏輯,用多路同頻同相的信號驅動后續電路,使平均到每路的扇出變低,這樣也就不需要插入Buffer,從而節約該信號的路徑延時。

如:例1:

input a,b,c,d;
input sel;
output dout;
assign dout = sel ? (a+b):(c+d);

綜合出兩個加法器和一個二選一選擇器

 

 例2:

input a,b,c,d;
input sel;
output dout;
wire ab,cd;
assign ab = sel ? a:c;
assign cd = sel ? b:d;
assign dout = ab + cd;

綜合出兩個二選一選擇器和一個加法器

 

 第一種方法占用資源多,但速度快些;第二種方法則正好相反。

第一種方法就是一種邏輯復制的設計方法,因為這個設計實現本來只需要一個加法器就可以了,但為了加快速度,就需要進行邏輯復制。第一種方法就是以增加面積為代價換來了速度。

當然,現在的很多綜合工具可以根據情況自動進行邏輯復制。

2、模塊復用

就是邏輯復制的逆過程,好處在於節省面積,但會以犧牲速度為代價。

(五)模塊化設計

 模塊划分的基本原則:子模塊功能相對獨立,模塊內部聯系盡量緊密,而模塊間的連接盡量簡單。

 

 一般情況下,不在頂層模塊做任何邏輯設計。

(六)時鍾設計技巧

 盡量避免使用FPGA內部邏輯產生的時鍾,因為它容易導致功能或時序出現問題。

1、內部邏輯產生的時鍾

組合邏輯產生的時鍾不可避免地會有毛刺出現,如果此時輸入端口的數據正處於變化過程,那么它將違反建立和保持時間要求,從而影響后續電路的輸出狀態,甚至導致整個系統運行失敗。

處理辦法:在輸出時鍾或者復位信號之前,再用系統專用時鍾信號(通常指外部晶振輸入時鍾或者PLL處理后的時鍾信號)打一拍,從而避免組合邏輯直接輸出,達到同步處理的效果。對於輸出的時鍾信號或復位信號,最好讓它走全局時鍾網絡,從而減小時鍾網絡延時,提升系統時序性能。

2、分頻時鍾與使能時鍾

時鍾滿天飛是很不好的設計風格

通常用FPGA內嵌的PLL或者DLL進行時鍾管理,這種時鍾分頻也是最穩定的。

但若對於無法使用PLL或者DLL資源的器件,則使用使能時鍾設計。

在使能時鍾設計中只使用原有的時鍾,讓分頻信號作為使能信號來用。

例:需得到一個50MHz輸入時鍾的5分頻信號即10MHz

input clk;                                          //50MHz時鍾信號
input rst_n;                                        //寫使能信號,低有效

reg [2:0] cnt;                                      //分頻計數寄存器
wire en;                                            //使能信號,高電平有效

//5分頻計數0~4
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) cnt <= 3'd0;
    else if(cnt < 3'd4) cnt <= cnt + 1'b1;
    else cnt <= 3'd0;
end

assign en = (cnt == 3'd4);                          //5個時鍾周期產生1個時鍾周期高脈沖

//使用使能時鍾
always @(posedge clk or negedge rst_n)
    if(!rst_n) ...;
    else if(en) ...;

使能信號不直接作為時鍾使用,而是作為數據輸入端的選擇信號,即可避免使用分頻時鍾。

 

3、門控時鍾

組合邏輯中多用門控時鍾,一般驅動門控時鍾的邏輯都是只包含一個與門(或門),如果有其他的附加邏輯,容易因競爭產生不希望的毛刺。門控時鍾通過一個使能信號控制時鍾的開或者關。當系統不工作時可以關閉時鍾,整個系統就處於未激活狀態,這樣能在某種程度上降低系統功耗。但使用門控時鍾不符合同步設計的思想,可能會影響系統設計的實現和驗證。

 

這里推薦一種既可以降低系統功耗,又能夠穩定可靠的替代門控時鍾。

 

對於上升沿有效的系統時鍾clk,它的下降沿先把門控信號gating signal打一拍,然后再用這個使能信號enable和系統時鍾clk相與后作為后續電路的門控時鍾。

 五、基於FPGA的跨時鍾域信號處理

對於一些復雜的應用,FPGA需要和多個時鍾域的信號進行通信。異步時鍾域所設計的兩個時鍾之間不同頻不同相。

 

 如上圖,對於接收域而言,來自發送域的信號data_a2b有可能在任何時刻變化。如果出現建立時間或者保持時間違規,接收域將會受到處於亞穩態的數據,引起嚴重后果。

跨時鍾域的信號成熟的基本思想是同步。

(一)同步設計思想

先舉反例:設計功能就是一個頻率計,FPGA除了脈沖計數外,還要響應CPU的讀取控制。

 

CPU的控制總線是指一個片選信號和一個讀選通信號,當二者都有效時,FPGA需要對CPU的地址總線進行譯碼,然后把采樣脈沖值送到CPU的數據總線上。如下圖為CPU的讀時序圖。

如果給出下面的以組合邏輯為主的實現方式,似乎可以。但對於這種時鍾滿天飛的設計,存在諸多亞穩態危害爆發的可能。脈沖信號和由CPU控制總線產生的選通信號是來自兩個異步時鍾域的信號,它們作為內部時鍾信號時,如果同一時刻出現一個時鍾在寫寄存器counter,另一個時鍾在讀寄存器counter,那么明顯存在着發生沖突的可能。(即若寄存器正處於改變狀態(被寫)時有讀取信號產生了,問題就會隨之而來)。

input clk;
input rst_n;
input pulse;
input cs_n;
input rd_n;
input [3:0] addr_bus;

output reg [15:0] data_bus;

reg [15:0] counter;

always @(posedge pulse or negedge rst_n) begin
    if(!rst_n) counter <= 16'd0;
    else if(pulse) counter <= counter + 1'b1;
end

wire dsp_cs = cs_n & rd_n;

always @(dsp_cs or addr_bus) begin
    if(dsp_cs) data_bus <= 16'hzzzz;
    else begin
        case(addr_bus)
            4'h0:data_bus <= counter;
            4'h1:...;
            ...
            default:;
        endcase
    end
end

脈沖信號pulse與CPU讀選通信號cpu_cs是異步信號,pulse何時出現上升沿與cpu_cs何時出現下降沿都是不可控的。如果它們一起觸發了,那么計數器counter[15:0]正在加1,這個自增過程還在進行中,CPU數據總線data_bus[15:0]來讀取counter[15:0],那么到底讀取的值是自增之前的值還是自增之后的值又或者是其他的值呢?

如下圖所示,為一個計數器的近似模型。當計數器自增1的時候,如果最低位為0,那么自增的結果只會使最低位翻轉;但是當最低位為1,自增的后果除了使最低位翻轉,還有可能使其它任何位翻轉,比如4'b1111自增1的后果會使4個位都翻轉。由於每個位之間從發生翻轉到翻轉完成都需要經過一段邏輯延時和走線延時,對於一個16位的計數器,要想使這16位寄存器的翻轉時間一致,那是不可能的。因此之前的設計出現沖突時被讀取的脈沖值很可能完全是錯誤的。

 

 要想解決沖突的問題,必須運用同步設計的思想,把這兩個異步時鍾域的信號同步到一個時鍾域里進行處理。

解決辦法:如下圖所示。先是使用脈沖檢測法把脈沖信號與系統時鍾信號clk同步,然后依然使用脈沖檢測法得到一個系統時鍾寬度的使能脈沖作為數據鎖存信號,也就將CPU的控制信號和系統時鍾信號clk同步了。如此處理后,兩個異步時鍾域的信號就不存在任何讀/寫沖突的情況了。

 

 (二)單向控制信號檢測

 (三)專用握手信號

所謂握手,即通信雙方使用了專用控制信號進行狀態指示。這個控制信號既有發送域給接收域的,也有接收域給發送域的。使用握手協議方式處理跨時鍾域數據傳輸時,只需要對雙方的握手信號(req和ack)分別使用脈沖檢測方法進行同步。

 

具體實現,假設req,ack,data總線在初始化都處於無效狀態,發送域先把數據放入總線,隨后發送有效的req信號給接收域;接收域在檢測到有效的req信號后鎖存數據總線,然后回送一個有效的ack信號表示讀取完成應答;發送域在檢測到有效ack信號后撤銷當前的req信號,接收域在檢測到req撤銷后也相應撤銷ack信號,此時完成一次正常握手通信。此后發送域開始繼續下一次握手通信,如此循環。

 

該方式能夠使接收到的數據穩定可靠,有效避免了亞穩態的出現,但控制信號握手檢測會消耗通信雙方較多的時間。

module handshack(
    clk,rst_n,
    req,datain,ack,dataout);
    
input clk;                              //50MHz系統時鍾頻率
input rst_n;                            //低電平復位信號
input req;                              //請求信號,高電平有效
input [7:0] datain;                     //輸入數據
output ack;                             //應答信號,高電平有效
output [7:0] dataout;                   //輸出數據,主要用於觀察是否和輸入一致

//————————————————————
//req上升沿檢測
reg reqr1,reqr2,reqr3;

always @(posedge clk or negedge rst_n)
    if(!rst_n) begin
        reqr1 <= 1'b1;
        reqr2 <= 1'b1;
        reqr3 <= 1'b1;
      end
    else begin
        reqr1 <= req;
        reqr2 <= reqr1;
        reqr3 <= reqr2;
      end
    end
//pos_req2比pos_req1延后一個時鍾周期,確保數據被穩定鎖存
wire pos_req1 = reqr1 & ~reqr2;
wire pos_req2 = reqr2 & ~reqr3;

//————————————————————
//數據鎖存
reg [7:0] dataoutr;

always @(posedge clk negedge rst_n) begin
    if(!rst_n) dataour <= 8'h00;
    else if(pos_req1) dataoutr <= datain;
end
assign dataout = dataoutr;

//————————————————————
//產生應答信號ack
reg ackr;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) ackr <= 1'b0;
    else if(pos_req2) ackr <= 1'b1;
    else if(!req) ack <= 1'b0;
end

assign ack = ackr;

endmodule

(四)搞定亞穩態

所有的數字器件的信號傳輸都會有一定的時序要求,從而保證每個寄存器將捕獲的輸入信號正確輸出。為了確保可靠,輸入寄存器的信號必須在時鍾沿的某段時間(寄存器的建立時間)之前保持穩定,並且持續到時鍾沿之后的某段時間(寄存器的保持時間)之后才能改變,而該寄存器的輸入反映到輸出則需要經過一定的延時。如果數據變化違反了建立時間和保持時間的要求,那么寄存器的輸出就會處於亞穩態,即輸出會在高電平1和低電平0之間盤旋一段時間,這也意味着寄存器的輸出達到一個穩定的高或者低電平的狀態所需要的時間大於一般情況下的延時。

同步系統一般不會出現亞穩態問題,亞穩態問題多發生在一些跨時鍾域信號的傳輸上。由於數據信號可能在任何時間到達異步時鍾域的目的寄存器,所以設計者無法保證滿足建立時間和保持時間的要求。然而並非所有違反建立時間和保持時間要求的信號都會出現亞穩態。進入了亞穩態的寄存器恢復穩定的時間取決於器件的制造工藝與工作環境(大多數情況下會很快)。

寄存器在時鍾沿采樣數據信號好比一個球從小山的一側拋到另一側。如下圖所示,小山的兩側代表數據的穩定狀態——舊的數據值或者新的數據值;山頂代表亞穩態。如果小球被拋到山頂上,但實際上它只要稍微有些動靜就會滾落山底。在一定時間內,球滾得越遠,它達到穩定狀態的時間也就越短。

 

如果數據信號的變化發生在時鍾沿的保持時間之后,就好像球跌落倒了小山的左側,輸出信號仍然保持時鍾變化前的值不變。如果數據信號的變化發生在時鍾沿的建立時間之前,並且持續到時鍾沿之后的保持時間都不再變化,就好像球跌落到了小山的右側,輸出數據達到穩定狀態的時間就是一般狀態下的延時。然而一個寄存器的輸入數據違反了建立時間和保持時間的要求,就像球被拋到了山頂,如果球在山頂停留得越久,那么它到達山底的時間也就越長,這就相應地延長了從時鍾變化到輸出數據達到穩定狀態的時間了。如下圖很好地闡述了亞穩態信號。

 

當球到達山底的時間超過了扣除寄存器一般延時時間以外的余量時間,那么問題就隨之而來。

當信號變化處於一個不相關的電路或者異步時鍾域,它在被使用前就需要先被同步到新的時鍾域中。新的時鍾域中的第一個寄存器將扮演同步寄存器的角色。

為了盡可能消除亞穩態的影響,設計者一般在目的時鍾域使用一串連續的寄存器將信號同步到新的時鍾域中,這些寄存器有額外的時間用於信號在被使用前從亞穩態達到穩定值。同步寄存器到寄存器路徑的時序余量,也就是亞穩態信號到達穩態的最大時間,也被認為時亞穩態持續時間。

同步寄存器鏈被定義為一串達到以下要求的連續寄存器:

1、鏈中的寄存器都由相同的時鍾或者相位相關的時鍾觸發;

2、鏈中的第一個寄存器由不相關時鍾域或者異步的時鍾來觸發;

3、每個寄存器的扇出值都為1,鏈中的最后一個寄存器可以例外。

 

設計者無法預測信號變化的順序或者說信號兩次變化間經過了幾個鎖存時鍾周期,因此使用雙時鍾FIFO傳輸信號或使用握手信號進行控制。

(五)借助於存儲器

借助於存儲器來完成跨時鍾域通信也是很常用的手段。雙口RAM更適合於需要互通信的設計,只要雙方對地址做好適當的分配,那么剩下的工作只要控制好存儲器的讀/寫時序。FIFO本身的特性(先進先出)決定了它更適合單向的數據傳輸。

 

異步FIFO在跨時鍾域通信中的使用

 

FIFI兩側會有相對獨立的兩套控制總線。若寫入請求wrreq在寫入時鍾wrclk的上升沿處於有效狀態,那么FIFO將在該時鍾沿鎖存寫入數據總線wrdata。同理,若讀請求rdreq在讀時鍾rdclk的上升沿處於有效狀態,那么FIFO將把數據放到讀數據總線rddata上,外部邏輯一般在下一個有效時鍾沿讀取該數據。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM