FPGA学习之路—应用程序—原码二位乘法器及Verilog代码分析

FPGA学习之路——原码二位乘法器及Verilog代码分析

原理

原码乘法可以分为原码一位乘和原码二位乘,两者在实现规则上大同小异。原码一位乘每次判断乘数的最低位,对被乘数和部分积进行相应操作。而原码二位乘则是对乘数的低二位进行判断,并执行相关操作。
两位乘数的取值可以有四种可能组合,每种组合对应于以下操作:

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绝对值相反数的补码。

举例:
原码二位乘例子

Verilog源码分析(完整项目链接见文末)

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依次进行更新。

项目链接

猜你喜欢

转载自blog.csdn.net/qq_42334072/article/details/105928385