位操作替代取模运算
在日常学习中,常常碰到一些巧妙的设计思路,好记性不如烂笔头,在此记录,方便后续自己的查阅复习,如果能帮助到其他同学那就更好了。活到老学到老,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_addr
和
rd_addr
的值始终相等,即 MyFlag
信号始终为
1。

2、注意事项
位操作替代取模运算的前提是模数为2的幂,否则无法使用该方法。
操作数为无符号数:有符号数的位操作会保留符号位,导致错误。
3、代码下载
博客中涉及到的代码均在我的GitHub仓库中,欢迎大家下载学习。
位操作替代取模运算
https://yao-jiangyu.github.io/位操作替代取模运算/