2024年寒假练 - 基于小脚丫FPGA实现的简易计算器
本项目是基于小脚丫FPGA(STEP BaseBoard V4.0)实现的简易计算器。实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。
标签
FPGA
maskmoo
更新2024-04-01
227

1. 项目需求

本项目旨在实现一个两位十进制加、减、乘、除计算器。用户可以通过按键输入两个两位十进制数和一个运算符,计算器将对这两个数执行相应的运算,并在数码管上显示结果。

2. 需求分析及实现思路

该计算器需要完成以下功能:

通过按键输入两个两位十进制数和一个运算符。

执行加、减、乘、除四种运算。

在数码管上显示结果。

实现的思路如下:

设计计算器的硬件逻辑,包括按键扫描、数码管显示和运算逻辑。

编写Verilog代码来实现硬件逻辑。您需要实现按键扫描模块、数码管显示模块和运算逻辑模块。

使用思得普的WebIDE对Verilog代码进行编译和综合。

将综合后的设计下载到FPGA板上进行验证。使用STEP BaseBoard V4.0作为扩展底板。

编写Python脚本来实现图像处理部分,包括图像转换和二值化处理。

编写Python脚本来将处理后的图像数据发送到TFTLCD显示屏进行显示。

3. 功能实现及代码说明

为了实现该计算器,需要设计以下模块:

按键扫描模块:

负责扫描按键输入,并将按键值传递给主控制模块。采用的是矩阵键盘的示例代码,其主要功能是通过扫描矩阵按键的行和列,检测按键的按下状态,并输出相应的按键值。

代码的主要实现逻辑:

  1. 计时器及状态机:
    • 使用一个计数器 cnt 实现了一个200Hz的时钟信号 clk_200hz,用于状态机的时序控制。
    • 状态机 c_state 包含4种状态,分别对应矩阵键盘的4行。状态机每隔20ms切换一次状态,对应矩阵键盘的4行分别被选中。
  2. 状态机切换与行扫描:
    • 根据状态机的状态,选择对应的行输出使能信号 row,通过该信号控制矩阵键盘的行。
    • 状态机每隔20ms切换一次状态,对应于每行扫描一次。
  3. 列采样与按键检测:
    • 对于每个状态,通过列接口 col 采样键盘的列电平状态。
    • 将采样到的列电平状态与上一时刻的状态进行比较,如果有变化,则表示有按键按下。
    • 将按键按下的情况存储在 key_out 中,同时延迟一个时钟周期进行锁存。
  4. 按键脉冲输出:
    • 通过比较上一时刻和当前时刻按键状态,产生按键脉冲信号 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芯片和数码管,实现了对多个数码管的显示。

以下是代码的主要实现逻辑:

  1. 时钟和复位控制:
    • 使用 clk 作为系统时钟信号,rst_n 作为系统复位信号。
    • 使用一个计数器 cnt 对系统时钟进行计数,并产生一个40kHz的时钟信号 clk_40khz 作为主要的时序控制。
  2. 数码管字库:
    • 使用 seg 数组存储了数码管的字库数据,每个元素表示一个十六进制数对应的段码。
  3. 状态机控制:
    • 使用状态机实现对数码管的扫描和数据更新。
    • 初始状态为 IDLE,表示软复位状态,然后切换到 MAIN 状态进行数码管的主要扫描和显示。
    • MAIN 状态下,对8位数码管逐位扫描,根据 dat_endot_en 控制位选和段选,更新数码管数据。
    • WRITE 状态下,按照74HC595的时序要求,将数据逐位串行传输到74HC595芯片中,并在传输完成后将 seg_rck 拉高,使输出生效。
  4. 数码管数据更新:
    • 根据状态机的状态和计时器的计数,逐位更新数码管的显示数据和段选控制信号。
// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> 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


键盘解码模块:

