对于两个N位数相乘 P=AB,之前的移位累加乘法器是先将B装载于一个2N位的寄存器中,然后B逐次移位,再根据A的各位情况累加。
现在我们将A按权展开:
以两个8位数相乘为例:
首先我们可以写一个函数实现1位数和8位数的相乘,以得到上面的八个相加的数;
然后我们可以将这八个数分组两两相加,第一次相加后得到4个中间结果,再将4个数分组两两相加,得到2个中间结果,最后再将这两个数相加得到最终结果。这样增加了芯片资源的耗用,但是可以提升速度。
在《数字系统设计与Verilog HDL》中对于8位二叉树(加法树)乘法器有如下代码:
module add_tree(out,a,b,clk);
output [15:0] out;
input [7:0] a,b;
input clk;
wire [15:0] out;
wire [14:0] out1,c1;
wire [12:0] out2;
wire [10:0] out3,c2;
wire [8:0] out4;
reg [14:0] temp0;
reg [13:0] temp1;
reg [12:0] temp2;
reg [11:0] temp3;
reg [10:0] temp4;
reg [9:0] temp5;
reg [8:0] temp6;
reg [7:0] temp7;
//该函数实现8x1乘法
function [7:0] mult8x1;
input [7:0] operand;
input sel;
begin
mult8x1=(sel)?(operand):8'b00000000;
end
endfunction
//调用函数实现b各位与a相乘
always @ ( posedge clk )
begin
temp7<=mult8x1(a,b[0]);
temp6<=(mult8x1(a,b[1])<<1);
temp5<=(mult8x1(a,b[2])<<2);
temp4<=(mult8x1(a,b[3])<<3);
temp3<=(mult8x1(a,b[4])<<4);
temp2<=(mult8x1(a,b[5])<<5);
temp1<=(mult8x1(a,b[6])<<6);
temp0<=(mult8x1(a,b[7])<<7);
end
assign out1=temp0+temp1;
assign out2=temp2+temp3;
assign out3=temp4+temp5;
assign out4=temp6+temp7;
assign c1=out1+out2;
assign c2=out3+out4;
assign out=c1+c2;
endmodule
上面代码在调用函数的时候,左移k位是为了实现公式中2的k次方这个权系数。
其实该代码其实是有错的,以temp6+temp7为例,temp6为9位,temp7为8位,相加的结果out4它定义为9位,如果两数相加结果为9位,则不会出现问题;
如果两数相加产生进位,则进位将丢失,得不到正确结果。该代码的前仿真结果如下:
由此可以看到,255×255时结果错误,就是因为有进位丢失。
现在我们修改上述中间变量的位宽。由于两个8位数相乘结果最多为16位,因此如果我们将所有中间变量都改为16位的,结果肯定不会出问题,但是这样比较浪费资源。
所以这里我们将temp6(9b)+temp7(8b)的结果out4设为10位,temp4(11b)+temp5(10b)的结果out3设为12位,temp2(13b)+temp3(12b)的结果out2设为14位,temp0(15b)+temp1(14b)的结果out1设为16位。
然后将out3(12b)+out4(10b)的结果c2设为13位,out1(16b)+out2(14b)的结果c1设为16位,这里就不用考虑进位丢失了,因此最终结果最多为16位。
修改的这部分代码如下:
wire [15:0] out;
wire [15:0] out1,c1;
wire [13:0] out2;
wire [11:0] out3;
wire [12:0] c2;
wire [9:0] out4;
现在再仿真的结果如下:(可以看到结果正确了)
该乘法器能在一个时钟周期内完成乘法运算。但是它不能插入流水线,因为后面的分组加法运算是组合逻辑实现的。我们可以看一下用XST综合的时序报告:
(位宽有问题的代码的综合结果)
Minimum input arrival time before clock: 3.687ns
Maximum output required time after clock: 16.068ns
现在我们将加法运算也放入always块内,在此需要先将wire型变量改为reg型,assign语句删除。always块如下:(位宽没改)
always @ ( posedge clk )
begin
temp7<=mult8x1(a,b[0]);
temp6<=(mult8x1(a,b[1])<<1);
temp5<=(mult8x1(a,b[2])<<2);
temp4<=(mult8x1(a,b[3])<<3);
temp3<=(mult8x1(a,b[4])<<4);
temp2<=(mult8x1(a,b[5])<<5);
temp1<=(mult8x1(a,b[6])<<6);
temp0<=(mult8x1(a,b[7])<<7);
out1<=temp0+temp1;
out2<=temp2+temp3;
out3<=temp4+temp5;
out4<=temp6+temp7;
c1<=out1+out2;
c2<=out3+out4;
out<=c1+c2;
end
由代码分析可知道从第一个数据输入到第一个数据输出有3个时钟周期延迟,仿真如下:
但是我们再看一下综合报告:
Minimum input arrival time before clock: 3.687ns
Maximum output required time after clock: 6.216ns
与之前的对比可知,建立时间一样,但是保持时间小多了,因此时钟周期也小多了。并且,该实现方式可以在加法器之间插入寄存器以实现流水线,从而再次提高时钟频率。