• 【黑金动力社区】【FPGA黑金开发板】Verilog HDL 整数乘法器


    声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/

    2

    第一章 整数乘法器

    1.1 整数的概念

    整数在 IEEE 的规定上有,短整数 short integer , 中整数 integer 长整数 long integer ,它们之间的关系如下:

    整数

    字节空间

    取值范围

    短整数

    一个字节

    -127 ~ 127

    中整数

    两个字节

    -32767~32767

    长整数

    和四个字节

    -2147483647~2147483647

    在这里笔者以短整数为笔记的主角。

    短整数的最高位是符号位,符号位的正负表示了该值是“正还是负”?。正值的表示方法很简单,反之负值的表示方法是以补码来表示。

    +127 亦即 8'b0111_1111;

    +4 亦即 8'b0000_0100;

    -127 亦即 8'b1000_0001;

    -4 亦即 8'b1111_1100;

    补码在英文又叫 2nd implementation   , 其实是“正值的求反又加一”的操作。(哎~年轻时的笔者曾经这个东西头疼过)。一个负值表示如 -4 ,是由 +4 求反由加一后而成。

    8'b0000_0100;  // 正值 4

    8'b1111_1011;  // 求反

    8'b1111_1100;  // 1 负值 4

    那么符号位和正值,负值,补码,取值由有什么关系呢?举个例子 A = 8'b0111_1111 (+127) B = 8'b1000_0001 ( -127 )

    当我们在进行判断一个短整数是正值还是负值的时候,我们可以这样表示:

     if ( !A[7] ) ...  // A是正值

     if ( B[7] ) ...  // B是负值

     

    在事实的事实上。我们知道短整数 28 ,亦即取值范围是 0~255,但是符号位的出现吃掉了最高位,所以造成由 28 的取值范围变成 27 = 0~171


    你知道吗?在短整数家族里面永远存在一个幽灵成员。该成员很神秘,它不是正值,即不是负值或者0值。而且它的能力也不可忽视,它划分了正值和负值的边界,它就是 8'b1000_0000

    +127     8'b0111_1111;

    划分      8'b1000_0000;

    -127      8'b1000_0001;

    换句话说,在 8'b1000_0000 之前的都是正值 ,然而在 8'b1000_0000 之后是负值。如果读者硬是要说 8'b1000_0000 “负0”,笔记也无话可说 ......

    从上述的内容,我们可以知道:正值可以进行求反又加一之后成为负值。那么负值如何变成正值?同样的一个道理“负值求反又加一后,成为正值”。

    8'b1111_1100;  // 4

    8'b0000_0011;  // 求反

    8'b0000_0100;  // 1 4

    1.2 传统乘法的概念

    笔者还记得笔者在上小学三年级的时候,老师在黑板上写上 3 x 4 = 12。笔者对这神秘的数学公式迷糊了头脑。后来老师解释道: " 3粒苹果重复加上4 次等于12粒苹果",小时的笔者顿时恍然大悟!

    当笔者上了初中,老师在黑板上写上 3 + -4 = -1。大伙们都明白那是整数,但是初中的笔者,脑袋过很迟钝。因为在现实中,初中的笔者认为没有“-3粒苹果”类似实体的概念纯在,后来老师解释道:“ 小明欠小黄4粒苹果,后来小明还了小黄1粒苹果,结果小明还欠小黄一粒苹果 ”,初中的笔者又恍然大悟。

    又在初中,当老师又在黑板上写上如下的内容。那时候的笔者,嘴巴长得大大 ,有好一段时间说不出话来 。好一段时间笔者都是自己在嘀咕 ....

     3 x 4 = 12;    " 3粒苹果重复叠加4次,等于12粒苹果"

    -3 x 4 = -12;    " 3粒苹果,重复欠4次,等于欠12粒苹果"

    3 x -4 = -12;    " 4粒苹果,重复欠3次,等于欠12粒苹果 "

    -3 x -4 = 12;    " @#%#*%…… " ( 嘀咕中 ... )

    读者们不要笑,上述的故事确实是笔者的真实故事。那时候的笔者,真的拿不到整数的乘法的门儿,考试还常常满江红,真的悲剧的初衷时代 ......

    在传统的概念上乘法等价于“重复几次”。打个比方:B = 4A x B 亦即 A要重复加四次才能得到答案。

    然而在乘法中“负值正值的关系”就是“异或的关系”。

    A

    B

    结果

    (0)

    (0)

    (0)

    (0)

    (1)

    (1)

    (1)

    (0)

    (1)

    (1)

    (1)

    (0)

     A x B = C

     3 x 4 = 12;   

    -3 x 4 = -12;   

    3 x -4 = -12;   

    -3 x -4 = 12; 

    从上面的内容看来,无论A值和B值是什么样的“正值和负值的关系”,结果C都是一样。

    那么我们可以换一个想法:

    “在作乘法的时候只是我们只要对正值进行操作。然而“负值和正值的结果”,我们用“异或”关系来判断 ...

    实验一 :传统的乘法器

    该乘法器的大致操作如下:

    (一)在初始化之际,取乘数和被乘数的正负关系,然后取被乘数和乘数的正值。

    (二)每一次累加操作,递减一次乘数。直到乘数的值为零,表示操作结束。

    (三)输出结果根据正负关系取得。

    multiplier_module.v

    clip_image002

    clip_image003

    3~11行是该模块的输入输出。看到 Start_Sig Done_Sig 是仿顺序操作的标志性结构,不明白的去看笔者之前写的笔记。Multiplicand Multiplier (被乘数和乘数),都是8位位宽,所以输出 Product 16位位宽。

    16~21行是该模块所使用的寄存器,i寄存表示步骤,Mcand 用来暂存 Multiplicand 的正值,Mer 用来暂存 Multiplier 的正值,Temp 寄存器是操作空间。然而 isNeg 标志寄存器是用来寄存 Multiplicand Multiplier 之间的正负关系。

    在步骤036~45行)是初始化的步骤。第39isNeg寄存“乘数和被乘数之间的正负关系”。第40行,Mcand寄存 Multiplicand 的正值,该行表示:如果被乘数的符号位是逻辑1的话,就将负值转换为正值,然后Mcand寄存该值,否则Mcand直接寄存 Multiplicand 的值。第41行是用来寄存Multiplier 的正值,该行的操作和40行很相识。

    在步骤147~49行),是“重复加几次”的操作。Temp寄存器的每一次值的叠加,Mer寄存就递减(49行)。直到Mer的值等于048行),就进入下一个步骤。步骤2~3是产生完成信号。

    62行,Product输出信号的输出值是由isNeg寄存器作决定,如果isNeg是逻辑1,那么Temp的结果从负值转换为正值。否则直接输出Temp的值。

    multiplier_module.vt

    clip_image005


    16~22行是复位信号和时钟信号的激励。第26~35行是 multiplier_module.v 的实例化。

    39行以下和普通的仿顺序操作的写法一样,不明白的话请看笔者以往写过的笔记。

    步骤0~3, 会输入不同的乘数和被乘数来激励 multiplier_module.v

    仿真结果:

    clip_image007

    clip_image009

     实验说明:

    其实传统的乘法器是很容易的,但是短整数的出现,负值和正值随着出现,使得设计上难以下手。但是只要掌握负值和正值的关系以后,乘法只作正值也“无问题”。只要在输出下一点手脚就行了。

    实验结论:

    传统的乘法器虽然简单,但是它有一个致命的问题。就是被乘数越大就越消耗时钟。具体的原因在下一章节解释 ......


    1.3 传统乘法器的改进

    Verilog HDL 语言所描述的乘法器的消耗是以“时钟”作为时间单位。反之,组合逻辑所建立的乘法器是以“广播时间”作为时间单位。说简单点就是,Verilog HDL 语言所描述的乘法器“快不快”是根据“时钟消耗”作为评估。

    假设 A = 10 , B = 20,  A x B ,那么时钟的消耗至少需要20个,因为 A值需要累加20次才能得到结果。到底有没有什么办法,改进这个缺点呢?

    有学过乘法的朋友都知道 A ( B ) = B ( A )。如果以实验一的乘法器作为基础,那么 A( B ) B( A ) 所消耗的时间就不一样了。所以我们可以这样改进:

    如果被乘数小于乘数,那么被乘数和乘数互换。

    { Multiplier , Multiplicand } = Multiplicand < Multiplier ? { Multiplicand Multiplier } :

                             {Multiplier Multiplicand }

    举个例子:Multiplicand = 2 Multiplicand = 10 ;

    更换之前 被乘数2 需要10次的累加,才能得到结果。 更换之后 被乘数为10 乘数为2,亦即被乘数10只要累加2次就能得到结果。

    如此一来,可以减少不少时钟的消耗。

    实验二 : 传统乘法器改进

    和实验一相比,在进行累加操作之间,多了一个被乘数和乘数比较的步骤。

    (一)在初始化之际,取乘数和被乘数的正负关系,然后取被乘数和乘数的正值。

    (二)乘数和被乘数比较,如果被乘数小于乘数,结果乘数和被乘数互换。

    (三)每一次累加操作,递减一次乘数。直到乘数的值为零,表示操作结束。

    (四)输出结果根据正负关系取得。

    multiplier_module_2.v

    clip_image011

    clip_image012

    和实验一先比,添加了一个比较的步骤(46~49行)。


    仿真结果:

    仿真 .vt 文件和实验一样。

    clip_image014

    在仿真的结果上,10 x 2 2 x 10 的时钟消耗都一样。

    实验说明:

    与实验一的乘法器比较,关于时钟的消耗多少都有改进。

    实验结论:

    传统的乘法器无论如何改进也好,当遇见如 127 x 127 的乘数和被乘数,咋也看不出什么优化 ......


    1.4 补码君存在的意义

    每一个人都有存在的意义,有的人用一生的时间去寻找自己的存在意义,有的人则是经过生活的大反转,看到了自己存在意义,有的人则不闻不问 ... 当然补码也有存在的意义,只是在前面的实验被笔者滥用而已。

    补码不仅可以执行正值和负值转换,其实补码存在的意义,就是避免计算机去做减法的操作。

         1101     -3

    +    1000     8

          0101     5

    假设 -3 + 8,只要将 -3 转为补码形式,亦即 0011 => 1101,然后和8,亦即1000相加

    就会得到 5,亦即0101。至于溢出的最高位可以无视掉。

    1101     -3

    +     1110      -2

       1011     -5

    其实你知道吗,如Quartus II 综合器 ,当我们使用“-”算术操作符的时候,其实就是使用补码的形式,具体如下:

    A = 8'd5;

    B = 8'd9;

    A B 等价于 A + ( ~B + 1'b1 )

    在实际的操作中,综合器都会如上优化。


    1.5Booth算法乘法器

    传统的乘法器是有极限的,因此位操作乘法器就出现了。笔者在网上冲浪找资源的时候,还常常撞到许多稀奇古怪的位操作乘法器。但是有一种位操作乘法器,吸引了笔者的眼球,它就是 Booth算法乘法器。实际上 Booth 算法是一种“加码”乘法运算。

    Booth 算法的概念也很简单,我们先从数学的角度去理解看看:

    B[0]

    B[-1]

    加码结果

    0

    0

    0(无操作)

    0

    1

    1+被乘数)

    1

    0

    1(-被乘数)

    1

    1

    0(无操作)

    B[-1] 是什么?先假设 B2的,然而B的最低位的右边后一个“负一位”那就是B[-1]

    0010 0  // LSB 右边出现的就是 -1

    那么上面那个加码表和乘法又有什么关系呢?其实要加码的目标是“乘数”,假设乘数为2, 那么乘数2的加码过程会是如下。

    一开始的时候在乘数2的“负一位”加上一个默认0

    0010 0

    先判断[0: -1],结果是2'b00,表示“0”亦即没有操作

    0010 0

    判断[2: 1],结果是2'b01,表示“1”亦即“-被乘数”操作

    0010 0

    判断[1: 0],结果是2'b10,表示“1”亦即“+被乘数”操作

    0010 0

    判断[3: 2],结果是2'b00,表示“0”亦即没有操作

    0010 0

    举个例子,被乘数为70111; 乘数为20010;结果会是什么?

          0111       - A被乘数

          0010 0  - B乘数

      ==========

          0110      - 乘数加码

      ==========

          0000     0

       111001      1 - 7

        0111       1 +7

       0000        0

      ==========

       0001110     14   

      ==========        

    clip_image016

    从左边的操作过程中,我们可以看到乘数被加码以后,

    操作的结果是 14

          

    从数学的角度看来,确实Booth算法是麻烦的存在,但是在位操作的角度来看就不是这么一回事。实际上在千奇百怪的位操作乘法中,Booth算法其中可以容纳“补码”亦即“负数”来执行操作。

    B[0]

    B[-1]

    加码结果

    0

    0

    无操作,右移一位

    0

    1

    +被乘数,右移一位

    1

    0

    -被乘数,右移一位

    1

    1

    无操作,右移一位

    上面的图表是位操作时候的 Booth 算法。Booth算法在位操作的时候,它使用一个很有个性的空间,就是P空间。

    先假设:被乘数A 7 (0111),乘数B2 (0010) ,它们n均为4位,所以 P空间的容量是 n x 2 + 1 , 亦即 9 位。

    _ _ _ _ _ _ _ _  _  // P空间

    那么P空间如何实现乘法的位操作呢?

    一开始先求出 -1 (被乘数)

     A = 0111A = 1001

    然后初始化 P 空间, 默认为0

     P = 0000 0000 0

    P空间的 [4..1] 填入乘数

     P = 0000 0010 0

    判断P[1:0],是2'b00 亦即“无操作”

     P = 0000 0010 0

    判断P[8], 如果是逻辑0右移一位补0,反之补1

     P = 0000 0001 0

    判断P[1:0],是2'b10 亦即“-被乘数”。

     P = 0000 0001 0

    P空间的[8..5] 被乘数 A 相加。

     P = 0000 0001 0

     +  1001     

     P = 1001 0001 0

    判断P[8], 如果是逻辑0右移一位,补0,反之补1

     P = 1100 1000 1

    判断P[1:0],是2'b01 亦即“+被乘数”。

     P = 1100 1000 1

    P空间的[8..5] 被乘数 A 相加。

     p = 1100 1000 1

     +  0111     

     P = 0011 1000 1 无视最高位溢出

    判断P[8], 如果是逻辑0右移一位补0,反之补1

     P = 0001 1100 0

    判断P[1:0],是2'b00 亦即“无操作”

     P = 0001 1100 0

    判断P[8], 如果是逻辑0右移一位,补0,反之补1

     P = 0000 1110 0

    最终 P空间的[8..1] 就是最终答案。

     P = 0000 1110 0

    从上面的操作看来,由于乘数和被乘数均为 n 位所以 “判断P[1:0],后操作,之后移位”的操作仅执行四次而已。


    clip_image018

    如左边的循环图。A为被乘数,A 为被乘数补码形式( -1(A) ),B为乘数,n为乘数和被乘数的位宽,P为操作空间。

    一开始 P空间会初始化,然后P空间的[4..1]

    位会填入B。然后进入P[1:0]的判断。每一次的判断过后的操作都会导致 P空间右移一次,至于右移过后的最高位是补0还是补1,是由当时P[8]说了算。

    当循环 n 次以后,最终结果会是P[8:1]

    实验三:Booth算法乘法器

    实验中建立的Booth算法乘法器大致的步骤正如 1.5章节所描述的那样。

    booth_multiplier_module.v

    clip_image020

    clip_image021

    13~15行是仿真的输出(S - Simulation , Q - Output)。第20~25行定义了该模块所使用的寄存器。a寄存器用来寄存 A 值,s寄存器用来寄存 -1(A) 的值,p寄存器是P空间。输入信号AB均为8位位宽,所以p寄存器是17位位宽。至于X寄存器是用来表示n位,用来指示 n 次循环。


    步骤040~41行),初始化了as寄存器。p[8:1]填入B值,亦即乘数,其余的位均为0值。

    步骤143~51行)是用来判断 p[1:0] 的操作。步骤253~55行)是执行右移一位,是补0还是补1,完全取决于p[16]。步骤1~2会重复交替执行,直到X的值达到8次,就会进入下一步步骤。

    步骤3~457~61行)是用来产生完成信号。第68行输出信号product 是由p空间的[16..1]来驱动。第72~74行是仿真用的输出信号,功能如字面上的意思。

    booth_multiplier_module.vt

    clip_image023

    clip_image024

    在仿真中,从步骤0~359~73行),激励了不同AB的值(被乘和数乘数)。

    仿真结果:

    clip_image026

    P空间的详细操作过程,自己代开modelsim看吧,界面有限的关系。从仿真结果上可以看到,4次的乘法操作所使用的时间都一样,尤其是 -127 x -127 的情形,不像传统乘法器那样累加127次,才能得到结果。(p空间的[ Width :1]是用来填入乘数B,然而p空间的 [Width * 2 : Width + 1 ] 是用来执行和被乘数A的操作)

    实验结论:

    按常理来说8位的乘数和被乘数,位操作会是使用8个时钟而已,但是实验3的乘法器,需要先操作后移位的关系,所以多出8个时钟的消耗 ......


    1.6 笔者情有独钟的步骤i

    在笔者初学Verilog HDL语言,笔者老是捉不好 Verilog HDL 语言和时序的关系,吃了不少苦头。世界就是很巧妙,脑子里就忽然间冒出步骤i

    步骤i是什么?

    有关《Verilog HDL 那些事儿》那本笔记,虽然笔者的实例都和“它”有关。但是在笔记中,笔者只是微微的带过“步骤i是仿顺序操作相关的写法 ... ”。但是要探讨步骤i是什么,那不是初学 Verilog HDL 的任务。步骤i的用法很简单,从概念上和“顺序操作”很类似,它可以补助初学者不必过度依赖功能仿真,也能“从代码中看到时序”。

    如果从低级建模的角度去探讨步骤i,低级建模里面有一个准则,就是“一个模块一个功能”,步骤i好比这个准则的支持者。步骤i0开始,表示了这个模块开始工作,直到i被清理,这也表示了这个模块已经结束工作。或者可以这样说“一个模块不会出现两个步骤i”。

    具体上,步骤i的“值”是指示着“第几个时钟沿”发生,然而 Verilog HDL语言里的“步骤”和C语言里的“步骤”是不一样。C语言里的“步骤”就好比“把大象放进冰箱需要几个步骤 ... ”。相反的 Verilog HDL 语言里的“步骤”,有如“时间点”的观念。

    clip_image028

    如上面的示意图所示, 在这个时间点里所发生的“决定”会产生不一样的未来。然而在这个时间点里“可以允许不同的决定在这一刻存在”。举一个例子:A的初值是4B的初值是0

    case( i )

    0:

    begin A <= A + 2'd2; B <= B + 2'd3; i <= i + 1'b1; end

    1:

    if( A > 3 ) begin B <= A; A = 0; i <= i + 1'b1; end

    else if i <= i + 1'b1;

    咋看是一个简单的代码,但是你知道里边包含的秘密吗?

    i = 0的时候,A 累加2B 累加 3

    i = 1的时候,如果A大于3, 就B寄存A的值将A清零。

    无论是i等于0还是等于1,它们“只是这一时间点发生的决定”,结果会在这个时间点的过后发生。如果用“生动”的话来描述的话。

    在时间点0的时候,这个模块决定A累加2,B累加3。然后在时间点0过后,结果就产生。直到迎来下一个时间点,这个模块才能再一次作决定。

    在时间点1的时候,这个模块判断A是否大于3。那么,问题来了“这个模块是以什么作为基础,判断A大于3呢?”。答案很简单就是“时间点1的过去的结果”或者说“在时间点0过后所产生的结果”。

    clip_image030

    上图完全将上述的内容表达了出来。在这里笔者有一个很在意的问题,那就是 "<=" 赋值操作符。在众多的参考书中“<=”赋值操作符被解释为“时间沿有效的赋值操作符”。笔者初学的时候的,完全不知道它是虾米 ... 如果换做时间点的概念来说“<=”的操作符,表示了“在这个时间点下决定”专用的赋值操作符。

    与“=”赋值操作符不一样,它是没有时间点的概念的赋值操作符。所以在 always @ ( posedge CLK ... ) 有效区域内,它是不适合使用,因为它会破坏这个模块的时间和结果。

    我们的人生,下错了决定只要知错,吸取教训还有从来的机会。但是模块下错了决定,就影响它的一生,所以我们在编辑的时候要特别小心,不然会可能因我们的疏忽,导致了这个模块的一生悲剧。

    小时候,笔者读道德教育的时候,有一句话是笔者一生受用,那就是“先三思而后行”。

    这个又和上述的内容有什么关系呢?

    我们知道“时间点”的概念就是“就是在这个时间点决定了什么,这个时间点的过后会产生什么”。难道模块的世界就是那么现实, 就连三思的机会也没有吗?这是一个很好的问题 ......

    举个例子,有一个模块他有 A BC三个寄存器,它们的初值都是0

    case( i )

      

       0:

       begin A <= 3; B <= 4; C <= 0; i <= i + 1'b1; end

       1:

       begin

           C <= A + B;

           if( C > 0 ) begin A <= 0; B <= 0 ; end

           else begin A <= 1; B <= 1; end

           i <= i + 1'b1;

       end

    从上面的代码,我们可以知道。在时间点0,该模块决定了 A 等于3B等于4C等于0。然后到了时间1, 问题来了“在时间点1,该模块是以什么作为基础去判断 C 的值呢?是时间点1过去的C值,还是在这一个瞬间 A + B 所产生的值?”。

    clip_image032

    答案如上图所示,if是以时间点1过去的C值作为判断的基础。所以说模块的现实是很残忍的,它们不但没有重来的机会,就连“思考”的时间也不给它。它们"只能以当前时间点过去的值,作为当前时间点下决定的参考 ......  ( 写到这里, 笔者流泪了! )


    实际上“=”不是不可以在 always @ ( posedge CLK ... ) 里出现,只不过它比较危险。

    case( i )

      

       0:

       begin A <= 3; B <= 4; C <= 0; i <= i + 1'b1; end

       1:

       begin

           C = A + B;

           if( C > 0 ) begin A <= 0; B <= 0 ; end

           else begin A <= 1; B <= 1; end

           i <= i + 1'b1;

       end

    笔者将上面的代码稍微修改了一下, 在步骤1 变成了 C = A + B

    clip_image034

    如果把步骤i按照“时间点”的概念,结果会是如上图。在时间点1,“=”造成了一个而外的时间停止空间,在这个空间里 C 不但可以“作决定”,而且“即时得到结果”。在某种程度上,它的存在会破坏和谐,如果没有步骤i的控制,它很容易暴走。笔者在设计模块中,除非出现“不得已”的情况,否则笔者在 always @ ( posedge CLK ... )区域内,绝对不会使用它。


    1.7 Booth算法乘法器的改进

    在实验三中所建立的Booth算法乘法器,要完成一次乘法计算,至少要消耗16个时钟,而且其中8个时间就是消耗在移位的方面上。那么有什么办法改进 实验三中的 Booth算法乘法器呢?

    1.6章节,笔者说了步骤i有如时间点的概念,假设我这样修改实验三的Booth乘法器

    case ( i )

       

       0: ... 初始化

      

       1,2,3,4,5,6,7,8:

       begin

           if( p[1:0] == 2'b01 ) p <= { p[16] , p[16:9] + a , p[8:1] };

           else if( p[1:0] == 2'b10 ) p <= { p[16] , p[16:9] + s , p[8:1]};

           else p <= { p[16] , p[16:1]};

           i <= i + 1'b1;

       end

    从上面的代码,读者能看出什么破绽吗?我们尝试回忆 Booth算法的流程图,先判断p[1:0] 的操作,然后右移一位,最高位补0还是补1,是取决与 p[1:0]操作之后的p[16]

    那么问题来了,从上面的代码看来 p <= { p[16] , p[16:9] + a , p[8:1]}; 其中的 p[16] 是以当前时间点的过去值作为基础,而不是p[1:0]操作过后的值, 所以上面的代码不行!

    case( i )

    0: ... 初始化

    1,2,3,4,5,6,7,8

    begin

        Diff1 = p[16:9] + a;  Diff2 = p[16:9] +s;

       

        if( p[1:0] == 2'b01 ) p <= { Diff1[7] , Diff1 , p[8:1]};

        else if( p[1:0] == 2'b10 ) p <= { Diff2[7] , Diff2 , p[8:1]};

        else p <= { p[16] , p[16:1]};

        i <= i + 1'b1;

    end


    上面的代码表示了,在步骤1~8 Diff1 寄存了 p[16:9] + a 的结果,反之 Diff2 寄存了 p[16:9] + s的结果。然后判断 p[1:0] 再来决定 p 的结果是取决于 Diff1 Diff2 或者其他。和第一段的代码不同,第二段代码的p输出值是一致的。在这里有一个重点是,Diff1 Diff2 没有使用“<=”而是使用 =”,换一句话说,Diff1 Diff2 结果的产生在“该时间点作决定的时候”,亦即“取得即时的结果”,而不是该时间点过后,才产生结果。

    实验四:Booth算法乘法器改进

    基于实验三的Booth算法乘法器,从原先的一次乘法需要16次、个时钟,优化至8个时钟。

    booth_multiplier_module_2.v

    clip_image036


    clip_image037

    同样是 Booth 算法的原理,和实验三不同的是在55~67行,是步骤1~8的循环操作。不再使用X寄存器作为循环计数,而是直接使用步骤来指示8个循环操作。在55~67行,这样的写法有一个好处,就是可以使得p的值输出一致,因此可以减少8个时钟。

    仿真结果:

    clip_image039

    实验四所使用的 .vt 文件和实验三的一样。


    从仿真结果看来,一次的乘法操作只消耗8个时钟而已(步骤0初始化,和步骤9~10完成信号产生除外)。现在我们把上面的仿真结果切成一块一块的来看。

    clip_image040

    00000000 10000001 0 值左边上升沿开始,即是第一个时间点 i = 0,亦即步骤0。步骤0之后就是初始化的结果。S是取反过后的a值,并且填充在p空间的[8:1]

    clip_image041

    00000000 10000001 0 值右边的上升沿,亦即步骤1。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 00000000 + 10000001, 结果为10000001Diff2 寄存过去的 p[16:9] + s,亦即 00000000 + 01111111, 结果为 01111111。经步骤1的“决定”,过去p[1:0]

    2'b10 ,所以p值的未来是 { Diff2[7] , Diff2 , p过去[8:1] }, 亦即

    0 01111111 10000001

    clip_image042

    00111111 11000000 1 值右边的上升沿,亦即步骤2。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 00111111 + 10000001, 结果为11000000Diff2 寄存过去的 p[16:9] + s,亦即 00111111 + 01111111, 结果为 10111110。经步骤2的“决定”,过去p[1:0]

    2'b01 ,所以p值的未来是 { Diff1[7] , Diff1 , p过去[8:1] }, 亦即

    1 11000000 11000000

    clip_image043

    11100000 01100000 0 值右边的上升沿,亦即步骤3。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 11100000 + 10000001, 结果为01100001Diff2 寄存过去的 p[16:9] + s,亦即 11100000 + 01111111, 结果为 01011111。经步骤3的“决定”,过去p[1:0]2'b00 ,所以p值的未来是 { p过去[16] , p过去[16:1] }, 亦即

    1 11100000 01100000

    clip_image044

    11110000 00110000 0 值右边的上升沿,亦即步骤4。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 11110000 + 10000001, 结果为01110001Diff2 寄存过去的 p[16:9] + s,亦即 11110000 + 01111111, 结果为 01101111。经步骤4的“决定”,过去p[1:0]2'b00 ,所以p值的未来是 { p过去[16] , p过去[16:1] }, 亦即

    1 11110000 00110000

     

    clip_image045

    11111000 00011000 0 值右边的上升沿,亦即步骤5。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 11111000 + 10000001, 结果为01111001Diff2 寄存过去的 p[16:9] + s,亦即 11111000 + 01111111, 结果为 01110111。经步骤5的“决定”,过去p[1:0]2'b00 ,所以p值的未来是 { p过去[16] , p过去[16:1] }, 亦即

    1 11111000 00011000

    clip_image046

    11111100 00001100 0 值右边的上升沿,亦即步骤6。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 11111100 + 10000001, 结果为01111101Diff2 寄存过去的 p[16:9] + s,亦即 11111100 + 01111111, 结果为 01111011。经步骤6的“决定”,过去p[1:0]2'b00 ,所以p值的未来是 { p过去[16] , p过去[16:1] }, 亦即

    1 11111100 00001100

    clip_image047

    11111110 000001100 0 值右边的上升沿,亦即步骤7。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 11111110 + 10000001, 结果为01111111Diff2 寄存过去的 p[16:9] + s,亦即 11111110 + 01111111, 结果为 01111101。经步骤7的“决定”,过去p[1:0]2'b00 ,所以p值的未来是 { p过去[16] , p过去[16:1] }, 亦即

    1 11111110 00000110

    clip_image048

    11111111 00000011 0 值右边的上升沿,亦即步骤8。此时:

    Diff1 寄存过去的p[16:9] + a ,亦即 11111111 + 10000001, 结果为10000000Diff2 寄存过去的 p[16:9] + s,亦即 11111111 + 01111111, 结果为 01111110。经步骤8的“决定”,过去p[1:0]2'b10 ,所以p值的未来是 {Diff2[7] , Diff2, p过去[8:1] }, 亦即

    0 01111110 00000011

    最终结果取值未来p[16:1] 00111111 00000001 亦即 16129

    实验说明:

    如果以“大象放进冰箱”这样的概念去理解步骤i,在实验四中可能会产生许多思考逻辑上的矛盾。换一个想法,如果以“时间点”的概念去理解步骤i的话,从仿真图看来是绝对逻辑的。(再唠叨的补充一下,p空间的[ Width :1]是用来填入乘数B,然而p空间的 [Width * 2 : Width + 1 ] 是用来执行和被乘数A的操作)

    实验结论:

    这一章节笔记的重点不是要“如何如何实现一个算法”,而是“以不同概念的理解去完成乘法器的改进”。


    1.8 LUT乘法器

    1.8章节以前的乘法器都可以归纳为“慢速乘法器”,当然它们不是真正意义上的慢,只不过它们无法达到急性一族人的任性而已。LUT乘法器,又成为查表乘法器。用傻瓜的话来说,就是先吧各种各样的结果储存在一个表中,然后将输入资源以“查表”的方式,许对比“等于的结果”。

    举个例子,笔者先建立一个 16 x 16 正值的查表:

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    1

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    2

    0

    2

    4

    6

    8

    10

    12

    14

    16

    18

    20

    22

    24

    26

    28

    30

    3

    0

    3

    6

    9

    12

    15

    18

    21

    24

    27

    30

    33

    36

    39

    42

    45

    4

    0

    4

    8

    12

    16

    20

    24

    28

    32

    36

    40

    44

    48

    52

    56

    60

    5

    0

    5

    10

    15

    20

    25

    30

    35

    40

    45

    50

    55

    60

    65

    70

    75

    6

    0

    6

    12

    18

    24

    30

    36

    42

    48

    54

    60

    66

    72

    78

    84

    90

    7

    0

    7

    14

    21

    28

    35

    42

    49

    56

    63

    70

    77

    84

    91

    98

    105

    8

    0

    8

    16

    24

    32

    40

    48

    56

    64

    72

    80

    88

    96

    104

    112

    120

    9

    0

    9

    18

    27

    36

    45

    54

    63

    72

    81

    90

    99

    108

    117

    126

    135

    10

    0

    10

    20

    30

    40

    50

    60

    70

    80

    90

    100

    110

    120

    130

    140

    150

    11

    0

    11

    22

    33

    44

    55

    66

    77

    88

    99

    110

    121

    132

    143

    154

    165

    12

    0

    12

    24

    36

    48

    60

    72

    84

    96

    108

    120

    132

    144

    156

    168

    180

    13

    0

    13

    26

    39

    52

    65

    78

    91

    104

    117

    130

    143

    156

    169

    182

    195

    14

    0

    14

    28

    42

    56

    70

    84

    98

    112

    126

    140

    154

    168

    182

    196

    210

    15

    0

    15

    30

    45

    60

    75

    90

    105

    120

    135

    150

    165

    180

    195

    210

    225

    假设A x B,它们均为4位,A10B2,那么结果会是 20。查表乘法器之所以被称为快速乘法器,就是上面的原因 ( 实际上许多硬件乘法器都是使用查表的方式 )

    如果 A x B ,它们均为8位,那么应该如何呢?难道再建立一个 256 x 256 乘法器!?这样会死人的。

    不知道读者有没有听过Quarter square 乘法查表呢?

    clip_image050

    上边是该算法的公式,在公式的结束得到 ab = ( ( a + b )2 )/4 - ( ( a - b )2 )/4


    如果再进一步细分的话,无论是 ( a + b )2/4  或者 ( a - b )2/4 ,经过幂运算后,得到的结果都是正值。

    假设a b的位宽都是 8 位的短整数话 ( 127 + 127 )2 / 4 = ( -127 - 127 )2 / 4。那么我们可以得到一个结论“ ( a + b )2/4  或者 ( a - b )2/4  使用同样的 (C)2/4 查表”。

    那么我们建立一个 C = 0 ~ 255 ,并且内容是 (C)2/4 的查表。

    clip_image052

    clip_image054

    clip_image055

    这个查表的寻址虽然是 0~255,但是实际上下限是254而已。因为我们知道两个短整数最大值相加仅有 -127 + -127 = -254 或者 127 + 127 = 254


    那么问题来了, 短整数的最大取值范围是 -127 ~ 127 而已,何来寄存 -254 ~ 254 呢?

    这里我们就涉及了“不同容量空间的相互赋值”。假设 C 9位位宽的不正规整数

    ,然而 A B 都是 8位位宽的正规整数,那么 C = A + B ?

        C = A + B

    等价于

        C = { A[7], A } + { B[7], B }

       

        A = 127 (0111 1111)

        B = 127 (0111 1111)

     A     0111 1111

     B     0111 1111  +

     C    01111 1110

    等价于

       

        A = 127 (00111 1111)

        B = 127 (00111 1111)

     A    00111 1111

     B    00111 1111  +

     C    01111 1110

       

        A = -127 (1000 0001)

        B = -127 (1000 0001)

       

     A     1000 0001

     B     1000 0001  +

     C    10000 0010

    等价于

        A = -127 (11000 0001)

        B = -127 (11000 0001)

       

     A    11000 0001

     B    11000 0001  +

     C    10000 0010

    接下来,我们来看一看下面的代码:

    reg [8:0]I1,I2;

    case( i )

    0:

    begin

        I1 <= { A[7], A } + { B[7], B };            // C =A + B;

        I2 <= { A[7], A } + { ~B[7], ( ~B + 1'b1 ) };  // C = A - B;

        i <= i + 1'b1;

    end

    1:  // 取正值

    begin

        I1 <= I1[8] ? ( ~I1 + 1'b1 ) : I1;

        I2 <= I2[8] ? ( ~I2 + 1'b1 ) : I2;

        i <= i + 1'b1;

    end

    上面的 I1 I2 均为9位位宽。在步骤0I1表示了 C = A + B,相反的 I2 表示了 C = A - B。由于短整数的赋值采用补码的表示方式,所以大大简化了正负转换的操作。

    假设 A = -1 ( 1111 1111 ) , B = -3 ( 1111 1101 ), 经过上面步骤0的操作:

    I1 = { 1 11111111 } + { 1 1111 1101 } = 1 1111 1100 (-4) 等价于 I1 = -1 + -3 = -4

    I2 = { 1 11111111 } + { 0 0000 0011 } = 0 0000 0010 ( 2) 等价于 I2 = -1 - (-3) = -1 + 3 = 2

    步骤1 I1 I2 从负值转换为正值。

    假设 I1 = -4 (1 111 1100) I2 = 2 (0 0000 0010), 经过步骤1的操作:

    I1 = 0 0000 0011 + 1 = 0 0000 0100;

    I2 = 0 0000 0010;

    为什么在步骤1中,要特意将负值转换为正值呢?笔者在前面已经说过,无论是 (-C)2 还是 (C)2 取得的结果都是一至。为了两者 I1 I2 共用相同的查表这是必须的步骤。

    如果用 I1I2 来表达 Quarter square 公式,那么:

     ( | I1 |2 / 4 ) - ( | I2 |2 / 4 )

    实验五:基于 Quarter square 的查表乘法器

    首先笔者手动建立 0~255 关于 (C)2/4 结果的 lut_module.v ,因为用Quartus II建立的rom 仿真不给力,很别扭。

    lut_module.v

    clip_image057


    clip_image058

    clip_image059

    clip_image060


    clip_image061

    clip_image062

    这是我目前,贴过最长的 .v 文件了 ...

    lut_multiplier_module.v

    这个模块的功能很简单。首先取得 I1 = A + B I2 = A - B,然后I1 I2 都正值值,将I1 I2 送至各自的查表,然后将得出的结果 Q1_Sig (I1的结果) Q2_Sig

    (I2的结果) , 执行相减。实际上是补码形式的相加,Q1_Sig + ( ~Q2_Sig + 1'b1 ) ,以致符合Quarter square的公式:

     ( a + b )2/4  - ( a - b )2/4 = ( |I1| )2/4  + [ ( |I2| )2/4]

                                   = Q1_Sig + [Q2_Sig]


    clip_image064


    clip_image065

    15~18行是仿真的输出。第26~27行建立 Q1_Sig Q2_Sig ,实际上这两个线型数据是U181~87行)和 U291~97行) 实例前申明的,可是modelsim 那么混蛋,偏偏就不给我通过。

    37~77行是该模块的主功能。步骤049~54行)是取 I1 I2 的值。步骤156~61行)是I1I2的正值化操作。步骤263~64行)是延迟一个时钟,给予足够的时间从 lut_module.v读出结果。步骤366~67行),是Quarter square公式操作的最后一步。

    89~99行是 lut_module.v 的实例化 U1是给 I1使用 U2是给I2使用,它们的输出连线分别是 Q1_Sig Q2_Sig 102行的Product 输出信号由 Data寄存器驱动。然而106~109行是仿真输出的驱动,分别有I1 , I2 ,Q1_Sig Q2_Sig 的仿真输出。


    lut_multiplier_module.vt

    clip_image067


    clip_image068

    .vt 文件的写法和之前的实验都一样,如果真的不知道笔者在写什么,就得好好看笔者之前写的笔记。

    仿真结果:

    clip_image070

    看吧!一次的乘法操作仅需4个时钟的而已。比起改进的Booth算法减少了一半的时钟消耗。真不愧是查表式的乘法器,佩服佩服。

    实验结论:

    说实话查表式的乘法器是“以空间换时间”的乘法器,所以说查表式的乘法器是很消耗空间。到底有什么乘法器“可以节约空间,又节省时钟”呢?

    你知道吗?传统查表的乘法器都有一个僵局,假设A(B),那么其中一个变量需要是“恒数”,否则建立查表的工作是非常的劳动。但是Quarter square 公式的出现把这个僵局给打破。感谢前人的努力吧,我们后人才能乘凉 ......


    1.9 Modified Booth 算法乘法器

    事先声明 modified booth 算法 改进的 booth 算法乘法器(实验四)是没有任何关系的。如字面上的意思 modified booth 算法是 booth 算法的升级版。我们稍微来回味一下 booth 算法。

    假设B4位位宽的乘数,那么 booth 算法会对 B[0: -1] , B[1:0], B[2:1], B[3:2] 加码,而使得乘法运算得到简化。booth 算法有典型数学做法,也有位操作的做法。Modified booth 算法比起 booth 算法,对于4位位宽B乘数的加码返回会更广,而使得 n/2 乘法运算的优化。再假设B4微微款的倍数,那么 modified booth 算法会对 B[1:-1] , B[3:1] 执行加码。

    如果站在位操作的角度上:

    B[1]

    B[0]

    B[-1]

    操作结果

    0

    0

    0

    无操作,右移两位

    0

    0

    1

    +被乘数,右移两位

    0

    1

    0

    +被乘数,右移两位

    0

    1

    1

    右移一位,+被乘数,右移一位

    1

    0

    0

    右移一位,-被乘数,右移一位

    1

    0

    1

    -被乘数,右移两位

    1

    1

    0

    -被乘数,右移两位

    1

    1

    1

    无操作,右移两位

    Modified booth 算法同样也有使用 p空间,假设乘数A,和被乘数B,均为4位,那么p空间的大小 n x 2 + 1 ,亦即 9位。乘数A7 (0111),被乘数B2 (0010)

    先求出 +被乘数 -被乘数,亦即 A A

       A = 0111 A = 1001

    P空间初始化为0,然后P空间的[4..1] 填入乘数

    亦即B

       P = 0000 0000 0

       P = 0000 0010 0

    先判断p[2:0],结果是 3'b100

    亦即“右移一位,-被乘数,右移一位”。

       P = 0000 0010 0

    右移一位

       P = 0000 0001 0

      

    p[8:5] 加上 A

       P = 0000 0001 0

       +  1001     

       P = 1001 0001 0

    右移一位

       p = 1100 1000 1

    判断p[2:0],结果是 3'b001

    亦即“+被乘数,右移二位”。

       p = 1100 1000 1


    p[8:5] 加上 A

       P = 1100 1000 1

       +  0111     

       P = 0011 1000 1

    右移二位

       P = 0000 1110 0

    最终取出p[8:1] 就是最终答案 8'b00001110

     ,亦即 14

       P = 0000 1110 0

    关于 4 位为位宽的乘数和被乘数操作流程图如下:

    clip_image072

    说实话 modified booth 算法的位操作是很不规则,从上面的流程图可以看到,不同的p[2:0]操作都有“不同的步骤次数”,这也使得它非常不适合作为运用。

    实验六:Modified Booth 乘法器

    这个模块大致的操作如上述的流程图。

    modified_booth_module.v

    clip_image074

     

    clip_image075

    clip_image076

    15~17行是仿真输出。43~94行是该模块的主功能。在步骤045~51行)取得被乘数A并且寄存在a寄存器,此外取得 -1(被乘数A) 并且寄存在s寄存器。在初始化p空间的同时,将乘数B填入p[8:1]

    (由于被乘数A和乘数B的位宽为8,所以p空间是 n x 2 + 1 亦即9。我知道我很长气,但是还是容许笔者补充一下:p空间的[ Width :1]是用来填入乘数B,然而p空间的 [Width * 2 : Width + 1 ] 是用来执行和被乘数A的操作)。

    步骤1253~62行)是p[2:0] 等于 3'b000 | 111 | 001 | 010 | 101 | 110 的操作。相反的,由于 modified booth 算法当 p[2:0] 等于3'b011 3'b100 所执行的步骤次数是不一样(56~57行)。

    所以在步骤3~566~73行)针对 p[2:0] 等于 3'b011 的操作(56行)。反之步骤6~8 (77~84)针对 p[2:0] 3'b100 的操作(57行)。

    步骤9~10产生完成信号。第102行的product输出信号是由 p[16:1]来驱动。第106~109的仿真输出信号,分别由寄存器 a s p来驱动。

    modified_booth_multiplier_module.v

    clip_image078


    clip_image079


    clip_image080

    这是激励文件,在写这个文件的时候,笔者心情很糟糕,所以在步骤5加入了类似for嵌套循环的东西。其他的和之前的 .vt 文件都是大同小异 ~ 自己看着吧。

    仿真结果:

    clip_image082

    在仿真结果中,可以很明显的看到当 2(4) 127-127)有明显的时钟消耗差异。

    实验结论:

    如果 Modified booth 算法用在“位操作”,虽然它是快速的乘法操作,但是很多时候它还是很别扭。换句话说,用它还要图运气,因为不同的乘数和被乘数都有不同的时钟消耗 ......


    1.10 Modified Booth 乘法器·改

    如果要把 Modified Booth 乘法器别扭的性格去掉,我们不得站在“数学的角度”去看 modified booth 算法。下表是从数学的角度去看 modified booth 针对乘数B的加码。

    clip_image084

    B[1]

    B[0]

    B[n-1]

    操作结果

    0

    0

    0

    无操作

    0

    0

    1

    +被乘数

    0

    1

    0

    +被乘数

    0

    1

    1

    +2(被乘数)

    1

    0

    0

    2(被乘数)

    1

    0

    1

    -被乘数

    1

    1

    0

    -被乘数

    1

    1

    1

    无操作

    我们假设 A被乘数和乘数B均为4位位宽 A=70111),B=20010)。

    A = (7) 0000 01112A = (14) 0000 1110-2A = (-14) 1111 0010

    在这里我们必须注意一下当 B[1:-1] 等于 011 或者 100 的时候,4位的被乘数A的取值范围最大是 -7 ~ 7 然而,+2(被乘数) 或者 2(被乘数) 都会使得A的最大值突破取值范围。所以需要从4位位宽的空间向更大的位位宽哦空间转换。这里就选择向8位位宽的空间转换吧。

    B乘数加码为 B[1:-1] = 3'b100 ,亦即 2(被乘数) B[3:1] = 3'b100 ,亦即 +被乘数。

        A      0 1 1 1

        B      0 0 1 0  0

        ==============

               +1  -2       B乘数加码

        ==============

      1 1 1 1 0 0 1 0

     + 0 0 0 0 0 1 1 1          << 2 左移两位

       ===============

        1 0 0 0 0 1 1 1 0      无视超过8位最高位的益处

       ===============

    还记得 booth算法在数学角度上的运算吗?4位的乘数和被乘数相乘,乘数必须加码n次,而且乘积也是 n 位的次数,亦即4次哦加码操作,和4次的乘积操作。相反的 modified booth 算法在数学的角度上运算的话,4位的乘数和被乘数相乘,乘数加码为 n/ 2 次,而且乘积也是 n/2 的次数,亦即 2次加码操作,和2次的乘积操作

    实验七:Modified Booth 乘法器·改

    modified_booth_multiplier_module_2.v

    clip_image086

     

    clip_image087

    29~27行是该模块所使用的寄存器。a是用来寄存Aa2是用来寄存2As是用来寄存 -As2是用来寄存 -2AM是用来表示每次乘积的偏移量。

    由于这个实验不是站在位操作的角度上,所以P空间仅是作为累加空间的存在。作为补偿寄存器N用来判别 booth 加码操作,所以寄存器N用于寄存乘数B的值。乘数B8位位宽,所以N空间的大小是 “乘数B的大小 + 1”。多出来的1个空间是用来寄存B[-1]的值。”

    在步骤054~65行),是用来初始化所有相关的寄存器。寄存器aa2ss2 在初始化的同时也进行 8 16 空间转换。寄存器pM都清零,至于寄存器N[8:1]是用来填充乘数BN[0] 填入零值。

    步骤1~467~79),也就是4次的乘积次数,因为受到 n/2 的关系。每一次的乘积操作都是先判别N[2:0],然后累加相关的值。

    我们知道传统的乘法,每一次的乘积操作,都有偏移量 ,打个比方。

       123

       111

    =====

       123  <= 十进制的第一个乘积是 偏移 0,没有左移位操作。

      123   <= 十进制的第二个乘积是 偏移10,也就是左移1位。

     123    <= 十进制的第三个乘积是 偏移100,也就是左移2位。

    =====

              

    同样的道理寄存器M 是用于记录二进制的每一次乘积偏移量,但是 modified booth乘法的乘积偏移量是普通2进制乘法乘积偏移量的2被。所以每一次乘积操作结束都左移+2

    至于寄存器N它寄存了 B[7:1] + B[-1] 的值。然而每一次用于的判别都是 N[2:0],所以每一次的乘积之后,N都需要右移两位。

    假设 B = 1101 0010 N 必然是 1101 0010 0

    乘积1

    B[1:-1] = 100

    N = 1101 0010 0

    乘积2

    B[3:1] = 001

    N = 0011 0100 1

    乘积3

    B[5:3] = 010

    N = 0000 1101 0

    乘积4

    B[7:5] = 110

    N = 0000 0011 0

    为什么说 8 位位宽的数据相乘,乘积运算次数是 n / 2 ,亦即 4。这是 Modified booth算法的一个特点。如果站在数学的角度上,他可以节省“乘积次数 / 2”。

    92行的 product 输出是由寄存器p驱动。前面笔者说过了,如果站在数学的角度,p空间只是累加空间的作用而已。然而p空间的大小是“乘数和被乘数位宽大小的相加”。

    96~101行是仿真输出信号的被驱动。有一点很特别,除了寄存a, a2, s, s2 N 以外,笔者还故意将该模块的 i 引出,这是为了观察 Modified booth 乘法使得乘积次数减半”这一事实。在仿真中,SQ_i 1~4经过,如果输出的结果是真确,那么可以证明 Modified booth 算法确实何以减少一半的乘积。

    modified_booth_multiplier_module_2.vt

    clip_image089


    clip_image090

    仿真结果:

    clip_image092

    从仿真结果上,我们可以看到,每一个乘法操作都消耗同样数目的时钟。此外还有一点, SQ_i 等于 4 之后,就会得到正确的答案。

    实验结论:

    实验七和实验六相比,不仅每一次乘法操作时钟消耗都一致,而且这样结果带来一个好处,就是- 实验七和实验六相比比起乘法运算更快。此外,从SQ_i信号等于4之后,product 就输出正确的结果,所以我们可以证明 modified booth算法是可以减半乘积的次数。


    总结:

    从实验一到实验七当中,笔者详细描述出四种乘法器的各有千秋,其中还有几种乘法器笔者还特意去优化和提升它们。从四种乘法器之中,传统乘法器,Booth 乘法器,LUT查表乘法器,和Modified Booth乘法器。LUT乘法器拥有最少的时钟消耗(最快的运算速度),但是LUT乘法器却暴露出消耗资源的弱点。

    如果将LUT乘法器排外,自然而然 Modified Booth 乘法器成为第二候选人,但是要建立Modified Booth 乘法器需要很好的理论基础,故很多新手都很怕它。至于Booth乘法和是最受欢迎的,如果设计的要求不像DSP那么任性,估计会有很多人喜欢它,因为它中庸,简单,容易亲近。

    剩下的传统的乘法器,它什么都不比上后者,难道我们就要鄙视它吗?这个不然,笔者接触各种各样的乘法,还是托它的副,不然我是不可能如此深入研究整数乘法器。传统的乘法器,最主要的功能是传达“乘法运算”的概念。正如笔者赞同的一句话:“前人造路,后人走路”,前者们的辛苦应该受到尊敬。

    整数乘法器所涉及的知识可真不小,Verilog HDL语言掌握的成熟性姑且不说,而且还涉及诸如补码,整数的表示方法,不同位空间的整数转换等等 ... 都是一些非常基础的知识。我们所使用的高级语言,如C语言:

    int C;

    short int A,B;

    C = A * B;

    假设笔者输入如同上述的代码,实际上我们是不知道和不被允许窥看它里边是如何操作(有传言说,C语言的乘法就是传统的乘法概念 ... (-_-!))

    虽然这本只有短短50多页的笔记,故事也只是围绕着着“整数乘法器”发展,显然还有很多地方都不给力。但是你知道吗,关于网上“Verilog HDL 整数乘法器”的求救贴已经达到很恐怖的数量,此外还有很多源码和实例都非常不给力,真是非常蛋疼!故笔者才有编辑这本笔记的初衷,虽然这本笔记不是什么非常给力的东西,但是作为参考已经切切有余。

    不知道读者们看完这本笔记后又会萌出什么奇怪的想法呢?

  • 相关阅读:
    SEO(Business)
    C#数组去掉重复的元素
    文件创建与文件格式的修改
    Filter(20160815)
    OmniGraffle导入stencils的两个方法以及优质的stencils下载网站推荐
    在Axure RP 8.0 中使用 Font Awesome 图标库完成设计并能在其他未安装该字体的电脑离线预览的方法
    社会性动物(艾略特•阿伦森)
    MacTex 在XeLaTex编译状态下插入的pdf格式图片无法显示问题的解决方案
    Markdown,别来无恙!
    男人来自火星 女人来自金星(约翰·格雷)
  • 原文地址:https://www.cnblogs.com/kingst/p/1913042.html
Copyright © 2020-2023  润新知