移位乘法器设计 ================ 本次实验需要理解移位加法乘法器的工作原理,并掌握有符号乘法通过无符号运算实现的方法。 二进制乘法的原理 -------------------- 设被乘数为 A,乘数为 B,且 A、B 都为 16 位无符号数: .. math:: B = \sum_{i=0}^{15} b_i \cdot 2^i 则乘法可以表示为: .. math:: A \times B = \sum_{i=0}^{15} (b_i \cdot A \cdot 2^i) 其中,当 ``b_i`` 为 1 时,对应的部分积为 ``A`` 左移 ``i`` 位; 当 ``b_i`` 为 0 时,对应部分积为 0。 阵列乘法器 ~~~~~~~~~~~~~~~~~~ .. figure:: ../picture/lab6/阵列乘法器.png :alt: 阵列乘法器 :align: center 在学习算数运算电路的时候,课堂上介绍了阵列乘法器,通过硬连线的方式实现移位操作, 使用全加器或者其他的加法器将部分积依次相加,得到最后的乘法结果。结构如上图所示。 实现了两个无符号的二进制数相乘。也介绍了阵列乘法器有符号数与无符号数,有符号数与有符号数的乘法实现。 多周期移位乘法器实现 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 对于两个n位宽的二进制数乘法电路,当n的值比较大时,使用阵列乘法器实现需要大量的门电路, 并且关键路径会非常长,单周期内难以完成乘法计算。 另一种方案是通过一个移位寄存器和一个加法器,通过若干个周期完成乘法计算。 对于16位的乘法器,每个周期完成以下操作: 1. 判断乘数最低位 2. 若该位为 1,则将被乘数加到部分积寄存器 3. 被乘数左移一位 4. 乘数右移一位 5. 计数器加 1 当计数器达到 15 时,运算结束,得到乘法最终的结果。 对于移位乘法器,硬件实现结构简单,运算时间与位宽成正比。 .. figure:: ../picture/lab6/移位乘法器.png :alt: 移位乘法器 :align: center 有符号数乘法的问题 ---------------------- 如果乘数是补码表示的正数,可以使用和无符号数一致的方法运算。 被乘数可以是补码表示的正数或者负数,当为负数时需要对符号位进行扩展。 而乘数是补码表示的负数时,则需要做额外的处理。 二进制数的补码表示 ~~~~~~~~~~~~~~~~~~~~~~~~~ 补码我们已经很熟悉了。 .. figure:: ../picture/lab6/补码表示.png :alt: 补码表示 :align: center 对于 N 位二进制补码数,其数值可以表示为: .. math:: B = -b_{N-1} \cdot 2^{N-1} + \sum_{i=0}^{N-2} b_i \cdot 2^i .. raw:: html

2的补码表示法

证明上面的 N 位二进制补码数的数值表示公式。若给出一个补码表示的负数,让我们计算表示的值,有两种常见的办法。 第一种对它求2的补码,也就是对它进行取反加一,得到它的绝对值,再加上负号。例如求补码 11111110 表示的值, 取反得到 00000001 ,再加一得到 00000010,也就是2,因此补码 11111110 表示-2。或者直接使用公式计算数值表示, 例如求补码 11000000 表示的值,也就是-128 + 64 = -64。可以根据1的个数,选取合适的方法快速计算出补码表示的负数的值。

