今天给大侠带来基于FPGA的电子计算器设计,由于篇幅较长,分三篇。今天带来第三篇,下篇,话不多说,上货。
导读
本篇介绍了一个简单计算器的设计,基于 FPGA 硬件描述语言 Verilog HDL,系统设计由计算部分、显示部分和输入部分四个部分组成,计算以及存储主要用状态机来实现。显示部分由六个七段译码管组成,分别来显示输入数字,输入部分采用4*4矩阵键盘,由0-9一共十个数字按键,加减乘除四个运算符按键,一个等号按键组成的。通过外部的按键可以完成加、减、乘、除四种功能运算,其结构简单,易于实现。本篇为本人毕业设计部分整理,各位大侠可依据自己的需要进行阅读,参考学习。
第三篇内容摘要:仿真测试验证部分以及结论,包括ModelSim简介、模块仿真验证分析、矩阵按键模块、计算仿真举例等相关内容。
六、仿真验证设计
6.1 ModelSim简介
在仿真设计时,用到了Mentor公司的Modelsim,这是一款硬件描述语言仿真软件,该款软件不单单能提供十分友好的仿真环境,而且它也是我们业界第一个也是仅此一个的单内核支持VHDL和Verilog语言混合仿真的软件。它采用直接优化的编译技术、Tcl/Tk技术、和单一内核仿真技术,从而达到令人编译仿真速度快的效果,而且编译代码和整个平台没有关系,这样就更容易保护IP核,它是FPGA/ASIC设计的首选仿真软件。
Modelsim有不同版本,例如:SE、PE、LE和OEM,其中最高级的版本是SE,而集成在 Actel、Atmel以及Lattice等FPGA厂商设计工具中的都是其OEM版本。
Modelsim SE支持PC、UNIX和LINUX的混合平台;能给出十分全面到位以及高性能的验证功能;全面支持业界设定的广泛标准;同时Mentor Graphics公司提供了整个行业最出色的技术支持与服务。
Modelsim的主要特点有:
1)支持单内核的VHDL和Verilog混合在一起进行仿真处理;
2)具有源代码模版、助手以及项目管理功能;
3)汇聚了性能考核、波形参考、代码覆盖、数据流Chase X、Signal Spy、虚拟对象Virtual Object、Assertion窗口、Memory窗口、源码窗口显示信号值、信号条件断点等众多调试功能;
4)C和Tcl/Tk接口,C调试;
5)能够实现对System C的直接支持功能,同时可以和HDL任意混合使用;
6)能够实现System Verilog的设计功能;
7)可以做到对系统级描述语言进行最全面的支持;
8)可以单独或同时进行行为(behavioral)、RTL级、和门级(gate-level)的代码。
9)能够实现RTL和门级优化,编译仿真速率非常快,跨平台跨版本的仿真。
6.2 模块仿真验证分析
FPGA设计流程包括设计输入,仿真,综合,生成,板级验证等很多阶段。在整个设计流程中,完成设计输入并成功进行编译仅仅能说明设计符合一定的语法规范,并不能说明设计功能的正确性,这时,我们就需要通过仿真对设计进行验证。我们主要进行的是功能仿真,又叫逻辑仿真,是指在不考虑器件延时和布线延时的理想情况下对源代码进行逻辑功能验证;而时序仿真是在布局布线后进行。我们仿真是为了保证设计的正确性。
6.2.1 矩阵按键模块
矩阵键盘测试程序流程框图如下:
图6-1 按键测试程序流程图
Figure 6-1 key testing program flow chart
首先,我们要先进行按键检测,判断是否有按键闭合,如果没有说明没有按键键入,那么返回就是我们常说的消抖,重新进行按键检测,直到有按键闭合。接下来会有10ms的延迟,保存键值;再继续判断按键是否松开,如果是则又会产生10ms的延迟,否则返回判断直到按键有松开为止,最后返回键值。
我们需要编译一个模拟键盘定义data0-15,然后模拟输入给FPGA一个行信号,FPGA接收行信号,同时输出给模拟键盘一个列信号,如果输出的列信号不存在低电平,那么行信号为4‘b1111,代表输入的按键不在本列上,继续扫描下一列直到找到相应的行信号为止。部分代码如图所示。
图6-2 键盘扫描部分代码
Figure 6-2 keyboard scan code
图6-3为键盘扫描仿真图,当我们按下1时,数据显示1,按下10显示10,按下2显示的是2,按下15显示15,仿真结果有效,程序编译正确。
图6-3 键盘扫描仿真
Figure 6-3 keyboard scanning simulation
6.2.2 计算仿真举例
加法计算举例,首先pnumber输入1,data_in输入也为1,扫描结果为1;然后输入10;pnumber输入为2,data_in输入为2,扫描结果为2;最后按键“=”,显示结果即为3。仿真显示结果正确,说明我们的编译代码没有问题,计算有效,计算器结果可信。
图6-4 1 2=3程序仿真图
Figure 6-4 1 2=3 process simulation diagram
七、结论
本次电子计算器的设计是基于FPGA设计的,计算器基本上可以实现的加减乘除的功能。系统的计算部分、存储部分、显示部分和输入部分四个部分都可以完成设计要求,输入部分采用键盘矩阵原理,存储部分用状态机来实现,并进行了仿真。实现了防消抖的要求,计算结果较精确。达到了预期的要求目标。
附录:设计主体源代码
二进制转BCD代码:
代码语言:javascript复制module bin2bcd_12bit(bin, bcd);
input [19:0] bin;
output reg [23:0] bcd;
always @ (*)
begin
bcd[3:0] = bin;
bcd[7:4] = bin/10;
bcd[11:8] = bin/100;
bcd [15:12] = bin/1000;
bcd[19:16] = bin/10000;
bcd[23:20] = bin/100000;
end
endmodule
计算模块代码:
代码语言:javascript复制module calculator (clk, rst_n, flag, key_data, bin_data);
input clk;
input rst_n;
input flag;
input [3:0] key_data;
output reg [19:0] bin_data;
reg [1:0] state;
reg [19:0] num1;
reg [3:0] opcode;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
state <= 0;
num1 <= 0;
bin_data <= 0;
opcode <= 0;
end
else
begin
case (state)
0 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= bin_data * 10 key_data;
end
else
begin
if (key_data == 14)
begin
state <= 0;
end
else
begin
opcode <= key_data;
state <= 1;
num1 <= bin_data;
bin_data <= 0;
end
end
end
else
begin
state <= 0;
end
end
1 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= bin_data * 10 key_data;
end
else
begin
if (key_data == 14)
begin
case (opcode)
10 : begin bin_data <= num1 bin_data; state <= 2; end
11 : begin bin_data <= num1 - bin_data; state <= 2; end
12 : begin bin_data <= num1 * bin_data; state <= 2; end
13 : begin bin_data <= num1 / bin_data; state <= 2; end
default : bin_data <= 0;
endcase
end
else
begin
state <= 1;
end
end
end
else
begin
state <= 1;
end
end
2 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= {16'd0,key_data};
state <= 0;
end
else
begin
if (key_data == 14)
begin
state <= 2;
end
else
begin
num1 <= bin_data;
opcode <= key_data;
bin_data <= 0;
state <=1;
end
end
end
else
begin
state <= 2;
end
end
endcase
end
end
endmodule
输入部分代码:
代码语言:javascript复制module key_board (clk, rst_n, row, col, data, valid, clk_1k);
input clk;
input rst_n;
input [3:0] row;
output reg [3:0] col;
output reg [3:0] data;
output reg valid;
output reg clk_1k;
reg [14:0] cnt;
parameter T1ms = 24999;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_1k <= 1'b1;
cnt <= 15'd0;
end
else
begin
if (cnt < T1ms)
begin
cnt <= cnt 15'd1;
end
else
begin
cnt <= 15'd0;
clk_1k <= ~clk_1k;
end
end
end
reg [7:0] row_col;
reg [1:0] state;
reg [4:0] count;
always @ (posedge clk_1k or negedge rst_n)
begin
if (!rst_n)
begin
col <= 4'b0000;
row_col <= 8'd0;
state <= 0;
valid <= 0;
count <= 0;
end
else
begin
case (state)
0 : begin
if (row == 4'b1111)
begin
col <= 4'b0000;
end
else
begin
state <= 1;
end
end
1 : begin
if (row == 4'b1111)
begin
state <= 0;
count <= 0;
end
else
begin
if (count < 19)
begin
count <= count 1;
end
else
begin
count <= 0;
state <= 2;
col <= 4'b0111;
end
end
end
2 : begin
if (row == 4'b1111)
begin
col <= {col[2:0],col[3]};
state <= 2;
end
else
begin
row_col <= {row,col};
state <= 3;
valid <= 1;
end
end
3 : begin
if (row == 4'b1111)
begin
state <= 0;
valid <= 0;
end
else
begin
valid <= 0;
state <= 3;
end
end
default : state <= 0;
endcase
end
end
always @ (*)
begin
case (row_col)
8'b0111_0111 : data = 4'hf;
8'b0111_1011 : data = 4'he;
8'b0111_1101 : data = 4'hd;
8'b0111_1110 : data = 4'hc;
8'b1011_0111 : data = 4'hb;
8'b1011_1011 : data = 4'ha;
8'b1011_1101 : data = 4'h9;
8'b1011_1110 : data = 4'h8;
8'b1101_0111 : data = 4'h7;
8'b1101_1011 : data = 4'h6;
8'b1101_1101 : data = 4'h5;
8'b1101_1110 : data = 4'h4;
8'b1110_0111 : data = 4'h3;
8'b1110_1011 : data = 4'h2;
8'b1110_1101 : data = 4'h1;
8'b1110_1110 : data = 4'h0;
default : data = 4'h0;
endcase
end
endmodule
数码管顶层代码:
代码语言:javascript复制module seven_seg (clk, rst_n, data_in, sel, seg);
input clk;
input rst_n;
input [23:0] data_in;
output [2:0] sel;
output [7:0] seg;
wire clk_1k;
freq freq_dut(
.clk(clk),
.rst_n(rst_n),
.clk_1k(clk_1k)
);
sel_seg_encode sel_seg_encode_dut(
.clk(clk_1k),
.rst_n(rst_n),
.data_in(data_in),
.sel(sel),
.seg(seg)
);
endmodule
分频部分代码:
代码语言:javascript复制module freq (clk, rst_n, clk_1k);
input clk;
input rst_n;
output reg clk_1k;
reg [14:0] cnt;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_1k <= 1;
cnt <= 0;
end
else
begin
if (cnt < 24_999)
begin
cnt <= cnt 1;
end
else
begin
cnt <= 0;
clk_1k <= ~clk_1k;
end
end
end
endmodule
位选段选连接代码:
代码语言:javascript复制module sel_seg_encode (clk, rst_n, data_in, sel, seg);
input clk;
input rst_n;
input [23:0] data_in;
output [2:0] sel;
output [7:0] seg;
wire [3:0] num;
seg_encode seg_encode_dut(
.rst_n(rst_n),
.num(num),
.seg(seg)
);
sel_encode sel_encode_dut(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.num(num),
.sel(sel)
);
endmodule
段选部分代码:
代码语言:javascript复制module seg_encode (rst_n, num, seg);
input rst_n;
input [3:0] num;
output reg [7:0] seg;
always @ (*)
begin
if (!rst_n)
begin
seg = 8'b0000_0000;
end
else
begin
case (num)
0 : seg = 8'b1100_0000;
1 : seg = 8'b1111_1001;
2 : seg = 8'b1010_0100;
3 : seg = 8'b1011_0000;
4 : seg = 8'b1001_1001;
5 : seg = 8'b1001_0010;
6 : seg = 8'b1000_0010;
7 : seg = 8'b1111_1000;
8 : seg = 8'b1000_0000;
9 : seg = 8'b1001_0000;
default : seg = 8'b0000_0000;
endcase
end
end
endmodule
位选部分代码:
代码语言:javascript复制module sel_encode (clk, rst_n, data_in, num, sel);
input clk;
input rst_n;
input [23:0] data_in;
output reg [3:0] num;
output reg [2:0] sel;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
sel <= 0;
end
else
begin
if (sel < 5)
begin
sel <= sel 1;
end
else
begin
sel <= 0;
end
end
end
always @ (*)
begin
if (!rst_n)
begin
num = 0;
end
else
begin
case (sel)
0 : num = data_in[23:20];
1 : num = data_in[19:16];
2 : num = data_in[15:12];
3 : num = data_in[11:8];
4 : num = data_in[7:4];
5 : num = data_in[3:0];
default : num = 0;
endcase
end
end
endmodule
本篇到此结束,各位大侠,有缘再见!
END
后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!