2024寒假练 - 基于小脚丫FPGA套件STEP BaseBoard V4.0制作的两位十进制加、减、乘、除计算器
该项目使用了WebIDE平台、Verilog HDL语言,实现了计算器的设计,它的主要功能为:实现两位十进制加、减、乘、除。
标签
FPGA
小脚丫
Verilog HDL
STEP BaseBoard V4.0
WebIDE
Glass
更新2024-03-29
华北电力大学
412

1.项目需求

实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。

4×4键盘按键分配图

基本要求:

运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

扩展要求:

使用TFTLCD来做显示,自行设计显示界面,代替数码管显示输入的参数、运算符以及计算后的结果。

FheM1anVmLBxLX0YIiIxNkkAIWf6


2.需求分析

在本次活动中,我只完成了基本要求,并未将拓展要求完成,所以后续讲解只针对基本要求进行分析和讲解。

2.1矩阵按键

在小脚丫FPGA套件STEP BaseBoard V4.0的实验板上,我们可以看到右下角有一块4*4的矩阵按键,供我们进行输入数字和运算符号。其中键盘的按键定义也如项目需求中的图片所示,除了第三行的321顺序反一下之外,其他均和图片保持一致。我们可以通过4根行线和4根列线(也就是8个I/O口)连接16个按键,读取16个按键的信号。

2.2数码管显示

小脚丫FPGA套件STEP BaseBoard V4.0的实验板上的八个数码管驱动原理和核心板上的两个数码管一样,但是驱动方式不同,实验板上采用了两个74HC595芯片,这两个芯片之间又形成级联,通过FPGA上的3个I/O口,控制数码管上的八个数字的显示。

2.3四则运算

如何将输入的数字进行四则运算并输出结果,是本次要求的另一个核心困难,加法比较简单,只需要考虑读取和输出的格式问题;减法,在加法的基础上,只需要注意负号的输出即可;乘法,我这里并未进行特别处理,直接用Verilog中的乘号,调用FPGA内部的乘法器进行乘法运算,然后输出结果;除法进行了特殊处理,通过移位等操作,实现保留整数的除法。


3.实现的方式

3.1矩阵键盘

image.png

通过原理图的连线,可以知道,4根列线通过上拉电阻接到VCC,所以我们可以把4根行线作为输入,4根列线作为输出,由输入和输出的对应关系,得到按键的状态。

下面我们可以通过一个例子,来了解一下其中的对应关系,某一时刻,当图中ROW1=0,ROW2=1,ROW3=1,ROW4=1时(输入),对于K1、K2、K3、K4按键,它们分别被按下时,COL1~COL4分别输出0,没被按下时,分别输出1;而对于K5~K16之间的按键,无论按下与否,4根列线都输出1。

因此我们可以按照扫描的方式,一共分为4个时刻,每个时刻分别拉低1根行线的输入,分别检测每根列线的输出,4个时刻依次循环,就完成了矩阵按键的全部扫描。

3.2数码管模块

image.png

下面简单介绍一下74HC595芯片:74HC595是较为常用的串行转并行的芯片,内部集成了一个8位移位寄存器、一个存储器和8个三态缓冲输出。在最简单的情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/O口控制两个级联的74HC595芯片,产生16路并行输出,连接到扫描显示的6位数码管上,可以轻松完成数码管驱动任务。

通过其数据手册,我们了解到OE#(G#)和MR#(SCLR#)信号分别为输出使能(低电平输出)和复位管脚(低电平复位),OE#(G#)我们接GND让芯片输出使能,MR#(SCLR#)我们接VCC让芯片的移位寄存器永远不复位,如此FPGA只需要控制SHCP(SCK)、STCP(RCK)和DS(SER)即可控制两个芯片的输入。

输入信号之后,两个芯片将FPGA的信号输出给8位数码管进行显示。

3.3数字的读取和输出

