FPGA加三移位算法:硬件邏輯實現二進制轉BCD碼


  本文設計方式采用明德揚至簡設計法。利用FPGA來完成顯示功能不是個很理想的方式,當顯示任務比較復雜,要通過各種算法顯示波形或者特定圖形時,當然要用單片機通過C語言完成這類流程控制復雜,又對時序要求不高的任務(這也堅定了我學習SOPC的決心)。但要驅動如LCD1602/LCD12864打印字符,顯示系統工作狀態還是比較方便的。

  數字系統內部均為二進制比特信息,而打印字符需要先將其轉換成BCD碼,並進一步轉為ASCII字符才能正常顯示。這一簡單算法的軟件實現非常簡單,但要是用硬件邏輯完成其中多個乘除法運算無疑浪費很多硬件資源,這時最常用的做法就是通過移位操作代替乘除法運算。適用於FPGA實現的二進制序列轉BCD碼算法是“加三移位”。小梅哥FPGA進階系列教程中的《二進制轉BCD》文章中對其進行了詳細說明【小梅哥FPGA進階教程】第二章 二進制轉BCD - FPGA/CPLD - 電子工程世界-論壇http://bbs.eeworld.com.cn/thread-510929-1-1.html

  本文僅重點闡述設計方式。加三移位算法以8位二進制轉BCD碼為例,BCD碼需要3位,一共12bit(8是2的3次方)。每次將剩余的待轉換二進制序列最高位左移進BCD碼寄存器,每移一位后判斷每一位BCD碼是否大於4,若是則加3調整,否則不變。直至移位8次后結束。注:最后一次移位不需要加3調整。可以發現上述過程可以利用一個非常簡單的狀態機實現:

  BCD碼以4bit為1位,非常適合存儲器模型,這里使用:reg [4-1:0]  bcd_code [3-1:0];//該存儲器由3個位寬為4bit的寄存器組成。每到SHIFT狀態下,進行一次左移操作,隨后進入ADD_3狀態判斷是否需要加3操作。當移位8次后進入ASCII狀態利用查表法找出ASCII中對應數字,最后等待LCD控制模塊完成顯示任務后回到IDLE狀態繼續響應后續數據。以下是完整代碼。

  1 `timescale 1ns / 1ps
  2 
  3 /*
  4 顯示編碼模塊:
  5     1 完成二進制數值與BCD碼的轉換
  6     2 完成BCD碼的字符編碼
  7     3 一次性送出拼接后編碼數據
  8 */
  9 
 10 module disp_code#(parameter DATA_W = 8)(
 11     input                       clk,
 12     input                       rst_n,
 13     //MAX30102_ctrl側接口
 14     input       [DATA_W-1:0]    din,
 15     input                       din_vld,
 16     output reg                  code_rdy,
 17     //LCD_CTRL側接口
 18     output reg [DATA_W-1:0]     dout,
 19     output reg                  dout_vld,
 20     input                       lcd_ctrl_rdy
 21 );
 22 
 23 /*
 24 編碼轉換流程:
 25 1 檢測BCD碼寄存器每四位數值是否大於4,若是則加3,否則不處理;
 26 2 左移一位,將待轉換二進制數最高位送入寄存器;
 27 3 第n次移位后進行字符編碼;
 28 */
 29 
 30 localparam LOG_DATA_W = 3,
 31            STA_W      = 5;
 32 
 33 localparam  IDLE        = 0 ;
 34 localparam  ADD_3       = 1 ;
 35 localparam  SHIFT       = 2 ;
 36 localparam  ASCII       = 3 ;
 37 localparam  WAIT_LCD    = 4 ;
 38 
 39 
 40 reg [ (8-1):0]      shift_cnt     ;
 41 wire                add_shift_cnt ;
 42 wire                end_shift_cnt ;
 43 reg [ (4-1):0]      char_cnt     ;
 44 wire                add_char_cnt ;
 45 wire                end_char_cnt ;
 46 reg [ (DATA_W-1):0] data_tmp     ;
 47 reg [ (DATA_W-1):0] tfrac_tmp     ;
 48 reg [4-1:0]         bcd_code [LOG_DATA_W-1:0];
 49 reg [ (4-1):0]      disp_data     ;
 50 wire                idle2shift     ;
 51 wire                add_32shift    ;
 52 wire                shift2add_3    ;
 53 wire                shift2ascii    ;
 54 wire                ascii2wait_lcd ;
 55 wire                wait_lcd2idle  ;
 56 reg                 lcd_rdy_r     ;
 57 reg                 busy_flag     ;
 58 reg [STA_W-1:0]     state_c;
 59 reg [STA_W-1:0]     state_n;
 60 wire                lcd_rdy_pos;
 61 wire                data_in_vld;
 62 
 63 //移位次數計數器
 64 always @(posedge clk or negedge rst_n) begin 
 65     if (rst_n==0) begin
 66         shift_cnt <= 0; 
 67     end
 68     else if(add_shift_cnt) begin
 69         if(end_shift_cnt)
 70             shift_cnt <= 0; 
 71         else
 72             shift_cnt <= shift_cnt+1 ;
 73    end
 74 end
 75 assign add_shift_cnt = (state_c == SHIFT);
 76 assign end_shift_cnt = add_shift_cnt  && shift_cnt == (DATA_W)-1 ;
 77 
 78 //字符個數計數器
 79 always @(posedge clk or negedge rst_n) begin 
 80     if (rst_n==0) begin
 81         char_cnt <= 0; 
 82     end
 83     else if(add_char_cnt) begin
 84         if(end_char_cnt)
 85             char_cnt <= 0; 
 86         else
 87             char_cnt <= char_cnt+1 ;
 88    end
 89 end
 90 assign add_char_cnt = (state_c == ASCII);
 91 assign end_char_cnt = add_char_cnt  && char_cnt == (LOG_DATA_W)-1 ;
 92 
 93 //數據寄存
 94 always @(posedge clk or negedge rst_n )begin 
 95     if(rst_n==0) begin
 96         data_tmp <= (0)  ;
 97     end
 98     else if(data_in_vld)begin
 99         data_tmp <= (din)  ;
100     end 
101 end
102 
103 
104 /*********************************************狀態機****************************************************/
105 always @(posedge clk or negedge rst_n) begin 
106     if (rst_n==0) begin
107         state_c <= IDLE ;
108     end
109     else begin
110         state_c <= state_n;
111    end
112 end
113 
114 always @(*) begin 
115     case(state_c)  
116         IDLE :begin
117             if(idle2shift ) 
118                 state_n = SHIFT ;
119             else 
120                 state_n = state_c ;
121         end
122         SHIFT :begin
123             if(shift2add_3 ) 
124                 state_n = ADD_3 ;
125             else if(shift2ascii ) 
126                 state_n = ASCII ;
127             else 
128                 state_n = state_c ;
129         end
130         ADD_3 :begin
131             if(add_32shift ) 
132                 state_n = SHIFT ;
133             else 
134                 state_n = state_c ;
135         end
136         ASCII :begin
137             if(ascii2wait_lcd ) 
138                 state_n = WAIT_LCD ;
139             else 
140                 state_n = state_c ;
141         end
142         WAIT_LCD:begin
143             if(wait_lcd2idle)
144                 state_n = IDLE;
145             else
146                 state_n = state_c;
147         end
148         default : state_n = IDLE ;
149     endcase
150 end
151 
152 assign idle2shift       = state_c == IDLE       && (din_vld);
153 assign shift2add_3      = state_c == SHIFT      && (!end_shift_cnt);
154 assign shift2ascii      = state_c == SHIFT      && (end_shift_cnt);
155 assign add_32shift      = state_c == ADD_3      && (1'b1);
156 assign ascii2wait_lcd   = state_c == ASCII      && (end_char_cnt);
157 assign wait_lcd2idle    = state_c == WAIT_LCD   && lcd_rdy_pos;
158 
159 /*********************************************編碼過程****************************************************/
160 //binary code ---> 8421bcd code
161 always @(posedge clk or negedge rst_n )begin 
162     if(rst_n==0) begin
163         bcd_code[0] <= (0)  ;
164     end
165     else if(state_c == ADD_3 && bcd_code[0] > 4'd4)begin
166         bcd_code[0] <= (bcd_code[0] + 4'd3)  ;
167     end 
168     else if(state_c == SHIFT)
169         bcd_code[0] <= {bcd_code[0][2:0],data_tmp[DATA_W-1-shift_cnt]};
170 end
171 
172 always @(posedge clk or negedge rst_n )begin 
173     if(rst_n==0) begin
174         bcd_code[1] <= (0)  ;
175     end
176     else if(state_c == ADD_3 && bcd_code[1] > 4'd4)begin
177         bcd_code[1] <= (bcd_code[1] + 4'd3)  ;
178     end 
179     else if(state_c == SHIFT)
180         bcd_code[1] <= {bcd_code[1][2:0],bcd_code[0][3]};
181 end
182 
183 always @(posedge clk or negedge rst_n )begin 
184     if(rst_n==0) begin
185         bcd_code[2] <= (0)  ;
186     end
187     else if(state_c == ADD_3 && bcd_code[2] > 4'd4)begin
188         bcd_code[2] <= (bcd_code[2] + 4'd3)  ;
189     end 
190     else if(state_c == SHIFT)
191         bcd_code[2] <= {bcd_code[2][2:0],bcd_code[1][3]};
192 end
193 
194 always @(posedge clk or negedge rst_n )begin 
195     if(rst_n==0) begin
196         disp_data <= (0)  ;
197     end
198     else if(add_char_cnt)begin
199         disp_data <= (bcd_code[LOG_DATA_W-1  - char_cnt])  ;
200     end 
201 end
202 
203 /*********************************************接口信號****************************************************/
204 
205 always @(posedge clk or negedge rst_n )begin 
206     if(rst_n==0) begin
207         lcd_rdy_r <= (0)  ;
208     end
209     else if(state_c == WAIT_LCD)begin
210         lcd_rdy_r <= (lcd_ctrl_rdy)  ;
211     end 
212 end
213 
214 assign lcd_rdy_pos = lcd_ctrl_rdy == 1 && lcd_rdy_r == 0;
215 
216 always @(posedge clk or negedge rst_n )begin 
217     if(rst_n==0) begin
218         busy_flag <= (0)  ;
219     end
220     else if(data_in_vld)begin
221         busy_flag <= (1'b1)  ;
222     end 
223     else if(wait_lcd2idle)begin
224         busy_flag <= (0)  ;
225     end 
226 end
227 
228 assign data_in_vld = state_c == IDLE && din_vld;
229 
230 always@(*)begin
231     if(!lcd_ctrl_rdy || busy_flag || data_in_vld)
232         code_rdy = 0;
233     else
234         code_rdy = 1;
235 end
236 
237 
238 /*********************************************編碼后數據輸出****************************************************/
239 // ASCII CODE
240 always@(*)begin
241     case(disp_data)
242         0:dout = "0";
243         1:dout = "1";
244         2:dout = "2";
245         3:dout = "3";
246         4:dout = "4";
247         5:dout = "5";
248         6:dout = "6";
249         7:dout = "7";
250         8:dout = "8";
251         9:dout = "9";
252         default:dout = "0";
253     endcase
254 end
255 
256 always @(posedge clk or negedge rst_n )begin 
257     if(rst_n==0) begin
258         dout_vld <= (0)  ;
259     end
260     else if(add_char_cnt)begin
261         dout_vld <= (1'b1)  ;
262     end 
263     else
264         dout_vld <= 0;
265 end
266 
267 endmodule 

   接下來用testbench仿真驗證邏輯功能,在測試向量中要模擬LCD控制模塊和數據源上游模塊的行為,並通過顯示編碼方式驗證待測試模塊狀態機當前狀態。

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Company: 
  4 // Engineer: 
  5 // 
  6 // Create Date: 2018/03/15 18:32:05
  7 // Design Name: 
  8 // Module Name: disp_code_tb
  9 // Project Name: 
 10 // Target Devices: 
 11 // Tool Versions: 
 12 // Description: 
 13 // 
 14 // Dependencies: 
 15 // 
 16 // Revision:
 17 // Revision 0.01 - File Created
 18 // Additional Comments:
 19 // 
 20 //////////////////////////////////////////////////////////////////////////////////
 21 
 22 
 23 module disp_code_tb;
 24 
 25 reg clk;
 26 reg rst_n;
 27 reg [8-1:0] din;
 28 reg din_vld;
 29 wire code_rdy;
 30 wire [8-1:0] dout;
 31 wire dout_vld;
 32 reg lcd_ctrl_rdy;
 33 
 34 reg [ (4-1):0]  wait_cnt ;
 35 wire        add_wait_cnt ;
 36 wire        end_wait_cnt ;
 37 reg [8*8-1:0] code_state;
 38 reg   lcd_ctrl_busy     ;
 39 
 40 //待測試模塊例化
 41  disp_code#(.DATA_W(8))
 42  uut(
 43      .clk         (clk),
 44      .rst_n       (rst_n),
 45     
 46      .din         (din),
 47      .din_vld     (din_vld),
 48      .code_rdy    (code_rdy),
 49     
 50      .dout        (dout),
 51      .dout_vld    (dout_vld),
 52      .lcd_ctrl_rdy(lcd_ctrl_rdy)
 53 );
 54 parameter CYC = 20,
 55           RST_TIM = 2;
 56 
 57 initial begin
 58     clk = 1;
 59     forever #(CYC/2) clk = ~clk;
 60 end
 61 
 62 initial begin
 63     rst_n = 1;
 64     #1;
 65     rst_n = 0;
 66     #(CYC*RST_TIM) 
 67     rst_n = 1;
 68     #100_000;
 69     $stop;
 70 end
 71 
 72 //模擬LCD控制模塊行為
 73 always @(posedge clk or negedge rst_n )begin 
 74     if(rst_n==0) begin
 75         lcd_ctrl_busy <= (0)  ;
 76     end
 77     else if(dout_vld)begin
 78         lcd_ctrl_busy <= (1'b1)  ;
 79     end 
 80     else if(end_wait_cnt)begin
 81         lcd_ctrl_busy <= (0)  ;
 82     end 
 83 end
 84 
 85 always@(*)begin
 86     if(lcd_ctrl_busy || dout_vld)
 87         lcd_ctrl_rdy  = 0;
 88     else
 89         lcd_ctrl_rdy = 1'b1;
 90 end
 91 
 92 always @(posedge clk or negedge rst_n) begin 
 93     if (rst_n==0) begin
 94         wait_cnt <= 0; 
 95     end
 96     else if(add_wait_cnt) begin
 97         if(end_wait_cnt)
 98             wait_cnt <= 0; 
 99         else
100             wait_cnt <= wait_cnt+1 ;
101    end
102 end
103 assign add_wait_cnt = (lcd_ctrl_rdy == 0);
104 assign end_wait_cnt = add_wait_cnt  && wait_cnt == (10)-1 ;
105 
106 //模擬數據源行為
107 always@(posedge clk or negedge rst_n)begin
108     if(!rst_n)
109         din <= 0;
110     else if(code_rdy)
111         din <= 8'h20;
112 end
113 
114 always @(posedge clk or negedge rst_n )begin 
115     if(rst_n==0) begin
116         din_vld <= (0)  ;
117     end
118     else if(code_rdy)begin
119         din_vld <= (1'b1)  ;
120     end 
121     else begin
122         din_vld <= (0)  ;
123     end 
124 end
125 
126 //狀態顯示編碼
127 always@(*)begin
128     case(uut.state_c)
129         5'd0:code_state = "IDLE";
130         5'd1:code_state = "ADD_3";
131         5'd2:code_state = "SHIFT";
132         5'd3:code_state = "ASCII";
133         5'd4:code_state = "WAIT_LCD";
134         default:code_state = "ERROR";
135     endcase
136 end
137 
138 endmodule

   分別看看顯示編碼模塊仿真波形的整體和局部放大圖:

  可以看出在LCD控制模塊准備好情況下(lcd_ctrl_rdy拉高),顯示編碼模塊也處於准備就緒狀態,上游模塊送入待轉碼數據8'h20,對應的十進制數是32,顯示編碼模塊輸出結果與數值相符合。


免責聲明!

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



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