对于两个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
与之前的对比可知,建立时间一样,但是保持时间小多了,因此时钟周期也小多了。并且,该实现方式可以在加法器之间插入寄存器以实现流水线,从而再次提高时钟频率。