本文為明德揚原創及錄用文章,轉載請注明出處!
1.1 總體設計
1.1.1 概述
BCD碼(Binary-Coded Decimal?),用4位二進制數來表示1位十進制數中的0~9這10個數碼,是一種二進制的數字編碼形式,用二進制編碼的十進制代碼。BCD碼這種編碼形式利用了四個位元來儲存一個十進制的數碼,使二進制和十進制之間的轉換得以快捷的進行。
1.1.2 設計目標
實現BCD譯碼并顯示十進制結果的程序,具體功能要求如下:
1. 串口發送8位十六進制數給FPGA;
2. FPGA接收串口數據并對其進行BCD譯碼;
3. 在數碼管上以十進制顯示串口發送的數值。
1.1.3 系統結構框圖
系統結構框圖如下圖一所示:

圖一
1.1.4模塊功能
- 串口接收模塊實現功能
1、 接收上位機PC發來的位寬為8的十六進制數據。
- BCD譯碼模塊實現功能
1、 對接收到的8位十六進制數據進行BCD譯碼。
- 數碼管顯示模塊實現功能
1、 顯示BCD譯碼后的十進制數值。
1.1.5頂層信號

1.1.6參考代碼
下面是使用工程的頂層代碼:
module top( clk , rst_n , rx_uart , seg_sel , segment ); parameter DATA_W = 8; parameter SEG_WID = 8; parameter SEL_WID = 3; parameter BCD_OUT = 12; input clk ; input rst_n ; input rx_uart ; output[SEL_WID-1:0] seg_sel ; output[SEG_WID-1:0] segment ; wire [SEL_WID-1:0] seg_sel ; wire [SEG_WID-1:0] segment ; wire [DATA_W-1 :0] rx_dout ; wire rx_dout_vld ; wire [BCD_OUT-1:0] bcd_dout ; wire bcd_dout_vld ; uart_rx u1( .clk ( clk ), .rst_n ( rst_n ), .din ( rx_uart ), .dout ( rx_dout ), .dout_vld ( rx_dout_vld ) ); bcd_water u2( .clk ( clk ), .rst_n ( rst_n ), .din ( rx_dout ), .din_vld ( rx_dout_vld ), .dout ( bcd_dout ), .dout_vld ( bcd_dout_vld ) ); seg_disp#(.SEG_NUM(SEL_WID)) u3( .clk ( clk ), .rst_n ( rst_n ), .din ( bcd_dout ), .din_vld ( bcd_dout_vld ), .seg_sel ( seg_sel ), .segment ( segment ) ); endmodule
1.2 串口接收模塊設計
1.2.1接口信號

