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