寫(xiě)在前面
FPGA基礎(chǔ)知識(shí)極簡(jiǎn)教程(9)講到了七段數(shù)碼管的顯示Verilog設(shè)計(jì),我們都知道,要在數(shù)碼管上顯示的數(shù)字,使用BCD編碼是具有優(yōu)勢(shì)的(或者是最正確的)他宛。拿數(shù)字時(shí)鐘來(lái)說(shuō),如果你的時(shí)鐘是12點(diǎn)欠气,難道你會(huì)讓數(shù)碼管顯示C厅各?
如果你愿意如此,那就給自己家里安裝一個(gè)這樣的時(shí)鐘吧预柒!
如果是23點(diǎn)呢队塘?不用BCD編碼的數(shù)字恐怕不能顯示了吧。
采用BCD碼的數(shù)字宜鸯,十位用一個(gè)數(shù)碼管顯示憔古,個(gè)位用一個(gè)數(shù)碼管顯示,例如23點(diǎn)淋袖,則2和3分別顯示鸿市,這樣才符合人類(lèi)的思維。
盡管如此即碗,存在這樣一個(gè)問(wèn)題焰情,我們?cè)谠O(shè)計(jì)計(jì)數(shù)器的時(shí)候,習(xí)慣于直接設(shè)計(jì)二進(jìn)制計(jì)數(shù)器剥懒,這樣的計(jì)數(shù)器計(jì)數(shù)結(jié)果是二進(jìn)制的内舟,我們需要將其轉(zhuǎn)換成BCD碼,這就是今天我們需要討論的問(wèn)題初橘。
當(dāng)然验游,值得討論的是這種方式是不是多此一舉呢?如果僅僅對(duì)于數(shù)字時(shí)鐘來(lái)說(shuō)保檐,時(shí)需要BCD編碼的模24計(jì)數(shù)器耕蝉,分以及秒則需要BCD編碼的模60計(jì)數(shù)器。
這都很容易實(shí)現(xiàn)夜只,例如上篇博客就是直接實(shí)現(xiàn)的BCD編碼的模60以及模24計(jì)數(shù)器赔硫。之后送入數(shù)碼管顯示模塊即可。
那么我們還有必要設(shè)計(jì)二進(jìn)制轉(zhuǎn)BCD碼的必要嗎盐肃?
有的爪膊!
- 首先這是一種方法,也算經(jīng)典砸王,它的另外一個(gè)名字叫:
Double-Dabble Binary-to-BCD Conversion Algorithm
我也不知道怎么翻譯合適推盛! - 其次,如果一個(gè)計(jì)數(shù)模塊谦铃,即用到了二進(jìn)制計(jì)數(shù)耘成,又必須把它顯示到數(shù)碼管上,我們恐怕就不能直接將這個(gè)計(jì)數(shù)器設(shè)計(jì)為BCD碼計(jì)數(shù)器了驹闰,更方便的方式是設(shè)計(jì)一個(gè)二進(jìn)制計(jì)數(shù)器瘪菌,需要顯示的話(huà),在調(diào)用二進(jìn)制轉(zhuǎn)BCD碼模塊嘹朗,接入數(shù)碼管顯示模塊即可师妙。
你可能會(huì)說(shuō),可以設(shè)計(jì)一個(gè)BCD碼計(jì)數(shù)器屹培,之后轉(zhuǎn)換成二進(jìn)制默穴!
呃,你認(rèn)為這個(gè)工作量會(huì)小一點(diǎn)嗎褪秀? - 最后蓄诽,我認(rèn)為這個(gè)算法還提供了一個(gè)思想,如何處理Verilog中的循環(huán)問(wèn)題媒吗?
我參考了互聯(lián)網(wǎng)上的資料仑氛,形成了本文,這里將分享一種雙重循環(huán)的設(shè)計(jì)方法闸英,以及一種將循環(huán)轉(zhuǎn)化為隱似的處理方法锯岖,也就是狀態(tài)機(jī)的方式來(lái)實(shí)現(xiàn)!
一起來(lái)看看吧自阱。
正文
快速認(rèn)識(shí)
為了快速了解這個(gè)算法嚎莉,我覺(jué)得先看一個(gè)小例子比較合適:
我們假設(shè)的二進(jìn)制數(shù)為8位(11110011),如何將其轉(zhuǎn)換為BCD碼呢沛豌?
8位2進(jìn)制數(shù)最大能表示的數(shù)字為255趋箩,用BCD碼表示,需要12位來(lái)表示加派。上面的二進(jìn)制數(shù)是1111_0011叫确,對(duì)應(yīng)的十進(jìn)制為243,我們知道芍锦,它的BCD碼形式為:0010_0100_0011竹勉。
有了這些先驗(yàn)知識(shí),我們來(lái)看看如何轉(zhuǎn)換的娄琉!
首先次乓,先將BCD碼計(jì)數(shù)器清零吓歇,之后將二進(jìn)制數(shù)和BCD碼計(jì)數(shù)器統(tǒng)統(tǒng)左移,二進(jìn)制數(shù)移出來(lái)的最高位放到BCD碼計(jì)數(shù)器的最低位票腰,如下表所示城看!
百 | 十 | 個(gè) | 二進(jìn)制 | 操作 |
---|---|---|---|---|
0000 | 0000 | 0000 | 11110011 | Initialization |
0000 | 0000 | 0001 | 11100110 | Shift |
0000 | 0000 | 0011 | 11001100 | Shift |
0000 | 0000 | 0111 | 10011000 | Shift |
0000 | 0000 | 1010 | 10011000 | Add 3 to ONES, since it was 7 |
0000 | 0001 | 0101 | 00110000 | Shift |
0000 | 0001 | 1000 | 00110000 | Add 3 to ONES, since it was 5 |
0000 | 0011 | 0000 | 01100000 | Shift |
0000 | 0110 | 0000 | 11000000 | Shift |
0000 | 1001 | 0000 | 11000000 | Add 3 to TENS, since it was 6 |
0001 | 0010 | 0001 | 10000000 | Shift |
0010 | 0100 | 0011 | 00000000 | Shift |
2 | 4 | 3 |
每一次移位之后都判斷下,BCD碼計(jì)數(shù)器的十杏慰、分以及個(gè)位是否大于4测柠,如果任何一位(4bit)大于4,則對(duì)其加3缘滥,之后繼續(xù)移位轰胁,如此下去,直到移位次數(shù)為二進(jìn)制數(shù)的位數(shù)之后朝扼,停止移位赃阀,此時(shí)得到的BCD碼計(jì)數(shù)值便是轉(zhuǎn)換后的值。
實(shí)現(xiàn)方式一
維基百科:給出了一種Verilog的實(shí)現(xiàn)方式:
`timescale 1ns / 1ps
module binTobcd
#( parameter W = 18) // input width
( input [W-1 :0] bin , // binary
output reg [W+(W-4)/3:0] bcd ); // bcd {...,thousands,hundreds,tens,ones}
integer i,j;
always @(bin) begin
for(i = 0; i <= W+(W-4)/3; i = i+1) bcd[i] = 0; // initialize with zeros
bcd[W-1:0] = bin; // initialize with input vector
for(i = 0; i <= W-4; i = i+1) // iterate on structure depth
for(j = 0; j <= i/3; j = j+1) // iterate on structure width
if (bcd[W-i+4*j -: 4] > 4) // if > 4
bcd[W-i+4*j -: 4] = bcd[W-i+4*j -: 4] + 4'd3; // add 3
end
endmodule
該實(shí)現(xiàn)方式采用了雙重循環(huán)的組合邏輯實(shí)現(xiàn)吟税,我對(duì)其進(jìn)行了行為仿真凹耙,如下:
看起來(lái)貌似沒(méi)有任何問(wèn)題,但不得不考慮的是肠仪,行為仿真時(shí)不考慮延遲的肖抱,如果在實(shí)際的電路中,這種實(shí)現(xiàn)方式會(huì)不會(huì)影響時(shí)序呢异旧?
答案幾乎是肯定的意述,請(qǐng)看下面的RTL原理圖,這種組合邏輯的延遲鏈很長(zhǎng)吮蛹!如果位寬更大荤崇,則延遲也隨著增大。
實(shí)現(xiàn)方式二
參考資料給出了一種狀態(tài)機(jī)的實(shí)現(xiàn)方式潮针。
其原理也是:
它以輸入的二進(jìn)制數(shù)為起點(diǎn)术荤。它將它一次移位一位到BCD輸出向量中。然后每篷,它將獨(dú)立查看每個(gè)4位BCD數(shù)字瓣戚。如果任何數(shù)字都大于4,則該數(shù)字將增加3焦读。對(duì)于輸入二進(jìn)制向量中的每個(gè)位子库,此循環(huán)都會(huì)繼續(xù)。請(qǐng)參見(jiàn)下圖矗晃,以直觀方式描述有限狀態(tài)機(jī)的編寫(xiě)方式仑嗅。
根據(jù)此狀態(tài)機(jī)以及轉(zhuǎn)換原理,得到的Verilog設(shè)計(jì)為:
module bin2bcd #(
parameter INPUT_WIDTH = 6,
parameter DECIMAL_DIGITS = 2
)(
input i_Clock,
input [INPUT_WIDTH - 1 : 0] i_Binary,
input i_Start,
output [DECIMAL_DIGITS * 4 - 1 : 0] o_BCD,
output o_DV
);
parameter s_IDLE = 3'b000, s_SHIFT = 3'b001, s_CHECK_SHIFT_INDEX = 3'b010, s_ADD = 3'b011,
s_CHECK_DIGIT_INDEX = 3'b100, s_BCD_DONE = 3'b101;
reg [2:0] r_SM_Main = s_IDLE;
// The vector that contains the output BCD
reg [DECIMAL_DIGITS*4 - 1 : 0] r_BCD = 0;
// The vector that contains the input binary value being shifted.
reg [INPUT_WIDTH-1:0] r_Binary = 0;
// Keeps track of which Decimal Digit we are indexing
reg [DECIMAL_DIGITS-1:0] r_Digit_Index = 0;
// Keeps track of which loop iteration we are on.
// Number of loops performed = INPUT_WIDTH
reg [7:0] r_Loop_Count = 0;
wire [3:0] w_BCD_Digit;
reg r_DV = 1'b0;
always @(posedge i_Clock) begin
case (r_SM_Main)
// Stay in this state until i_Start comes along
s_IDLE :
begin
r_DV <= 1'b0;
if (i_Start == 1'b1)
begin
r_Binary <= i_Binary;
r_SM_Main <= s_SHIFT;
r_BCD <= 0;
end
else
r_SM_Main <= s_IDLE;
end
// Always shift the BCD Vector until we have shifted all bits through
// Shift the most significant bit of r_Binary into r_BCD lowest bit.
s_SHIFT :
begin
r_BCD <= r_BCD << 1;
r_BCD[0] <= r_Binary[INPUT_WIDTH-1];
r_Binary <= r_Binary << 1;
r_SM_Main <= s_CHECK_SHIFT_INDEX;
end
// Check if we are done with shifting in r_Binary vector
s_CHECK_SHIFT_INDEX :
begin
if (r_Loop_Count == INPUT_WIDTH-1)
begin
r_Loop_Count <= 0;
r_SM_Main <= s_BCD_DONE;
end
else
begin
r_Loop_Count <= r_Loop_Count + 1;
r_SM_Main <= s_ADD;
end
end
// Break down each BCD Digit individually. Check them one-by-one to
// see if they are greater than 4. If they are, increment by 3.
// Put the result back into r_BCD Vector.
s_ADD :
begin
if (w_BCD_Digit > 4)
begin
r_BCD[(r_Digit_Index*4)+:4] <= w_BCD_Digit + 3;
end
r_SM_Main <= s_CHECK_DIGIT_INDEX;
end
// Check if we are done incrementing all of the BCD Digits
s_CHECK_DIGIT_INDEX :
begin
if (r_Digit_Index == DECIMAL_DIGITS-1)
begin
r_Digit_Index <= 0;
r_SM_Main <= s_SHIFT;
end
else
begin
r_Digit_Index <= r_Digit_Index + 1;
r_SM_Main <= s_ADD;
end
end
s_BCD_DONE :
begin
r_DV <= 1'b1;
r_SM_Main <= s_IDLE;
end
default :
r_SM_Main <= s_IDLE;
endcase
end // always @ (posedge i_Clock)
assign w_BCD_Digit = r_BCD[r_Digit_Index*4 +: 4];
assign o_BCD = r_BCD;
assign o_DV = r_DV;
endmodule // Binary_to_BCD
需要注意兩個(gè)位寬問(wèn)題,一是輸入二進(jìn)制數(shù)的位寬INPUT_WIDTH仓技,還有一個(gè)參數(shù)DECIMAL_DIGITS表示的是BCD碼的個(gè)數(shù)鸵贬,這里的個(gè)數(shù)指的是寫(xiě)成十六進(jìn)制后的個(gè)數(shù),每4位二進(jìn)制數(shù)算一個(gè)浑彰。(表達(dá)真特么別扭)
如果細(xì)心看這段代碼的話(huà)恭理,會(huì)發(fā)現(xiàn)狀態(tài)機(jī)是真的強(qiáng)大,我們常聽(tīng)說(shuō)一切皆可狀態(tài)機(jī)郭变,關(guān)鍵是你的狀態(tài)機(jī)設(shè)計(jì)的是否正確,之后是否巧妙涯保!
代碼中诉濒,從s_SHIFT狀態(tài)到s_CHECK_DIGIT_INDEX 是構(gòu)成循環(huán)的部分。如下:
s_SHIFT :
begin
r_BCD <= r_BCD << 1;
r_BCD[0] <= r_Binary[INPUT_WIDTH-1];
r_Binary <= r_Binary << 1;
r_SM_Main <= s_CHECK_SHIFT_INDEX;
end
// Check if we are done with shifting in r_Binary vector
s_CHECK_SHIFT_INDEX :
begin
if (r_Loop_Count == INPUT_WIDTH-1)
begin
r_Loop_Count <= 0;
r_SM_Main <= s_BCD_DONE;
end
else
begin
r_Loop_Count <= r_Loop_Count + 1;
r_SM_Main <= s_ADD;
end
end
// Break down each BCD Digit individually. Check them one-by-one to
// see if they are greater than 4. If they are, increment by 3.
// Put the result back into r_BCD Vector.
s_ADD :
begin
if (w_BCD_Digit > 4)
begin
r_BCD[(r_Digit_Index*4)+:4] <= w_BCD_Digit + 3;
end
r_SM_Main <= s_CHECK_DIGIT_INDEX;
end
// Check if we are done incrementing all of the BCD Digits
s_CHECK_DIGIT_INDEX :
begin
if (r_Digit_Index == DECIMAL_DIGITS-1)
begin
r_Digit_Index <= 0;
r_SM_Main <= s_SHIFT;
end
else
begin
r_Digit_Index <= r_Digit_Index + 1;
r_SM_Main <= s_ADD;
end
end
從狀態(tài)轉(zhuǎn)移圖中也可以清晰看出這個(gè)循環(huán):
先對(duì)二進(jìn)制以及BCD碼寄存器進(jìn)行移位夕春,
s_SHIFT :
begin
r_BCD <= r_BCD << 1;
r_BCD[0] <= r_Binary[INPUT_WIDTH-1];
r_Binary <= r_Binary << 1;
r_SM_Main <= s_CHECK_SHIFT_INDEX;
end
之后判斷循環(huán)結(jié)束了嗎未荒?
s_CHECK_SHIFT_INDEX :
begin
if (r_Loop_Count == INPUT_WIDTH-1)
begin
r_Loop_Count <= 0;
r_SM_Main <= s_BCD_DONE;
end
else
begin
r_Loop_Count <= r_Loop_Count + 1;
r_SM_Main <= s_ADD;
end
end
循環(huán)條件是循環(huán)次數(shù),我們都知道及志,循環(huán)次數(shù)是二進(jìn)制數(shù)的位數(shù)片排。
如果循環(huán)結(jié)束,則進(jìn)入下一個(gè)狀態(tài)s_BCD_DONE:
s_BCD_DONE :
begin
r_DV <= 1'b1;
r_SM_Main <= s_IDLE;
end
在這個(gè)狀態(tài)內(nèi)速侈,我們可以使用BCD值率寡,r_DV是一個(gè)標(biāo)志,有效則代表BCD值可以使用了倚搬。
如果沒(méi)有結(jié)束循環(huán)冶共,則進(jìn)入狀態(tài)s_ADD,
s_ADD :
begin
if (w_BCD_Digit > 4)
begin
r_BCD[(r_Digit_Index*4)+:4] <= w_BCD_Digit + 3;
end
r_SM_Main <= s_CHECK_DIGIT_INDEX;
end
判斷BCD碼的個(gè)位是否大于4每界,如果大于4捅僵,則加3,之后進(jìn)入狀態(tài):s_CHECK_DIGIT_INDEX
s_CHECK_DIGIT_INDEX :
begin
if (r_Digit_Index == DECIMAL_DIGITS-1)
begin
r_Digit_Index <= 0;
r_SM_Main <= s_SHIFT;
end
else
begin
r_Digit_Index <= r_Digit_Index + 1;
r_SM_Main <= s_ADD;
end
end
如果BCD碼只有個(gè)位眨层,則進(jìn)入s_SHIFT狀態(tài) 庙楚,移位,判斷循環(huán)結(jié)束了沒(méi)趴樱!
否則馒闷,還有十位,甚至百位伊佃,則繼續(xù)進(jìn)入s_ADD窜司,判斷是否大于4,如果大于4航揉,加3塞祈,直到遍歷了BCD的各個(gè)位(這里的位指的是4位二進(jìn)制數(shù)組成的一位BCD碼)。
講到這里帅涂,大概程序就沒(méi)什么問(wèn)題了议薪。
最后還需要注意的是尤蛮,程序里面的輸入輸出,寄存器變量以及wire變量斯议,以及參數(shù)的命名方式都遵循博文所提倡的代碼風(fēng)格产捞。
做一個(gè)簡(jiǎn)單的仿真:
仿真文件:
`timescale 1ns / 1ps
module bin2bcd_tb(
);
parameter INPUT_WIDTH = 6;
parameter DECIMAL_DIGITS = 2;
reg i_Clock;
reg [INPUT_WIDTH - 1 : 0] i_Binary;
reg i_Start;
wire [DECIMAL_DIGITS * 4 - 1 : 0] o_BCD;
wire o_DV;
initial begin
i_Clock = 0;
forever begin
#5 i_Clock = ~i_Clock;
end
end
initial begin
i_Start = 0;
i_Binary = 31;
repeat(5) @(posedge i_Clock);
i_Start = 1;
repeat(6) @(negedge i_Clock);
i_Binary = 18;
end
bin2bcd #(.INPUT_WIDTH(INPUT_WIDTH), .DECIMAL_DIGITS(DECIMAL_DIGITS))
inst_bin2bcd(
.i_Clock(i_Clock),
.i_Binary(i_Binary),
.i_Start(i_Start),
.o_BCD(o_BCD),
.o_DV(o_DV)
);
endmodule
仿真波形:
好了,就到這里吧哼御,相信你已經(jīng)很明白了坯临。
寫(xiě)在最后
最近在忙畢業(yè)的事情,還有入職前租房子的各種事情恋昼,所以看靠,博客落下了幾天,斷斷續(xù)續(xù)地準(zhǔn)備這個(gè)話(huà)題液肌,終于可以發(fā)了一篇挟炬。
對(duì)了,這里面的代碼也用到了一個(gè)2001之后新增語(yǔ)法:
assign w_BCD_Digit = r_BCD[r_Digit_Index*4 +: 4];
可以在下面這篇博客里找到解釋?zhuān)∮腥苏f(shuō)這個(gè)語(yǔ)法嗦哆,花里胡哨谤祖,但是到今天,我發(fā)現(xiàn)這種用法很常見(jiàn)老速,因此粥喜,學(xué)著去用吧。
最后的最后烁峭,推薦下我的微信公眾號(hào):FPGA LAB容客,可以關(guān)注我,沒(méi)事约郁,我都會(huì)每天推送文章缩挑,閱讀更方便。