# SoC 中 SRAM 的读写原理与总线时序 SRAM (Static Random Access Memory) 是 SoC 中最常见的片上存储器:CPU 的一级/二级缓存、紧耦合内存 (TCM)、总线 FIFO、各种硬件加速器的缓冲区,本质上都是 SRAM 或由 SRAM 阵列构成的。本文从 6T 单元开始,逐步讲到总线节点上的读写并发,再讨论电压变化对时序的影响,以及芯片如何控制时序。 ## 1. SRAM 存储单元:6T 结构 一个经典 SRAM 单元由 6 个晶体管组成 (6T): - 两个交叉耦合的反相器 (P1/N1 与 P2/N2),构成双稳态锁存器,存储 1 bit - 两个存取管 (access transistor) N3、N4,栅极接字线 WL,把存储节点接到位线上 ```{mermaid} flowchart TB VDD1["VDD"] --> P1["P1 (PMOS)"] VDD2["VDD"] --> P2["P2 (PMOS)"] P1 --> Q["存储节点 Q"] P2 --> QB["存储节点 QB"] Q --> N1["N1 (NMOS)"] QB --> N2["N2 (NMOS)"] N1 --> GND1["GND"] N2 --> GND2["GND"] BL["BL 位线"] --- N3["N3 存取管"] N3 --- Q BLB["BLB 位线(反相)"] --- N4["N4 存取管"] N4 --- QB WL["WL 字线"] -.->|"栅极"| N3 WL -.->|"栅极"| N4 ``` 为什么叫"静态":只要不掉电,交叉耦合反相器的正反馈让 Q/QB 永远稳定在互补的两个电平上,不需要像 DRAM 那样周期性刷新。 假设 Q=1 (VDD)、QB=0 (GND),那么 N1 导通、P2 导通,Q 被 P1 拉高、QB 被 N2 拉低,状态自持。 ## 2. 读操作:预充、放电与灵敏放大 SRAM 的读不是"直接把存储节点的电平送出去",而是通过位线放电 + 灵敏放大完成,分四步: **第一步:预充 (Precharge)。** 预充管把两条位线 BL、BLB 都充到 VDD,再断开,位线处于"悬空但带满电荷"的高阻状态。 **第二步:字线打开。** WL 拉高,N3/N4 导通,存储节点与位线连通。 **第三步:位线差分放电。** 假设单元存 Q=1、QB=0: - QB 侧:QB 是 GND,位线 BLB 是 VDD → 电流从 BLB 经 N4、N2 流向地,BLB 电压开始缓慢下降 - Q 侧:Q 是 VDD,BL 也是 VDD → 无压差,不放电 位线电容很大 (一条位线挂几百个单元),而单元电流很小,所以 BLB 只是"缓慢下滑",产生一个 ΔV ≈ 100 mV 左右的微小差分。 **第四步:灵敏放大 (Sense Amplifier)。** 等 ΔV 足够大,灵敏放大器使能信号 SAE 打开,差分放大器把 100 mV 的小信号迅速放大到全摆幅,送到数据总线上。然后位线重新预充,准备下一次访问。 ``` 时钟 ___|‾‾‾|___|‾‾‾|___|‾‾‾|___ 预充 PC ‾‾‾|_______|‾‾‾‾‾‾‾‾‾|‾‾ WL 字线 ____|‾‾‾‾‾‾‾|__________ BLB (读"0"侧) ~~~~~‾‾‾‾‾‾‾‾\___________ ← 从 VDD 开始缓慢放电 \ ΔV ≈ 100mV SAE 灵敏放大 ____________|‾‾‾‾|________ ← 检测到 ΔV 后打开 数据输出 ________________|‾‾‾|______ ← 全摆幅输出 ``` 两个关键细节: - **读是破坏性扰动。** 放电电流流过 N4/N2 时,QB 节点电位会被轻微抬高 (read disturb)。如果抬高超过单元的静态噪声容限 SNM (Static Noise Margin),单元会意外翻转——读一次就把数据读坏了。所以单元里**下拉管 N1/N2 必须比存取管 N3/N4 强**,两者宽长比 (cell ratio, β ratio) 一般取 1.5~2,保证扰动被下拉管"按住"。 - **自定时 (self-timed)。** SAE 什么时候打开?不是靠设计者定一个固定延迟,而是放一条**复本位线 (replica bitline)**:模拟最坏情况下位线放电的速度。电压低、温度高时,复本位线放电同样变慢,SAE 自动后移——这个时机天然跟踪 PVT (工艺/电压/温度) 变化,这是 SRAM 内部"控制时间"的核心手段。 ## 3. 写操作:强驱动压过锁存器 写操作则相反,要**主动破坏**锁存器的状态: 1. 写驱动 (write driver) 把 BL 拉到 0、BLB 拉到 1 (假设要写 0 到 Q) 2. WL 拉高,N3 导通 3. 此时 Q=VDD,P1 正在把 Q 往上拉,而写驱动通过 N3 把 Q 往下拽。写驱动是"大管子",驱动能力远强于单元内部的 PMOS——Q 被强行拉低 4. Q 一旦跌破反相器 1 的翻转阈值,正反馈立刻接管:QB 被推高 → 又反过来拉低 Q → 翻转完成,锁存器锁住新值 要保证"压得过",**存取管必须比单元内的上拉 PMOS 强** (pull-up ratio, γ ratio)。如果写驱动压不过 P1,就会写失败——这就是**写裕量 (write margin)**。 读和写对单元的要求是对立的:读要求存取管弱 (防止扰动翻转),写要求存取管强 (压过 PMOS)。6T 单元的两个管子尺寸比例就是在这两个约束之间取平衡点。 ## 4. 总线视角:AXI 通道与 SRAM 控制器 在 SoC 里,CPU 不会直接操作 6T 单元,而是通过总线访问 SRAM。最常用的是 AMBA AXI 总线,它有 **5 个独立通道**: | 通道 | 方向 | 作用 | |------|------|------| | AW | 主 → 从 | 写地址 | | W | 主 → 从 | 写数据 | | B | 从 → 主 | 写响应 | | AR | 主 → 从 | 读地址 | | R | 从 → 主 | 读数据 | 关键点:**读通路 (AR/R) 和写通路 (AW/W/B) 是物理上完全独立的导线**,没有共享数据总线。所以"节点一边从上游收读数据、一边向下游发写数据"在物理层就没有冲突——这就是全双工。 ```{mermaid} flowchart LR M["Master 主设备"] N["总线节点\n(互连/桥/SRAM控制器)"] S["SRAM 从设备"] M -->|"AR: 读地址"| N N -->|"AR: 读地址"| S S -->|"R: 读数据"| N N -->|"R: 读数据"| M M -->|"AW/W: 写地址+写数据"| N N -->|"AW/W: 写地址+写数据"| S S -->|"B: 写响应"| N N -->|"B: 写响应"| M ``` 每个通道用 VALID/READY 握手:发送方拉高 VALID 表示"我这拍的数据有效",接收方拉高 READY 表示"我能收"。**只有 VALID 和 READY 同时为高的那一拍,数据才算被采样**。这是数据正确性的第一道防线——数据线电平本身不能说明数据有效,握手信号才能。 注意:SRAM 阵列本身通常是**单端口**的 (同一时刻要么读要么写),所以控制器的仲裁逻辑会把同时到达的读写请求分时排队。阵列之外的"同时收发"靠的是通道分离 + 控制器内的寄存器缓冲。 ## 5. 总线节点"边读边写":寄存器隔离与流水线 这是最容易困惑的地方:节点 (比如一个总线桥,或者 SRAM 控制器本身) 在同一个时钟周期里,既要从上游采样数据,又要向下游驱动数据,两条数据流为什么不会串? 答案是:**节点内部用两级物理独立的寄存器把"收"和"发"隔开**——输入寄存器负责从上游捕获,输出寄存器负责向下游驱动,而输出寄存器里放的是**上一拍 (甚至更早) 捕获的数据**。 ```{mermaid} flowchart LR U["上游"] -->|"本拍数据"| IR["输入寄存器\n(捕获上游)"] IR -->|"上一拍数据"| OR["输出寄存器\n(驱动下游)"] OR -->|"本拍输出"| D["下游"] CLK["时钟"] --> IR CLK --> OR ``` 时序上看: ``` 周期 N-1 N N+1 时钟 ____|‾‾‾‾|____|‾‾‾‾|____|‾‾‾‾|____ 上游送出 < A > < B > < C > 输入寄存器 [ A ]→ [ B ]→ [ C ]→ ← 每个上升沿捕获上游 输出寄存器 [ Z ]→ [ A ]→ [ B ]→ ← 每个上升沿更新为"上一拍"的输入 下游看到 < Z > < A > < B > ``` 第 N 拍的时钟沿上,节点**同时**做了两件事: - 输入寄存器把上游的 B 关进去 (读上游) - 输出寄存器把上一拍捕获的 A 锁出来驱动下游 (写下游) 下游在第 N 拍看到的是 A——上游第 N-1 拍的数据。上游第 N 拍送出的 B,最早也要第 N+1 拍才会出现在下游。**数据经过节点必然"停留"至少一个时钟周期**,这就是流水线的一级 (register slice / pipeline cut)。 换句话说,"边读边写"不是同一份数据在同一拍从上游流到下游,而是**两拍数据在节点内部接力**:拍 N 收 B、发 A;拍 N+1 收 C、发 B。输入寄存器和输出寄存器是两组物理上不同的触发器,时钟沿同时触发时各干各的,数据线互不连接,所以不存在串扰的物理通路。 对比一下:如果节点不做寄存 (纯组合逻辑直连),下游确实会**同拍**看到上游数据——但那样就完全没有隔离和缓冲了,长路径时序难以收敛,所以真正的总线节点都会插寄存器。 ## 6. 会不会"写错数据"?——数据归属如何保证 针对"先读了上游数据,写的时候会不会把上游数据写出去/写进自己"的疑问,结论分两层: **正常设计下:不会。** 有三道防线保证"写的数据一定是自己的数据": 1. **通道分离**:读数据走 R 通道、写数据走 W 通道,物理导线不同,读回来的数据根本进不了写数据通路 2. **寄存器隔离**:如上一节时序图,下游第 N 拍收到的值最晚在第 N-1 拍的时钟沿之前就锁定了;第 N 拍上游才送来的新数据,不可能在第 N 拍就出现在下游。时序上"读上游"永远晚于"写下游"一拍,写出去的永远是早先已锁存的数据 3. **握手保护**:写数据只有在 VALID && READY 同时为高的拍才被打入写数据寄存器;SRAM 阵列的写端口只连接写数据寄存器,读数据寄存器只从阵列接收。两条通路在寄存器层面物理隔离 以 SRAM 控制器为例:第 N 拍,读数据寄存器 (RDR) 里是从阵列读出的数据 (自己存储的数据),经 R 通道发往下游;同时写数据寄存器 (WDR) 捕获上游 W 通道送来的写数据。第 N+1 拍阵列执行写操作时,写端口接的是 WDR——**阵列永远不会把 RDR 的内容写进存储单元**,除非设计本身连错了线。 **真正可能出问题的场景**,都是时序违例,而不是设计意图: - **保持时间 (hold) 违例 → 寄存器"透明化"**:如果数据在时钟沿之后的保持窗口内就发生变化 (数据路径太快,或者时钟偏斜太大),寄存器来不及把旧值锁死,新数据会**在同一拍穿透**。这正好会表现为"刚读到的上游数据在错误的一拍被写下去"。hold 违例在**快工艺角** (高电压、低温、快硅) 下最危险,而且**降频解决不了 hold 问题**,只能插缓冲器修。 - **跨时钟域 (CDC) 没有同步器 → 亚稳态**:如果上游和节点时钟不同源,采样时刻恰好落在数据跳变沿上,触发器进入亚稳态,采到既非 0 也非 1 的中间值,甚至不同触发器采出互相矛盾的值。解决手段是两级触发器同步器或异步 FIFO。 - **控制信号出错**:写使能 (WEN) 或地址译码出错会把数据写进错误的单元。这与数据来源无关,属于功能错误,ECC 可以检测但不能预防。 所以总结:**只要 setup/hold 收敛、跨时钟域有同步器,就不会出现"写了上游数据而不是自身数据"**——寄存器隔离在时序上把"读上游"和"写下游"严格错开了一拍。 ## 7. 电压变化对时间差的影响 门的传播延迟近似服从 α 幂律模型 (Sakurai-Newton): ``` C_load · VDD t_d ∝ ─────────────── ≈ VDD / (VDD - Vth)^α I_drain ``` VDD 越高,驱动电流越大,延迟越小;VDD 越低,延迟越大,而且**越接近阈值电压 Vth,延迟增长得越陡**: ``` 延迟 ↑ | * | * | * | * | * | * | * +─────────────────────────→ VDD Vth Vnom ``` 对 SRAM 读周期的影响尤其明显:读电流 ∝ (VDD−Vth)^α,低压下位线放电变慢,**从字线打开到 SAE 使能的时间差显著拉长**——只不过自定时的复本位线会自动跟着变慢,把 SAE 时机同步后移,抵消了大部分影响。这正是自定时设计在低压场景下的价值。 标准 PVT 工艺角如下: | 工艺角 | 工艺 | 电压 | 温度 | 最坏影响 | |--------|------|------|------|----------| | SS | 慢 | -10% | 高温 | setup 违例 (数据来得太晚) | | FF | 快 | +10% | 低温 | hold 违例 (数据来得太早) | | TT | 典型 | 标称 | 25°C | 参考基准 | 注意温度效应在现代深亚微米工艺里是**高温更慢**。 另外要区分静态和动态: - **静态电压设定** (DVFS 档位) 决定整个芯片的平均速度 - **动态电压跌落** (IR drop / di/dt 噪声):大量单元同时翻转瞬间把电源拉低几十到上百 mV,局部电路**瞬时变慢**,可能造成本来收敛的 setup 路径在那一瞬间违例。这就是芯片要留电压裕量和时序裕量的原因 ## 8. 芯片如何控制时序 芯片控制"时间"的机制可以分成五层: **第一层:同步设计 + 静态时序分析 (STA)。** 所有时序都以时钟沿为基准,STA 工具用 SDC 约束检查每一条寄存器到寄存器的路径是否满足 setup/hold,并按工艺角留裕量 (OCV/AOCV/POCV 补偿片上变异)。收敛的标准不是"仿真过了",而是"所有角都过了"。 **第二层:时钟网络。** PLL (锁相环) 从外部晶振倍频出芯片主时钟,分频器产生各模块时钟,时钟树综合 (CTS) 把时钟均衡地送到每个触发器——skew (时钟到达不同触发器的时间差) 直接影响 hold 裕量。所以 hold 违例的修法是调整时钟树/插缓冲,而不是降频。 **第三层:SRAM 内部自定时。** 前面说的复本位线:SAE、预充、字线脉冲都由内部模拟路径产生,天然跟踪 PVT,不需要外部精确计时。 **第四层:DVFS (动态电压频率调节)。** 电压决定速度上限,频率决定工作节奏,两者必须协调: ```{mermaid} flowchart LR A["当前工作点 (V, f)"] -->|"升频: 先升电压"| B["V↑ (PMIC/LDO)"] B -->|"电压稳定后"| C["f↑ (PLL)"] C --> D["新工作点 (V', f')"] A -->|"降频: 先降频率"| E["f↓"] E -->|"频率稳定后"| F["V↓"] F --> D ``` 原则是:**升频时电压先行** (频率永远不能超过当前电压支撑的上限),**降频时频率先行** (电压永远不能低于当前频率所需)。顺序反了就会在过渡期出现 setup 违例。 **第五层:AVS (自适应电压调节)。** 用片内环形振荡器、canary 电路甚至 Razor 触发器**实时监测硅片的实际速度**,把电压动态调到"刚好能满足时序"的最低值,省电且抗老化。低压下电压裕量小,这种闭环调节比固定档位更有效。 ## 9. 总结 - SRAM 读 = 预充 → 字线开 → 位线差分放电 → 灵敏放大;写 = 写驱动强压锁存器翻转。β ratio 保读稳定,γ ratio 保写成功 - 总线节点"边读边写"靠**通道分离 (全双工) + 寄存器隔离 (流水线)**:本拍写的永远是上一拍已锁存的数据,读上游和写下游在时序上天然错开一拍 - "写错数据"正常不会发生;唯一接近的失效模式是 **hold 违例导致寄存器同拍穿透**,以及 CDC 亚稳态——都是时序问题,不是设计问题 - 电压下降 → 延迟按 (VDD−Vth)^α 规律拉长,SRAM 读放电时间显著变长;自定时复本位线自动补偿 - 芯片靠 STA 收敛 + PLL/时钟树 + SRAM 自定时 + DVFS (升压先于升频、降频先于降压) + AVS 闭环,五层机制共同控制时序 ## 参考资料 - Neil Weste, David Harris, *CMOS VLSI Design: A Circuits and Systems Perspective* (第 4 版),第 12 章 (Array Subsystems) - Jan Rabaey, *Digital Integrated Circuits: A Design Perspective*,第 12 章 - Sakurai & Newton, *Alpha-Power Law MOSFET Model and its Applications to CMOS Inverter Delay and Other Formulas*, IEEE JSSC 1990 - ARM, *AMBA AXI and ACE Protocol Specification* - Seevinck et al., *Static-Noise Margin Analysis of MOS SRAM Cells*, IEEE JSSC 1987