3. 定点乘法器设计

定点乘法器设计 学习卡片

任务说明

设计一个参数化定点乘法器RTL模块,支持有符号与无符号两种操作数模式,输出位宽自动扩展以避免截断,并提供溢出标志。输入为两个N位操作数,输出为2N位乘积结果,同时输出1位溢出指示(当结果无法由N位表示时置位)。

端口定义

module fixed_mult #(
    parameter N = 8          // 操作数位宽
)(
    input  wire             clk,
    input  wire             rst_n,
    input  wire             sign_en,   // 1:有符号乘法, 0:无符号乘法
    input  wire [N-1:0]     a,
    input  wire [N-1:0]     b,
    input  wire             valid_in,
    output reg  [2*N-1:0]   product,   // 完整乘积
    output reg              overflow,  // 溢出标志
    output reg              valid_out
);

实现方案

乘法器采用组合逻辑完成核心乘法运算,在时钟上升沿寄存结果。有符号模式将输入转换为补码后执行乘法;无符号模式直接相乘。溢出判定规则:

  • 无符号模式:乘积高N位非零即溢出
  • 有符号模式:乘积超出N位有符号数范围[-2^(N-1), 2^(N-1)-1]即溢出

RTL代码

module fixed_mult #(
    parameter N = 8
)(
    input  wire             clk,
    input  wire             rst_n,
    input  wire             sign_en,
    input  wire [N-1:0]     a,
    input  wire [N-1:0]     b,
    input  wire             valid_in,
    output reg  [2*N-1:0]   product,
    output reg              overflow,
    output reg              valid_out
);

    // 内部信号
    wire [2*N-1:0] prod_uns;      // 无符号乘积
    wire [2*N-1:0] prod_sgn;      // 有符号乘积
    wire [2*N-1:0] prod_sel;      // 选择后的乘积
    wire           ovf_uns;       // 无符号溢出
    wire           ovf_sgn;       // 有符号溢出
    wire           ovf_sel;       // 选择后的溢出

    // 无符号乘法
    assign prod_uns = a * b;

    // 有符号乘法(将输入扩展为2N位后相乘)
    wire signed [2*N-1:0] a_sgn = {{N{a[N-1]}}, a};
    wire signed [2*N-1:0] b_sgn = {{N{b[N-1]}}, b};
    assign prod_sgn = a_sgn * b_sgn;

    // 根据模式选择乘积
    assign prod_sel = sign_en ? prod_sgn : prod_uns;

    // 溢出检测
    // 无符号:高N位非零
    assign ovf_uns = (prod_uns[2*N-1:N] != 0);

    // 有符号:结果超出N位有符号范围
    // 检查高N位是否全为符号扩展位(全0或全1)
    wire [N-1:0] high_part = prod_sgn[2*N-1:N];
    wire sign_bit = prod_sgn[2*N-1];
    wire [N-1:0] sign_ext  = {N{sign_bit}};
    assign ovf_sgn = (high_part != sign_ext);

    assign ovf_sel = sign_en ? ovf_sgn : ovf_uns;

    // 时序输出
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            product   <= 0;
            overflow  <= 0;
            valid_out <= 0;
        end else begin
            product   <= prod_sel;
            overflow  <= ovf_sel;
            valid_out <= valid_in;
        end
    end

endmodule

测试平台

module tb_fixed_mult;
    parameter N = 8;
    reg clk, rst_n, sign_en, valid_in;
    reg [N-1:0] a, b;
    wire [2*N-1:0] product;
    wire overflow, valid_out;

    fixed_mult #(.N(N)) dut (
        .clk(clk), .rst_n(rst_n), .sign_en(sign_en),
        .a(a), .b(b), .valid_in(valid_in),
        .product(product), .overflow(overflow), .valid_out(valid_out)
    );

    initial clk = 0;
    always #5 clk = ~clk;

    // 测试向量
    initial begin
        rst_n = 0; sign_en = 0; valid_in = 0; a = 0; b = 0;
        #10 rst_n = 1;

        // 测试1:无符号 200 * 3 = 600 (0x258)
        @(posedge clk);
        sign_en = 0; a = 8'd200; b = 8'd3; valid_in = 1;
        @(posedge clk);
        valid_in = 0;
        // 预期 product=16'd600, overflow=0
        $display("Test1: product=%0d overflow=%b", product, overflow);

        // 测试2:无符号 200 * 200 = 40000 (溢出)
        @(posedge clk);
        sign_en = 0; a = 8'd200; b = 8'd200; valid_in = 1;
        @(posedge clk);
        valid_in = 0;
        // 预期 product=40000, overflow=1
        $display("Test2: product=%0d overflow=%b", product, overflow);

        // 测试3:有符号 -100 * 2 = -200
        @(posedge clk);
        sign_en = 1; a = -8'sd100; b = 8'sd2; valid_in = 1;
        @(posedge clk);
        valid_in = 0;
        // 预期 product=-200 (补码), overflow=0
        $display("Test3: product=%0d overflow=%b", $signed(product), overflow);

        // 测试4:有符号 -100 * -100 = 10000 (溢出)
        @(posedge clk);
        sign_en = 1; a = -8'sd100; b = -8'sd100; valid_in = 1;
        @(posedge clk);
        valid_in = 0;
        // 预期 product=10000, overflow=1
        $display("Test4: product=%0d overflow=%b", $signed(product), overflow);

        #20 $finish;
    end

    initial begin
        $monitor("t=%0t sign_en=%b a=%0d b=%0d product=%0d overflow=%b valid_out=%b",
                 $time, sign_en, a, b, product, overflow, valid_out);
    end
endmodule

验证方法

使用ModelSim或Vivado运行上述测试平台,观察打印输出。判定标准:

  • Test1输出product=600, overflow=0
  • Test2输出product=40000, overflow=1
  • Test3输出product=-200, overflow=0
  • Test4输出product=10000, overflow=1

若四个测试用例的product与overflow均与预期一致,则验证通过。

综合与落地

该模块可直接用于ASIC或FPGA综合。综合时设置参数N为目标位宽(如16、32)。时序路径为:输入a/b → 乘法器 → 溢出比较 → 输出寄存器,关键路径延迟由乘法器决定。若需更高吞吐率,可插入流水级拆分乘法运算。

a[N-1:0] b[N-1:0] sign_en 符号扩展 至2N位 乘法器 (2N×2N) 直接相乘 MUX 溢出检测 (高N位检查) 寄存器 (clk) 寄存器 product overflow sign_en 数据通路 溢出判定:无符号→高N位非零;有符号→高N位≠符号扩展