Booth乘法器:硬件加速乘法的核心算法与Verilog实现

📅 2026/8/13 3:22:56
Booth乘法器:硬件加速乘法的核心算法与Verilog实现
1. 从“纸笔计算”到“硬件加速”为什么我们需要乘法器如果你写过任何一段需要做乘法的代码比如c a * b你可能从未想过这行简单的语句在计算机内部是如何被执行的。对于早期的CPU或者在一些极简的嵌入式内核里乘法操作可能真的就是通过软件模拟一连串的“移位”和“加法”来实现的效率低下。但现代处理器动辄每秒执行数十亿次运算如果每次乘法都靠这种“笨办法”性能将惨不忍睹。于是硬件乘法器应运而生它是一块专门为执行乘法运算而设计的数字电路其目标只有一个用尽可能少的时钟周期完成两个二进制数的相乘。在众多硬件乘法器的实现方案中Booth乘法器是一个无法绕开的名字。它不像最直观的“移位相加”那样简单粗暴而是采用了一种更为巧妙的算法能够显著减少部分积的数量从而提升运算速度、降低电路功耗和面积。我第一次在数字逻辑设计课上接触Booth算法时感觉它像是一个“魔术”——通过观察乘数的相邻位就能决定是加、减被乘数还是什么都不做。这种基于“编码”的思想是硬件设计从“蛮力”走向“智慧”的一个经典缩影。今天我们就来彻底拆解这个经典的Booth乘法器。无论你是正在学习计算机体系结构的学生还是从事FPGA或ASIC设计的工程师理解Booth乘法器不仅是为了应付考试或完成项目更是为了掌握一种优化硬件设计的核心思路。我们会从最基础的原理开始一步步推导出算法并用Verilog代码实现它最后再聊聊在实际设计中你会遇到的那些“坑”和高级变种。放心我会尽量用“说人话”的方式把这件事讲明白。2. Booth算法的核心洞察把连续的“1”变成一次加法和一次减法要理解Booth乘法器为什么快我们得先看看它要替代的“对手”——最基本的移位相加乘法器是怎么工作的。假设我们要计算5 * 3二进制0101 * 0011。 传统方法是这样0101 (被乘数 5) x 0011 (乘数 3) ------ 0101 (乘数最低位为1加0101) 01010 (左移一位乘数次低位为1加0101) 000000 (左移两位乘数位为0加0) 0000000 (左移三位乘数位为0加0) --------- 0001111 (结果 15)你会发现乘数里有几个‘1’我们就需要生成几个部分积Partial Product并进行相加。当乘数中‘1’很多尤其是出现连续多个‘1’时计算步骤就很多。Booth算法的天才之处在于它发现了一串连续的‘1’可以被转化为一次加法和一次减法从而减少操作次数。它的核心是对乘数进行“重编码”。2.1 Booth编码规则看两位定操作Booth算法不是单看乘数的当前位而是观察当前位和它右边一位即低位的组合。我们引入一个辅助位初始为0放在乘数最低位的右边。这样我们每次检查的是乘数的一对位(Q_i, Q_{i-1})。操作规则如下表所示当前位 (Q_i)低位/辅助位 (Q_{i-1})操作说明00部分积右移一位相当于不做加减只移位01部分积加上被乘数然后右移10部分积减去被乘数然后右移11部分积右移一位这个规则怎么来的我们可以用一个简单的数学推导来理解。考虑一个二进制数中有一段连续的‘1’例如...011...110...。这段连续的‘1’从权重为2^k的位开始到权重为2^m的位结束m k。它的值可以表示为2^{k1} - 2^m例如0011100(28) 可以看作0100000(32) 减去0000100(4)。Booth算法通过检测01和10的边界来识别这样的连续‘1’串的起点和终点当遇到01时从0变1意味着连续‘1’串的开始执行减法减去2^m对应的值即被乘数左移m位。当遇到10时从1变0意味着连续‘1’串的结束执行加法加上2^{k1}对应的值即被乘数左移k1位。遇到00或11时处于连续‘0’串或连续‘1’串的内部不执行加减只移位。2.2 手工演算用Booth算法计算 5 * (-3)为了展示Booth算法处理负数的优势我们选一个带负数的例子5 * (-3)。我们用4位二进制补码表示。被乘数 M 5 (0101)乘数 Q -3。3的二进制是0011-3的补码是取反(0011) 1 1100 1 1101。初始部分积 A 0000 辅助位 Q_{-1} 0。乘数位数 n 4。我们一步步来步骤0初始化A 0000, Q 1101, Q_{-1} 0, M 0101 检查 (Q_0, Q_{-1}) (1, 0) - 规则10执行A A - M。 A 0000 - 0101。在补码系统中减法等于加负数的补码。-0101的补码是1011。 所以 A 0000 1011 1011。 然后算术右移(A, Q, Q_{-1})。注意A的最高位符号位是1右移时补1。 结果A 1101, Q 1110, Q_{-1} 1 (原Q的最低位0被移出存入Q_{-1})。步骤1检查 (Q_0, Q_{-1}) (0, 1) - 规则01执行A A M。 A 1101 0101 0010 (进位溢出位丢弃保留4位)。 算术右移A 0001, Q 0111, Q_{-1} 0。步骤2检查 (Q_0, Q_{-1}) (1, 0) - 规则10执行A A - M。 A 0001 - 0101 0001 1011 1100。 算术右移A 1110, Q 0011, Q_{-1} 1。步骤3检查 (Q_0, Q_{-1}) (1, 1) - 规则11只执行右移。 算术右移A 1111, Q 0001, Q_{-1} 1。步骤4(因为n4我们已经完成了4次判断和移位操作但通常算法会多执行一次这里需要注意经典Booth算法在n位乘法时需要进行n次迭代。我们上面已经做了4次。最终结果在(A, Q)寄存器中但此时Q中可能还包含未处理完的位这里是一个常见的理解混淆点。实际上标准的Booth算法流程在初始化后会进行n次循环每次循环包含“检查-加减-右移”。我们上面步骤0到步骤3正好是4次循环。最终乘积是A和Q合并起来但A是高位部分积Q是低位部分积/剩余的乘数。让我们验证一下结果。)最终 A 1111, Q 0001。 将A和Q组合1111 0001。这是一个8位的补码数。 它的十进制值是多少最高位是1是负数。求其原码取反(11110001) 1 0000 1110 1 0000 1111 15。所以是 -15。 正确5 * (-3) -15。注意这里演示的是最基本的Booth算法有时被称为Radix-2 Booth。它有一个关键点乘数的位数决定了迭代次数。对于n位乘数需要进行n次检查、加减和移位操作。我们的演算过程是准确的。有些资料会提到在循环开始前或结束后多进行一次操作那通常是另一种变体如Modified Booth或是对算法边界条件的特殊处理。通过这个例子你看到了Booth算法如何优雅地处理补码数无需像原码乘法那样单独处理符号位。这是它被广泛用于有符号数乘法的根本原因。3. 从算法到电路Booth乘法器的硬件实现框图理解了算法我们来看看如何用数字电路硬件来实现它。一个最基本的Booth乘法器主要由以下几个部分组成下图清晰地展示了数据通路[被乘数 M] (n位) | v ------------------- | 求补器电路 | --- (控制信号Subtract) | (2‘s Complement) | ------------------- | v ------------------- | 多路选择器 | --- (控制信号来自Booth编码器) | (MUX) | 选择输出M, -M, 或 0 ------------------- | v [加法器] (n位) --- 另一输入端来自累加寄存器A的高位部分 | v ------------------------------------- | 累加寄存器 A (n位) | ------------------------------------- | | v v [算术右移控制] [乘数寄存器 Q (n位)] | | --------- 1 --------------- (Q的最低位移出) | | v v [新的A] [新的Q] | v [辅助位 Q_{-1}] | v --------------- | Booth编码器 | | (根据Q_0, Q_{-1})| --------------- | v [生成控制信号Add, Subtract, Shift]3.1 核心部件详解寄存器组A寄存器 (Accumulator)n位宽初始为0。用于存放高位部分积。Q寄存器 (Multiplier)n位宽初始存放乘数Q。Q_{-1}寄存器1位宽就是那个关键的辅助位初始为0。M寄存器 (Multiplicand)n位宽存放被乘数M在整个计算过程中保持不变。Booth编码器这是一个组合逻辑电路输入是Q[0]乘数寄存器的最低位和Q_{-1}输出是三个控制信号通常用两位编码即可Add当(Q[0], Q_{-1}) 01时有效控制多路选择器输出M。Subtract当(Q[0], Q_{-1}) 10时有效控制多路选择器输出-M即M的补码。Shift当(Q[0], Q_{-1}) 00或11时有效或者它本身是一个始终进行的操作而Add/Subtract决定是否要先执行加减。求补器与多路选择器 (MUX)为了得到-M我们需要一个求补器电路它对M寄存器的值执行“按位取反再加1”的操作。这个操作可以由一个反向器链和一个加法器实现但更常见的做法是利用加法器本身当Subtract信号有效时将M的每一位取反后送入加法器并将加法器的进位输入Cin设为1这等价于加上(~M 1)即-M。因此多路选择器实际是集成在加法器的输入端的。它根据Add/Subtract信号选择将M、~M或0送到加法器的一个输入端。n位加法器这是核心运算单元。它的一个输入来自多路选择器M,-M或0另一个输入来自A寄存器。输出结果写回A寄存器。移位逻辑这是一个并行的、带符号扩展的右移电路。在每次加减操作或直接之后它将{A, Q, Q_{-1}}这个整体进行算术右移一位。这意味着A的最高位符号位在右移时保持不变即移入自身同时A的最低位被移入Q的最高位。Q的最低位被移入Q_{-1}。Q_{-1}的旧值被丢弃。这个操作可以通过一组D触发器和连线实现不需要一个真正的“移位器”模块。控制单元 (Control Unit)这是一个有限状态机FSM负责控制整个乘法过程的节奏。它主要做两件事初始化清零A和Q_{-1}加载M和Q。循环控制生成一个计数器从0计数到n-1n为乘数位数。每个时钟周期它使能Booth编码器、加法器、移位器工作一次并更新计数器。当计数器计满n次后发出Done信号此时{A, Q}中存放的就是最终的乘积。3.2 数据流与时钟周期在一个典型的时钟周期内假设是同步设计时钟上升沿根据当前Q[0]和Q_{-1}的值Booth编码器产生Add/Subtract/Shift信号。组合逻辑阶段多路选择器根据控制信号选择M、~M或0。加法器计算A (选择的结果)。移位电路准备好右移后的新值{A_new, Q_new, Q_{-1}_new}。下一个时钟上升沿控制单元将加法器结果或移位后的值锁存到A寄存器将移位后的Q和Q_{-1}锁存到对应寄存器。计数器加1。重复以上过程直到计数器等于n。整个乘法需要n个时钟周期完成。这就是Booth乘法器的流水线节奏。虽然它不是单周期完成但相比纯组合逻辑的阵列乘法器在面积和时序上更容易优化也更容易集成到流水线CPU中。4. 用Verilog实现一个参数化的Booth乘法器理论讲完了是时候动手了。下面我将给出一个可综合的、参数化的Booth乘法器Verilog代码。这个实现严格遵循我们前面描述的硬件结构。module booth_multiplier #( parameter WIDTH 8 // 支持任意位宽默认为8位 )( input wire clk, input wire rst_n, // 低电平复位 input wire start, // 启动信号高电平有效 input wire signed [WIDTH-1:0] multiplicand, // 被乘数 M input wire signed [WIDTH-1:0] multiplier, // 乘数 Q output reg done, // 计算完成标志 output reg signed [(2*WIDTH)-1:0] product // 乘积结果 ); // 定义状态机状态 localparam IDLE 2b00; localparam CALC 2b01; localparam DONE 2b10; reg [1:0] state, next_state; reg [WIDTH-1:0] count; // 迭代计数器 reg signed [WIDTH:0] A; // 累加寄存器扩展1位用于防止溢出 reg signed [WIDTH-1:0] Q; // 乘数寄存器 reg Q_minus1; // 辅助位 Q_{-1} reg signed [WIDTH-1:0] M; // 被乘数寄存器 // Booth编码信号 wire add, sub; assign {add, sub} booth_encoder(Q[0], Q_minus1); // Booth编码器函数 function [1:0] booth_encoder; input qi; input qim1; begin case ({qi, qim1}) 2b01: booth_encoder 2b10; // 加 (Add) 2b10: booth_encoder 2b01; // 减 (Sub) default: booth_encoder 2b00; // 无操作 endcase end endfunction // 状态机主进程 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; A 0; Q 0; Q_minus1 0; M 0; count 0; product 0; done 1b0; end else begin state next_state; case (state) IDLE: begin if (start) begin A 0; Q multiplier; Q_minus1 0; M multiplicand; count 0; done 1b0; end end CALC: begin // 1. 根据Booth编码执行加减法 if (add) begin A A M; end else if (sub) begin A A - M; end // 注意这里加减法结果在同一个时钟周期末被锁存。 // 实际硬件中加减和移位是组合逻辑在下一个时钟沿一起锁存。 // 为了清晰我们将移位操作放在下一个always块或同一个块但用临时变量。 // 下面我们采用更符合实际数据流的描述方式。 end DONE: begin product {A[WIDTH-1:0], Q}; // 拼接最终结果 done 1b1; end endcase end end // 组合逻辑计算下一个状态和移位操作 reg signed [WIDTH:0] A_temp; always (*) begin next_state state; A_temp A; // 默认值 case (state) IDLE: begin if (start) next_state CALC; end CALC: begin // 先进行加减法组合逻辑部分 A_temp A; if (add) begin A_temp A M; end else if (sub) begin A_temp A - M; end // 加减法后执行算术右移 // {A, Q, Q_minus1} 整体右移一位 // 注意A_temp是(WIDTH1)位我们需要将其视为扩展后的A进行移位 // 实际上我们操作的是{A, Q}这个整体但A我们用了扩展位。 // 一个更清晰的实现是定义一个临时的大寄存器。 // 这里为了概念清晰我们描述移位过程 // 新的A {A_temp[WIDTH], A_temp[WIDTH:1]}; // 算术右移最高位符号扩展 // 新的Q {A_temp[0], Q[WIDTH-1:1]}; // 新的Q_minus1 Q[0]; // 判断迭代是否完成 if (count WIDTH - 1) begin next_state DONE; end else begin next_state CALC; end end DONE: begin next_state IDLE; end default: next_state IDLE; endcase end // 移位和计数器更新进程在状态机周期末执行 always (posedge clk) begin if (state CALC) begin // 执行移位操作 // 这里使用一个中间变量来清晰地展示移位 // 实际综合时这部分逻辑会合并 begin reg signed [WIDTH:0] A_shifted; reg [WIDTH-1:0] Q_shifted; A_shifted {A_temp[WIDTH], A_temp[WIDTH:1]}; // 算术右移 Q_shifted {A_temp[0], Q[WIDTH-1:1]}; Q_minus1 Q[0]; A A_shifted; Q Q_shifted; count count 1; end end end endmodule4.1 代码关键点解析与避坑指南位宽与符号处理我们使用了signed关键字让Verilog编译器处理有符号数的加减法和移位。这是最省事的方法但你要确保你的综合工具支持signed类型的算术运算。累加器A的位宽是WIDTH1位。为什么因为在进行A /- M操作时可能会产生一个WIDTH1位的结果例如两个较大的正数相加。多出的这一位用于防止中间结果溢出。最终取乘积时我们取A[WIDTH-1:0]和Q拼接。状态机设计采用了经典的三段式状态机次态逻辑、状态寄存器、输出逻辑清晰且易于综合。IDLE状态等待开始信号并初始化所有寄存器。CALC状态是核心循环每个周期完成一次Booth编码、加减法、移位和计数。DONE状态输出结果并拉高完成信号。移位操作的实现这是最容易出错的地方。注意算术右移必须保持符号。对于有符号数A_temp直接使用运算符Verilog中的算术右移是最安全的A_shifted A_temp 1;。在我们的描述性代码中我们手动实现了算术右移{A_temp[WIDTH], A_temp[WIDTH:1]}这等效于A_temp 1。移位操作作用于{A_temp, Q}这个整体。A_temp的最低位A_temp[0]移入Q的最高位Q的最低位Q[0]移入Q_minus1。一个常见的综合问题上面的代码为了教学清晰将加减法和移位分在了不同的always块或逻辑段。在实际可综合代码中你需要确保对同一个寄存器如A的赋值在一个时钟沿只发生一次避免多驱动。更严谨的写法是将加减法和移位的组合逻辑计算放在一个always (*)块中生成一个“下一次更新值”然后在时钟沿统一赋值。我上面提供的代码结构已经朝这个方向努力但你可能需要根据综合器的要求进行微调。实操心得在FPGA上实现Booth乘法器时一定要做充分的仿真测试特别是边界测试两个最大的正数相乘、两个最大的负数相乘、一正一负相乘、乘以0、乘以1等。使用ModelSim或Vivado Simulator编写全面的testbench比对你的硬件结果和软件计算使用$signed或直接使用*运算符的结果是否一致。这是确保设计正确的唯一途径。5. 超越基础Booth算法的变体与性能权衡基本的Radix-2 Booth算法已经比原始的移位相加法优秀但它仍有提升空间。最主要的优化方向是减少部分积的数量。Radix-2算法每次检查2位乘数的1位辅助位平均每两个乘数位产生一个部分积。我们可以通过一次检查更多位来进一步压缩部分积。5.1 Radix-4 Booth编码Modified Booth Algorithm这是最著名也是最实用的变体。它一次检查乘数的3位当前两位和低一位将乘数重编码为{-2, -1, 0, 1, 2}这几个值。这样对于n位的乘数只需要生成大约n/2个部分积迭代次数减少了一半编码规则如下假设每次查看的3位为Q_{i1}, Q_i, Q_{i-1}Q_{i1}Q_iQ_{i-1}操作解释0000连续0001M序列结束 (01)010M单个10112M序列开始 (10) 实际上是连续1中的情况但编码为2M-? 这里需要仔细推敲。标准的Radix-4 Booth编码表是100-2M101-M110-M111-0更准确的标准Radix-4 Booth编码表是乘数位组 (Q_{i1}, Q_i, Q_{i-1})编码值操作000000011M0101M01122M100-2-2M101-1-M110-1-M1110-0它的优势很明显部分积减半速度几乎翻倍。但代价是需要生成±2M这个操作这可以通过将被乘数M左移一位实现这需要额外的硬件一个简单的移位器或更宽的数据通路。加法器可能需要处理来自±2M的更大范围的输入。控制逻辑比Radix-2稍复杂。然而在现代处理器设计中Radix-4 Booth几乎是标配因为它在面积增加不大的情况下带来了显著的性能提升。5.2 更高基数的Booth编码理论上还可以使用Radix-8一次看4位、Radix-16等。它们能进一步减少部分积但需要生成±3M、±4M等倍数。生成3MM 2M不能通过简单移位得到需要额外的加法操作这反而会增加关键路径延迟和硬件复杂度。因此Radix-8及以上的编码在实际中较少使用性价比不高。5.3 华莱士树与部分积累加Booth算法解决了生成部分积的效率问题。生成了多个部分积之后如何快速地将它们加起来是另一个关键问题。最朴素的方法是使用一个多操作数加法器链但这会导致很长的进位传播延迟。华莱士树Wallace Tree是一种高效的多操作数加法方案。它使用全加器FA和半加器HA以树形结构组织将多个部分积的相加过程并行化极大地减少了最终得到两个相加数所需的逻辑级数。然后再用一个快速的进位传播加法器如超前进位加法器CLA将这两个数相加得到最终乘积。在现代高性能乘法器中通常是Booth编码 华莱士树 快速CLA的组合拳。Booth编码减少部分积数量华莱士树高速压缩这些部分积最后CLA完成最后一击。5.4 在实际项目中如何选择对面积和功耗极其敏感的场合如超低功耗MCU可能仍然使用简单的移位相加或者最基本的Radix-2 Booth。因为逻辑最简单静态功耗和面积最小。通用嵌入式处理器或中端FPGA应用Radix-4 Booth是一个非常好的平衡点。它在速度和面积之间取得了很好的折衷实现也不复杂。高性能CPU、DSP或GPU一定会采用Radix-4 Booth并结合华莱士树甚至更先进的压缩树如4-2压缩器。这些设计会进行大量的流水线划分以追求极高的时钟频率。需要可变精度的场合如支持8位、16位、32位乘法需要设计更灵活的电路可能采用基-2或基-4的迭代结构通过控制迭代次数来适应不同位宽。经验之谈当你需要在FPGA里自己实现一个乘法器时首先问问自己真的需要吗大多数FPGA都有丰富的DSP Slice里面集成了高度优化的硬核乘法器其性能、功耗和面积都远胜于你用逻辑单元LUT搭建的任何软核乘法器。只有在DSP资源耗尽或者你需要实现一些非常特殊的乘法如复数乘法、混合精度乘法时才考虑用Booth算法自己设计。这时Radix-4 Booth通常是首选。6. 调试与验证如何确保你的Booth乘法器真的对了设计写完了不代表工作结束了。硬件设计的生命线是验证。下面分享一套我常用的Booth乘法器验证方法。6.1 编写全面的Testbench你的testbench必须覆盖各种边界情况和随机情况。timescale 1ns/1ps module tb_booth_multiplier(); parameter WIDTH 8; reg clk, rst_n, start; reg signed [WIDTH-1:0] mulc, mulr; wire done; wire signed [2*WIDTH-1:0] product; booth_multiplier #(.WIDTH(WIDTH)) uut ( .clk(clk), .rst_n(rst_n), .start(start), .multiplicand(mulc), .multiplier(mulr), .done(done), .product(product) ); // 时钟生成 always #5 clk ~clk; // 参考模型直接使用“*”运算符 function signed [2*WIDTH-1:0] expected_product; input signed [WIDTH-1:0] a, b; begin expected_product a * b; end endfunction integer i, error_count; reg signed [2*WIDTH-1:0] exp_prod; initial begin // 初始化 clk 0; rst_n 0; start 0; mulc 0; mulr 0; error_count 0; #20 rst_n 1; // 测试1边界值测试 $display( 开始边界值测试 ); test_case(8sd127, 8sd127); // 最大正数 * 最大正数 test_case(8sd127, -8sd128); // 最大正数 * 最小负数 test_case(-8sd128, 8sd127); // 最小负数 * 最大正数 test_case(-8sd128, -8sd128); // 最小负数 * 最小负数注意溢出但补码乘法有定义 test_case(8sd0, 8sd0); test_case(8sd1, 8sd1); test_case(8sd1, -8sd1); test_case(-8sd1, -8sd1); // 测试2随机测试 $display(\n 开始随机测试 ); for (i0; i1000; ii1) begin mulc $random; mulr $random; test_case(mulc, mulr); end // 测试3特定序列测试例如乘数有很多连续1或01边界的数 $display(\n 开始特定序列测试 ); mulc 8sd5; mulr 8b0011_0011; // 包含多个01和10边界 test_case(mulc, mulr); mulr 8b1111_0000; test_case(mulc, mulr); // 总结 if (error_count 0) $display(\n*** 所有测试通过 ***); else $display(\n*** 发现 %0d 个错误 ***, error_count); $finish; end task test_case; input signed [WIDTH-1:0] a, b; begin mulc a; mulr b; start 1; (posedge clk); #1 start 0; // 启动一个周期即可 wait(done); // 等待计算完成 exp_prod expected_product(a, b); if (product ! exp_prod) begin $display(错误%d * %d %d (预期 %d), a, b, product, exp_prod); error_count error_count 1; end else begin $display(正确%d * %d %d, a, b, product); end (posedge clk); // 等待一个周期再开始下一个测试 end endtask endmodule6.2 常见的错误与排查手段结果完全不对首先检查Booth编码表是否实现正确。(Q[0], Q_{-1})到Add/Sub的映射是否与理论一致这是根源。符号错误当结果为负数时出错。重点检查算术右移是否真的做了符号扩展在Verilog中对于有符号寄存器使用运算符。对于手动拼接确保高位补的是符号位。减法操作A - M是否正确实现在补码系统中A - M A (~M 1)。确保你的加法器在Sub有效时一个输入是M的反码并且进位输入Cin1。迭代次数错误结果是正确值的2倍或一半。检查你的计数器逻辑。对于n位乘法Radix-2 Booth需要n次迭代。你的计数器是从0数到n-1还是从1数到n确保循环次数是n。中间溢出结果在高位部分有错误。检查你的累加器A的位宽是否足够。它需要比被乘数M多至少1位以容纳中间加/减法的可能溢出。在我们的设计中A是WIDTH1位。时序问题在高速时钟下组合逻辑路径加法器移位器可能成为关键路径导致建立时间违例。解决方法流水线化将一次迭代拆分成多个时钟周期。例如用一个周期做Booth编码和加减法下一个周期做移位和更新。这会增加延迟但能提高时钟频率。使用更快的加法器如超前进位加法器CLA。寄存器重定时调整寄存器位置平衡组合逻辑延迟。6.3 使用波形调试在仿真器中如ModelSim, VCS, Verdi打开波形图添加所有关键信号clk,rst_n,start,donestate(状态机状态)count(迭代计数器)A,Q,Q_minus1,Madd,sub(Booth编码输出)product手动跟踪一个计算过程比如5 * -3对照我们第二节的手工演算步骤观察每一个时钟上升沿后A、Q、Q_{-1}的值是否与预期一致。这是定位问题最直观的方法。踩坑实录我曾经遇到一个棘手的Bug乘法器在大多数情况下工作正常但每当乘数是特定的模式如8‘b01010101时结果偶尔出错。通过波形调试发现是状态机在从CALC跳转到DONE时done信号提前了一个周期拉高导致testbench在结果被稳定锁存前就读取了product。原因是我的计数器判断条件写成了count WIDTH而不是count WIDTH-1。这个错误在随机测试中很难被发现因为错误是间歇性的。教训边界条件的测试必须手工精心设计不能依赖随机测试。