本文设计方式采用明德扬至简设计法。利用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,显示编码模块输出结果与数值相符合。

posted on 2018-03-15 19:24  没落骑士  阅读(3998)  评论(1编辑  收藏  举报