其中最高位 ``b_{N-1}`` 为符号位,具有负权重。 这意味着补码中的最高位并不能像普通的位一样 通过“移位加法”直接参与部分积计算。 计算有符号数乘法 ~~~~~~~~~~~~~~~~~~ 方法1 ^^^^^^^^^^^^^^^^^^^^^ 有符号数通常使用补码来表示。对于补码表示的负数,我们可以求2的补码,统一转换为正数进行计算。如果乘数和被乘数都是补码表示的负数, 那么结果不需要再进行改变,如果只有乘数或者被乘数是补码表示的负数,则对计算结果也求2的补码,得到最终的结果。 为正确实现有符号乘法,可以采用如下步骤: 1. 判断输入 A 和 B 的符号位 2. 若为负数,取其补码,转换为无符号数 3. 使用无符号移位乘法器计算乘积 4. 根据 A 和 B 的原始符号,对结果进行符号修正 最终结果的符号由以下关系确定: :: result_sign = A_sign ^ B_sign 若 ``result_sign`` 为 1,则对无符号乘积求2的补码。 方法2 ^^^^^^^^^^^^^^^^^^^^^ 当然也可以通过另一种办法计算有符号数乘法,被乘数乘以乘数的某一位得到部分积,最后每个部分积之间做加法,补码是可以直接进行加法的。 如果被乘数是补码表示的负数,部分积则需要扩展符号位。一种更好的方式是,相较于上面的“移位加乘法器”的结构,乘数和被乘数的移位方向都反过来。 被乘数A使用算数右移,而乘数B使用左移,先对高位相乘,依次往下计算部分积并相加。 被乘数A则不是放入移位寄存器的最低位,而是放在高位,并在最高位补上符号位,这样算数右移即可实现符号位的扩展。 .. figure:: ../picture/lab6/被乘数算数右移.png :alt: 被乘数算数右移 :align: center 对于乘数B,如果是补码表示的负数,可以求2的补码,转换为正数进行计算,然后对计算结果也求2的补码,得到最终的结果。 当然也可以根据公式,最高位具有负权重,需要对部分积求2的补码,其余位则保持一致即可。当然也可以最后对结果进行修正, 本来应该乘以 -2^n-1 ,但正常处理就乘以了 2^n-1 ,因此最后减去 A * (2^n) ,就能得到正确的结果。 设计多周期移位乘法器 ----------------------------------- 多周期移位乘法器本身就是适用于两个无符号数计算相乘,通过额外的电路结构,即可计算有符号数乘法,在上面已经讲了一些实现的方法, 需要你去仔细的思考、比较,并选择出一种合理的方案:既可以计算两个无符号数相乘,也可以计算两个补码表示的有符号数相乘, 还能计算一个无符号数和一个补码表示的有符号数相乘。 .. raw:: html

设计16位多周期移位乘法器

我们的乘法器需要能够完成上述功能,既可以计算两个无符号数相乘,也可以计算两个补码表示的有符号数相乘, 还能计算一个无符号数和一个补码表示的有符号数相乘。我们通过两个输入信号表明乘数和被乘数分别是无符号数, 还是补码表示的有符号数。写出你认为合适的方案,并参照上面给出的移位乘法器的电路结构,画出你的电路结构。

求2的补码 ~~~~~~~~~~~~~~~~~~ 求2的补码有多种方式,最常见的方式就是取反加一(其1的补码加1),或者减一再取反,这两种方式都可以用一个加法器和反相器简单实现。 也可以不使用加法器实现,我们保留从右往左的第一个"1"及其右边的"0"不变,其余位全部取反。 比如对 11100000 求2的补码,保留从右往左的第一个"1"及其右边的"0",也就是低6位保持不变,高2位取反, 得到 00100000,随便尝试一些数求2的补码,检查是否与取反加一的结果一致。 .. raw:: html

求2的补码

保留从右往左的第一个"1"及其右边的"0"不变,其余位全部取反,为什么就能求2的补码,尝试给出证明。 如何实现该电路呢?我们可以从低位一直或到最高位,检查最低位是不是1,如果不是,最低为或上次低位,如果为1,则次低位为1,如果不是,将或的结果与更高一位相或... 这样就能得知第一个"1"的位置,并且第一个"1"之后的或会一直传播下去,然后利用异或门可控取反。

输入为 0 的特殊情况 ~~~~~~~~~~~~~~~~~~~~~ 若 A 或 B 任意一个为 0,则乘法结果必然为 0。 在这种情况下,状态机可以直接跳转到完成状态, 无需进入移位计算过程,从而简化计算。 .. raw:: html

处理输入为 0

如何处理输入为 0 更好呢,实现并分别描述数据A、数据B的输入为 0 ,状态机是如何运行的。