数字的读取,通过我们在3.2中给予芯片的信号进行读取,4个数码管信号分别对应4个数字,分别对应第一个数字的十位个位,第二个数字的十位个位,这里为了节省资源,我们用移位,来达到乘十的效果,将第一个数字的十位左移3位加上第一个数字的十位左移1位即为第一个数字乘10的大小,然后加上个位数字即可。

数字的输出,得到的结果是答案数字的二进制表达,所以并不能直接输出给数码管,我们需要将二进制先转为BCD码,再进行显示。

3.4四则运算

由于3.3解决了数字读取和输出的问题,所以我们可以直接进行数字的加减乘,至于除法的思路,详见第五部分的代码及说明。


4.功能框图

image.png

5.代码及说明

5.1 按键扫描(array_keyboard.v)

该.v文件,存储的是进行按键扫描的Verilog代码,借鉴了小脚丫FPGA教程和例程中的思路以及代码加以使用。其中3.1提到过,扫描分为四个时刻,每个时刻为5ms,这样一个循环之后,正好可以检测20ms进行按键消抖的判断。代码中通过一个时序逻辑,将前一刻的按键值延迟锁存,通过一个组合逻辑,通过前后两个时刻的按键值判断作为最后的按键信号。并且这样使用,长时间按下也只会触发一次有效的按键信号,避免后续逻辑混乱。

module array_keyboard #
(
parameter CNT_200HZ = 60000
)
(
input clk,
input rst_n,
input [3:0] col,
output reg [3:0] row,
output reg [15:0]key_out,
output [15:0]key_pulse
);

localparam STATE0 = 2'b00;
localparam STATE1 = 2'b01;
localparam STATE2 = 2'b10;
localparam STATE3 = 2'b11;

/*
因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态
在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样
周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间
对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理
*/

//计数器计数分频实现5ms周期信号clk_200hz,系统时钟12MHz
reg [15:0] cnt;
reg clk_200hz;
always@(posedge clk or negedge rst_n) begin //复位时计数器cnt清零,clk_200hz信号起始电平为低电平
if(!rst_n) begin
cnt <= 16'd0;
clk_200hz <= 1'b0;
end else begin
if(cnt >= ((CNT_200HZ>>1) - 1)) begin //数字逻辑中右移1位相当于除2
cnt <= 16'd0;
clk_200hz <= ~clk_200hz; //clk_200hz信号取反
end else begin
cnt <= cnt + 1'b1;
clk_200hz <= clk_200hz;
end
end
end

reg [1:0] c_state;
//状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效
always@(posedge clk_200hz or negedge rst_n) begin
if(!rst_n) begin
c_state <= STATE0;
row <= 4'b1110;
end else begin
case(c_state)
//状态c_state跳转及对应状态下矩阵按键的row输出
STATE0: begin c_state <= STATE1; row <= 4'b1101; end
STATE1: begin c_state <= STATE2; row <= 4'b1011; end
STATE2: begin c_state <= STATE3; row <= 4'b0111; end
STATE3: begin c_state <= STATE0; row <= 4'b1110; end
default:begin c_state <= STATE0; row <= 4'b1110; end
endcase
end
end

reg [15:0] key,key_r;
//因为每个状态中单行有效,通过对列接口的电平状态采样得到对应4个按键的状态,依次循环
always@(negedge clk_200hz or negedge rst_n) begin
if(!rst_n) begin
key_out <= 16'hffff; key_r <= 16'hffff; key <= 16'hffff;
end else begin
case(c_state)
//采集当前状态的列数据赋值给对应的寄存器位
//对键盘采样数据进行判定,连续两次采样低电平判定为按键按下
STATE0: begin key_out[ 3: 0] <= key_r[ 3: 0]|key[ 3: 0]; key_r[ 3: 0] <= key[ 3: 0]; key[ 3: 0] <= col; end
STATE1: begin key_out[ 7: 4] <= key_r[ 7: 4]|key[ 7: 4]; key_r[ 7: 4] <= key[ 7: 4]; key[ 7: 4] <= col; end
STATE2: begin key_out[11: 8] <= key_r[11: 8]|key[11: 8]; key_r[11: 8] <= key[11: 8]; key[11: 8] <= col; end
STATE3: begin key_out[15:12] <= key_r[15:12]|key[15:12]; key_r[15:12] <= key[15:12]; key[15:12] <= col; end
default:begin key_out <= 16'hffff; key_r <= 16'hffff; key <= 16'hffff; end
endcase
end
end

