乘法器实验
注释:这段乘法器代码是自己学习特权老师后的一个总结,由于特权的视频很短(视频和具体代码可到网站上下载),讲得也不是很详细,特别是实现乘法的那段代码,经过学习,自己用左移的方式实现了乘法器,但是并不是很好。还有,由于自己初学FPGA,所以希望更多的指点和建议。
乘法器
相关理论:
乘法器是众多数字系统中的基本模块,从原理上说它是属于组合逻辑电路的范畴,但从工程实际设计上来说,它往往会利用时序逻辑设计的方法来实现,属于时序逻辑的范畴。
通过这个实验使大家能够掌握利用FPGA/CPLD设计乘法器的思想,并且能够将我们设计的乘法器应用到实际工程中,乘法器的设计方法有两种,组合逻辑设计方法和时序设计方法,采用组合逻辑设计方法电路事先将所有的乘积项全部计算出来,最后加法运算,采用时序逻辑设计方法,电路将已经得到的乘积项右移,然后与乘积项相加并保存和值,反复迭代上述步骤直至计算出最终结果。
在该实验中就是要利用时序;逻辑设计方法来设计一个16位乘法器,则就会利用时钟信号控制乘法运算。用时序逻辑设计方法与组合逻辑设计方法比较,它有什么好处呢?利用时序逻辑设计方法可以使整体设计具备流水线结构特征,能适用在各种实际设计中。
在提及乘法器的速度时,可以先了解一下数据吞吐量的概念,数据吞吐量使指芯片在一定时钟频率条件下所能处理的有效数据量。假设本实验设计的芯片时钟频率可达到300MH,那么该芯片的数据吞吐量是多少呢?
由于芯片完成一次乘法运算需要一个以上的时钟周期,因此,即使芯片采用300MHz的时钟频率,它每秒钟所能处理的有效数据吞吐量也一定小于300M,对于16位乘法器而言,ain和bin均为0xFFFF时,芯片的运算量最大,计算所需的时间也最长,这种情况才能作为我们计算数据吞吐量的依据。
假设芯片在200MHz的条件下ain和bin均为0xFFFF时需要16个时钟周期才能得到乘法结果,那么芯片在200MHz的条件下的数据吞吐量就为200M/16=12.5M。
信号名称 |
方向 |
描述 |
clk |
Input |
时钟信号,50MHz |
rst_n |
input |
复位信号,低电平有效 |
start |
input |
使能信号,为0表示信号无效;为1表示读入乘数和被乘数,该信号由0到1变化后,会进行一次当前ain和bin的乘法运算,进行下一次运算则需从新拉低后在拉高。 |
ain |
Input |
输入被乘数,位宽16位 |
bin |
Input |
输入乘数,位宽16位 |
yout |
output |
乘积输出,位宽32位 |
done |
output |
输出有效标志位,有效时,保持一个时钟周期高脉冲,为1表示乘法运算完成,yout端的数据稳定,得到最终的乘积;为0表示乘法运算未完成,yout端的数据不稳定。 |
设计思想:
采用时序逻辑设计方法,电路将已经得到的乘积项右移(最后一次计算不移位),然后与乘积项相加并保存和值,反复迭代上述步骤直至计算出最终结果。例如:两个四位的数相乘,产生一个八位的数。
设计代码:
if(areg[i-1])
yout_r = {1'b0,yout[6:3]+breg,yout_r[2:1]}; //累加并移位
else yout_r <= yout_r>>1; //移位不累加
end
else if(i == 5'd8 && areg[7]) yout_r[7:4] <= yout_r[7:4]+breg; //累加不移位
assign yout=yout_r;
由代码可知:如果我们用4位乘4位,则在进行前3次运算的时候,都要移位,实际进行了三次移位(最后一次没移位),我们每次加的时候都是取结果的第四位到第七位与被乘数相加,将2,3位作为新和的最低两位,最高位补0,实现了先加,再将加合移位的目的,为什么是取结果的第四位到第七位与乘数相加呢?这个我也想了很久才想通。看例:
代码解释:(我用的四位乘得八位的来做的)
1 1 1 1(被乘数)
X 1 1 0 1(乘数)
由于4位相乘得到的是八位的积:所以我们初始化结果的时候是:
(位)编号:7654 3210
乘积: 0000 0000(yout_r)
由代码可知,第一次运算结果是:
0011 1100(yout_r) 为什么不是0111 1000呢?因为两个四位数的相加的结果是5位,只是平时我们把最高位的0省略了,但是这里不能省略,否则就会出错。开始我就是这里出错了,害得自己纠结了好久。
第二次计算:由于乘数的第二位是0,所以移位不相加。结果:0001 1110
第三次计算:
6543(四位到第七位与被乘数相加)
0011
+1111
-----------------
10010
相加后最高位补0,最低两位是上次运算结果的2,3位。
拼接结果:1001 011(没移位补0)
由于下次是最后一次计算,所以不移位。
第四次计算:1001 011(只有七位,因为没移位补0)
+1111
----------------------
1100 0011
经过对这种乘法器实现方式的学习之后,自己编写了一个用左移的方式来实现乘法器的方式。这里我们还是用两个四位相乘的运算来作为例子。
例:
1111(被乘数)
X 1101(乘数)
----------------------
1111
0000
1111
1111
--------------------
11000011
由计算,我是对被乘数进行移位在与上次计算的结果相加,但是,从计算看出,第一次不移位。后面三次都是移位了的。
第一次计算结果:00001111
被乘数移位得:00011110
00001111
+00000000
第二次计算结果: 00001111
被乘数移位得:00111100
00001111
+ 00111100
第三次次计算结果: 01001011
被乘数移位得:01111000
01001011
+ 01111000
第四次次计算结果: 11000011
其实这种方法更容易理解些,我最开始知道的也就是这种方法,今天通过过对第一种方法的学习,自己后来用FPGA把这种也实现了。
关键代码:这里我采用的是阻塞赋值语句的方式,开始采用的是非阻塞的方式,但是得不到自己想要的结果,但是经过自己多次检查修改,在逻辑上没有任何错误,后来改成阻塞的方式,结果就正确了。不过自己对什么时候用非阻塞,什么时候用非阻塞还是不明白。需多看书练习。这段代码繁琐了点,但是简单易懂。但是第一种方式的实现更好,资源利用得更少。这个代码还需在优化才行。
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n) begin
muxa_r = 4'h0;
muxb_r = 4'h0;
muxout_r = 8'h00;
end
else if(i==3'd0) begin
muxa_r = {{4{1'b0}},muxa[3:0]};
muxb_r = muxb;
end
else if(i > 3'd0 && i < 3'd5)
begin
if(muxb_r == 0)
muxout_r = 1'b0; //乘数为0则不用计算了
else
begin
if(i == 1)
begin//第一次运算不移位
if(muxb_r[0])
muxout_r = muxa_r; //乘数的第一位为1,则输出等于被乘数
else muxout_r =1'b0; //否则输出为0
end
else if(muxb_r[i-1]) //计算后三位
begin
muxa_r = muxa_r << 1'b1;
muxout_r= muxa_r + muxout;//移位后与上一次的结果相加。
end
else
muxa_r = muxa_r << 1'b1;//若乘数为第i位为0则被乘数移位不加上次结果,上次结果变。
end
end
end
assign muxout = muxout_r;
仿真,为了较好的比较两种代码,我将自己的代码也改成了16*16的,
仿真时间最大延迟336959ps=336.959ns。
对于第一种延时 301672ps=301.672ns。
从仿真中看出这两种代码延迟差不多。
但是自己设计的这个代码并不好,因为在时序电路设计中应尽量使用非阻塞赋值语句,我本来想用非阻塞赋值的,但是没成功,所以希望那个指点哈。