位操作替代取模运算

在日常学习中,常常碰到一些巧妙的设计思路,好记性不如烂笔头,在此记录,方便后续自己的查阅复习,如果能帮助到其他同学那就更好了。活到老学到老,FPGA设计技巧系列将持续更新,内容如有不妥之处,欢迎评论区指正,共同学习进步。

1、位操作替代取模运算

在数字电路设计中,当模数为2的幂时(如\(64=2^6\)),可以用位操作替代取模运算。

  • 二进制特性:$ N = 2^n $ 的二进制形式为 1 后跟 \(n\) 个 0(如 64 = 1000000)

  • \(N-1\) 的二进制:\(N-1\) 的二进制为 \(n\) 个 1(如 63 = 0111111)

  • 位操作效果:\(x\) & \((N-1)\) 会直接保留 \(x\) 的低 \(n\) 位(等效于取模)

// 假设 base_addr + data_cnt = 127 (01111111₂)
assign rd_addr_mod = 127 % 64;    // 结果 = 63 (0111111₂)
assign rd_addr_and = 127 & 63;    // 结果 = 63 (0111111₂)

// 假设 base_addr + data_cnt = 65 (01000001₂)
assign rd_addr_mod = 65 % 64;     // 结果 = 1 (0000001₂)
assign rd_addr_and = 65 & 63;     // 结果 = 1 (0000001₂)

当然,我们也可以简单仿真验证一下:

module mod_2_n(
    input clk,
    input rst_n,
    output MyFlag
);

reg [5:0] data_cnt;
reg [5:0] rd_addr;
reg [5:0] wr_addr;

always @(posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        data_cnt <= 6'd0;
    end else begin
        data_cnt <= data_cnt + 1;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        rd_addr <=  6'd0;
        wr_addr <=  6'd0;
    end else begin
        rd_addr = (48 + data_cnt) % 64;
        wr_addr = (48 + data_cnt) & 6'h3F; // 与63(111111)进行位与操作
    end
end

assign MyFlag = wr_addr == rd_addr;

endmodule
`timescale 1ns/1ps

module tb_mod_2_n();
    // 定义测试信号
    reg clk;
    reg rst_n;
    wire MyFlag;
    
    // 实例化被测模块
    mod_2_n dut (
        .clk(clk),
        .rst_n(rst_n),
        .MyFlag(MyFlag)
    );
    
    // 时钟生成
    initial begin
        clk = 0;
        forever #5 clk = ~clk; // 10ns周期(100MHz)时钟
    end
    
    // 测试过程
    initial begin
        // 波形输出
        $dumpfile("tb_mod_2_n.vcd");
        $dumpvars(0, tb_mod_2_n);
        
        // 初始复位
        rst_n = 0;
        #20;
        rst_n = 1;
        
        // 运行足够长的时间以观察多个周期
        #1500; // 运行150个时钟周期,足以观察64个计数和环绕
        
        // 再次复位测试
        rst_n = 0;
        #20;
        rst_n = 1;
        #680;
        
        $display("测试完成");
        $finish;
    end
    
    // 监控信号
    initial begin
        $monitor("Time=%0t, rst_n=%b, wr_addr=%d, rd_addr=%d, MyFlag=%b", 
                 $time, rst_n, dut.wr_addr, dut.rd_addr, MyFlag);
    end
    
endmodule

仿真结果如下图所示,我们可以看到 wr_addrrd_addr 的值始终相等,即 MyFlag 信号始终为 1。

仿真结果

2、注意事项

  • 位操作替代取模运算的前提是模数为2的幂,否则无法使用该方法。

  • 操作数为无符号数:有符号数的位操作会保留符号位,导致错误。

3、代码下载

博客中涉及到的代码均在我的GitHub仓库中,欢迎大家下载学习。


位操作替代取模运算
https://yao-jiangyu.github.io/位操作替代取模运算/
作者
小姚
发布于
2025年3月17日
许可协议