状态机设计 ~~~~~~~~~~~~~~~~~~~~~ 我们可以通过有限状态机控制乘法过程,参考的状态划分如下: - IDLE:等待开始信号 - PREPARE:符号判断与无符号转换、输入是否为 0 - CALC:移位加法计算 - SIGN_FIX:结果修正 - DONE:计算完成,输出结果,标明计算结果已经完成 你可以根据设计情况自由修改状态机。 .. raw:: html

FSM 状态转移图

完成好乘法器设计之后,画出你的完整的状态转移图。

.. raw:: html

实现16位多周期移位乘法器

实现多周期移位乘法器的RTL编写,我们本次不给出框架代码,请自行思考。输入两个16位的数,以及指示两个数是有符号数还是无符号数,得到32位的结果。 实现后,编写 testbench 程序,验证你的多周期移位乘法器,需要对计算两个无符号数相乘,计算两个补码表示的有符号数相乘, 计算一个无符号数和一个补码表示的有符号数相乘进行测试。目前不需要上FPGA板,等之后完成了输入输出的部分再进行上板实验。

乘法器输入 -------------------- 我们的乘法器的输入部分设计如下: FPGA 的 SW0~SW15 作为数据A或者B输入、SW16 用于指示数据A或者数据B是有符号数还是无符号数,当SW16为1时, 为有符号数,0时为无符号数。SW17 作为数据A 输入的标志位,SW18 作为数据B 输入的标志位,SW19作为开始计算的标志位。 我们需要约定如下状态机,在 S0 阶段,先输入数据A,使用SW0~SW15指示数据A的值,SW16 用于指示数据A是有符号数还是无符号数。 当SW17为1,存储数据A的值和数据类型,进入 S1 阶段;输入数据B,同样使用SW0~SW15指示数据B的值,SW16 用于指示数据B是有符号数还是无符号数。 当SW18为1,存储数据B的值和数据类型,进入 S2 阶段;当SW19为1时,进入 S3 阶段; 此时将乘法器的数据输入valid信号置为有效,数据A、B和它们的数据类型传入乘法器,开始进行乘法计算。 在若干个周期乘法器计算完成之后,乘法器会将finish信号置为有效。finish信号用于标志数码管显示的结果, 当finish有效时,数码管上应该显示乘法器的32位结果,否则显示16位的数据A和16位的数据B,以{DataA, DataB}的方式位拼接起来显示。 当我们想要重新输入数据,通过reset信号,将各个状态机恢复如初,然后重新按照上面的方法输入数据。 输入设计的参考状态机如下: - S0:等待数据A和数据类型的输入 - S1:等待数据B和数据类型的输入 - S2:用于显示数据A和数据B,等待准备开始乘法计算 - S3:将乘法器的输入valid置为有效,数据A、B和它们的数据类型传入乘法器 输入valid之后,乘法器开始正常工作,直到计算出结果,将结果显示在七段数码管上。 .. raw:: html

完成16位多周期移位乘法器上板

参照输入设计,完成输入功能,并将结果给七段数码管模块,功能需要按照规定实现, 但如何设计,状态机划分可以自定义实现。完成所有的设计。

下面是代码框架参考: .. code-block:: v :caption: 乘法器代码框架参考 :linenos: module top ( clk ,reset ,data ,code ,cs_o ); input wire clk; // 100 Mhz System Clock input wire reset; input wire [23:0] data; output wire [7:0] code; output wire [7:0] cs_o; wire [15:0] a, b; wire sign_a, sign_b, valid; fsm_input u_fsm_input( .clk (clk ) // <>o>> ,.b (b ) // >>o>> ,.sign_a (sign_a ) // >>o>> ,.sign_b (sign_b ) // >>o>> ,.valid (valid ) // >>o>> ); wire [31:0] result; wire finish; mul u_mul( .clk (clk ) // <>o>> ,.finish (finish ) // >>o>> ); wire [31:0] display_data; assign display_data = finish ? result : {a, b}; seg_driver u_seg_driver( .clk (clk ) // <>o>> ,.cs_o (cs_o ) // >>o>> ); endmodule