FPGA計數(shù)器的藝術(shù)
計數(shù)器構(gòu)成了一個基本的FPGA構(gòu)建塊。 它們有各種形狀和形式......
本文引用地址:http://m.butianyuan.cn/article/202312/454304.htm計數(shù)器 1 - 二進制計數(shù)器
最簡單的計數(shù)器
可以使用幾行 Verilog 構(gòu)建快速高效的二進制計數(shù)器。例如,下面是一個 32 位計數(shù)器。
reg [31:0] cnt;
always @(posedge clk) cnt <= cnt+1;
此類計數(shù)器從 0 計數(shù)到 4294967295,然后回滾 0 以繼續(xù)其進程。 它占用的資源很少,并且在FPGA中運行速度快,這要歸功于隱藏的攜帶鏈(稍后會詳細介紹)。 現(xiàn)在,讓我們看看一些變化。
首先,最好明確給出一個起始值,即使它是 0。
reg [31:0] cnt = 0;
always @(posedge clk) cnt <= cnt+1;
請注意,如果我們不指定起始值,仿真工具將拒絕工作,并且某些合成工具也可能會自行更改起始值......因此,最好始終指定一個起始值。 我們也可以使用異步重置來指定起始值,但最簡單的方法如上所示。
現(xiàn)在,如果您需要更多功能,這里有一個 10 位計數(shù)器(最多計數(shù) 1023)的示例,該計數(shù)器從 300 開始計數(shù),并具有啟用和方向控制。
reg [9:0] cnt = 10'd300; // 10bit counter, starts at 300
wire cnt_enable; // 0 to disable the counter, 1 to enable it
wire cnt_direction; // 0 to counter backward, 1 to count forward
always @(posedge clk) if(cnt_enable) cnt <= cnt_direction ? cnt+1 : cnt-1;
請注意,FPGA綜合工具必須采取一些技巧才能使計數(shù)器從300開始。 FPGA 內(nèi)部的觸發(fā)器總是從 0 開始,因此您可以認為計數(shù)器初始值必須為 0...但是,通過在邏輯中放置一些位置良好的逆變器,任何起始值都是可能的。 逆變器在FPGA中是“免費”的,因此沒有缺點。
計數(shù)器滴答聲
假設(shè)我們需要一個“滴答”信號,該信號每 1024 個時鐘斷言一次。 最有可能的是,我們會創(chuàng)建一個 10 位計數(shù)器和一些邏輯來生成“滴答聲”。 讓我們看看如何做到這一點。
首先,我們制作 10 位計數(shù)器。它從 0 到 1023 計數(shù)。
reg [9:0] cnt = 0;
always @(posedge clk) cnt <= cnt+1;
現(xiàn)在我們可以決定,當計數(shù)器達到其最大值時(就在回滾到 0 之前),我們的“tick”被斷言。
wire tick = (cnt==1023);
另一種寫作方式是
wire tick = &cnt; // assert "tick" when all the cnt bits are 1這些滴答信號的缺點是它們會產(chǎn)生一大塊邏輯(這里是 10 位 AND 門)。 只有 10 位沒什么大不了的,但如果我們的計數(shù)器是 32 位或更大,那將是一種浪費。 另一種方法是依賴FPGA在后臺使用的(通常是隱藏的)進位鏈。 我們只需要稍微扭動一下手臂來說服FPGA提供他隱藏的信息......
reg [31:0] cnt = 0; // 32bit counter
wire [32:0] cnt_next = cnt+1; // next value calculated with 33bit (one bit more than the counter)
always @(posedge clk) cnt <= cnt_next[31:0]; // now the counter uses only 32 bits
wire tick = cnt_next[32]; // but we access to the last bit of the carry chain to create the tick (asserted when the counter reaches its maximum value)
嘗試一下,你會發(fā)現(xiàn)它的工作原理是一樣的,但在FPGA中占用的空間更少 (注意:在撰寫本文時,我們嘗試了 ISE 和 Quartus-II,并且都以 0 作為起始值做得很好)。
計數(shù)器 2 - 特殊計數(shù)器
模量計數(shù)器
模量計數(shù)器是在其自然結(jié)束值之前回滾的二進制計數(shù)器。 例如,假設(shè)你想要一個模數(shù) 10 計數(shù)器(從 0 到 9 計數(shù)),你可以寫這個。
reg [3:0] cnt = 0; // we need 4 bits to be able to reach 9
always @(posedge clk)
if(cnt==9)
cnt <= 0;
else
cnt <= cnt+1;
或者這個(更緊湊一點)
reg [3:0] cnt = 0;
always @(posedge clk) cnt <= (cnt==9) ? 0 : cnt+1;
現(xiàn)在,如果您意識到我們實際上不需要將計數(shù)器的所有 4 位與 9 位進行比較,則可以進行一些(免費)優(yōu)化。 下面的代碼在比較中僅使用位 0 和位 3。
always @(posedge clk) cnt <= ((cnt & 9)==9) ? 0 : cnt+1;
灰色計數(shù)器
灰色計數(shù)器是一種二進制計數(shù)器,一次只有一個位發(fā)生變化。
以下是 4bit Gray 計數(shù)器的運行方式。
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000
and then wraps back to 0000...
格雷碼對于跨時鐘域發(fā)送值很有用(這樣它的不確定性僅為 1)。
創(chuàng)建 Gray 計數(shù)器的最簡單方法是首先創(chuàng)建一個二進制計數(shù)器,然后將值轉(zhuǎn)換為 Gray。
module GrayCounter(
input clk,
output [3:0] cnt_gray
);
reg [3:0] cnt = 0;
always @(posedge clk) cnt <= cnt+1; // 4bit binary counter
assign cnt_gray = cnt ^ cnt[3:1]; // then convert to gray
endmodule
也可以創(chuàng)建本機 Gray 計數(shù)器。
module GrayCounter(
input clk,
output reg [3:0] cnt_gray = 0
);
wire [3:0] cnt_cc = {cnt_cc[2:1] & ~cnt_gray[1:0], ^cnt_gray, 1'b1}; // carry-chain type logic
always @(posedge clk) cnt_gray <= cnt_gray ^ cnt_cc ^ cnt_cc[3:1];
endmodule
計數(shù)器 3 - LFSR 計數(shù)器
假設(shè)您想要一個或多或少“隨機”計數(shù)的計數(shù)器,您可以使用 LFSR。
下面是一個示例。
如您所見,LFSR是帶有一些XOR門的移位寄存器。 上圖所示的是 8 抽頭 LFSR(它使用 8 個人字拖)。
輸出序列開始如下(假設(shè)所有觸發(fā)器都從“1”開始):
11111111
11000111
11011011
11010101
11010010
01101001
10001100
01000110
00100011
10101001
11101100
01110110
00111011
10100101
...
這是 LFSR 源代碼。
module LFSR8_11D(
input clk,
output reg [7:0] LFSR = 255 // put here the initial value
);
wire feedback = LFSR[7];
always @(posedge clk)
begin
LFSR[0] <= feedback;
LFSR[1] <= LFSR[0];
LFSR[2] <= LFSR[1] ^ feedback;
LFSR[3] <= LFSR[2] ^ feedback;
LFSR[4] <= LFSR[3] ^ feedback;
LFSR[5] <= LFSR[4];
LFSR[6] <= LFSR[5];
LFSR[7] <= LFSR[6];
end
endmodule
請注意,我們從 255 開始。 這是因為 0 是死胡同狀態(tài),因此我們可以選擇除 0 以外的任何起始值。 之后,LFSR 在每個時鐘周期更改值,但永遠不會達到 0,因此在重新開始之前,它只經(jīng)過 255 個 8 位值(256 位輸出的 8 種可能組合)。
現(xiàn)在,我們可以只輸出一位,而不是輸出完整的 255 位(即選擇任何觸發(fā)器進行輸出,將其他 0 位保留在內(nèi)部)。 這會產(chǎn)生一個由 1 個 255 和 <> 組成的字符串,看起來是隨機的(盡管它在 <> 個時鐘后會重復(fù))。 對噪聲發(fā)生器很有用...
定制
您可以調(diào)整 LFSR:
選擇不同數(shù)量的水龍頭(我們在上面選擇了 8 個)。
改變反饋網(wǎng)絡(luò)的接線方式 - 例如更改XOR門的數(shù)量,它們的放置位置,或用XNOR替換XOR門。
某些反饋配置將創(chuàng)建可能值的孤島。 例如,此 LFSR 看起來與第一個 LFSR 類似,但僅循環(huán)遍歷 30 個值。
module LFSR8_105(
input clk,
output reg [7:0] LFSR = 255
);
wire feedback = LFSR[7];
always @(posedge clk)
begin
LFSR[0] <= feedback;
LFSR[1] <= LFSR[0];
LFSR[2] <= LFSR[1] ^ feedback;
LFSR[3] <= LFSR[2];
LFSR[4] <= LFSR[3];
LFSR[5] <= LFSR[4];
LFSR[6] <= LFSR[5];
LFSR[7] <= LFSR[6];
end
endmodule
也可以在反饋中添加一些邏輯,以便LFSR達到所有可能的狀態(tài)。
module LFSR8_11D(
input clk,
output reg [7:0] LFSR = 255
);
wire feedback = LFSR[7] ^ (LFSR[6:0]==7'b0000000); // modified feedback allows reaching 256 states instead of 255
always @(posedge clk)
begin
LFSR[0] <= feedback;
LFSR[1] <= LFSR[0];
LFSR[2] <= LFSR[1] ^ feedback;
LFSR[3] <= LFSR[2] ^ feedback;
LFSR[4] <= LFSR[3] ^ feedback;
LFSR[5] <= LFSR[4];
LFSR[6] <= LFSR[5];
LFSR[7] <= LFSR[6];
end
endmodule
LFSR測試臺
我們制作了一個小型 Windows 實用程序,允許對 LFSR 設(shè)計進行試驗。
在此處下載。
計數(shù)器4-攜帶鏈
進位鏈是允許 FPGA 高效運算(計數(shù)器、加法器等)的功能。 讓我們更多地了解使用計數(shù)器的進位鏈。 使用 T 字拖可以輕松構(gòu)建計數(shù)器。
T 字拖非常簡單。 在時鐘上升沿,如果 T 輸入為高電平,則其 Q 輸出切換,如果 T 為低電平,則不會改變。
FPGA 內(nèi)部使用 D 觸發(fā)器,但 D 和 T 觸發(fā)器很容易互換,只需一些邏輯即可。 因此,我們在此頁面上使用T觸發(fā)器,因為我們知道FPGA軟件可以很容易地將它們映射到FPGA中。
紋波計數(shù)器
最小的二進制計數(shù)器是紋波計數(shù)器。 這是一個 4 位紋波計數(shù)器。
基本上,每個T觸發(fā)器輸出驅(qū)動下一個觸發(fā)器的時鐘。 它在硬件方面非常高效,但對于FPGA來說不是很好,因為我們現(xiàn)在的時鐘域與計數(shù)器中的位數(shù)一樣多。 FPGA 針對同步電路進行了優(yōu)化,因此我們需要所有計數(shù)器位同時切換的東西。
同步計數(shù)器
在同步計數(shù)器中,時鐘同時饋送所有觸發(fā)器,因此只有一個時鐘域。
現(xiàn)在,如果我們看一下二進制計數(shù)器的計數(shù)方式,我們會看到 bit0 總是切換,并且對于任何要切換的更高位,所有低階位都需要為 1。 因此,我們的同步計數(shù)器通過使用幾個 AND 門來形成。
只要柜臺小就好。 我們的示例 4 位計數(shù)器只需要兩個 AND 門(顯然還有觸發(fā)器),因此它非常高效。 但這并不能很好地擴展。 對于 32 位計數(shù)器,我們需要 30 個 AND 門,最后一個有 31 個輸入...... 但是,我們可以很容易地以這種方式重新繪制計數(shù)器(這次我們制作了一個 6 位計數(shù)器)。
基本上,我們沒有讓 AND 門變大,而是將它們保持較小并將它們鏈接起來。
這就是 FPGA 實現(xiàn)計數(shù)器的方式。 它在硬件方面是高效的,但問題是速度...... 例如,一個 32 位計數(shù)器需要 30 個鏈式 AND 門。 而這個鏈是計數(shù)器“關(guān)鍵路徑”(設(shè)置最大計數(shù)器時鐘速度)的主要部分。 因此,保持這條道路的快速性很重要......FPGA 有一個很好的技巧來保持速度。 它被稱為...
攜帶鏈
FPGA 由“邏輯元件”組成,每個元件包含一個 LUT 和一個 D 觸發(fā)器。 每個邏輯元件可以實現(xiàn)一個計數(shù)器位(一個 32 位計數(shù)器需要 32 個邏輯元件)。
邏輯元素可以通過通用路由結(jié)構(gòu)與周圍環(huán)境進行通信,但這速度很慢。 因此,F(xiàn)PGA 設(shè)計人員確保并排放置的邏輯元件具有額外的本地路由信號。
實現(xiàn)為
此本地路由通常用作進位鏈。 每次要求FPGA軟件實現(xiàn)二進制計數(shù)器時,它都會將位彼此相鄰映射,以便將本地路由用作進位鏈。 這給映射增加了一些限制,但軟件會處理它。
FPGA 制造商還確保邏輯元件針對沿攜帶鏈路徑的速度進行了大量優(yōu)化。 結(jié)果是計數(shù)器可以在數(shù)百MHz的頻率下輕松運行...計數(shù)器的速度通常不是問題(FPGA設(shè)計的關(guān)鍵路徑比進位鏈更有可能通過常規(guī)邏輯)。 當然,這取決于您希望以多快的速度運行設(shè)計。 大型計數(shù)器具有較長的進位鏈,因此無法像小型計數(shù)器那樣快速計時。 如果這是一個問題,您可以分解進位鏈(即使用一系列小計數(shù)器)或選擇不使用進位鏈的計數(shù)器架構(gòu)。
對于那些喜歡冒險的人,請單擊此處查看 Spartan-3A FPGA 設(shè)計中實現(xiàn)計數(shù)器的切片(兩個邏輯元件)的 ISE FPGA 編輯器屏幕截圖。 該視圖適用于計數(shù)器的第 6 位和第 7 位。 我們可以立即識別出從下到上穿過中間切片的攜帶鏈。 不太明顯的是 AND 門和 T 形觸發(fā)器在哪里。 他們實際上都在那里...... AND門是使用運載鏈線上的大多路復(fù)用器制成的 T觸發(fā)器使用異或門和D觸發(fā)器輸出,環(huán)回LUT輸入(通過邏輯元件外部的布線)。 LUT 只是直通。
進位鏈也用于加法器和比較器。 但感謝數(shù)百名工程師致力于構(gòu)建智能 HDL 工具,我們可以使用攜帶鏈的強大功能而不必擔心它們。 生活是美好的。
評論