2024年寒假练 - 基于小脚丫FPGA套件STEP BaseBoard V4.0设计的两位十进制加减乘除计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了两位十进制加减乘除计算器的设计,它的主要功能为:计算两位十进制加减乘除。
标签
FPGA
ty1441212902
更新2024-04-01
华东理工大学
132

项目需求

本项目旨在设计并实现一款基于`小脚丫FPGA套件STEP BaseBoard V4.0`的两位十进制数计算器,该计算器具备加、减、乘、除四种基本运算功能。具体需求如下:


  • 输入模块:通过独立的按键矩阵输入两位十进制数以及运算符(加、减、乘、除)。输入过程应遵循从低位到高位的顺序,即用户先输入个位数,随后输入的十位数会自动移至更高位显示,原个位数码管则留作输入新的数字。


  • 显示模块:采用2个4位8段数码管进行数据显示,数码管由74HC595芯片控制,节省io口。


  • 运算逻辑:根据用户输入的运算符进行相应的加法、减法、乘法和除法运算,并确保运算结果的正确性。考虑了溢出和除0的情况。

需求分析

  1. 对于两位数计算的理解:输入数据在两位十进制数以内,即0~99;输出则根据实际情况显示。由于项目审核时要求计算结果为四位数时也得正常工作,故升级8位二进制转3位bcd为16位二进制转5位bcd,顺便解除了两位数的限制到四位数。
  2. 输出数字小于9999
  3. 如果结果大于9999,或者减法结果为负数,则发生溢出,在另外设计的溢出指示灯上闪烁。按C键清空寄存器并退出溢出状态。
  4. 对于除法,不计算浮点结果,只计算商。除0时,按等号键不会进行计算,直到输入非0的除数。
  5. 综上所述,该计算器可以计算结果在0~9999内的加减乘除法。

为便于上板调试,引出了一些非必要的引脚:

  • y_reg_led:以二进制显示y寄存器内低八位的数值。
  • op_reg_led:用rgb led的不同颜色指示运算符寄存器的数值。

功能框图

其中箭头表示数据的流向,由控制逻辑控制。

代码说明

顶层输入输出

module top(
    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管脚
    output  reg [2:0]   of_led,         //溢出指示led
    // 下面的用于debug
    output      [7:0]   y_reg_led,      //y寄存器输出led
    output      [2:0]   op_reg_led      //运算符寄存器输出led
);
  • clk, rst_n: 时钟、复位是必要的,用于数码管的扫描显示和键盘的扫描读取。
  • col, row: 矩阵键盘的两组公共引脚。
  • seg_rck, seg_sck, seg_din是74HC595的三个输入引脚,用于向芯片发送显示信息。
  • of_led用于在计算发生溢出时输出1Hz的方波驱动led指示灯提示溢出。 由于核心板上为RGB LED,所以将另外两个引脚也引入,拉高电平,使其在不工作时熄灭。

寄存器