reg [15:0] key_out_r;
//Register low_sw_r, lock low_sw to next clk
always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_out_r <= 16'hffff;
else key_out_r <= key_out; //将前一刻的值延迟锁存

//wire [15:0] key_pulse;
//Detect the negedge of low_sw, generate pulse
assign key_pulse= key_out_r & ( ~key_out); //通过前后两个时刻的值判断

endmodule
5.2数码管配合按键显示(key_decode.v 和 digit_show.v)

key_decode中判断按下的是哪一颗按键,并将相应的信号传入digit_show中进行进一步的处理。为了保证可以重复按同一个数字,在这里还加入了新的信号位,即key_flag作为标志传入下一级,保证各部分结构独立和完整。

module key_decode(
input clk,
input rst_n,
input [15:0]key_pulse, //按键消抖后动作脉冲信号
output reg [3:0] seg_data, //高4位代表十位,低4位代表个位
output reg key_flag
);

//key_pulse transfer to seg_data
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
seg_data <= 4'd0;
end else begin
case(key_pulse) //key_pulse脉宽等于clk_in的周期
16'h0001: begin seg_data <= 4'd7; key_flag <= 1'b1; end//编码
16'h0002: begin seg_data <= 4'd8; key_flag <= 1'b1; end
16'h0004: begin seg_data <= 4'd9;key_flag <= 1'b1; end
16'h0008: begin seg_data <= 4'd10;key_flag <= 1'b1; end //除号
16'h0010: begin seg_data <= 4'd4;key_flag <= 1'b1; end
16'h0020: begin seg_data <= 4'd5;key_flag <= 1'b1; end
16'h0040: begin seg_data <= 4'd6;key_flag <= 1'b1; end
16'h0080: begin seg_data <= 4'd11;key_flag <= 1'b1; end //乘号
16'h0100: begin seg_data <= 4'd1;key_flag <= 1'b1; end
16'h0200: begin seg_data <= 4'd2;key_flag <= 1'b1; end
16'h0400: begin seg_data <= 4'd3;key_flag <= 1'b1; end
16'h0800: begin seg_data <= 4'd12;key_flag <= 1'b1; end //减号
16'h1000: begin seg_data <= 4'd0;key_flag <= 1'b1; end
16'h8000: begin seg_data <= 4'd13;key_flag <= 1'b1; end //加号
16'h4000: begin seg_data <= 4'd14;key_flag <= 1'b1; end //等于
16'h2000: begin seg_data <= 4'd15;key_flag <= 1'b1; end //小数点
default: begin seg_data <= seg_data; key_flag <= 1'b0; end //无按键按下时保持
endcase
end
end

endmodule

在digit_show中,按照题目要求中的内容,“输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示”,我们加入了num作为输入的两个数的标志,pos作为数位的标志,配合按键标志,实现上述的功能。当输入非数字时,更新cal_state的信号,也就是运算符号,传入下一级,同时当按下的是“=”键时,也会与其他运算符区别开来。因为不用输入“.”,所以,在输入时,我把小数点作为清零功能使用。

module  digit_show
(
input wire clk,
input wire rst_n,
input wire [3:0] seg_data,
input wire key_flag,
output reg [15:0] dat,
output reg [7:0] dat_en,
output reg equ_flag,
output reg [3:0] cal_state
);

