本文设计方式采用明德扬至简设计法。利用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状态继续响应后续数据。以下是完整代码。
`timescale 1ns / 1ps /*
显示编码模块:
1 完成二进制数值与BCD码的转换
2 完成BCD码的字符编码
3 一次性送出拼接后编码数据
*/ module disp_code#(parameter DATA_W = )(
input clk,
input rst_n,
//MAX30102_ctrl侧接口
input [DATA_W-:] din,
input din_vld,
output reg code_rdy,
//LCD_CTRL侧接口
output reg [DATA_W-:] dout,
output reg dout_vld,
input lcd_ctrl_rdy
); /*
编码转换流程:
1 检测BCD码寄存器每四位数值是否大于4,若是则加3,否则不处理;
2 左移一位,将待转换二进制数最高位送入寄存器;
3 第n次移位后进行字符编码;
*/ localparam LOG_DATA_W = ,
STA_W = ; localparam IDLE = ;
localparam ADD_3 = ;
localparam SHIFT = ;
localparam ASCII = ;
localparam WAIT_LCD = ; reg [ (-):] shift_cnt ;
wire add_shift_cnt ;
wire end_shift_cnt ;
reg [ (-):] char_cnt ;
wire add_char_cnt ;
wire end_char_cnt ;
reg [ (DATA_W-):] data_tmp ;
reg [ (DATA_W-):] tfrac_tmp ;
reg [-:] bcd_code [LOG_DATA_W-:];
reg [ (-):] disp_data ;
wire idle2shift ;
wire add_32shift ;
wire shift2add_3 ;
wire shift2ascii ;
wire ascii2wait_lcd ;
wire wait_lcd2idle ;
reg lcd_rdy_r ;
reg busy_flag ;
reg [STA_W-:] state_c;
reg [STA_W-:] state_n;
wire lcd_rdy_pos;
wire data_in_vld; //移位次数计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
shift_cnt <= ;
end
else if(add_shift_cnt) begin
if(end_shift_cnt)
shift_cnt <= ;
else
shift_cnt <= shift_cnt+ ;
end
end
assign add_shift_cnt = (state_c == SHIFT);
assign end_shift_cnt = add_shift_cnt && shift_cnt == (DATA_W)- ; //字符个数计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
char_cnt <= ;
end
else if(add_char_cnt) begin
if(end_char_cnt)
char_cnt <= ;
else
char_cnt <= char_cnt+ ;
end
end
assign add_char_cnt = (state_c == ASCII);
assign end_char_cnt = add_char_cnt && char_cnt == (LOG_DATA_W)- ; //数据寄存
always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
data_tmp <= () ;
end
else if(data_in_vld)begin
data_tmp <= (din) ;
end
end /*********************************************状态机****************************************************/
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end always @(*) begin
case(state_c)
IDLE :begin
if(idle2shift )
state_n = SHIFT ;
else
state_n = state_c ;
end
SHIFT :begin
if(shift2add_3 )
state_n = ADD_3 ;
else if(shift2ascii )
state_n = ASCII ;
else
state_n = state_c ;
end
ADD_3 :begin
if(add_32shift )
state_n = SHIFT ;
else
state_n = state_c ;
end
ASCII :begin
if(ascii2wait_lcd )
state_n = WAIT_LCD ;
else
state_n = state_c ;
end
WAIT_LCD:begin
if(wait_lcd2idle)
state_n = IDLE;
else
state_n = state_c;
end
default : state_n = IDLE ;
endcase
end assign idle2shift = state_c == IDLE && (din_vld);
assign shift2add_3 = state_c == SHIFT && (!end_shift_cnt);
assign shift2ascii = state_c == SHIFT && (end_shift_cnt);
assign add_32shift = state_c == ADD_3 && ('b1);
assign ascii2wait_lcd = state_c == ASCII && (end_char_cnt);
assign wait_lcd2idle = state_c == WAIT_LCD && lcd_rdy_pos; /*********************************************编码过程****************************************************/
//binary code ---> 8421bcd code
always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
bcd_code[] <= () ;
end
else if(state_c == ADD_3 && bcd_code[] > 'd4)begin
bcd_code[] <= (bcd_code[] + 'd3) ;
end
else if(state_c == SHIFT)
bcd_code[] <= {bcd_code[][:],data_tmp[DATA_W--shift_cnt]};
end always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
bcd_code[] <= () ;
end
else if(state_c == ADD_3 && bcd_code[] > 'd4)begin
bcd_code[] <= (bcd_code[] + 'd3) ;
end
else if(state_c == SHIFT)
bcd_code[] <= {bcd_code[][:],bcd_code[][]};
end always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
bcd_code[] <= () ;
end
else if(state_c == ADD_3 && bcd_code[] > 'd4)begin
bcd_code[] <= (bcd_code[] + 'd3) ;
end
else if(state_c == SHIFT)
bcd_code[] <= {bcd_code[][:],bcd_code[][]};
end always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
disp_data <= () ;
end
else if(add_char_cnt)begin
disp_data <= (bcd_code[LOG_DATA_W- - char_cnt]) ;
end
end /*********************************************接口信号****************************************************/ always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
lcd_rdy_r <= () ;
end
else if(state_c == WAIT_LCD)begin
lcd_rdy_r <= (lcd_ctrl_rdy) ;
end
end assign lcd_rdy_pos = lcd_ctrl_rdy == && lcd_rdy_r == ; always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
busy_flag <= () ;
end
else if(data_in_vld)begin
busy_flag <= ('b1) ;
end
else if(wait_lcd2idle)begin
busy_flag <= () ;
end
end assign data_in_vld = state_c == IDLE && din_vld; always@(*)begin
if(!lcd_ctrl_rdy || busy_flag || data_in_vld)
code_rdy = ;
else
code_rdy = ;
end /*********************************************编码后数据输出****************************************************/
// ASCII CODE
always@(*)begin
case(disp_data)
:dout = "";
:dout = "";
:dout = "";
:dout = "";
:dout = "";
:dout = "";
:dout = "";
:dout = "";
:dout = "";
:dout = "";
default:dout = "";
endcase
end always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
dout_vld <= () ;
end
else if(add_char_cnt)begin
dout_vld <= ('b1) ;
end
else
dout_vld <= ;
end endmodule
接下来用testbench仿真验证逻辑功能,在测试向量中要模拟LCD控制模块和数据源上游模块的行为,并通过显示编码方式验证待测试模块状态机当前状态。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/03/15 18:32:05
// Design Name:
// Module Name: disp_code_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////// module disp_code_tb; reg clk;
reg rst_n;
reg [-:] din;
reg din_vld;
wire code_rdy;
wire [-:] dout;
wire dout_vld;
reg lcd_ctrl_rdy; reg [ (-):] wait_cnt ;
wire add_wait_cnt ;
wire end_wait_cnt ;
reg [*-:] code_state;
reg lcd_ctrl_busy ; //待测试模块例化
disp_code#(.DATA_W())
uut(
.clk (clk),
.rst_n (rst_n), .din (din),
.din_vld (din_vld),
.code_rdy (code_rdy), .dout (dout),
.dout_vld (dout_vld),
.lcd_ctrl_rdy(lcd_ctrl_rdy)
);
parameter CYC = ,
RST_TIM = ; initial begin
clk = ;
forever #(CYC/) clk = ~clk;
end initial begin
rst_n = ;
#;
rst_n = ;
#(CYC*RST_TIM)
rst_n = ;
#100_000;
$stop;
end //模拟LCD控制模块行为
always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
lcd_ctrl_busy <= () ;
end
else if(dout_vld)begin
lcd_ctrl_busy <= ('b1) ;
end
else if(end_wait_cnt)begin
lcd_ctrl_busy <= () ;
end
end always@(*)begin
if(lcd_ctrl_busy || dout_vld)
lcd_ctrl_rdy = ;
else
lcd_ctrl_rdy = 'b1;
end always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
wait_cnt <= ;
end
else if(add_wait_cnt) begin
if(end_wait_cnt)
wait_cnt <= ;
else
wait_cnt <= wait_cnt+ ;
end
end
assign add_wait_cnt = (lcd_ctrl_rdy == );
assign end_wait_cnt = add_wait_cnt && wait_cnt == ()- ; //模拟数据源行为
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
din <= ;
else if(code_rdy)
din <= 'h20;
end always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
din_vld <= () ;
end
else if(code_rdy)begin
din_vld <= ('b1) ;
end
else begin
din_vld <= () ;
end
end //状态显示编码
always@(*)begin
case(uut.state_c)
'd0:code_state = "IDLE";
'd1:code_state = "ADD_3";
'd2:code_state = "SHIFT";
'd3:code_state = "ASCII";
'd4:code_state = "WAIT_LCD";
default:code_state = "ERROR";
endcase
end endmodule
分别看看显示编码模块仿真波形的整体和局部放大图:
可以看出在LCD控制模块准备好情况下(lcd_ctrl_rdy拉高),显示编码模块也处于准备就绪状态,上游模块送入待转码数据8'h20,对应的十进制数是32,显示编码模块输出结果与数值相符合。