原码乘法可以分为原码一位乘和原码二位乘,两者在实现规则上大同小异。原码一位乘每次判断乘数的最低位,对被乘数和部分积进行相应操作。而原码二位乘则是对乘数的低二位进行判断,并执行相关操作。
两位乘数的取值可以有四种可能组合,每种组合对应于以下操作:
00相当于0×X,部分积Dresult右移2位,不进行其他运算;
01相当于1×X,部分积Dresult+x之后再右移两位;
10相当于2×X,部分积Dresult+2x后再右移两位;
11相当于3×X,部分积Dresult+3x后再右移两位;
上述过程出现了+x,+2x,+3x三种情况,+x容易实现,+2x可以将x左移一位,但是+3x一般不能在一个时钟周期完成,分两个时钟完成又降低运算速度。所以解决方法是:以+(4x-x)来代替+3x运算,在本次运算中执行-x,将+4x归并到下一步执行。因为下一步运算时,前一次的部分积已经右移了两位,所以上一步欠下的+4x在现在已经变成了+x。实际实现中用触发器C来记录是否有欠下的+4x操作尚未执行,若有,则C<=1。所以实际操作要用乘数的低两位和C三位的组合值来控制乘法运算操作,运算规则如下图所示。
1、求乘数、被乘数的绝对值,对被乘数绝对值的相反数求补;初始化部分积为000.0000000(本文以8位数据为例,数点左边为3位符号位,右边7位为绝对值的数值位)
2、对乘数最高位补0,引入标志位C,C初始化为0。
3、根据表格判断相应操作,移位时先将乘数右移两位,高两位由部分积的低两位来补。
4、乘数移动完毕后,被乘数用补码三位符号位移位规则进行右移两位。
5、重复3、4步骤直到移位次数为乘数位数/2。
6、此时还不能得出结果,需要再次对乘数低两位和标志位C进行判断,执行最后一次操作,但乘数不进行移位。
7、结果组成为{乘数和被乘数符号位进行异或(1位),部分积的低6位,乘数8位(绝对值为7位,补完0后变8位)}共15位。
注意事项:
1、步骤2中,乘数位数是奇数位则补1位0即可;若乘数位数是偶数则最高位补2个0。
2、对部分积右移时,采用3位符号的补码形式移位,即最高位符号位不动,正数在最高位添0,负数在最高位添1。
3、表格中的-|x|操作即加上x绝对值相反数的补码。
input clk,en; //取en对应的上升沿时刻对原码形式的dataA和dataB进行乘法运算
input [7:0] dataA,dataB; //被乘数与乘数
output dataO; //结果输出
reg [10:0]dataAreg; //寄存器存放取样时刻dataA的值 被乘数
reg [7:0]dataBreg; //寄存器存放取样时刻dataB的值 乘数
reg [10:0]negdataAreg; //存放被乘数绝对值的相反数的补码 -|x|的补码
reg [10:0]twodataAreg; //存放被乘数绝对值的两倍 2|x|
reg [10:0]Dresult; //部分积
reg [10:0]Dresult1; //部分积+\x\
reg [10:0]Dresult2; //部分积+2\x\
reg [10:0]Dresult3; //部分积-|x|
reg [2:0]cnt; //计数子,记录移位次数
reg C; //标志位C
reg [14:0]dataO; //输出结果
reg sig=1'b0; //标志位,标志该时钟周期更新Dresult或是Dresult1、2、3
//Dresult和Dresult1、2、3不可同时更新,他们互为基础,需按序更新
always @(posedge clk)begin
if(en)begin
dataAreg<={1'b0,1'b0,1'b0,dataA[6:0]}; //|x|
negdataAreg<={1'b1,~{1'b0,1'b0,1'b0,dataA[6:0]}}+1'b1; //-|x|的补码
twodataAreg<={1'b0,1'b0,dataA[6:0],1'b0}; //2|x|
dataBreg<={1'b0,dataB[6:0]}; //乘数
Dresult<=11'b0;
Dresult1<={1'b0,1'b0,1'b0,dataA[6:0]}; //Dresult1、2、3赋初值
Dresult2<={1'b0,1'b0,dataA[6:0],1'b0};
Dresult3<={1'b1,~{1'b0,1'b0,1'b0,dataA[6:0]}}+1'b1;
C<=1'b0;
cnt<=3'd1;
end
end
always @(posedge clk)begin
if(!en&sig)begin //未使能且是更新Dresult1、2、3的时钟周期
Dresult1=Dresult+dataAreg;
Dresult2=Dresult+twodataAreg;
Dresult3=Dresult+negdataAreg;
end
if(!sig && (cnt!=3'b0))begin //周期开始时,计数子从1记到5,记到5时乘数不进行移位操作。
//且是更新Dresult1、2、3的时钟周期
……
//查表对部分积Dresult和乘数dataBreg进行更新
end
if(cnt==3'd5)begin //进行数据输出
cnt<=3'd0;
dataO<={(dataA[7]^dataB[7]),Dresult[5:0],dataBreg[7:0]};
end
sig<=~sig;
end
仿真结果:
其中dataA,dataB和dataO的数据格式选择Signed Magnitude,原码格式。
1、编写Verilog代码前先进行数据流程的分析,分析在每个时钟周期下数据的预期结果,再与波形图对比是否符合预期,如果不符,观察数据是否在紧接着的时钟周期出现。
2、摈弃C语言的顺序思维,好好分析数据的时序。可以引入标志位实现按序更新数据。如本例中引入sig标志位对Dresult和Dresult1、2、3依次进行更新。
项目链接