54 / 63 · 概念
多周期 unsigned 移位加法乘法器
将部分积累加分配到 W 个时钟,验证接受与完成时刻、末次累加、位宽及不变式。
课时内容可免费阅读,选课后可保存学习进度。
先修知识:应理解时序电路、非阻塞非阻塞赋值 时序 RTL 中以 <= 表示的赋值。先计算右侧,再预约更新,使同一边沿的各寄存器能够依据旧状态计算。了解更多赋值、计数器、移位与加法器。
跨多个时钟复用一个加法器
unsignedunsigned 将位串解释为非负整数的规则。n 位范围为 0 到 2^n−1,可能与同一位串的 signed 解释不同。了解更多 W 位乘法,是由乘数 B 的各位选择的部分积之和。
同时累加所有部分积需要大量加法逻辑。本设计改为把一个加法器使用 W 次,以面积换取吞吐能力。RTL 中写一个循环并不会自动变成多周期电路,还需要跨时钟保存计算状态的寄存器寄存器 存储多位状态的电路。本课程中的同步寄存器在时钟边沿存储指定输入。了解更多与终止条件。
累加器 P 为 2W 位,左移的被乘数 M 也为 2W 位,右移的乘数 Q 为 W 位。开始时 P=0、M=zero-extend(A)、Q=B。每个工作时钟,若 Q[0]=1,就把 M 加到 P;随后 M 左移一位,Q 右移一位。
工作 W 次后,Q=0,P 就是完整乘积。2W 位足够,因为最大值 小于 ,且 unsigned 部分积累加中的中间 P 不会超过最终乘积。
以 A=13、B=11、W=4 为例,Q 最低位依次为 1、1、0、1。P 为 0→13→39→39→143,使用的 M 为 13→26→52→104。即使第三次工作 Q[0]=0,也必须移位并递增计数。
每列表示请求捕获或工作边沿之后的状态。第一列为计算前初始化,随后四列才是工作步骤。第三次工作时,Q 的旧最低位为 0,因此只有 P 保持,M、Q 仍移动。
查看波形数据
| 信号 | 波形 | 总线值 |
|---|---|---|
| edge | 23452 | capture → work1 → work2 → work3 → work4 |
| P after | 234.5 | 0 → 13 → 39 → 143 |
| M after | 23452 | 13 → 26 → 52 → 104 → 208 |
| Q after | 23452 | 11 → 5 → 2 → 1 → 0 |
| done after | 0...1 |
先规定请求与完成时刻
下面的接口把 idle 时 start=1 的上升沿上升沿 时钟从 0 变为 1 的瞬间。它不同于表示整个 CLK=1 区间的电平。了解更多定义为接受请求。该边沿 t0 只存储输入,从 t1 到 tW 执行 W 次计算,在 tW 之后 product 和 done 有效。从接受到完成经过 W 个时钟周期,若连起始边沿也计入,则共有 W+1 个边沿。
busy=1 期间忽略 start。完成边沿之前 busy 仍为 1,因此该边沿也不接受新请求。下一请求最早在 tW+1 接受,连续请求的最小接受间隔为 W+1 个周期。若 start 持续为 1,返回 idle 后的下一个边沿会重新启动,调用方通常提供对准接受时刻的脉冲。
module mul_iter #(parameter int W = 8) (
input logic clk, rst, start,
input logic [W-1:0] a, b,
output logic busy, done,
output logic [2*W-1:0] product
);
localparam int CW = (W <= 1) ? 1 : $clog2(W);
localparam logic [CW-1:0] LAST = CW'(W-1);
logic [CW-1:0] step;
logic [2*W-1:0] acc, m, next_acc;
logic [W-1:0] q;
assign next_acc = acc + (q[0] ? m : {2*W{1'b0}});
always_ff @(posedge clk) begin
if (rst) begin
busy <= 1'b0;
done <= 1'b0;
product <= '0;
acc <= '0;
m <= '0;
q <= '0;
step <= '0;
end else begin
done <= 1'b0;
if (!busy) begin
if (start) begin
busy <= 1'b1;
acc <= '0;
m <= {{W{1'b0}}, a};
q <= b;
step <= '0;
end
end else begin
acc <= next_acc;
m <= m << 1;
q <= q >> 1;
if (step == LAST) begin
product <= next_acc;
busy <= 1'b0;
done <= 1'b1;
end else begin
step <= step + 1'b1;
end
end
end
end
endmodule本设计要求 W≥1,并将 CW 限制为至少 1,避免 W=1 时产生零位宽计数器。reset 为同步高有效,优先于正在进行的运算。接受请求后,即使外部 a、b 改变,也使用保存的 m、q 计算,不影响结果。product 只在 reset 或完成时改变。done 是一个周期的脉冲,不是保持到结果被消费的 valid。
最后的赋值必须是 product<=next_acc,而非 product<=acc。非阻塞赋值右侧读取更新前的值,若输出 acc,就会漏掉最后一个乘数位对应的部分积。同理,即使把 q 的移位赋值写在前面,next_acc 仍根据当前 Q[0] 计算。
计算不变式与实际设计选择
执行 k 次工作之后,保持以下关系。
将 Q 分解为偶数部分与最低位,可验证这一关系在下一步仍成立。k=W 时 Q=0,因此 P=AB。这个不变式不变式 所有允许的执行过程中都必须始终成立的条件。例如,深度为 4 的 FIFO 中,存储条目数必须始终在 0 到 4 之间。了解更多比只观察小例子波形更清楚地说明了累加与移位顺序为何正确。
单步临界路径包含一个 2W 位加法器与输入选择。去掉乘号并不保证一定更小或更快,还应比较 FPGA 专用 DSP 块、综合结果及所需吞吐率吞吐率 单位时间内接受或完成的工作数量。流水线即使不缩短单个任务的延迟,也可以提高吞吐率。了解更多。本模块没有实现提前结束、signed 运算或结果背压,消费端必须保证不漏掉 done。
验证应包含 0、1、最大值以及只有最高位为 1 的 B,并检查 busy 期间的 start、输入变化和 reset 取消。尤其是仅最高位为 1 的 B,能暴露最后一次累加遗漏。
自己试试
W=4、A=9、B=10 的运算在 t0 接受。求 t1~t4 之后的 acc、q、done,并说明最早的下一次接受边沿。若最后写成 product<=acc,会输出什么?
阅读解释
初始 acc=0、m=9、q=1010。t1:最低位为 0,因此 acc=0、q=0101、done=0。t2:加上 m=18,得到 acc=18、q=0010、done=0。t3:最低位为 0,因此 acc=18、q=0001、done=0。t4:加上 m=72,得到 acc=90、q=0000、done=1、busy=0,product=90。t4 边沿前 busy=1,无法接受 start,因此最早下一次接受为 t5。若写 product<=acc,非阻塞赋值读取旧 acc=18,会输出漏掉最后部分积 72 的 18。