这一篇在干嘛?
面积是数字设计三大物理特性之二。本章讲如何从架构(拓扑)层面省面积:把上一章展开的流水线重新”卷起来”复用逻辑、用状态机控制复用、跨模块共享资源;以及一个反直觉的重点——复位策略会极大影响面积,错误的复位写法能让综合器放弃专用硬件资源,面积暴涨几个数量级。
背景:拓扑优化 vs 电路级优化
**拓扑(topology)**指设计的高层组织方式,与具体器件无关;而综合和布局布线工具做的”电路级优化”只是把局部设计的门数减到最少,还可能依赖具体器件。工具救不了错误的拓扑——省面积必须从架构选型入手。面积优化的核心思想是:让逻辑资源被最大程度复用,代价往往是吞吐量(速度)。这正是第 1 章流水线换面积的反向操作。
2.1 卷起流水线(Rolling Up the Pipeline)
上一章”展开循环”用面积换吞吐;想要小面积,就反过来——把流水线卷起来,让各级重复的逻辑复用同一份资源。适用于流水线各级有重复逻辑的设计。
看一个 8 位定点小数乘法器:A 是普通整数格式(定点小数点在 LSB 右侧),B 的小数点在 MSB 左侧——即 B 把 A 缩放到 0~1 之间。先看”一拍出结果”的常规实现:
module mult8(
output [7:0] product,
input [7:0] A,
input [7:0] B,
input clk);
reg [15:0] prod16;
assign product = prod16[15:8];
always @(posedge clk)
prod16 <= A * B;
endmodule点评:每个时钟都能出一个个新乘积。表面上看这个设计没有明显的流水线寄存器,但乘法器本身就是一条很长的逻辑链,完全可以用中间寄存器层流水化——我们要”卷”的正是这个乘法器。把它改成移位-相加(shift and add)结构:
module mult8(
output done,
output reg [7:0] product,
input [7:0] A,
input [7:0] B,
input clk,
input start);
reg [4:0] multcounter; // counter for number of shift/adds
reg [7:0] shiftB; // shift register for B
reg [7:0] shiftA; // shift register for A
wire adden; // enable addition
assign adden = shiftB[7] & !done;
assign done = multcounter[3];
always @(posedge clk) begin
// increment multiply counter for shift/add ops
if (start) multcounter <= 0;
else if (!done) multcounter <= multcounter + 1;
// shift register for B
if (start) shiftB <= B;
else shiftB[7:0] <= {shiftB[6:0], 1'b0};
// shift register for A
if (start) shiftA <= A;
else shiftA[7:0] <= {shiftA[7], shiftA[7:1]};
// calculate multiplication
if (start) product <= 0;
else if (adden) product <= product + shiftA;
end
endmodule点评(结构见图 2.1):
- 原理就是手算竖式乘法:B 的每一位是 1 时,就把(相应左移的)A 加进累加器;
shiftB每拍左移 1 位({shiftB[6:0], 1'b0}),把各 bit 轮流顶到最高位;shiftA每拍右移 1 位({shiftA[7], shiftA[7:1]}),保持权重对应;adden = shiftB[7]决定本拍是否累加;- 一次性乘法器的那棵大逻辑树被彻底消灭,换成了几个移位寄存器 + 一个加法器,面积非常紧凑;
- 代价:一次乘法要花 8 个时钟;还有
done需要握手,吞吐量大幅下降; - 妙处在于:这个数据流有自然流动方向,靠一个计数器就能调度,不需要额外控制逻辑——下一节的例子就没这么幸运了。

核心结论
卷起流水线可以优化各级逻辑重复的流水线设计的面积,代价是吞吐量与延迟。
2.2 基于控制的逻辑复用(Control-Based Logic Reuse)
当共享逻辑的面积大于控制逻辑的面积时,用控制电路调度复用就是划算的。上一节的乘法器有自然数据流,不需要控制;但更多应用里,被复用资源的输入来源复杂多变,就需要状态机来指挥。
看一个低通 FIR 滤波器:
三个乘积共用一个乘法器逐个算,需要一个状态机按顺序喂入系数和采样:
module lowpassfir(
output reg [7:0] filtout,
output reg done,
input clk,
input [7:0] datain, // X[0]
input datavalid, // X[0] is valid
input [7:0] coeffA, coeffB, coeffC); // coeffs for low pass filter
// define input/output samples
reg [7:0] X0, X1, X2;
reg multdonedelay;
reg multstart; // signal to multiplier to begin computation
reg [7:0] multdat;
reg [7:0] multcoeff; // the registers that are multiplied together
reg [2:0] state; // holds state for sequencing through mults
reg [7:0] accum; // accumulates multiplier products
reg clearaccum; // sets accum to zero
reg [7:0] accumsum;
wire multdone; // multiplier has completed
wire [7:0] multout; // multiplier product
// shift-add multiplier for sample-coeff mults
mult8 mult8 (.clk(clk), .dat1(multdat),
.dat2(multcoeff), .start(multstart),
.done(multdone), .multout(multout));
always @(posedge clk) begin
multdonedelay <= multdone;
// accumulates sample-coeff products
accumsum <= accum + multout[7:0];
// clearing and loading accumulator
if (clearaccum) accum <= 0;
else if (multdonedelay) accum <= accumsum;
// do not process state machine if multiply is not done
case(state)
0: begin
// idle state
if (datavalid) begin
// if a new sample has arrived
// shift samples
X0 <= datain;
X1 <= X0;
X2 <= X1;
multdat <= datain; // load mult
multcoeff <= coeffA;
multstart <= 1;
clearaccum <= 1; // clear accum
state <= 1;
end
else begin
multstart <= 0;
clearaccum <= 0;
done <= 0;
end
end
1: begin
if (multdonedelay) begin
// A*X[0] is done, load B*X[1]
multdat <= X1;
multcoeff <= coeffB;
multstart <= 1;
state <= 2;
end
else begin
multstart <= 0;
clearaccum <= 0;
done <= 0;
end
end
2: begin
if (multdonedelay) begin
// B*X[1] is done, load C*X[2]
multdat <= X2;
multcoeff <= coeffC;
multstart <= 1;
state <= 3;
end
else begin
multstart <= 0;
clearaccum <= 0;
done <= 0;
end
end
3: begin
if(multdonedelay) begin
// C*X[2] is done, load output
filtout <= accumsum;
done <= 1;
state <= 0;
end
else begin
multstart <= 0;
clearaccum <= 0;
done <= 0;
end
end
default
state <= 0;
endcase
end
endmodule点评(结构见图 2.2):
- 整个设计只有一个乘法器和一个累加器(MAC);状态机在 0→1→2→3 四个状态间循环,依次调度 coeffA×X[0]、coeffB×X[1]、coeffC×X[2] 三次乘法;
multdonedelay是multdone打一拍的延迟——因为accumsum要等一拍才有效,状态机的跳转必须与之对齐,这是复用控制里最常见的时序对齐问题;- 为什么需要状态机?因为这里没有像移位-相加乘法器那样”自然流动”的递归数据流——输入是几个任意的寄存器(采样和系数),要按顺序组合出所有乘积,最高效的调度方式就是状态机。

核心结论
当共享逻辑比控制逻辑大得多时,用控制(状态机)来指挥逻辑复用是划算的。
2.3 资源共享(Resource Sharing)
这里说的资源共享不是布局布线工具做的低级优化,而是更高层的架构级共享:把不同功能模块里”长得像”的资源拉到层次结构的全局点,供多个功能区共用。只要某个功能块在其他地方(甚至其他模块)也能用,就值得共享。
最简单的例子是系统计数器。很多设计里散落着大量计数器:定时器、序列发生器、状态机……它们常常可以抽到更高层次统一分发。比如模块 A 用 8 位自由运行计数器,每 256 个时钟翻转一次(100 MHz 下即每 2.56 μs 触发一次);模块 B 用 11 位计数器生成 5.5 kHz 定频、占空比可变的 PWM 脉冲(100 MHz 时钟下周期为十六进制 700 个时钟,计数到预设值 1666 复位)。

两个模块的功能完全独立,计数器特性也不同(一个 8 位自由跑、一个 11 位到点清零),但它们完全可以合并成一个全局 11 位计数器,模块 A、B 各取所需:

核心结论
面积敏感的紧凑设计中,主动搜索其他模块里的”同类资源”,把它们上提到层次结构的全局点共享。
2.4 复位对面积的影响
一个常见误解是:复位结构是”纯全局”的东西,对设计规模影响不大。事实恰恰相反——坚持给每个触发器都定义全局复位,看似好习惯,实际可能让设计更大更慢。原因是:某些功能本可以按 FPGA 的细粒度架构(fine-grain architecture)优化映射到专用资源,但把复位引进每个同步元件,会迫使综合与映射工具退回更粗的实现。
核心结论
不当的复位策略会让设计无谓地变大,并抑制某些面积优化。
2.4.1 没有复位能力的资源(Resources Without Reset)
看一个最简单的 15 位移位寄存器的两种写法:
// IMPLEMENTATION 1: Synchronous Reset
always @(posedge iClk)
if(!iReset) sr <= 0;
else sr <= {sr[14:0], iDat};
// IMPLEMENTATION 2: No Reset
always @(posedge iClk)
sr <= {sr[14:0], iDat};点评:两种写法功能上只差一个复位态,看起来无关痛痒。关键在于:Xilinx 器件里内置了 SRL16 移位寄存器专用资源,综合器要把代码”直接映射”上去的前提是代码形态匹配。而 SRL16 没有复位控制信号——一旦代码里定义了复位,SRL16 就用不了,只能退化成离散触发器实现:


资源对比(表 2.1):
| 实现 | Slices | Flip-flops |
|---|---|---|
| 定义了复位 | 9 | 16 |
| 无复位 | 1 | 1 |
去掉复位信号后,9 个 slice / 16 个触发器缩减到 1 个 slice / 1 个触发器——同时拿到了最紧凑、最高速的移位寄存器实现。
核心结论
给专用资源分配了不兼容的复位,它就不会被使用,功能将退化为通用元件实现,占用更多面积。
2.4.2 没有置位能力的资源(Resources Without Set)
类似的问题也出现在”置位(set,复位到全 1 而不是全 0)“上。以 8×8 乘法器为例:
module mult8(
output reg [15:0] oDat,
input iReset, iClk,
input [7:0] iDat1, iDat2
);
always @(posedge iClk)
if(!iReset) oDat <= 16'hffff;
else oDat <= iDat1 * iDat2;
endmodule点评:与移位寄存器例子的唯一区别是复位值——这里是 16'hffff(置位)而不是 0。大多数 FPGA 的乘法器硬核内置了复位资源,但没有置位资源。坚持要置位功能,综合器就只能像图 2.7 那样:乘法器输出每个位上外挂一个门电路,在复位有效时强行输出 1,乘法器自带的复位反而闲置浪费。

资源对比(表 2.2):
| 实现 | Slices | Flip-flops | LUTs | Mult16 |
|---|---|---|---|---|
| Reset(复位到 0) | 9 | 16 | 1 | 1 |
| Set(置位到全 1) | 1 | 1 | 1 | 1 |
把置位改成复位操作后,同样缩减到 1 个 slice / 1 个触发器,得到最紧凑高速的乘法器实现。设计时优先用”清零”而不是”置位”,就能直接映射硬件资源。
2.4.3 没有异步复位能力的资源(Resources Without Asynchronous Reset)
新一代高性能 FPGA 提供了大量多用途内置模块(DSP、BRAM 等),它们通常有复位功能,但对复位拓扑类型有限制。以 Xilinx Virtex-4 的 DSP 乘累加(MAC,Multiply-Accumulate)模块为例,其内部结构只有同步复位能力(见图 2.8)。如果代码非要写成异步复位:
module dspckt(
output reg [15:0] oDat,
input iReset, iClk,
input [7:0] iDat1, iDat2);
reg [15:0] multifactor;
always @(posedge iClk or negedge iReset)
if (!iReset) begin
multifactor <= 0;
oDat <= 0;
end
else begin
multifactor <= (iDat1 * iDat2);
oDat <= multifactor + oDat;
end
endmodule点评:
always @(posedge iClk or negedge iReset)是异步复位的经典写法——复位不吃时钟,立即生效;- 但 DSP 硬核的复位引脚只接受同步复位(见图 2.8),综合器只能在 DSP 核外面搭一堆额外的逻辑来模拟异步复位行为。

资源对比(表 2.3):
| 架构 | Slices | Flip-flops | LUTs | DSPs |
|---|---|---|---|---|
| 异步复位 | 17 | 32 | 16 | 1 |
| 同步复位 | 0 | 0 | 0 | 1 |
同步复位版本可以让综合器直接用上 DSP 硬核,外围逻辑为 0;异步复位则白白多出 17 个 slice、16 个 LUT。
核心结论
DSP 等多功能内置资源对复位策略的适配性通常很差——用它们之前先查清支持哪种复位。
2.4.4 复位 RAM(Resetting RAM)
很多 FPGA 的内置 RAM 资源也只支持同步复位。对 RAM 模块强行做异步复位对面积是灾难性的:不像乘法器+加法器可以拼出 MAC,RAM 没有更小的基本元件可以”缝”出大 RAM(只有更小的 RAM),综合器也没法在输出端加几个门就模拟出异步复位功能。
module resetckt(
output reg [15:0] oDat,
input iReset, iClk, iWrEn,
input [7:0] iAddr, oAddr,
input [15:0] iDat);
reg [15:0] memdat [0:255];
always @(posedge iClk or negedge iReset)
if (!iReset)
oDat <= 0;
else begin
if (iWrEn)
memdat[iAddr] <= iDat;
oDat <= memdat[oAddr];
end
endmodule点评:这段代码就是一个 256×16 的 RAM 读写。在 Xilinx Virtex-4 上,BRAM(Block RAM,块存储器)只有同步复位。同步复位写法可以直接映射为一个 BRAM(见图 2.9);而上面这段异步复位代码,综合器只能用分布式小 RAM 块拼接出同等容量的 RAM,外加额外的译码逻辑和异步复位实现逻辑(见图 2.10)。


资源对比(表 2.4)——差距触目惊心:
| 实现 | Slices | Flip-flops | 4 输入 LUTs | BRAMs |
|---|---|---|---|---|
| 异步复位 | 3415 | 4112 | 2388 | 0 |
| 同步复位 | 0 | 0 | 0 | 1 |
核心结论
复位 RAM 通常是糟糕的设计实践,尤其是异步复位——不当的 RAM 复位对面积是灾难性的。
2.4.5 利用触发器的置位/复位引脚(Utilizing Set/Reset Flip-Flop Pins)
反过来看:set/reset 引脚也可以被综合器”征用”来实现部分组合逻辑,从而减少查找表(LUT,Look-Up Table,FPGA 里实现组合逻辑的基本单元)负担、加速数据路径。
比如图 2.11 的 OR 门逻辑,综合器可以选择用触发器的置位(set)引脚来实现,如图 2.12 所示——门被省掉了;同理,图 2.13 的 AND 门可以用触发器的复位(CLR)引脚吸收掉,如图 2.14 所示。




看一个具体例子(Xilinx Spartan-3)。先写带外部异步复位的版本:
module setreset(
output reg oDat,
input iReset, iClk,
input iDat1, iDat2);
always @(posedge iClk or negedge iReset)
if (!iReset)
oDat <= 0;
else
oDat <= iDat1 | iDat2;
endmodule点评:由于规定了异步复位,综合器只能用带异步复位的触发器(图 2.15),OR 功能用离散逻辑实现——逻辑元件不可避免。

如果去掉复位、保留同样的逻辑功能,综合器可以选用 FDS 元件(带同步 set 和 reset 的触发器),把 OR 操作直接放进 set 引脚(图 2.16)——零逻辑元件实现。

还可以更进一步,同时利用同步 set 和 reset。要实现逻辑:
可以这样写,让 set 和 reset 资源都派上用场:
module setreset (
output reg oDat,
input iClk,
input iDat1, iDat2, iDat3);
always @(posedge iClk)
if (iDat3)
oDat <= 0;
else if (iDat1)
oDat <= 1;
else
oDat <= iDat2;
endmodule点评:iDat3 的优先级最高,类似触发器的复位引脚(清 0);iDat1 命中时置 1,对应 set 引脚;否则透传 iDat2。结果(图 2.17):取反、与、或三个逻辑操作全部由一个触发器实现,零 LUT。当然,这种优化在设计架构阶段往往不可预知——所以结论是:当面积是首要考虑时,尽量避免使用 set 或 reset。

2.5 本章要点速查
- 卷起流水线可优化各级逻辑重复的流水线设计的面积。
- 共享逻辑大于控制逻辑时,可用控制(状态机)指挥逻辑复用。
- 面积优先时,主动把各模块的同类资源(如计数器)上提到全局共享。
- 不当的复位策略会让设计无谓变大并抑制优化;给专用资源分配不兼容的复位会导致其退化成通用元件。
- DSP 等多功能资源对复位策略适配性差;不当复位 RAM(尤其异步复位)是面积灾难。
- set/reset 引脚可以被综合器用来吸收组合逻辑;但约束复位也会限制可用元件库——面积优先时尽量少用 set/reset。
常见坑
最隐蔽的坑是”为了安全”给每个寄存器都加复位,尤其是异步复位。后果是分层的:轻则像移位寄存器那样丢掉 SRL16(9 倍面积),重则像 RAM 例子那样直接损失一个 BRAM、多掏 3415 个 slice——工程后期发现资源不够时,回头改复位策略要动几乎所有模块。正确的做法是:架构阶段就明确哪些寄存器真正需要复位(控制通路、状态机),数据通路上的移位寄存器、流水线寄存器尽量不加。
通关标准:
学完本篇你应该能做到:
- 把一段一拍出结果的乘法(或 MAC)逻辑改写成移位-相加的复用结构,并算清它多花了几拍;
- 判断一个复用需求该用”自然数据流”还是”状态机调度”来实现;
- 拿到目标 FPGA 器件手册时,能查出 SRL16 / DSP / BRAM 分别支持哪种复位,并据此决定代码写法;
- 理解”复位到全 0”(reset)和”复位到全 1”(set)在硬件资源上的本质差别。
自测:卷起流水线和展开循环分别用什么换什么?
展开循环(unrolling)用面积换吞吐量:n 级流水并行处理,吞吐提升 n 倍,但每级都要独立资源。卷起流水线(rolling up)正好相反,用吞吐量/延迟换面积:各级重复逻辑折叠成一套资源循环使用(如移位-相加乘法器),面积大幅缩小,但一次运算要多花若干拍。
自测:为什么 FIR 复用例子需要状态机,而移位-相加乘法器不需要?
移位-相加乘法器的数据有自然流动方向:B 左移、A 右移、累加器累加,一个计数器就能调度。而 FIR 的乘法输入是几个任意寄存器(X0/X1/X2 和 coeffA/B/C 的各种组合),没有自然流动顺序,需要状态机显式地按序加载操作数并对齐 done 信号。
自测:给 16 位移位寄存器加了同步复位,在 Xilinx 上会损失什么?
损失 SRL16 专用移位寄存器资源(它没有复位引脚),实现从 1 个 slice 退化到 9 个 slice、16 个触发器——面积增大且速度特性更差。数据通路上的移位寄存器如果初始值无所谓,就不要写复位。
自测:256×16 的 RAM 用异步复位,Virtex-4 上的资源对比是什么量级?
同步复位:1 个 BRAM,其余全为 0;异步复位:0 个 BRAM,3415 个 slice、4112 个触发器、2388 个 4 输入 LUT。因为 BRAM 只支持同步复位,异步复位迫使综合器用分布式小 RAM 和额外逻辑重新拼出整个 RAM。
自测:为什么"面积优先时尽量避免 set/reset"?这和 2.4.5 节"用 set/reset 引脚做优化"矛盾吗?
不矛盾,这是同一枚硬币的两面:不约束复位策略时,综合器可以自由选择带 set/reset 引脚的触发器库元件,把 OR/AND 之类的组合逻辑吸收进引脚,实现零 LUT 优化(2.4.5 节的收益);而一旦你显式要求了复位(尤其是异步复位、指定置位值),综合器的元件库被限制,反而做不了这类优化,甚至丢掉专用资源。所以原则是:把 set/reset 的决定权留给综合器,只在功能真正需要时才写。