reg     [15:0]  x_reg;      //x寄存器
reg [15:0] y_reg; //y寄存器
reg [7:0] dat_en; //数码管位使能
reg [15:0] x_bcd_reg; //x寄存器对应bcd码
reg [1:0] op_reg; //运算符寄存器
wire [15:0] bcd_out; //bcd转换输出
wire [15:0] key_out; //按键输出(无用)
wire [15:0] key_pulse; //按键输出脉冲
wire of_flag; //溢出
wire bl_wire; //1Hz脉冲闪烁
wire fin; //bcd转换完成标识
  • x_reg, y_reg: 加法、减法、乘法、除法都是二元运算,而同一时刻显示在数码管上的只有一个数字,所以,设计了两个寄存器x、y,以便输入第二个数字时记录第一个数字。其中,x寄存器为16位(实际只需14位,因为包含溢出的最大可能结果为99*99=9801,转换为二进制数为14'b10_0110_0100_1001),y寄存器为8位。
  • op_reg: 运算符号寄存器。有四种运算,所以设置2位。
  • dat_en: 判断当前数字位数,控制显示模块显示数字个数。
  • x_bcd_reg: 存储x寄存器中数值的bcd码。
  • bcd_out: bcd转换输出。
  • key_pulse: 实时按键检测。
  • of_flag: 溢出标志。
  • bl_wire: 1Hz脉冲源,在溢出时输出闪烁信号。
  • fin: bcd转换完成标志。

组合逻辑

assign y_reg_led = ~y_reg[7:0];
assign op_reg_led = {1'b0,op_reg};
assign of_flag=(x_reg>9999);
// ... ... ... ...
always@(*) begin
if(x_reg>=1000) //大于1000时显示4个数字
dat_en<=8'b00001111;
    else if(x_reg>=100) //大于100时显示3个数字
        dat_en<=8'b00000111;
    else if(x_reg >=10) //大于10时显示2个数字
        dat_en<=8'b00000011;
    else //否则只显示1个数字
        dat_en<=8'b00000001;

    if(of_flag) //溢出mux
        of_led[0]<=bl_wire;
    else
        of_led[0]<=1;
end​
  • 第一行:debug使用,将y寄存器内容取反输出,因为FPGA控制LED灯的负极电平。
  • 第二行:debug使用,将运算符指示灯的一个无用引脚拉低,稳定颜色。
  • 第三行:溢出检测。当出现溢出时,x寄存器中的计算结果的高位会存在1,所以将x寄存器的高八位连接一个多输入或门,溢出时产生信号。
  • 行为建模第一部分: 判断x寄存器中数字长度,控制显示模块的数字显示位数。
  • 行为建模第二部分: 一个二选一选择器。溢出时将1Hz脉冲源输出到指示灯上。

BIN转BCD

module bin2bcd16
(
input CLK,
input RST,

input en,
input [15:0] bin,

output [3:0] bcd0,
output [3:0] bcd1,
output [3:0] bcd2,
output [3:0] bcd3,
output [3:0] bcd4,

output busy,
output fin
);

reg [15:0] bin_r;
reg [3:0] bitcount;
reg [3:0] bcd[0:4];
wire [3:0] bcdp[0:4];

assign bcd0 = bcd[0];
assign bcd1 = bcd[1];
assign bcd2 = bcd[2];
assign bcd3 = bcd[3];
assign bcd4 = bcd[4];

localparam s_idle = 2'b00;
localparam s_busy = 2'b01;
localparam s_fin = 2'b10;
reg [1:0] state;

assign busy = state != s_idle;
assign fin = state == s_fin;

always @(posedge CLK or negedge RST)
if (!RST) begin
state <= s_idle;
end else begin
case (state)
s_idle:
if (en)
state <= s_busy;

s_busy:
if (bitcount == 4'd15)
state <= s_fin;

s_fin:
state <= s_idle;

default: ;
endcase
end

always @(posedge CLK) begin
case (state)
s_idle:
if (en)
bin_r <= bin;
s_busy:
bin_r <= {bin_r[14:0], 1'b0};
default: ;
endcase
end

always @(posedge CLK or negedge RST)
if (!RST) begin
bitcount <= 4'd0;
end else begin
case (state)
s_busy:
bitcount <= bitcount + 4'd1;

default:
bitcount <= 4'd0;
endcase
end

generate
genvar g;

for (g=0; g<=4; g=g+1) begin : GEN_BCD

wire [3:0] s;
wire [3:0] prev;

assign bcdp[g] = (bcd[g] >= 4'd5) ? bcd[g] + 4'd3 : bcd[g];

if (g != 0) begin
assign prev = bcdp[g-1];
end else begin // if (g != 0)
assign prev = {bin_r[15], 3'b0};
end
assign s
= ((bcdp[g] << 1) | (prev >> 3));

always @(posedge CLK or negedge RST)
if (!RST) begin
bcd[g] <= 4'd0;
end else begin
case (state)
s_idle:
bcd[g] <= 4'd0;

s_busy:
bcd[g] <= s;

default: ;
endcase
end

end
endgenerate

endmodule

利用加3移位实现。四位二进制大于15才进位,而8421BCD码是大于9就进位,若四位二进制大于9时进位,这样得到的就是15的BCD码,因此将大于9的四位二进制数加6就能得到其BCD码。对于大于四位的二进制数,通过左移,逢9加6进位,即可转换任意位的二进制数,比如说,对于5位二进制数,由高4位二进制数左移一位得到,那么将前4位得到的BCD码也左移一位,并重新判断低四位是否大于9,若大于9,则加6进位,即可得到5位二进制数对应的BCD码。

这种实现方法易于理解,代码容易编写,但综合后电路层级复杂,延时较大,但做8位二进制以内数据转换影响不大。

实际上最初本来想直接利用所有数码管制作8位数的计算器,但按此方法综合转换模块后,再加上后续乘除法查找表的资源占用,使用资源已经超过了该芯片,因此只能缩减了配置。

溢出闪烁

module blink(
    input clk,
    input rst_n,
    output reg blink
    );
    reg [23:0] counter;
   
    always@(posedge clk)begin
        if(!rst_n)begin
            counter<=0;
            blink <= 0;
            end
        else begin
            counter<=counter+1;
            if(counter == 12000000) begin
                counter <= 0;
                    blink <= !blink;
            end
        end
    end
endmodule

非常简单的利用计时器制作的闪烁功能。

模块例化

bin2bcd bb(x_reg,x_bcd_reg);
blink bl(clk,rst_n,bl_wire);
segment_scan ss(
        .clk(clk),                  
        .rst_n(rst_n),  
        .dat_1(4'b0),  
        .dat_2(4'b0),  
        .dat_3(4'b0),  
        .dat_4(4'b0),  
        .dat_5(4'b0),  
        .dat_6(x_bcd_reg[11:8]),    
        .dat_7(x_bcd_reg[7:4]),
        .dat_8(x_bcd_reg[3:0]),
        .dat_en(dat_en),    
        .dot_en(8'd0),  
        .seg_rck(seg_rck),  
        .seg_sck(seg_sck),  
        .seg_din(seg_din)  
    );

array_keyboard ak(
            .clk(clk),
            .rst_n(rst_n),
            .col(col),
            .row(row),
            .key_out(key_out),
            .key_pulse(key_pulse)
    );

分别为bin转bcd码、1Hz脉冲源、扫描显示、矩阵键盘的例化。

时序逻辑

always@(posedge clk) begin
    if(!rst_n) begin //按下复位重置所有状态
        x_reg<=0;  
        y_reg<=0;
        op_reg<=0;
        of_led[2:1]<=2'b11;//拉高共阳指示灯的蓝色和绿色引脚,使其在发生溢出时,闪烁纯正的红色
    end else begin
        case(key_pulse)
            16'h0001: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+7;
                end
            end
            16'h0002: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+8;
                end
            end
            16'h0004: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+9;
                end
            end
            16'h0008: begin
                y_reg<=x_reg;x_reg<=0;op_reg<=2'd3;
            end
            16'h0010: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+4;
                end
            end
            16'h0020: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+5;
                end
            end
            16'h0040: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+6;
                end
            end
            16'h0080: begin
                y_reg<=x_reg;x_reg<=0;op_reg<=2'd2;
            end
            16'h0100: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+1;
                end
            end
            16'h0200:begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+2;
                end
            end
            16'h0400:begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10+3;
                end
            end
            16'h0800: begin
                y_reg<=x_reg;x_reg<=0;op_reg<=2'd1;
            end
            16'h1000: begin
                if(x_reg<10)begin
                    x_reg<=x_reg*10;
                end
            end
            16'h2000: x_reg <= 0;
            16'h4000: begin
                case(op_reg)
                2'd0:x_reg <= y_reg+x_reg;
                2'd1:x_reg <= y_reg-x_reg;
                2'd2:x_reg <= y_reg*x_reg;
                2'd3:begin //除数为0时不操作
                    if(x_reg!=0)
                        x_reg <= y_reg/x_reg;
                    end
                endcase
            end
            16'h8000: begin
                y_reg<=x_reg;x_reg<=0;op_reg<=2'd0;
            end
        endcase
    end
end

复位、按键操作部分。

  • 用户按下数字按键时:先对x寄存器内数值进行判断,如果为单位数,则允许用户继续输入;如果多于一位则不做处理。输入时,对应不同的数字按键分支,将x寄存器中的数值乘10后加上新输入的数字。
  • 当用户输入完第一个操作数后,按下运算符号按键,将输入的运算符号记录到op寄存器中,并将x寄存器中的数值转移到y寄存器中。
  • 同上述过程,输入完第二个操作数,这时需要按下等号按键。按下等号按键后,若为加、减、乘,则进行运算后把结果送入x寄存器;若为除法,则判断除数是否为0,若为0,则跳过不做任何处理,直到用户输入非零除数。
  • 任何时候用户按下清零按键,都会令x寄存器中数值为0(包括用于检测溢出的位)。

显示、输入部分

这部分代码复用了stepfpga的例程,不多做赘述。

矩阵键盘

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;
   
    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;
    always @ ( posedge clk  or  negedge rst_n )
        if (!rst_n) key_out_r <= 16'hffff;
        else  key_out_r <= key_out;   //将前一刻的值延迟锁存
   
    assign key_pulse= key_out_r & ( ~key_out);   //通过前后两个时刻的值判断
   
endmodule

主要原理是对键盘的四行循环输入高电平,如果该行某列的按键为按下,则在输出端会接收到这个高电平,依次将16个电平状态存入一个寄存器。

而如果在一定间隔内,电平状态上升且不变,就说明该按键被稳定地按下(消抖)。

显示

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

按照74HC595的时序进行扫描输入,每次sck上升沿,都会将din的数据存入移位寄存器。最后给rck下降沿刷新数据。

开发板上595芯片前8位输出为位选,后8位为段选,依次位选输出数字后切换下一位继续显示。

仿真波形图

使用WebIDE模拟。

显示模块

键盘模块

1Hz 脉冲

二进制转BCD

FPGA资源利用说明

Design Summary:

Number of registers: 238 out of 4635 (5%)

PFU registers: 238 out of 4320 (6%)

PIO registers: 0 out of 315 (0%)

Number of SLICEs: 668 out of 2160 (31%)

SLICEs as Logic/ROM: 668 out of 2160 (31%)

SLICEs as RAM: 0 out of 1620 (0%)

SLICEs as Carry: 433 out of 2160 (20%)

Number of LUT4s: 1324 out of 4320 (31%)

Number used as logic LUTs: 458

Number used as distributed RAM: 0

Number used as ripple logic: 866

Number used as shift registers: 0

Number of PIO sites used: 27 + 4(JTAG) out of 105 (30%)

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%)

使用webide查看。

未来计划

这个平台的资源和外设极为丰富,常用外设一应俱全,未来计划在这个芯片上构建FC游戏机的理光6502CPU、RP2C02 PPU,将其模拟成一台FC游戏机,或者老式Apple II计算机。

同时,本人正在参加“一生一芯”项目,若有可能,将会在此平台实现一部分项目中的功能。

建议

以下是对于这个板卡平台的建议:

  • 核心板最好还是能支持JTAG,或者引出接口
  • 数码管使用74HC595,以及矩阵键盘扫描,需要一些资源。可以考虑换成专职芯片如天微(TM)公司的一系列芯片,走总线方式传数据,避免扫描;或者可以设计两个并排的核心板插入位置,一侧用于练习在FPGA上驱动物理外设,一侧用于通过芯片连接外设
  • 对于存储方面的外设比较少,建议添加SD卡槽
  • FPGA内置ram只有92Kb,而屏幕以8位色显示图形需要600Kb ram,无上位机情况下,内置资源只能支持显示黑白图片,建议添加一块spi psram作为显存

最后,再次感谢硬禾学堂电子森林对本次活动的大力支持!

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