1.2.2 設計思路
在前面的案例中已經有串口接收模塊的介紹,所以這里不在過多介紹,詳細介紹請看下方鏈接:
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=1074&fromuid=100105
1.2.3參考代碼
module uart_rx( clk , rst_n , din , dout , dout_vld ); parameter DATA_W = 8 ; parameter NUM_W = 4 ; parameter CNT_W = 14 ; parameter BPS = 5208 ; parameter BPS_P = BPS/2; input clk ; input rst_n ; input din ; output[DATA_W-1:0] dout ; output dout_vld ; reg [DATA_W-1:0] dout ; reg dout_vld ; reg [NUM_W-1 :0] data_num ; reg [DATA_W-1:0] rx_temp_data ; reg din_ff0 ; reg din_ff1 ; reg din_ff2 ; reg flag_add ; wire end_cnt ; wire end_cnt_p ; wire add_data_num ; wire end_data_num ; reg [CNT_W-1:0] cnt ; wire add_cnt ; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin din_ff0 <= 1'b1; din_ff1 <= 1'b1; din_ff2 <= 1'b1; end else begin din_ff0 <= din; din_ff1 <= din_ff0; din_ff2 <= din_ff1; end end always @ (posedge clk or negedge rst_n)begin if(!rst_n) begin flag_add <= 1'b0; end else if(din_ff2 & ~din_ff1) begin flag_add <= 1'b1; end else if(data_num==4'd8&&end_cnt) begin flag_add <= 1'b0; end end always @ (posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 0; end else if(add_cnt)begin if(end_cnt)begin cnt <= 0; end else begin cnt <= cnt+1'b1; end end else begin cnt <= 0; end end assign add_cnt = flag_add; assign end_cnt = add_cnt && cnt == BPS-1; always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin data_num <= 0; end else if(add_data_num) begin if(end_data_num) data_num <= 0; else data_num <= data_num+1 ; end end assign add_data_num = end_cnt; assign end_data_num = add_data_num && data_num == 9-1 ; always @ (posedge clk or negedge rst_n)begin if(!rst_n) begin dout <= 8'd0; end else if(add_cnt && cnt==BPS_P-1 && data_num!=0) begin dout<={din,{dout[7:1]}}; end else begin dout<=dout; end end always @ (posedge clk or negedge rst_n)begin if(!rst_n) begin dout_vld <= 1'b0; end else if(add_data_num && data_num == 4'd8) begin dout_vld <= 1'b1; end else begin dout_vld <= 1'b0; end end endmodule
1.3 BCD譯碼模塊設計
1.3.1接口信號

1.3.2設計思路
- 左移加3算法
此處二進制轉 BCD 碼的硬件實現,采用左移加 3 的算法,具體描述如下:(此處以 8-bit 二進制碼為例)
1、左移要轉換的二進制碼 1 位
2、左移之后,BCD 碼分別置于百位、十位、個位
3、如果移位后所在的 BCD 碼列大于或等于 5,則對該值加 3
4、繼續左移的過程直至全部移位完成
舉例:將十六進制碼 0xFF 轉換成 BCD 碼

1.3.3參考代碼
module bcd_water( clk , rst_n , din , din_vld , dout , dout_vld ); input clk ; input rst_n ; input [ 7:0] din ; input din_vld ; output [11:0] dout ; wire [11:0] dout ; output dout_vld ; wire dout_vld ; reg [19:0] din_temp ; reg [19:0] din_temp_ff0 ; reg [19:0] din_temp_ff1 ; reg [19:0] din_temp_ff2 ; reg [19:0] din_temp_ff3 ; reg [19:0] din_temp_ff4 ; wire [20:0] din_shift_temp ; wire [20:0] din_shift_temp_ff0 ; wire [20:0] din_shift_temp_ff1 ; wire [20:0] din_shift_temp_ff2 ; wire [20:0] din_shift_temp_ff3 ; wire [ 7:0] din_a_temp ; wire [ 3:0] din_b_temp ; wire [ 3:0] din_c_temp ; wire [ 3:0] din_d_temp ; wire [ 7:0] din_add_a_temp ; wire [ 3:0] din_add_b_temp ; wire [ 3:0] din_add_c_temp ; wire [ 3:0] din_add_d_temp ; wire [ 7:0] din_a_temp_ff0 ; wire [ 3:0] din_b_temp_ff0 ; wire [ 3:0] din_c_temp_ff0 ; wire [ 3:0] din_d_temp_ff0 ; wire [ 7:0] din_a_temp_ff1 ; wire [ 3:0] din_b_temp_ff1 ; wire [ 3:0] din_c_temp_ff1 ; wire [ 3:0] din_d_temp_ff1 ; wire [ 7:0] din_a_temp_ff2 ; wire [ 3:0] din_b_temp_ff2 ; wire [ 3:0] din_c_temp_ff2 ; wire [ 3:0] din_d_temp_ff2 ; wire [ 7:0] din_a_temp_ff3 ; wire [ 3:0] din_b_temp_ff3 ; wire [ 3:0] din_c_temp_ff3 ; wire [ 3:0] din_d_temp_ff3 ; wire [ 7:0] din_add_a_temp_ff0 ; wire [ 3:0] din_add_b_temp_ff0 ; wire [ 3:0] din_add_c_temp_ff0 ; wire [ 3:0] din_add_d_temp_ff0 ; wire [ 7:0] din_add_a_temp_ff1 ; wire [ 3:0] din_add_b_temp_ff1 ; wire [ 3:0] din_add_c_temp_ff1 ; wire [ 3:0] din_add_d_temp_ff1 ; wire [ 7:0] din_add_a_temp_ff2 ; wire [ 3:0] din_add_b_temp_ff2 ; wire [ 3:0] din_add_c_temp_ff2 ; wire [ 3:0] din_add_d_temp_ff2 ; wire [ 7:0] din_add_a_temp_ff3 ; wire [ 3:0] din_add_b_temp_ff3 ; wire [ 3:0] din_add_c_temp_ff3 ; wire [ 3:0] din_add_d_temp_ff3 ; reg dout_vld_temp ; reg dout_vld_temp_ff0 ; reg dout_vld_temp_ff1 ; reg dout_vld_temp_ff2 ; reg dout_vld_temp_ff3 ; reg dout_vld_temp_ff4 ; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_temp <= 20'b0; end else if(din_vld)begin din_temp <= {9'b0,din,3'b0}; end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld_temp <= 1'b0; end else if(din_vld)begin dout_vld_temp <= 1'b1; end else begin dout_vld_temp <= 1'b0; end end assign din_a_temp = din_temp[ 7: 0] ; assign din_b_temp = din_temp[11: 8] ; assign din_c_temp = din_temp[15:12] ; assign din_d_temp = din_temp[19:16] ; assign din_add_a_temp = din_a_temp ; assign din_add_b_temp = din_b_temp + ((din_b_temp>=5)?4'd3:4'd0) ; assign din_add_c_temp = din_c_temp + ((din_c_temp>=5)?4'd3:4'd0) ; assign din_add_d_temp = din_d_temp + ((din_d_temp>=5)?4'd3:4'd0) ; assign din_shift_temp = {din_add_d_temp,din_add_c_temp,din_add_b_temp,din_add_a_temp,1'b0} ; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_temp_ff0 <= 20'b0; end else begin din_temp_ff0 <= din_shift_temp[19:0]; end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld_temp_ff0 <= 1'b0 ; end else begin dout_vld_temp_ff0 <= dout_vld_temp; end end assign din_a_temp_ff0 = din_temp_ff0[ 7: 0] ; assign din_b_temp_ff0 = din_temp_ff0[11: 8] ; assign din_c_temp_ff0 = din_temp_ff0[15:12] ; assign din_d_temp_ff0 = din_temp_ff0[19:16] ; assign din_add_a_temp_ff0 = din_a_temp_ff0 ; assign din_add_b_temp_ff0 = din_b_temp_ff0 + ((din_b_temp_ff0>=5)?4'd3:4'd0) ; assign din_add_c_temp_ff0 = din_c_temp_ff0 + ((din_c_temp_ff0>=5)?4'd3:4'd0) ; assign din_add_d_temp_ff0 = din_d_temp_ff0 + ((din_d_temp_ff0>=5)?4'd3:4'd0) ; assign din_shift_temp_ff0 = {din_add_d_temp_ff0,din_add_c_temp_ff0,din_add_b_temp_ff0,din_add_a_temp_ff0,1'b0}; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_temp_ff1 <= 20'b0; end else begin din_temp_ff1 <= din_shift_temp_ff0[19:0]; end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld_temp_ff1 <= 1'b0; end else begin dout_vld_temp_ff1 <= dout_vld_temp_ff0; end end assign din_a_temp_ff1 = din_temp_ff1[ 7: 0] ; assign din_b_temp_ff1 = din_temp_ff1[11: 8] ; assign din_c_temp_ff1 = din_temp_ff1[15:12] ; assign din_d_temp_ff1 = din_temp_ff1[19:16] ; assign din_add_a_temp_ff1 = din_a_temp_ff1 ; assign din_add_b_temp_ff1 = din_b_temp_ff1 + ((din_b_temp_ff1>=5)?4'd3:4'd0) ; assign din_add_c_temp_ff1 = din_c_temp_ff1 + ((din_c_temp_ff1>=5)?4'd3:4'd0) ; assign din_add_d_temp_ff1 = din_d_temp_ff1 + ((din_d_temp_ff1>=5)?4'd3:4'd0) ; assign din_shift_temp_ff1 = {din_add_d_temp_ff1,din_add_c_temp_ff1,din_add_b_temp_ff1,din_add_a_temp_ff1,1'b0}; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_temp_ff2 <= 20'b0; end else begin din_temp_ff2 <= din_shift_temp_ff1[19:0]; end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld_temp_ff2 <= 1'b0; end else begin dout_vld_temp_ff2 <= dout_vld_temp_ff1; end end assign din_a_temp_ff2 = din_temp_ff2[ 7: 0] ; assign din_b_temp_ff2 = din_temp_ff2[11: 8] ; assign din_c_temp_ff2 = din_temp_ff2[15:12] ; assign din_d_temp_ff2 = din_temp_ff2[19:16] ; assign din_add_a_temp_ff2 = din_a_temp_ff2 ; assign din_add_b_temp_ff2 = din_b_temp_ff2 + ((din_b_temp_ff2>=5)?4'd3:4'd0) ; assign din_add_c_temp_ff2 = din_c_temp_ff2 + ((din_c_temp_ff2>=5)?4'd3:4'd0) ; assign din_add_d_temp_ff2 = din_d_temp_ff2 + ((din_d_temp_ff2>=5)?4'd3:4'd0) ; assign din_shift_temp_ff2 = {din_add_d_temp_ff2,din_add_c_temp_ff2,din_add_b_temp_ff2,din_add_a_temp_ff2,1'b0}; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_temp_ff3 <= 20'b0; end else begin din_temp_ff3 <= din_shift_temp_ff2[19:0]; end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld_temp_ff3 <= 1'b0; end else begin dout_vld_temp_ff3 <= dout_vld_temp_ff2; end end assign din_a_temp_ff3 = din_temp_ff3[ 7: 0] ; assign din_b_temp_ff3 = din_temp_ff3[11: 8] ; assign din_c_temp_ff3 = din_temp_ff3[15:12] ; assign din_d_temp_ff3 = din_temp_ff3[19:16] ; assign din_add_a_temp_ff3 = din_a_temp_ff3 ; assign din_add_b_temp_ff3 = din_b_temp_ff3 + ((din_b_temp_ff3>=5)?4'd3:4'd0) ; assign din_add_c_temp_ff3 = din_c_temp_ff3 + ((din_c_temp_ff3>=5)?4'd3:4'd0) ; assign din_add_d_temp_ff3 = din_d_temp_ff3 + ((din_d_temp_ff3>=5)?4'd3:4'd0) ; assign din_shift_temp_ff3 = {din_add_d_temp_ff3,din_add_c_temp_ff3,din_add_b_temp_ff3,din_add_a_temp_ff3,1'b0}; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_temp_ff4 <= 20'b0; end else begin din_temp_ff4 <= din_shift_temp_ff3[19:0]; end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld_temp_ff4 <= 1'b0; end else begin dout_vld_temp_ff4 <= dout_vld_temp_ff3; end end assign dout = din_temp_ff4[19:8]; assign dout_vld = dout_vld_temp_ff4 ; endmodule
1.4 數碼管顯示模塊設計
1.4.1接口信號

1.4.2設計思路
在前面的案例中已經有數碼管顯示的介紹,所以這里不在過多介紹,詳細介紹請看下方鏈接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=399
1.4.3參考代碼
module seg_disp( rst_n , clk , din , din_vld , seg_sel , segment ); parameter DATA_IN = 12 ; parameter TIME_30MS = 15000 ; parameter SEG_WID = 8 ; parameter SEG_NUM = 8 ; parameter CNT_WID = 10 ; parameter NUM_0 = 8'b1100_0000; parameter NUM_1 = 8'b1111_1001; parameter NUM_2 = 8'b1010_0100; parameter NUM_3 = 8'b1011_0000; parameter NUM_4 = 8'b1001_1001; parameter NUM_5 = 8'b1001_0010; parameter NUM_6 = 8'b1000_0010; parameter NUM_7 = 8'b1111_1000; parameter NUM_8 = 8'b1000_0000; parameter NUM_9 = 8'b1001_0000; parameter NUM_ERR = 8'b1111_1111; input clk ; input rst_n ; input [DATA_IN - 1:0] din ; input din_vld ; output [SEG_NUM - 1:0] seg_sel ; output [SEG_WID - 1:0] segment ; reg [SEG_NUM - 1:0] seg_sel ; reg [SEG_WID - 1:0] segment ; reg [ 31 : 0] cnt_30us ; reg [SEG_NUM - 1:0] sel_cnt ; reg [ 4 - 1 : 0] seg_tmp ; wire add_cnt_30us ; wire end_cnt_30us ; wire add_sel_cnt ; wire end_sel_cnt ; always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt_30us <= 0; end else if(add_cnt_30us) begin if(end_cnt_30us) cnt_30us <= 0; else cnt_30us <= cnt_30us+1 ; end end assign add_cnt_30us = 1; assign end_cnt_30us = add_cnt_30us && cnt_30us == TIME_30MS-1 ; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin sel_cnt <= 0; end else if(add_sel_cnt)begin if(end_sel_cnt) sel_cnt <= 0; else sel_cnt <= sel_cnt + 1; end end assign add_sel_cnt = end_cnt_30us; assign end_sel_cnt = add_sel_cnt && sel_cnt == SEG_NUM-1; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel <= {SEG_NUM{1'b1}}; end else begin seg_sel <= ~(1'b1 << sel_cnt); end end always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_tmp <= 0; end else begin seg_tmp <= din[4*(sel_cnt+1)-1 -:4]; end end always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin segment<=NUM_0; end else begin case (seg_tmp) 0 : segment <= NUM_0; 1 : segment <= NUM_1; 2 : segment <= NUM_2; 3 : segment <= NUM_3; 4 : segment <= NUM_4; 5 : segment <= NUM_5; 6 : segment <= NUM_6; 7 : segment <= NUM_7; 8 : segment <= NUM_8; 9 : segment <= NUM_9; default : segment <= NUM_ERR; endcase end end endmodule
1.5 效果和總結
? 下圖是該工程在db603開發板上的現象——串口發送數據8’h93,數碼管顯示12’d147。


? 下圖是該工程在mp801試驗箱上的現象——串口發送數據8’he9,數碼管顯示12’d233。


? 下圖是該工程在ms980試驗箱上的現象——串口發送數據8’h52,數碼管顯示12’d082。


由于該項目的上板現象是串口發送8位的十六進制數經過BCD譯碼后,在數碼管上顯示對應的十進制數值,想觀看完整現象的朋友可以看一下上板演示的視頻。
本案例設計教學視頻和工程源碼,請到明德揚論壇學習。
感興趣的朋友也可以訪問明德揚論壇進行FPGA相關工程設計學習,也可以看一下我們往期的文章:
《基于FPGA的密碼鎖設計》
《波形相位頻率可調DDS信號發生器》
《基于FPGA的曼徹斯特編碼解碼設計》
《基于FPGA的出租車計費系統》
《數電基礎與Verilog設計》
《基于FPGA的頻率、電壓測量》
《基于FPGA的漢明碼編碼解碼設計》
《關于鎖存器問題的討論》
《阻塞賦值與非阻塞賦值》
《參數例化時自動計算位寬的解決辦法》
明德揚是一家專注于FPGA領域的專業性公司,公司主要業務包括開發板、教育培訓、項目承接、人才服務等多個方向。點撥開發板——學習FPGA的入門之選。
MP801開發板——千兆網、ADDA、大容量SDRAM等,學習和項目需求一步到位。網絡培訓班——不管時間和空間,明德揚隨時在你身邊,助你快速學習FPGA。周末培訓班——明天的你會感激現在的努力進取,升職加薪明德揚來助你。就業培訓班——七大企業級項目實訓,獲得豐富的項目經驗,高薪就業。專題課程——高手修煉課:提升設計能力;實用調試技巧課:提升定位和解決問題能力;FIFO架構設計課:助你快速成為架構設計師;時序約束、數字信號處理、PCIE、綜合項目實踐課等你來選。項目承接——承接企業FPGA研發項目。人才服務——提供人才推薦、人才代培、人才派遣等服務。