1. 项目需求
本项目旨在实现一个两位十进制加、减、乘、除计算器。用户可以通过按键输入两个两位十进制数和一个运算符,计算器将对这两个数执行相应的运算,并在数码管上显示结果。
2. 需求分析及实现思路
该计算器需要完成以下功能:
通过按键输入两个两位十进制数和一个运算符。
执行加、减、乘、除四种运算。
在数码管上显示结果。
实现的思路如下:
设计计算器的硬件逻辑,包括按键扫描、数码管显示和运算逻辑。
编写Verilog代码来实现硬件逻辑。您需要实现按键扫描模块、数码管显示模块和运算逻辑模块。
使用思得普的WebIDE对Verilog代码进行编译和综合。
将综合后的设计下载到FPGA板上进行验证。使用STEP BaseBoard V4.0作为扩展底板。
编写Python脚本来实现图像处理部分,包括图像转换和二值化处理。
编写Python脚本来将处理后的图像数据发送到TFTLCD显示屏进行显示。
3. 功能实现及代码说明
为了实现该计算器,需要设计以下模块:
按键扫描模块:
负责扫描按键输入,并将按键值传递给主控制模块。采用的是矩阵键盘的示例代码,其主要功能是通过扫描矩阵按键的行和列,检测按键的按下状态,并输出相应的按键值。
代码的主要实现逻辑:
- 计时器及状态机:
- 使用一个计数器
cnt
实现了一个200Hz的时钟信号clk_200hz
,用于状态机的时序控制。 - 状态机
c_state
包含4种状态,分别对应矩阵键盘的4行。状态机每隔20ms切换一次状态,对应矩阵键盘的4行分别被选中。
- 使用一个计数器
- 状态机切换与行扫描:
- 根据状态机的状态,选择对应的行输出使能信号
row
,通过该信号控制矩阵键盘的行。 - 状态机每隔20ms切换一次状态,对应于每行扫描一次。
- 根据状态机的状态,选择对应的行输出使能信号
- 列采样与按键检测:
- 对于每个状态,通过列接口
col
采样键盘的列电平状态。 - 将采样到的列电平状态与上一时刻的状态进行比较,如果有变化,则表示有按键按下。
- 将按键按下的情况存储在
key_out
中,同时延迟一个时钟周期进行锁存。
- 对于每个状态,通过列接口
- 按键脉冲输出:
- 通过比较上一时刻和当前时刻按键状态,产生按键脉冲信号
key_pulse
,用于检测按键的按下动作。
- 通过比较上一时刻和当前时刻按键状态,产生按键脉冲信号
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
数码管显示模块:
将计算结果显示在数码管上。Verilog代码实现了一个数码管扫描模块,通过控制74HC595芯片和数码管,实现了对多个数码管的显示。
以下是代码的主要实现逻辑:
- 时钟和复位控制:
- 使用
clk
作为系统时钟信号,rst_n
作为系统复位信号。 - 使用一个计数器
cnt
对系统时钟进行计数,并产生一个40kHz的时钟信号clk_40khz
作为主要的时序控制。
- 使用
- 数码管字库:
- 使用
seg
数组存储了数码管的字库数据,每个元素表示一个十六进制数对应的段码。
- 使用
- 状态机控制:
- 使用状态机实现对数码管的扫描和数据更新。
- 初始状态为
IDLE
,表示软复位状态,然后切换到MAIN
状态进行数码管的主要扫描和显示。 - 在
MAIN
状态下,对8位数码管逐位扫描,根据dat_en
和dot_en
控制位选和段选,更新数码管数据。 - 在
WRITE
状态下,按照74HC595的时序要求,将数据逐位串行传输到74HC595芯片中,并在传输完成后将seg_rck
拉高,使输出生效。
- 数码管数据更新:
- 根据状态机的状态和计时器的计数,逐位更新数码管的显示数据和段选控制信号。
// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: segment_scan
//
// Author: Step
//
// Description: Display with Segment tube
//
// Web: www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2015/11/11 |Initial ver
// --------------------------------------------------------------------
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 [15: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'h77; // A
seg[11] = 7'h7c; // b
seg[12] = 7'h39; // C
seg[13] = 7'h5e; // d
seg[14] = 7'h79; // E
seg[15] = 7'h71; // F
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
键盘解码模块:
代码实现了一个按键解码模块,用于将按键的消抖后的动作脉冲信号转换为按键的对应数字编码,并输出对应的数码管数据显示使能信号。
按键的功能映射可以在这里修改
以下是代码的主要实现逻辑:
- 时钟和复位控制:
- 使用
clk
作为系统时钟信号,rst_n
作为系统复位信号。
- 使用
- 按键解码:
- 在时钟的上升沿或复位时,通过对输入的
key_pulse
进行检测,判断按键是否按下。 - 根据不同的按键编码,设置
key_num
的值。 - 当检测到按键按下时,将
key_num_en
置为 1,表示已接收到按键数据;否则将key_num_en
置为 0。
- 在时钟的上升沿或复位时,通过对输入的
- 数码管数据显示使能:
- 在复位时,将
data_en
置为 0,表示数码管的显示数据未使能。 - 在时钟的下降沿或复位时,将
data_en
置为全1,表示所有数码管的显示数据均使能。
- 在复位时,将
module key_decode(
input clk,
input rst_n,
input [15:0]key_pulse, //按键消抖后动作脉冲信号
output reg [3:0] key_num,
output reg [7:0] data_en, //接收到的数据
output reg key_num_en //接收到的数据
);
// key_pulse 转换为 key_num
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_num <= 4'b0000;
key_num_en <= 1'b0;
end else begin
case (key_pulse)
16'h0001: begin key_num <= 4'd7; key_num_en <= 1'b1; end // 7
16'h0002: begin key_num <= 4'd8; key_num_en <= 1'b1; end // 8
16'h0004: begin key_num <= 4'd9; key_num_en <= 1'b1; end // 9
16'h0008: begin key_num <= 4'd13; key_num_en <= 1'b1; end // div
16'h0010: begin key_num <= 4'd4; key_num_en <= 1'b1; end // 4
16'h0020: begin key_num <= 4'd5; key_num_en <= 1'b1; end // 5
16'h0040: begin key_num <= 4'd6; key_num_en <= 1'b1; end // 6
16'h0080: begin key_num <= 4'd12; key_num_en <= 1'b1; end // X
16'h0100: begin key_num <= 4'd1; key_num_en <= 1'b1; end // 3
16'h0200: begin key_num <= 4'd2; key_num_en <= 1'b1; end // 2
16'h0400: begin key_num <= 4'd3; key_num_en <= 1'b1; end // 1
16'h0800: begin key_num <= 4'd11; key_num_en <= 1'b1; end // -
16'h1000: begin key_num <= 4'd0; key_num_en <= 1'b1; end // 0
16'h2000: begin key_num <= 4'd15; key_num_en <= 1'b1; end // .
16'h4000: begin key_num <= 4'd14; key_num_en <= 1'b1; end // =
16'h8000: begin key_num <= 4'd10; key_num_en <= 1'b1; end // +
default: begin key_num <= key_num; key_num_en <= 1'b0; end // 无按键按下时保持
endcase
end
end
//移位寄存器,对应8位数码管数据显示使能
always @ ( negedge rst_n) begin
if(!rst_n)
data_en <= 1'b0;
else
data_en <= {8'b11111111};
end
endmodule
数据移位模块:
该模块实现了一个简单的数据位移功能,将输入的数字左移一位。
- 输入端口:
num_buff
:一个14位的输入端口,表示要进行位移操作的数字。key_num_en
:一个单比特输入端口,表示是否启用键值输入。key_num
:一个4位的输入端口,表示键值。假设是按照BCD码输入的,即0到9的数字。
- 输出端口:
out_value
:一个14位的输出端口,表示位移后的值。num_change_flag
:一个单比特输出端口,表示数字是否发生了变化。
- 总体逻辑:
- 如果
key_num_en
为高电平且key_num
小于10(即4位BCD码小于10),则启用数据位移操作。 - 在位移操作中,将输入的
num_buff
左移一位(相当于乘以10),然后加上key_num
。 - 输出结果到
out_value
。 - 设置
num_change_flag
为高电平,表示数字已经发生了变化。 - 如果
key_num_en
为低电平或者key_num
大于等于10,则不执行位移操作,直接将num_change_flag
设置为低电平,表示数字未发生变化。
- 如果
module data_shift (
input wire [13:0]num_buff,
input wire key_num_en,
input wire[3:0]key_num,
output reg[13:0]out_value,
output reg num_change_flag
);
always@(*)
if(key_num_en && (key_num < 4'b1010)) begin
out_value <= num_buff * 10 + key_num;
num_change_flag <= 1'b1;
end
else
num_change_flag <= 1'b0;
endmodule
BCD解码模块:
该模块实现了将输入的二进制数字转换为BCD码表示的数字的功能。 将二进制表示的数字转换为BCD码表示的数字并最终用于数码管的显示:
- 输入端口:
binary
:一个14位的输入端口,表示输入的二进制数字。
- 输出端口:
g
、s
、b
、q
:每个输出端口都是一个4位的BCD码。
- 内部寄存器:
z
:一个30位的寄存器,用于存储BCD码和二进制码的转换结果。
- 总体逻辑:
- 在
always
块内,首先将寄存器z
清零。 - 将输入的二进制数字
binary
存储到z
的低14位中。 - 通过一个循环重复14次,进行BCD码的转换。每次循环:
- 如果当前BCD码的高4位大于4,说明需要加3来进行BCD码的调整。
- 将各个BCD码的四位进行相应的调整。
- 将
z
中的所有位向左移动一位。
- 最后,将调整后的BCD码从
z
中提取出来,分别分配给g
、s
、b
、q
输出端口。
- 在
module bcd_decode(
input wire [13:0] binary,
output wire [3:0] g,
output wire [3:0] s,
output wire [3:0] b,
output wire [3:0] q
);
reg [29:0] z;
always @ (*)
begin
z = 29'b0;
z[13:0] = binary;
repeat (14)
begin
if(z[17:14]>4)
z[17:14] = z[17:14] + 2'b11;
if(z[21:18]>4)
z[21:18] = z[21:18] + 2'b11;
if(z[25:22]>4)
z[25:22] = z[25:22] + 2'b11;
if(z[29:26]>4)
z[29:26] = z[29:26] + 2'b11;
z[29:1] = z[28:0];
end
end
assign q = z[29:26];
assign b = z[25:22];
assign s = z[21:18];
assign g = z[17:14];
endmodule
运算逻辑模块:
该模块实现了一个简单的计算器功能,可以进行加减乘除四则运算。
- 输入端口:
num_a
、num_b
:分别表示输入的两个操作数,每个都是14位的。dis_flag
:一个单比特输入端口,表示显示标志,用于指示是否显示操作数。opera_flag
:一个4位的输入端口,表示运算符标志,用于指示进行何种运算。en
:一个单比特输入端口,表示使能信号,用于控制是否执行计算操作。key_num
:一个4位的输入端口,用于接收用户输入的操作符。
- 输出端口:
out_a
、out_b
:分别表示输出的两个操作数,每个都是14位的。dis_flago
:一个单比特输出端口,表示显示标志的输出。opera_flago
:一个4位的输出端口,表示运算符标志的输出。out_flagd
:一个单比特输出端口,表示操作完成标志的输出。
- 总体逻辑:
- 在
always
块内,通过组合逻辑实现了根据输入条件执行不同的操作。 - 如果使能信号
en
为高电平且用户输入的操作符key_num
大于9(即大于9表示用户输入的是运算符),则开始执行计算操作。 - 如果显示标志
dis_flag
为低电平,表示当前不需要显示结果,此时将操作符和输入的操作数分别传递到输出端口,并设置显示标志为高电平,表示需要显示操作数。 - 如果显示标志
dis_flag
为高电平,表示需要显示结果,此时根据输入的运算符进行相应的计算操作: - 如果是加法,则将两个操作数相加,并将结果传递到输出端口。
- 如果是减法,则将两个操作数相减,并将结果传递到输出端口。
- 如果是乘法,则将两个操作数相乘,并将结果传递到输出端口。
- 如果是除法,则将两个操作数相除,并将结果传递到输出端口。
- 在执行完计算后,将显示标志设置为低电平,表示不再需要显示操作数,同时将操作完成标志
out_flagd
设置为高电平,表示操作完成。 - 如果使能信号
en
为低电平,则将操作完成标志out_flagd
设置为低电平,表示操作未完成。
- 在
module calculator(
input wire [13:0]num_a,
input wire [13:0]num_b,
input wire dis_flag,
input wire [3:0]opera_flag,
input wire en,
input wire[3:0]key_num,
output reg[13:0]out_a,
output reg[13:0]out_b,
output reg dis_flago,
output reg [3:0]opera_flago,
output reg out_flagd
);
always@(*)
if(en && (key_num > 4'b1001)) begin
if(key_num < 4'b1111)
if(dis_flag == 1'b0) begin
opera_flago <= key_num;
out_a <= num_a;
out_b <= num_b;
dis_flago <= 1'b1;
end
else if(dis_flag == 1'b1) begin
if(opera_flag == 4'b1010)begin
out_a <= num_a + num_b;
out_b <= 14'h0;
end
else if(opera_flag == 4'b1011) begin
out_a <= num_a - num_b;
out_b <= 14'h0;
end
else if(opera_flag == 4'b1100) begin
out_a <= num_a * num_b;
out_b <= 14'h0;
end
else if(opera_flag == 4'b1101) begin
out_a <= num_a / num_b;
out_b <= 14'h0;
end
opera_flago <= key_num;
dis_flago <= 1'b0;
end
else if((dis_flag == 1'b1) && (key_num == 4'b1110)) begin
if(opera_flag == 4'b1010)begin
out_a <= num_a + num_b;
out_b <= 14'h0;
end
else if(opera_flag == 4'b1011) begin
out_a <= num_a - num_b;
out_b <= 14'h0;
end
else if(opera_flag == 4'b1100) begin
out_a <= num_a * num_b;
out_b <= 14'h0;
end
else if(opera_flag == 4'b1101) begin
out_a <= num_a / num_b;
out_b <= 14'h0;
end
dis_flago <= 1'b0;
end
out_flagd = 1'b1;
end
else begin
out_flagd <= 1'b0;
end
endmodule
4. 仿真波形图
仿真环境搭建
1 开源方案
用VS Code + iverilog + GTKwave仿真Verilog_iverilog+gtk-CSDN博客
实测上面的方案仿真后有点卡,不确定是电脑原因还是其他原因,所以又试了下modelsim方案
Windows下高效Verilog/System Verilog 开发环境搭建 - 知乎 (zhihu.com)
整体用下来感觉还挺流畅,对tb_array_keyboard.v进行仿真
5 扩展功能实现
使用TFTLCD来做显示,自行设计显示界面,代替数码管显示输入的参数、运算符以及计算后的结果。
这里只实现了驱动TFTLCD以及显示界面的设计
在lab13_picture_display的基础之上通过修改pic_ram数据将设计好的界面刷到TFT屏模上,其中pic_ram数据通过picture_binary_print.py脚本生成。
from PIL import Image
# 打开图像文件
img = Image.open('OIP.jpg')
# 将图像调整为 240x240 大小
resized_img = img.resize((240, 320))
# 保存调整大小后的图像
resized_img.save('output_image_resized.jpg')
# 将彩色图像转换为灰度图像(黑白)
gray_img = resized_img.convert("L")
# 设置阈值进行二值化处理
threshold = 200
binary_img = gray_img.point(lambda p: 0 if p < threshold else 255, "1")
# 保存二值化后的图像
binary_img.save("binary_image.jpg")
# 获取图像大小
width, height = binary_img.size
print("width ", width)
print("height ", height)
# 循环遍历每一行像素并打印
for y in range(height):
# 获取当前行的像素值
pixels = [binary_img.getpixel((x, y)) for x in range(width)]
# 将像素值转换为十六进制格式并打印
hex_pixels = "".join(["1" if p == 0 else "0" for p in pixels])
# 翻转像素顺序
hex_pixels_reversed = hex_pixels[::-1]
hex_pixels_reversed = hex(int(hex_pixels_reversed, 2))[2:].zfill(width // 4)
# 9'd0 : q = 240
hex_pixels_reversed = "9'd"+str(y)+" : q = 240'h" +hex_pixels_reversed + ";"
print(hex_pixels_reversed)
实现效果
6. FPGA的资源利用说明
FPGA的资源利用情况:
Design Summary:
Number of registers: 627 out of 4635 (14%)
PFU registers: 627 out of 4320 (15%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1144 out of 2160 (53%)
SLICEs as Logic/ROM: 1144 out of 2160 (53%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 353 out of 2160 (16%)
Number of LUT4s: 2278 out of 4320 (53%)
Number used as logic LUTs: 1572
Number used as distributed RAM: 0
Number used as ripple logic: 706
Number used as shift registers: 0
Number of PIO sites used: 19 + 4(JTAG) out of 105 (22%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)
7 GPT助手
在完成项目的过程中可以通过ChatGPT加速开发过程,提高开发效率。
通过ChatGPT来学习Verilog:
代码语法错误检测:
脚本编写:
总结
通过本项目,实现了一个两位十进制加、减、乘、除计算器。用户可以通过按键输入两个数和运算符,计算器将对这两个数执行相应的运算,并在数码管上显示结果。在项目开发过程中,通过ChatGPT的帮助,加速了Verilog代码的学习、编写和调试过程,提高了开发效率。