设计一个乘累加器(MAC: Multiply Accumulator)
s u m = ∑ i a i b i sum=\sum_{i}a_{i} b_{i} sum=∑iaibi
模块要去实现上述公式的乘累加操作;数据输入接口中 din_a, din_b 为位宽为 5 的有符号整数;数据输出接口中 dout 为位宽为 12 的有符号整数;当累加器内部检测到有溢出时,overflow信号赋 1 以向外报告。
首先声明模块接口信号的输入、输出类型以及位宽。数据输入接口 din_a, din_b ,以及数据输出接口 dout 设置为 signed 类型。这里在模块接口定义处,声明输出信号 dout_valid 为寄存器类型,也可以在模块接口定义外声明。
module mac (
input reset ,
input clk ,
input enable ,
input signed [4:0] din_a ,
input signed [4:0] din_b ,
input din_valid ,
output signed [11:0] dout ,
output reg dout_valid ,
output overflow
);
//模块处理代码
endmodule
为了处理 溢出 问题,sum 扩展了 1 bit 位宽。仅当输入信号有效且出现使能信号时,乘累加器正常工作,即累加 din_a, din_b 的乘积,并将输出有效信号赋 1。
Verilog 支持有符号整数的乘法(此时要保证足够的位宽,保证符号位不受影响,且乘号两侧都是有符号数),乘累加的实现直接使用乘法运算符即可。
要避免同一个变量在不同的 always 块里赋值,不同逻辑的代码要分代码块写。虽然按 C 风格写仿真容易上手,但在板子上很有可能会综合出谜之逻辑。
reg signed [12:0] sum;
always @(posedge clk)
if (reset)
begin
sum <= 0;
dout_valid <= 1'b0;
end
else
if (din_valid & enable)
begin
sum <= sum + din_a * din_b;
dout_valid <= 1'b1;
end
else
dout_valid <= 1'b0;
对于乘累加结果,按要求输出 12 位的有符号数,即 sum 的低 12 位。
对于溢出信号 overflow 的处理,可以简单地通过将存放乘累加值的寄存器宽度拓展一个 bit 来实现。因为有符号数是以 补码 形式存储,判断加法溢出可以简单地通过符号位进位 异或 数值最高位进位来判断,若结果为 1,出现溢出;反之,没有溢出。
分正数、负数情况考虑,很容易发现其数值最高位进位即是符号位的值,符号位进位则是符号位左一位的值,于是将存放乘累加值的寄存器宽度拓展一个 bit ,以简单实现加法溢出的判断。
assign dout = sum[11:0];
assign overflow = sum[11] ^ sum[12];
需要先判断溢出再进行赋值操作的话,可以用中间结果的位运算实现。使用阻塞赋值可以写得更简洁一些。
if ((1 & ((din_a * din_b + sum) >> 11)) ^ (1 & ((din_a * din_b + sum) >> 12)))
begin
//数据溢出需要进行的操作
end