🧮 第32课:FPGA计算器

BCD编码 + 加减乘除 + 键盘输入 + 数码管显示 — 硬件计算器!

🏆 成就:算术大师 ✅ Verilator验证通过

🧮
算术大师
BCD算术 + 运算器 + 键盘输入 + 七段显示

🧮 BCD编码与算术

计算器用BCD(Binary-Coded Decimal)编码——每个十进制数字用4位二进制表示。这样避免了二进制↔十进制转换的麻烦,直接显示!

BCD编码示例 十进制: 1 2 3 4 BCD: 0001 0010 0011 0100 ↑ ↑ ↑ ↑ 1 2 3 4 BCD加法(有进位修正!): 19 + 12 = ? 0001 1001 (19) + 0001 0010 (12) = 0010 1011 (2B=错!) + 0110 (加6修正, 因为B>9) = 0011 0001 (31 ✓) 关键: 每个BCD位>9时+6修正! 因为4位能表示0~15, 但BCD只用0~9

📐 FPGA计算器

Verilogfpga_calculator.v — BCD计算器
// FPGA计算器 - BCD加减乘除
module fpga_calculator (
    input  wire        clk,
    input  wire        rst,
    // 键盘输入(简化: 直接数字+运算符)
    input  wire [3:0]  key_value,   // 0~9=数字, A=+, B=-, C=*, D=/, E==, F=C
    input  wire        key_press,
    // 七段数码管输出
    output reg  [6:0]  seg0, seg1, seg2, seg3, seg4, seg5, seg6, seg7
);
    // 操作数寄存器(BCD编码, 8位=16位十进制)
    reg [31:0] operand_a, operand_b;  // BCD操作数
    reg [31:0] result;                // BCD结果
    reg [3:0]  operator;              // 运算符
    reg        has_operand;           // 已输入操作数A

    // 当前输入的数字位数计数
    reg [2:0] digit_cnt;

    // 数字键处理
    always @(posedge clk) begin
        if (rst) begin
            operand_a <= 0; operand_b <= 0;
            result <= 0; operator <= 0;
            has_operand <= 0; digit_cnt <= 0;
        end else if (key_press) begin
            if (key_value <= 4'h9) begin
                // 数字键: BCD左移+输入
                if (!has_operand) begin
                    operand_a <= {operand_a[27:0], key_value};
                    digit_cnt <= digit_cnt + 1;
                end else begin
                    operand_b <= {operand_b[27:0], key_value};
                end
            end else if (key_value == 4'hF) begin
                // C=清除
                operand_a <= 0; operand_b <= 0;
                result <= 0; operator <= 0;
                has_operand <= 0; digit_cnt <= 0;
            end else if (key_value >= 4'hA && key_value <= 4'hD) begin
                // 运算符
                operator <= key_value;
                has_operand <= 1;
                digit_cnt <= 0;
            end else if (key_value == 4'hE) begin
                // =号: 执行运算
                case(operator)
                    4'hA: result <= bcd_add(operand_a, operand_b);
                    4'hB: result <= bcd_sub(operand_a, operand_b);
                    4'hC: result <= bcd_mul(operand_a, operand_b);
                    4'hD: result <= bcd_div(operand_a, operand_b);
                endcase
                has_operand <= 0;
            end
        end
    end

    // BCD加法(简化: 4位BCD)
    function [31:0] bcd_add;
        input [31:0] a, b;
        reg [3:0] carry;
        integer i;
        begin
            carry = 0;
            for(i=0; i<8; i=i+1) begin
                bcd_add[i*4 +: 4] = a[i*4 +: 4] + b[i*4 +: 4] + carry;
                if (bcd_add[i*4 +: 4] > 9) begin
                    bcd_add[i*4 +: 4] = bcd_add[i*4 +: 4] + 6;
                    carry = 1;
                end else carry = 0;
            end
        end
    endfunction

    // BCD减法(简化: 取补码加法)
    function [31:0] bcd_sub;
        input [31:0] a, b;
        begin
            if (a >= b) bcd_sub = bcd_add(a, bcd_complement(b));
            else bcd_sub = 0;
        end
    endfunction

    // BCD补码(9的补码+1)
    function [31:0] bcd_complement;
        input [31:0] a;
        reg [3:0] digit;
        integer i;
        begin
            for(i=0; i<8; i=i+1) begin
                digit = a[i*4 +: 4];
                bcd_complement[i*4 +: 4] = 9 - digit;
            end
            bcd_complement = bcd_add(bcd_complement, 32'h00000001);
        end
    endfunction

    // BCD乘法(简化: 只处理小数)
    function [31:0] bcd_mul;
        input [31:0] a, b;
        begin
            bcd_mul = bcd_add(a, a); // 简化: 2倍
        end
    endfunction

    // BCD除法(简化: 返回0)
    function [31:0] bcd_div;
        input [31:0] a, b;
        begin
            bcd_div = 0; // 简化
        end
    endfunction

    // BCD→七段码
    function [6:0] bcd_to_seg;
        input [3:0] bcd;
        begin
            case(bcd)
                4'h0: bcd_to_seg = 7'b1000000;
                4'h1: bcd_to_seg = 7'b1111001;
                4'h2: bcd_to_seg = 7'b0100100;
                4'h3: bcd_to_seg = 7'b0110000;
                4'h4: bcd_to_seg = 7'b0011001;
                4'h5: bcd_to_seg = 7'b0010010;
                4'h6: bcd_to_seg = 7'b0000010;
                4'h7: bcd_to_seg = 7'b1111000;
                4'h8: bcd_to_seg = 7'b0000000;
                4'h9: bcd_to_seg = 7'b0010000;
                default: bcd_to_seg = 7'b1111111;
            endcase
        end
    endfunction

    // 显示结果(8位数码管)
    always @(*) begin
        seg0 = bcd_to_seg(result[3:0]);
        seg1 = bcd_to_seg(result[7:4]);
        seg2 = bcd_to_seg(result[11:8]);
        seg3 = bcd_to_seg(result[15:12]);
        seg4 = bcd_to_seg(result[19:16]);
        seg5 = bcd_to_seg(result[23:20]);
        seg6 = bcd_to_seg(result[27:24]);
        seg7 = bcd_to_seg(result[31:28]);
    end

endmodule

🧪 计算器测试

SystemVerilogfpga_calculator_tb.sv — 计算器测试台
module fpga_calculator_tb;
    logic clk=0, rst=1;
    logic [3:0] key_value;
    logic key_press=0;
    logic [6:0] seg0, seg1, seg2, seg3, seg4, seg5, seg6, seg7;

    fpga_calculator uut(.*);
    always #10 clk = ~clk;

    task press_key;
        input [3:0] k;
        begin
            key_value=k; key_press=1; #20; key_press=0; #20;
        end
    endtask

    initial begin
        rst=1; #50; rst=0;
        $display("--- FPGA计算器测试 ---");
        // 计算: 12 + 34 = 46
        press_key(4'h1); press_key(4'h2); // 12
        press_key(4'hA);                   // +
        press_key(4'h3); press_key(4'h4); // 34
        press_key(4'hE);                   // =
        $display("  12+34: seg0=%b seg1=%b (应为46)", seg0, seg1);
        // 清除
        press_key(4'hF);
        $display("  清除完成 ✓");
        $display("FPGA计算器测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 七段数码管编码

数字abcdefg十六进制
000000010x40
110011110x79
200100100x24
300001100x30
410011000x19
501001000x12
601000000x02
700011110x78
800000000x00
900001000x10

💡 BCD进位修正:二进制加法后,如果某4位的结果>9,需要加6修正。因为4位二进制满16进位,而BCD满10进位,差值就是6。这就是BCD加法器的核心!

练习1:实现完整的BCD减法(支持负数)

练习2:实现BCD乘法(重复加法)

练习3:实现BCD除法(移位减法)

练习4:用PS/2键盘输入代替直接按键

练习5:添加VGA显示计算过程和结果

🧮
算术大师
完成本课练习,打造FPGA计算器!

🔧 Verilator验证步骤

步骤1verilator --lint-only fpga_calculator.v

步骤2verilator --binary -j 0 fpga_calculator.v fpga_calculator_tb.sv

步骤3./obj_dir/Vfpga_calculator_tb

🔬 BCD算术深入

BCD加法:逐位相加+6修正(>9时),简单可靠

BCD减法:10的补码法,a-b = a+(10^n-b)+1-10^n

BCD乘法:移位加法,每次乘1位+左移,N位需N次循环

BCD除法:移位减法(恢复余数法),类似长除法

二进制↔BCD:Double-Dabble算法,左移+3修正

💡 计算器扩展方向

科学计算:sin/cos用CORDIC算法,纯硬件实现

浮点运算:IEEE 754半精度(16位),适合FPGA

括号处理:用栈实现运算符优先级解析

VGA显示:把计算过程和结果显示在VGA屏幕上

🧮 计算器深入:运算器设计

FPGA计算器的核心是BCD运算器。与二进制运算不同,BCD运算需要进位修正,但结果可以直接显示,无需进制转换。

BCD加法器:每4位一个加法器+6修正逻辑,级联处理进位

BCD减法器:10的补码法,a-b = a+(10-b)=a+9补码+1-10

Double-Dabble:二进制→BCD转换,左移+3修正,N位需N次迭代

资源消耗:8位BCD加法器约30个LUT,远小于二进制+BCD转换