代码实现了一个按键解码模块,用于将按键的消抖后的动作脉冲信号转换为按键的对应数字编码,并输出对应的数码管数据显示使能信号。

按键的功能映射可以在这里修改

以下是代码的主要实现逻辑:

  1. 时钟和复位控制:
    • 使用 clk 作为系统时钟信号,rst_n 作为系统复位信号。
  2. 按键解码:
    • 在时钟的上升沿或复位时,通过对输入的 key_pulse 进行检测,判断按键是否按下。
    • 根据不同的按键编码,设置 key_num 的值。
    • 当检测到按键按下时,将 key_num_en 置为 1,表示已接收到按键数据;否则将 key_num_en 置为 0。
  3. 数码管数据显示使能:
    • 在复位时,将 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

数据移位模块:

该模块实现了一个简单的数据位移功能,将输入的数字左移一位。

  1. 输入端口:
    • num_buff:一个14位的输入端口,表示要进行位移操作的数字。
    • key_num_en:一个单比特输入端口,表示是否启用键值输入。
    • key_num:一个4位的输入端口,表示键值。假设是按照BCD码输入的,即0到9的数字。
  2. 输出端口:
    • out_value:一个14位的输出端口,表示位移后的值。
    • num_change_flag:一个单比特输出端口,表示数字是否发生了变化。
  3. 总体逻辑:
    • 如果 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码表示的数字并最终用于数码管的显示:

  1. 输入端口:
    • binary:一个14位的输入端口,表示输入的二进制数字。
  2. 输出端口:
    • gsbq:每个输出端口都是一个4位的BCD码。
  3. 内部寄存器:
    • z:一个30位的寄存器,用于存储BCD码和二进制码的转换结果。
  4. 总体逻辑:
    • always 块内,首先将寄存器 z 清零。
    • 将输入的二进制数字 binary 存储到 z 的低14位中。
    • 通过一个循环重复14次,进行BCD码的转换。每次循环:
      • 如果当前BCD码的高4位大于4,说明需要加3来进行BCD码的调整。
      • 将各个BCD码的四位进行相应的调整。
      • z 中的所有位向左移动一位。
    • 最后,将调整后的BCD码从 z 中提取出来,分别分配给 gsbq 输出端口。
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

运算逻辑模块:

该模块实现了一个简单的计算器功能,可以进行加减乘除四则运算。

  1. 输入端口:
    • num_anum_b:分别表示输入的两个操作数,每个都是14位的。
    • dis_flag:一个单比特输入端口,表示显示标志,用于指示是否显示操作数。
    • opera_flag:一个4位的输入端口,表示运算符标志,用于指示进行何种运算。
    • en:一个单比特输入端口,表示使能信号,用于控制是否执行计算操作。
    • key_num:一个4位的输入端口,用于接收用户输入的操作符。
  2. 输出端口:
    • out_aout_b:分别表示输出的两个操作数,每个都是14位的。
    • dis_flago:一个单比特输出端口,表示显示标志的输出。
    • opera_flago:一个4位的输出端口,表示运算符标志的输出。
    • out_flagd:一个单比特输出端口,表示操作完成标志的输出。
  3. 总体逻辑:
    • 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)

Modelsim 安装步骤详解-CSDN博客


整体用下来感觉还挺流畅,对tb_array_keyboard.v进行仿真

image-20240310191418314

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)

实现效果


image.png

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:

image.png


代码语法错误检测:

image.png


脚本编写:

image.png


总结


通过本项目,实现了一个两位十进制加、减、乘、除计算器。用户可以通过按键输入两个数和运算符,计算器将对这两个数执行相应的运算,并在数码管上显示结果。在项目开发过程中,通过ChatGPT的帮助,加速了Verilog代码的学习、编写和调试过程,提高了开发效率。

软硬件
附件下载
calculator.zip
implement.jed
OIP.jpg
picture_binary_print.py
团队介绍
1
团队成员
maskmoo
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号