reg num;
reg pos;
always@ (negedge key_flag or negedge rst_n)begin
if(!rst_n) begin
dat = 15'd0;
dat_en = 8'd0;
num <= 1'b0;
pos <= 1'b1;
equ_flag <= 0;
end else begin
if(seg_data <= 4'd9) begin
equ_flag <= 0;
if(num == 1'b0) begin
if(pos == 1'b1) begin
dat[7:4] <= seg_data;
dat_en[6] <= 1'b1;
pos <= ~pos;
end else begin
dat[3:0] <= dat[7:4];
dat[7:4] <= seg_data;
dat_en[7] <= 1'b1;
pos <= ~pos;
end
end else begin
if(pos == 1'b1) begin
dat[15:12] <= seg_data;
dat_en[4] <= 1'b1;
pos <= ~pos;
end else begin
dat[11:8] <= dat[15:12];
dat[15:12] <= seg_data;
dat_en[5] <= 1'b1;
pos <= ~pos;
end
end
end else if(seg_data == 4'd15) begin
dat = 15'd0;
dat_en = 8'd0;
num <= 1'b0;
pos <= 1'b1;
equ_flag <= 0;
end else if(seg_data == 4'd14) begin
equ_flag <= 1;
num <= 1'b0;
pos <= 1'b1;
end else begin
equ_flag <= 0;
cal_state <= seg_data;
num <= ~num;
pos <= 1'b1;
end
end
end

endmodule
5.3数码管和74HC595驱动(segment_scan.v)

该部分也是借鉴了小脚丫FPGA的教程和例程中的代码加以使用。通过不断给74HC595芯片传输信息,更新数码管的显示,让数码管一直保持在我们需要的状态。

module segment_scan(
input clk, //系统时钟 12MHz
input rst_n, //系统复位 低有效
input [3:0] dat_1, //SEG1 显示的数据输入
input [3:0] dat_2, //SEG2 显示的数据输入
input [3:0] dat_3, //SEG3 显示的数据输入
input [3:0] dat_4, //SEG4 显示的数据输入
input [3:0] dat_5, //SEG5 显示的数据输入
input [3:0] dat_6, //SEG6 显示的数据输入
input [3:0] dat_7, //SEG7 显示的数据输入
input [3:0] dat_8, //SEG8 显示的数据输入
input [7:0] dat_en, //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]
input [7:0] dot_en, //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
output reg seg_rck, //74HC595的RCK管脚
output reg seg_sck, //74HC595的SCK管脚
output reg seg_din //74HC595的SER管脚
);

localparam CNT_40KHz = 300; //分频系数

localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam WRITE = 3'd2;
localparam LOW = 1'b0;
localparam HIGH = 1'b1;

//创建数码管的字库,字库数据依段码顺序有关
//这里字库数据[MSB~LSB]={G,F,E,D,C,B,A}
reg[6:0] seg [10:0];
always @(negedge rst_n) begin
seg[0] = 7'h3f; // 0
seg[1] = 7'h06; // 1
seg[2] = 7'h5b; // 2
seg[3] = 7'h4f; // 3
seg[4] = 7'h66; // 4
seg[5] = 7'h6d; // 5
seg[6] = 7'h7d; // 6
seg[7] = 7'h07; // 7
seg[8] = 7'h7f; // 8
seg[9] = 7'h6f; // 9
seg[10] = 7'b1000000;
end

//计数器对系统时钟信号进行计数
reg [9:0] cnt = 1'b0;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) cnt <= 1'b0;
else if(cnt>=(CNT_40KHz-1)) cnt <= 1'b0;
else cnt <= cnt + 1'b1;
end

//根据计数器计数的周期产生分频的脉冲信号
reg clk_40khz = 1'b0;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) clk_40khz <= 1'b0;
else if(cnt<(CNT_40KHz>>1)) clk_40khz <= 1'b0;
else clk_40khz <= 1'b1;
end

//使用状态机完成数码管的扫描和74HC595时序的实现
reg [15:0] data;
reg [2:0] cnt_main;
reg [5:0] cnt_write;
reg [2:0] state = IDLE;
always@(posedge clk_40khz or negedge rst_n) begin
if(!rst_n) begin //复位状态下,各寄存器置初值
state <= IDLE;
cnt_main <= 3'd0; cnt_write <= 6'd0;
seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
end else begin
case(state)
IDLE:begin //IDLE作为第一个状态,相当于软复位
state <= MAIN;
cnt_main <= 3'd0; cnt_write <= 6'd0;
seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
end
MAIN:begin
cnt_main <= cnt_main + 1'b1;
state <= WRITE; //在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序
case(cnt_main)
//对8位数码管逐位扫描
//data [15:8]为段选, [7:0]为位选
3'd0: data <= {{dot_en[7],seg[dat_1]},dat_en[7]?8'hfe:8'hff};
3'd1: data <= {{dot_en[6],seg[dat_2]},dat_en[6]?8'hfd:8'hff};
3'd2: data <= {{dot_en[5],seg[dat_3]},dat_en[5]?8'hfb:8'hff};
3'd3: data <= {{dot_en[4],seg[dat_4]},dat_en[4]?8'hf7:8'hff};
3'd4: data <= {{dot_en[3],seg[dat_5]},dat_en[3]?8'hef:8'hff};
3'd5: data <= {{dot_en[2],seg[dat_6]},dat_en[2]?8'hdf:8'hff};
3'd6: data <= {{dot_en[1],seg[dat_7]},dat_en[1]?8'hbf:8'hff};
3'd7: data <= {{dot_en[0],seg[dat_8]},dat_en[0]?8'h7f:8'hff};
default: data <= {8'h00,8'hff};
endcase
end
WRITE:begin
if(cnt_write >= 6'd33) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
//74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用
//74HC595的时序实现,参考74HC595的芯片手册
6'd0: begin seg_sck <= LOW; seg_din <= data[15]; end //SCK下降沿时SER更新数据
6'd1: begin seg_sck <= HIGH; end //SCK上升沿时SER数据稳定
6'd2: begin seg_sck <= LOW; seg_din <= data[14]; end
6'd3: begin seg_sck <= HIGH; end
6'd4: begin seg_sck <= LOW; seg_din <= data[13]; end
6'd5: begin seg_sck <= HIGH; end
6'd6: begin seg_sck <= LOW; seg_din <= data[12]; end
6'd7: begin seg_sck <= HIGH; end
6'd8: begin seg_sck <= LOW; seg_din <= data[11]; end
6'd9: begin seg_sck <= HIGH; end
6'd10: begin seg_sck <= LOW; seg_din <= data[10]; end
6'd11: begin seg_sck <= HIGH; end
6'd12: begin seg_sck <= LOW; seg_din <= data[9]; end
6'd13: begin seg_sck <= HIGH; end
6'd14: begin seg_sck <= LOW; seg_din <= data[8]; end
6'd15: begin seg_sck <= HIGH; end
6'd16: begin seg_sck <= LOW; seg_din <= data[7]; end
6'd17: begin seg_sck <= HIGH; end
6'd18: begin seg_sck <= LOW; seg_din <= data[6]; end
6'd19: begin seg_sck <= HIGH; end
6'd20: begin seg_sck <= LOW; seg_din <= data[5]; end
6'd21: begin seg_sck <= HIGH; end
6'd22: begin seg_sck <= LOW; seg_din <= data[4]; end
6'd23: begin seg_sck <= HIGH; end
6'd24: begin seg_sck <= LOW; seg_din <= data[3]; end
6'd25: begin seg_sck <= HIGH; end
6'd26: begin seg_sck <= LOW; seg_din <= data[2]; end
6'd27: begin seg_sck <= HIGH; end
6'd28: begin seg_sck <= LOW; seg_din <= data[1]; end
6'd29: begin seg_sck <= HIGH; end
6'd30: begin seg_sck <= LOW; seg_din <= data[0]; end
6'd31: begin seg_sck <= HIGH; end
6'd32: begin seg_rck <= HIGH; end //当16位数据传送完成后RCK拉高,输出生效
6'd33: begin seg_rck <= LOW; state <= MAIN; end
default: ;
endcase
end
default: state <= IDLE;
endcase
end
end

endmodule
5.4计算模块(calculat.v)

加法与减法,以及最后的显示思路已经在2.3和3.3中进行阐述,这里就不再赘述,具体转换过程见TURN部分。乘法则是直接调用IP核内部乘法器进行运算,显示思路同加减法保持一致。其中除法,则是利用一个除法器的思路,将被除数依次移位,和除数判断大小,然后相减,具体思路详见代码,只能做到整除,无法做到保留小数。整个代码需要注意非阻塞赋值和阻塞赋值的使用,并且我的乘法与除法代码思路以及显示思路都比较暴力(有更好实现方式可以使用更好的实现方式),但是实际可行。

module  calculate
(
input clk, //系统时钟 12MHz
input rst_n, //系统复位 低有效
input equ_flag,
input [3:0] cal_state,
input [3:0] dat_1, //SEG1 显示的数据输入 第一个数的十位
input [3:0] dat_2, //SEG2 显示的数据输入 第一个数的个位
input [3:0] dat_3, //SEG3 显示的数据输入 第二个数的十位
input [3:0] dat_4, //SEG4 显示的数据输入 第二个数的个位
output reg [3:0] dat_5 = 4'd0, //SEG5 显示的数据输入
output reg [3:0] dat_6 = 4'd0, //SEG6 显示的数据输入
output reg [3:0] dat_7 = 4'd0, //SEG7 显示的数据输入
output reg [3:0] dat_8 = 4'd0, //SEG8 显示的数据输入
output reg [7:0] dot_en, //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
output reg [7:0] dat_en
);

reg [7:0] num_1;
reg [7:0] num_2;

reg start_flag;
reg [3:0] State = 3'd0;

localparam IDLE = 3'd0;
localparam WORK = 3'd1;
localparam TURN = 3'd2;
localparam SHOW = 3'd3;
localparam SPE = 3'd4;
localparam SPET = 3'd5;

localparam PLUS = 4'd13;
localparam MINUS = 4'd12;
localparam MULTI = 4'd11;
localparam DIVID = 4'd10;

integer i;

always@ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
num_1 <= 8'd0;
num_2 <= 8'd0;
start_flag <= 1'd0;
end else if(equ_flag == 1) begin
num_1 <= {dat_1,3'd0} + {dat_1,1'd0} + dat_2;
num_2 <= {dat_3,3'd0} + {dat_3,1'd0} + dat_4;
start_flag <= 1'd1;
end else begin
num_1 <= 8'd0;
num_2 <= 8'd0;
start_flag <= 1'd0;
end
end

reg [15:0] result = 16'd0;
reg [3:0] thousands = 4'd0;
reg [3:0] hundreds = 4'd0;
reg [3:0] tens = 4'd0;
reg [3:0] ones = 4'd0;
reg minus_flag = 0;
reg [15:0] temp_b = 16'd0;

always@ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
State <= IDLE;
end else begin
case(State)
IDLE:begin
if(start_flag) State <= WORK;
else begin
dot_en <= 8'h00;
dat_en <= 8'h00;
thousands <= 4'd0;
hundreds <= 4'd0;
tens <= 4'd0;
ones <= 4'd0;
result <= 16'd0;
minus_flag <= 0;
temp_b = 16'd0;
if(dat_5 != 4'd0) dat_5 <= 0;
else dat_5 <= dat_5;
if(dat_6!= 4'd0) dat_6 <= 0;
else dat_6 <= dat_6;
if(dat_7 != 4'd0) dat_7 <= 0;
else dat_7 <= dat_7;
if(dat_8 != 4'd0) dat_8 <= 0;
else dat_8 <= dat_8;
end
end
WORK:begin
case(cal_state)
PLUS:begin
result <= num_1 + num_2;
State <= TURN;
end
MINUS:begin
if(num_1 < num_2) begin
result <= num_2 - num_1;
minus_flag <= 1;
State <= TURN;
end else begin
result <= num_1 - num_2;
minus_flag <= 0;
State <= TURN;
end
end
MULTI:begin
result <= num_1 * num_2;
State <= TURN;
end
DIVID:begin
result = {8'h00,num_1};
temp_b = {num_2,8'h00};
for(i=0;i<8;i=i+1) begin
result = {result[14:0],1'b0};
if(result[15:8]>=num_2)
result = result - temp_b + 1'b1;
else
result = result;
end
State <= SPET;
end
endcase
end
SPET:begin
for(i = 7; i >= 0; i = i - 1) begin
if(ones >= 4'd5) ones = ones + 4'd3;
else ones = ones;
if(tens >= 4'd5) tens = tens + 4'd3;
else tens = tens;
tens = {tens[2:0],ones[3]};
ones = {ones[2:0],result[i]};
end
State <= SHOW;
end
TURN:begin
for(i = 15; i >= 0; i = i - 1) begin
if(ones >= 4'd5) ones = ones + 4'd3;
else ones = ones;
if(tens >= 4'd5) tens = tens + 4'd3;
else tens = tens;
if(hundreds >= 4'd5) hundreds = hundreds + 4'd3;
else hundreds = hundreds;
if(thousands >= 4'd5) thousands = thousands + 4'd3;
else thousands = thousands;
thousands = {thousands[2:0],hundreds[3]};
hundreds = {hundreds[2:0],tens[3]};
tens = {tens[2:0],ones[3]};
ones = {ones[2:0],result[i]};
end
if(cal_state == MINUS) State <= SPE;
else State <= SHOW;
end
SHOW:begin
case(cal_state)
PLUS:begin
if(thousands != 0) dat_en <= 8'b00001111;
else if(thousands == 0 && hundreds != 0) dat_en <= 8'b00000111;
else if(thousands == 0 && hundreds == 0 && tens != 0) dat_en <= 8'b00000011;
else dat_en <= 8'b00000001;
end
MINUS:begin
dat_en <= dat_en;
end
MULTI:begin
if(thousands != 0) dat_en <= 8'b00001111;
else if(thousands == 0 && hundreds != 0) dat_en <= 8'b00000111;
else if(thousands == 0 && hundreds == 0 && tens != 0) dat_en <= 8'b00000011;
else dat_en <= 8'b00000001;
end
DIVID:begin
if(thousands != 0) dat_en <= 8'b00001111;
else if(thousands == 0 && hundreds != 0) dat_en <= 8'b00000111;
else if(thousands == 0 && hundreds == 0 && tens != 0) dat_en <= 8'b00000011;
else dat_en <= 8'b00000001;
end
endcase
dat_5 = thousands;
dat_6 = hundreds;
dat_7 = tens;
dat_8 = ones;
if(start_flag == 0) State <= IDLE;
end
SPE:begin
if(minus_flag)begin
if(thousands != 0) dat_en <= 8'b00001111;
else if(thousands == 0 && hundreds != 0) begin dat_en <= 8'b00001111;thousands <= 4'd10;end
else if(thousands == 0 && hundreds == 0 && tens != 0) begin dat_en <= 8'b00000111;hundreds <= 4'd10;end
else begin dat_en <= 8'b00000011;tens <= 4'd10;end
end else begin
if(thousands != 0) dat_en <= 8'b00001111;
else if(thousands == 0 && hundreds != 0) dat_en <= 8'b00000111;
else if(thousands == 0 && hundreds == 0 && tens != 0) dat_en <= 8'b00000011;
else dat_en <= 8'b00000001;
end
State <= SHOW;
end
endcase
end
end
endmodule
5.5总和(type_system)

在这里还用了一个思路,将前四位数字的显示和存储与后四位数字的显示与存储分开,分别是[15:0] dat,dat_5,dat_6,dat_7,dat_8,dat_en_1和dat_en_2,保证两部分显示相互独立,数据之间不会相互干扰,方便逻辑和流程的实现。

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: type_system
//
// Author: Step
//
// Description: 矩阵键盘输入控制,按压按键在小脚丫数码管显示编号
//
// Web: www.stepfapga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2016/04/20 |Initial ver
// --------------------------------------------------------------------
module type_system(
input clk,
input rst_n,
input [3:0] col,
output [3:0] row,
output seg_rck, //74HC595的RCK管脚
output seg_sck, //74HC595的SCK管脚
output seg_din //74HC595的SER管脚
);

wire [15:0] key_out;
wire [15:0] key_pulse;
wire [3:0] seg_data;
//Array_KeyBoard
array_keyboard u1(
.clk(clk),
.rst_n(rst_n),
.col(col),
.row(row),
.key_out(key_out),
.key_pulse(key_pulse)
);

wire key_flag;
//key_decode
key_decode u2(
.clk (clk ),
.rst_n (rst_n ),
.key_pulse (key_pulse ),
.seg_data (seg_data ), //高4位代表十位,低4位代表个位
.key_flag (key_flag )
);

wire [15:0] dat;
wire [7:0] dat_en_1;
wire equ_flag;
wire [3:0] cal_state;

digit_show u3(
.clk (clk),
.rst_n (rst_n),
.seg_data (seg_data),
.key_flag (key_flag),
.dat (dat),
.dat_en (dat_en_1),
.equ_flag (equ_flag),
.cal_state (cal_state)
);

wire [3:0] dat_5; //SEG5 显示的数据输入
wire [3:0] dat_6; //SEG6 显示的数据输入
wire [3:0] dat_7; //SEG7 显示的数据输入
wire [3:0] dat_8; //SEG8 显示的数据输入
wire [7:0] dot_en;
wire [7:0] dat_en_2;

calculate u4
(
.clk (clk), //系统时钟 12MHz
.rst_n (rst_n), //系统复位 低有效
.equ_flag (equ_flag),
.cal_state (cal_state),
.dat_1 (dat[3:0]), //SEG1 显示的数据输入
.dat_2 (dat[7:4]), //SEG2 显示的数据输入
.dat_3 (dat[11:8]), //SEG3 显示的数据输入
.dat_4 (dat[15:12]), //SEG4 显示的数据输入
.dat_5 (dat_5), //SEG5 显示的数据输入
.dat_6 (dat_6), //SEG6 显示的数据输入
.dat_7 (dat_7), //SEG7 显示的数据输入
.dat_8 (dat_8), //SEG8 显示的数据输入
.dot_en (dot_en), //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
.dat_en (dat_en_2)
);

//segment_scan display module
segment_scan u5(
.clk(clk), //系统时钟 12MHz
.rst_n(rst_n), //系统复位 低有效
.dat_1(dat[3:0]), //SEG1 显示的数据输入
.dat_2(dat[7:4]), //SEG2 显示的数据输入
.dat_3(dat[11:8]), //SEG3 显示的数据输入
.dat_4(dat[15:12]), //SEG4 显示的数据输入
.dat_5(dat_5), //SEG5 显示的数据输入
.dat_6(dat_6), //SEG6 显示的数据输入
.dat_7(dat_7), //SEG7 显示的数据输入
.dat_8(dat_8), //SEG8 显示的数据输入
.dat_en(dat_en_1 | dat_en_2), //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]
.dot_en(dot_en), //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
.seg_rck(seg_rck), //74HC595的RCK管脚
.seg_sck(seg_sck), //74HC595的SCK管脚
.seg_din(seg_din) //74HC595的SER管脚
);

endmodule


6.仿真波形图

针对array_keyboard.v的仿真

image.png

针对segment_scan.v的仿真

image.png

针对key_decode.v的仿真

image.png

针对digit_show.v的仿真

image.png

针对calculat.v的仿真 image.png


7.FPGA的资源利用说明

image.png

8.演示视频



附件下载
archive.zip
type_system_impl1(此为Diamond生成,功能完整).jed
此为Diamond生成,功能完整
implement(此为WebIDE生成,除法功能不正常).jed
此为WebIDE生成,除法功能不正常
团队介绍
华北电力大学 电气与电子信息工程学院 电子信息工程 王亚楠
团队成员
Glass
和自己相比,有进步吗?
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号