做 SoC 的同学应该都有这种经历:内存编译器生成的 SRAM 宏时序不够,或者面积比预想大一圈,综合阶段怎么优化周围逻辑都救不回来,结果最后定位到 memory compiler 里的 Number of banks 设置上。这个参数看起来只是下拉菜单里的一个数字,实际上牵扯到 SRAM 的物理结构、位线负载、功耗分布和后端物理实现。我自己在 28nm 和更先进工艺的项目里都吃过这个参数的亏,也花了不少时间把不同配置的 datasheet 翻了个遍。这篇文章就把 SRAM compiler 里 Number of banks 这个设置讲透,包括它的底层原理、对 PPA 的具体影响、不同编译器产品里的差异,以及实际项目里应该怎么定这个值。
1. 先说清楚一件事:SRAM 里的 bank 到底切的是什么
1.1 从一条位线讲起,理解为什么需要切 bank
很多人第一次看到 Number of banks 选项时,下意识把它当成芯片顶层的 memory bank 划分,这是最常见的误解。实际上编译器里的 bank 切在 SRAM 宏内部,是阵列物理结构的一种组织方式。
SRAM 的基本存储单元是 6T cell,每个 cell 靠两根位线(bitline)和一根字线(wordline)访问。位线本质上是给整列 cell 共用的,字线则是给整行 cell 共用。当你访问某个地址时,字线把这一行的所有 cell 打开,然后位线上形成微小的电压差,交给读出放大器去判断是 0 还是 1。
问题就出在这里:如果一列上挂了太多 cell,位线的寄生电容就大,读出放大器的输入端信号摆幅建立得慢,访问时间自然拉长。同时,字线从行解码器一路走到阵列最远端,RC 延迟也会累积。这个现象和你在马路上越多的车排队越堵是一个道理,位线就是那条马路,cell 就是排队等待上车的乘客。
切 bank 的本质,就是把一整块阵列分成若干个子阵列,每个子阵列独立完成本地的字线驱动、位线预充电和读出放大,再通过第二级数据通路把选中 bank 的数据送出去。这样每条局部位线上挂的 cell 数量变少了,位线电容减小,访问速度得以提升。与此同时,地址的高位作为 bank select,决定本次访问开哪个 bank。
1.2 编译器视角下的 bank 定义:并非你在芯片顶层看到的东西
在 SRAM compiler 的工具文档里,bank 通常被定义为“可以由地址选择信号独立激活的存储子阵列”。每个 bank 内部有自己的一套局部控制电路、灵敏放大器和写入驱动,但共享全局地址预解码、全局数据总线和输入输出控制逻辑。
以总深度 8192、宽度 32 的 SRAM 为例,如果设置 Number of banks = 2,那么地址最高 1 bit 作为 bank select,每个 bank 内部是深度 4096 的阵列;如果设置为 4,则高位 2 bit 做 bank select,每个 bank 内部是深度 2048 的阵列。也就是说,增加 bank 数量,等价于把横向的“行”规模拆小,而不是把纵向的“列”宽度拆小。
这里要特别留意一点:部分编译器的 bank 机制和 column mux 是配合使用的,有些编译器里的 bank 甚至可以直接改变位线方向和 IO 布局方式。所以在动手设置之前,一定要先打开对应工艺库的 compiler user guide,确认 bank 切的是字线方向还是位线方向。这个方向性直接决定了你到底是缩短了字线延迟还是位线延迟,对最终时序的影响路径完全不同。我见过不少工程师只凭经验猜,结果改完参数之后性能反而更差,就是因为方向搞反了。
1.3 一个 analog 视角:局部位线和全局位线的两级结构
现代大型 SRAM 宏基本都采用两级位线结构。局部位线连接一小段 cell,配合局部读出放大器,把一次小摆幅信号转成满摆幅;全局位线再把各个局部读出放大器的结果汇合到最终的数据总线上。这种两级结构在编译器里往往不会直接画给你看,但当你拉大 Number of banks 时,局部位线长度会下降,局部读出放大器数量却会增加。
所以从电路设计者的角度,Number of banks 绝对不是越大越好。每多一个 bank,就多一份局部读出放大器、多一份 bank 选择逻辑、多一块隔离区。放大器的功耗和面积是实实在在的,而位线变短节省下来的时间却可能不是线性增长的。这个权衡关系是这篇文章后面所有讨论的基础,先把它记在心里。
2. 调大 Number of banks 之后,面积、时序和功耗各发生了什么
2.1 面积:省下来的位线电容又花在了哪些地方
很多人第一反应是,bank 越多,每个 bank 的行数越少,位线电容越小,SRAM 应该更小。实际情况没那么理想。
先说位线。位线电容来自整列的 cell 扩散区结电容和金属线本身,减少每列挂的 cell 数量确实能减小电容,也让读出放大器的输入更灵敏。但代价是,你需要在每个 bank 边界加一圈 dummy cell 和隔离结构,防止边缘 cell 受到工艺偏差影响。bank 数量越多,这些边缘开销占的比例越大。
更明显的是控制逻辑开销。全局地址预解码,原来只要解出一根长字线;分 4 个 bank 之后,除了全局解码,还要额外做 4 选 1 的 bank 选择、局部字线的二次驱动、局部读出放大器的动态使能信号对齐等。电路变复杂,面积自然上去。还有全局数据总线,每个 bank 的局部读出放大器输出都要汇集到一组全局线上,如果编译器不主动做多级树形汇合,布线资源也会跟着涨。
实际项目里我做过的对比大致是:在 64K×32、单端口 SRAM、标准 Vt 条件下,从 1 bank 切到 2 bank,面积大约增加 3~5%;从 2 bank 切到 4 bank,再增加 2~3%。这些数字不是绝对的,每个工艺节点、每款编译器都不太一样,但趋势很明确:bank 数量增加大概率会带来面积惩罚,只是大型阵列的惩罚比例相对更小。这也是为什么大容量 SRAM 用多 bank 很常见,小容量 SRAM 用多 bank 就很不划算。
2.2 时序:位线变短,但选择路径变长
时序是选择 bank 数量时最敏感的部分。访问时间可以粗略拆成三段:地址输入到内部预解码、预解码到字线/位线建立、读出放大器到数据输出。
增加 bank 数量,第一段时间会变长。因为 bank select 信号要在预解码阶段就生成,然后和局部字线信号做类似“与”的逻辑,等于在关键路径上多插入了一级选择逻辑。如果编译器把 bank select 放在地址路径后段,这部分延迟会更明显。第二段,也就是 row decoder 到 cell 的距离,会缩短,因为每个 bank 的行数少了。第三段会略微变长,因为局部读出放大器的输出要经过多一级仲裁才能到达 IO。
所以这就形成一个 U 形曲线:bank 太少时位线/字线延迟占主导,bank 太多时控制逻辑和选择逻辑延迟占主导,中间存在一个最优区间。我曾经在 45nm 左右工艺下测试过 128K×32 的宏,从 1 bank 到 4 bank 访问时间一路变好,但到 8 bank 开始变差,16 bank 时已经明显差于 4 bank。原因就是 U 形曲线的右半段。
还有个容易被忽略的时序因素:不同 bank 的读写时序偏差。多 bank 结构下,如果某个 bank 离 IO 布局位置更远,它的局部位线到全局数据通路的走线长度差异会变成额外 skew,编译器通常会把时序模型做得更悲观,导致.lib 里的 setup/hold 数值更差。你在仿真阶段看不出来,但综合和后端会立刻体会到这种“惩罚”。
2.3 功耗:开关功耗、泄漏功耗和 bank 级 clock gating
动态功耗方面,bank 化之后明显会占便宜。因为每次访问只有一个 bank 的位线在翻转,其他 bank 的位线保持预充电状态。位线翻转在 SRAM 访问功耗里是大头,把这个大头砍掉一部分,整块宏的动态功耗会显著下降。
但泄漏功耗不一定降。泄漏功耗主要取决于 cell 数量和工艺电压,阵列切分成多个 bank 并不会让 cell 的漏电消失。反倒是 bank 周边多出来的读出放大器、解码器、隔离管,一直处于上电状态,他们的泄漏是不可忽视的额外开销。特别是在低功耗项目里,真正想靠 bank 省泄漏,必须配合 bank 级 power gating,也就是在不用时把一个 bank 完全关断。这就不是 compiler 单独能做主的,需要后端在设计里加电源开关单元,还要做 UPF 策略设计。
功耗切换带来的另一个麻烦是电源完整性的局部热斑。多个 bank 同时切换时,如果编译器没有做好的时序错开,电流峰值会很集中。某些 compiler 在配置里会提供 bitline precharge 时序调节,本质上就是在分散这种切换噪声。如果你把 bank 数量调的很大,建议顺便看一下 compiler 生成的 peak current 报告,这块在低功耗芯片里经常是后端的痛点。
2.4 附带收益:良率、冗余和抗干扰
bank 切分还有一个隐藏的好处,就是对工艺缺陷的容忍度提升。一个大阵列里如果某个 cell 坏了,整个宏可能直接报废;切成多个 bank 后,配合行冗余和列冗余,坏掉的 bank 可以用备用单元替换,剩下正常的 bank 继续工作。很多编译器在开启 bank 之后会自动开启 redundancy 选项,就是这个原因。
对于容量特别大的 memory,比如几百 Kbit 以上的 SRAM,这种良率收益可能会超过面积代价。这也是为什么高密度 SRAM 编译器一般默认就会让你在几个 bank 数量之间做选择,因为纯单 bank 的良率风险已经无法接受了。
但从电路抗干扰角度,bank 数量也不是越多越好。bank 间隔里的隔离区和电源走线,如果密度过高,会和相邻 bank 的字线形成耦合电容,产生串扰噪声。这个问题在先进工艺的低电压 SRAM 里会放大,有些后端同事反馈在 signoff 阶段补充噪声分析时,多 bank 宏比单元件更容易报出问题,基本就是这个原因。
3. 在 Compiler 界面上找到这个选项只是第一步:配置路径与常见工具差异
3.1 典型 GUI 菜单里的位置与参数联动
不同工艺库的 memory compiler 界面风格差异很大,但基本逻辑是一致的。在 SRAM generator 或者 memory compiler 的主配置界面里,你会先填 Depth、Width,然后看到 Number of banks 或 Number of sub-arrays 之类的下拉选项。旁边几乎总伴随着另外两个参数:Column mux(也写成 Multiplexer/MUX factor)和 Word-line partitioning。
这三个参数经常是联动的。你改 Number of banks,编译器会按照内部规则自动调整可选的 Column mux 范围;反过来,你手动改 Column mux,某些 bank 数量选项会变灰或者调整上限。这是工具为了保证内布局合法,避免出现位线长度和字线长度搭配失衡的情况。
比如在某个 28nm 工艺的 compiler 上,我见过这样的规则:当 Depth 小于等于 4096 时,Number of banks 只允许 1 或 2;Depth 在 8192 以上才会开放 4;Depth 到 16384 以上才有 8。这就是编译器自身的电路设计团队经过 PVT 仿真后限制出来的物理可行范围,强行改成不支持的值,工具也会在生成的网表中做不了电路连接,直接报错。
所以一个实用的建议是:不要试图在 GUI 里乱填一个 bank 数量然后硬跑,而是先把三个关键输入确定好——目标容量、目标频率、允许的功耗预算,然后在 compiler 支持范围内挑选。GUI 里每个下拉选项背后其实是编译器预置的一组 floorplan 模板,不同模板之间并不是连续可调的,而是类似“套餐式”的跳变。
3.2 命令行/配置脚本的等价写法
项目到了后期,没人愿意每次开 GUI 点点点,都会把 compiler 的配置整理成脚本或配置文件。以常见商用工具为例,一个典型的配置可能是这样:
set depth 16384 set width 32 set banks 4 set column_mux 4 set write_mask enable set power_gating enable不同编译器的语法有些差异,有的叫-number_of_banks,有的叫-split,还有的直接用配置文件里的字典项。关键不在于背命令,而在于掌握“配置项名称只是表象,底层 floorplan 才是决定因素”这个原则。你完全可以在命令行里把每一种 bank 数量跑一遍,比在 GUI 里一个个点快得多。
我建议把编译器的配置文件和生成报告放到版本管理里,和 RTL、约束一起存档。后面回看设计时,能够轻易复盘“当时的 SRAM 为什么选 4 bank 而不是 2”,对团队协作和问题回溯很有帮助。
3.3 不同编译器产品之间的命名差异
业内主流的 SRAM compiler 虽然都服务于同一个目的,但术语并不完全统一。
Synopsys 系 compiler 里,常称为 Number of banks 或者 Number of subarrays,层次结构为“global IO + local subarray”。ARM Artisan memory compiler 里,更多叫 Number of banks 或 SB(sub-bank),和 column mux 一起出现在 memory config 表格里。一些代工厂自研的编译器,可能会直接叫 Bank split 或者 Row segment,本质上都是同一件事。
更需要注意的一点是,某些 compiler 的 bank 选项其实是在做 “multiple instance”,而不是“单个宏内部切分”。这两种做法的物理布局很像,但外部接口和控制逻辑完全不同。跑生成之后你把弹出的 Symbol 和 Verilog model 拉出来看,多实例方案的 bank select 信号会很清晰地出现在端口上;单宏内切分方案则把这些信号收在宏内部,外部只看到一个普通 SRAM 接口。搞混这两者的后果是,你在做低功耗设计时,可能为宏内部 bank 设计了一堆电源控制端口,结果生成的网表根本没有这些端口,整个 UPF 策略都要重做。
3.4 配置好后生成的文件里哪里能看到 bank 的影子
配置写完后,compiler 会生成一大堆文件:真值表、.lib 时序库、.sdc 约束、Verilog/VHDL 行为模型、LVS/DRC 用的 GDS 和 CDL,还有 datasheet 和数据手册。
想确认 bank 设置是否生效,最直接的方法是在生成的 Verilog 模型里搜 bank select 相关信号。以 4 bank 为例,你大概率能看到类似bank_sel[1:0]这样的内部信号,并且地址最高两位会参与它的译码。如果采用的 compiler 支持 bank-level power gating,还会有类似sleep_bank_0、iso_bank_0这样的电源控制端口出现在行为模型里。
.datasheet 或者 summary report 里的信息同样关键。它会告诉你这个配置下最坏访问时间、面积、每个 bank 的功耗估计,以及推荐的时钟周期。把这些数据和你在 GUI 里填的参数对应起来,才能确认工具按照你的意图完成了编译。
4. 到底选几个 bank:按设计场景的决策流程
4.1 先区分三种典型场景:单端口、双端口、低功耗
单端口 SRAM 只需要一个访问通道,bank 数量的影响主要落在面积和延时曲线上。这类场景建议直接做参数扫描,挑出访问时间和面积的 Pareto 最优组合,然后再考虑良率因素。
双端口或伪双端口 SRAM 情况要复杂一些,因为两个端口可能同时访问同一个 bank,也可以访问不同 bank。Compiler 通常会提供“同一 bank 冲突”的检测机制,当两个端口选中同一 bank 时延迟增加,选中不同 bank 时延迟较小。你可以根据实际应用里读写地址的分布,倾向性选择让两个端口更容易落在不同 bank 上的配置。不过要注意,如果两个端口地址经常访问同一个 bank,多 bank 不但没有收益,还会因为仲裁逻辑增加额外延迟。
低功耗场景是最能体现 bank 价值的场景。智能手环、IoT 这类设备里,SoC 常处于休眠状态,只有少量 SRAM 在保持数据。如果 SRAM 支持 bank 级 power gating,你可以把不需要保存数据的 bank 关掉,保持电流大幅降低。此时 bank 数量不是一个简单的时序选择,而是由“需要保持的现场数据量”决定的。比如你必须保存 8KB 上下文,而单片 bank 是 4KB,那至少得加到 4 bank 才能做到只唤醒 8KB、剩余全部关断的功耗目标。
4.2 容量和宽深比是决定 bank 数量下限的硬条件
听上去很绕,但实际操作里 bank 数量先要看容量。容量太小,比如 4Kb 甚至 2Kb 的 SRAM,单 bank 就是最合理的,硬加 bank 纯粹是给面积和功耗添负担。这种小型 FIFO、寄存器堆类的 SRAM,编译器往往也默认只给 1 bank 的选项。
容量到了 32Kb 以上,就要开始认真考虑 2 bank 了。容量超过 128Kb,一般来说至少需要 4 bank 才能让位线延迟和字线延迟处于均衡状态。容量超过 512Kb,4 bank 起步是比较保险的做法,部分激进的高性能设计甚至会用到 16 到 32 bank,但此时 memory compiler 生成的不再是纯粹意义的 SRAM macro,而更接近一个小型存储子系统的雏形。
宽深比也要留意。宽而浅的 SRAM,比如 512×128,每个 bank 的行数很少,位线本来就短,这时加 bank 的收益有限,反而面积惩罚明显;窄而深的 SRAM,比如 65536×8,位线负载和字线延迟都很突出,bank 化带来的收益就很可观。所以同样的存储容量,配置可能是完全不同的。
4.3 用一次 PPA 扫描代替拍脑袋
我在实际项目中遇到最多的问题,不是不知道该看哪些维度,而是大家习惯拍脑袋定一个 bank 数量。比如默认 4 bank,或者听供应商销售说“我们普遍推荐 2”,这都谈不上严谨。
我更推荐做一个简单的 PPA 扫描:选定目标容量和宽度后,在编译器支持范围内把 bank 数量从最小值到最大值依次跑一遍,记录面积、读时间、写时间、泄漏功耗、动态功耗,以及可选的冗余和良率信息。这些数据通常半小时内就能跑完,得到的表格比任何经验都可靠。
下面给一个我在某 28nm 工艺、64K×32 单端口 SRAM 上实测风格的示例结果,具体数值因工艺库而异,但曲线形状很有代表性:
| Number of banks | 面积 (μm²) | 读时间 (ps) | 动态功耗 (mW) | 泄漏功耗 (mW) | 备注 |
|---|---|---|---|---|---|
| 1 | 152000 | 1820 | 34.5 | 4.2 | 位线负载极大 |
| 2 | 156800 | 1330 | 22.1 | 4.6 | 时序提升明显 |
| 4 | 162100 | 1080 | 17.3 | 5.1 | 平衡点 |
| 8 | 174600 | 1150 | 16.8 | 6.2 | 控制逻辑开始拖后腿 |
| 16 | 198300 | 1340 | 17.6 | 7.5 | 已经明显不划算 |
这个例子里 4 bank 是比较理想的选择。如果你在项目里看到类似的表格,判断规则也很简单:读时间和功耗在某个 bank 数量后不再变好,甚至开始反弹,那么这个拐点附近就是最优区间。
4.4 和编译器里的 column mux、wordline partition 一起看,不要孤立调 bank
Number of banks 通常会释放物理收益,而这个收益能不能被宏观设计实际接住,很大程度取决于你怎么配合 column mux。
column mux 表示的是每个 IO 对应的位线组比例。举例来说,如果数据宽度是 32,column mux 设为 4,那么物理 bitcell 阵列的每 4 列位线共用 1 个读出放大器,实际 IO 列数要乘 4。这会显著增加单个 word 的物理跨度,同时减小每 bit 对应的位线数量。
当 bank 数增多、column mux 也增大时,两者叠加会让阵列的地图比例发生很大的变化。有些编译器允许的组合,会让位线方向和读出放大器之间的走线出现拥挤,生成报告里的 area 会虚高;另一些组合则可能让局部字线驱动器的扇出失衡,导致读路径半段超差。
所以我习惯把 “深度 × 宽度 × 物理读端口数” 一起代入编译器,然后同时对 Number of banks 和 Column mux 做二维扫描。有时候你会发现 bank=2、column mux=8 的组合,比 bank=4、column mux=4 的面积和时序都好,就是因为物理布局的布线资源占用不同。参数之间是联动的,千万不要孤立地只调一个。
5. 实际项目里因为 bank 设置踩过的坑
5.1 案例一:把 bank 从 1 调到 16,时序反而崩了
之前做一颗 MCU 芯片,里边有个 512Kb 的 cache 数据存储 SRAM,默认配置是 1 bank。综合时发现这条路径始终满足不了时钟频率,于是有人想当然地把 Number of banks 调成了 16,认为位线变短一定能提升速度。
结果重新编译后,访问时间不仅没有下降,反而比原来多了 200ps 左右。打开 datasheet 一看,bank select 逻辑出现在全局预解码到局部字线的关键路径上,16 级选择逻辑的延迟超过了位线电容节省下来的时间。后来把 bank 降到 4,访问时间才明显改善。这个案例让我彻底明白,bank 数量的影响是 U 形曲线,不是单调递减的。
排查这类问题最快的方式,是在生成 report 后对比不同 bank 数量下“地址到输出”和“片选到输出”两类时序弧的变化。如果片选到输出变差很多,但地址到输出变好,问题的根源就在 bank select 路径上。此时调 bank 数量没有意义,需要从 RTL 侧考虑是否可以把片选信号提前,或者用更快的局部译码结构。
5.2 案例二:低功耗模块的 bank 休眠控制逻辑被漏接
另一个项目里,我们看中多 bank 的休眠特性,把 4 bank SRAM 的电源关断端口接到了电压域管理器上。RTL 仿真没问题,但到了后端的 UPF 检查阶段,工具报了 ERC 错误,原因是 compiler 生成的 power gate 控制信号命名规则和我们预期的完全不一致。
一开始以为 tool 配置错了,后来翻 user guide 才发现,这款 compiler 里 bank 级 power gating 默认是“内部自动控制”模式,与外部 UPF 期望的开关控制端口并不是直接对应关系。需要在配置里显式打开某个external_power_switch选项,才会把每个 bank 的 sleep 和 isolation 控制引脚暴露出来,同时还要按照 compiler 要求的方法连接 UPF 里对应的 power switch cell。
这个坑提醒我:任何带电源管理需求的 SRAM 配置,在确认 bank 数量时,一定要同步确认 compiler 生成的 macro 端口列表和 datasheet 里的上电时序要求。如果内部 bank 电源开关的开启速度跟不上时钟,唤醒时读取的第一个数据可能是非法的,需要额外插入等待周期或者隔离逻辑。
5.3 案例三:DFT/BIST 只配了一个 bank 的入口,结果漏覆盖
还有一次踩坑是在 DFT 阶段。memory BIST 逻辑设计和 SRAM bank 配置没有对齐。我们选的 SRAM 是 4 bank,但 BIST 控制器是按照单 bank 线性地址写的,没有在算法里正确处理 bank select 的切换顺序,导致测试时只完整扫了两个 bank,另外两个 bank 的固定故障漏检。
这个案例的教训不是让你别用多 bank,而是提醒你:在项目早期做 memory 测试计划时,就要把 bank 数量告知 DFT 工程师。现在主流 EDA 工具的 memory BIST 生成器都能从编译器输出的 memory model 里解析出 bank 信息,自动生成带 bank 遍历的算法。但如果你用的是自定义 BIST,或者第三方 MBIST IP,一定要核实地址解码逻辑是否能覆盖到所有 bank。
5.4 我的经验清单:拿到新工艺库先做的几件事
换新工艺节点或者换新 compiler 版本时,我会先花半天时间做以下事情:
- 把目标 SRAM 容量下支持的 bank 范围完整跑一遍,记录 PPA 变化,特别是读时间的 U 形曲线拐点。
- 确认 compiler 的 bank 切分方向,是切字线还是切位线,并和 datasheet 的位置信息比对。
- 检查生成的行为模型里是否有 bank 相关使能或电源控制端口,以及它们是否影响 RTL 仿真模型。
- 把不同 bank 配置下的面积、功耗、时序数据做成表格存进项目 wiki,方便后端和项目经理随时查询。
这套动作做完,基本能避开大多数因为 bank 设置不合理导致的返工。
6. 动手验证的思路:让编译器数据替你作证
6.1 从数据手册里找什么,才能判断这个 bank 设置是否适合
拿到 compiler 生成的 datasheet,不要只看访问时间那一个数字。一般 datasheet 里包含 area、power、 pin capacitance、setup/hold time、min/max frequency 多个维度。判断 bank 设置是否合理,建议先看 area 和 access time 的比例关系。如果 access time 比预期快很多,但面积比预期大很多,说明 bank 可能选多了。
再看输入电容。编译器生成报告里每个 address 或 data 引脚的输入电容变化,也能反映 bank 数量。bank 多时,预解码逻辑重,输入引脚电容明显上升。这会直接影响前端综合工具对输入链路的优化,有时甚至会导致外部逻辑 buffer 变多。
最后看功耗报告里的 peak average power。动态功耗下降但峰值功耗反而上升,说明 bank 切换导致电流集中,这种情况在低功耗和电源完整性检查里都要关注。
6.2 用 .lib、.sdc 和 Verilog 模型交叉确认
拿 .lib 里的时序弧是最直接的确认方式。翻开.lib,查找timing()区块里的cell_rise、cell_fall,能看到地址输入到输出、时钟到输出的延迟值。如果多 bank 配置下的时钟到输出延迟明显下降,說明位线路径的收益确实被工具建模进去了。
.sdc 文件里通常会给出 compiler 推荐的时钟约束和输入输出延迟。这部分很容易被忽略。有些 compiler 会根据 bank 数量生成不同的 recommended constraint,你如果直接沿用默认约束,可能在仿真阶段收益很小,因为约束把 bank 延迟裕量吃掉了。建议对比修改 bank 前后的 .sdc 差异,理解工具把哪些路径列为了关键路径。
Verilog 模型层面,除了确认 bank select 信号,还建议跑一条简单的 RTL 仿真,覆盖 bank 边界地址切换。比如 4 bank 配置下,从 bank0 的最高地址跳到 bank1 的最低地址,观察读出数据是否有多拍延时的额外风险。虽然.lib 里已经考虑了这个 transition,但真实宏在边界 bank 的走线偏斜仍然可能存在,早期 RTL 仿真时把边界地址用例加上,能提前暴露一些模型层面的问题。
6.3 迭代步骤建议:从一个增量改变开始
不要一次性把所有参数全改掉,那样出了问题你根本不知道是哪个参数引起的。正确做法是:保持深度和宽度不变,先单独把 bank 从当前值改为相邻值,比如从 2 改成 4,跑一遍完整编译,比较报告;再把 column mux 改半档,再比较一次。每次只有一个变量变化,记录到的差异才能归因于这个变量。
等找到一组偏好的配置后,别忘了做 PVT 变化下的 robustness 检查。同一 bank 数量在 SS corner 和 FF corner 下,时序曲线的拐点位置不一定相同。有些设计在典型条件下 4 bank 最优,但在极慢工艺角下 8 bank 反而更抗延迟,因为工艺变慢时位线延迟的增长比选择逻辑延迟更剧烈。所以如果项目分布跨多个电压和温度范围,bank 数量的选择要在最差 corner 下验证,而不是只看典型条件。
迭代过程中也要保留中间产品,就是每次编译生成的 .lib 和 datasheet。后面如果视图层或者布局规划有变化,需要重新评估 SRAM 选型时,这些历史数据能帮你快速定位“当时为什么选了 4 bank 而不是 2”。
说到底,Number of banks 没有一个放之四海而皆准的默认值。它和具体工艺、容量、宽深比、工作电压、功耗目标、甚至后端布局都有关系。与其到处问别人推荐值,不如自己花半天时间把参数扫描跑一遍。我看过太多项目,明明半小时的扫描实验就能解决的事,最后硬是在综合阶段靠加班调逻辑拖了两周才解决。数据就摆在编译器生成报告里,只要你愿意读,它比任何人都诚实。