本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:FPGA是一种可编程逻辑器件,广泛用于自定义硬件设计,而SRAM常作为其内部高速存储单元。本文通过Verilog语言深入讲解SRAM在FPGA中的建模与控制方法,涵盖地址解码、读写操作、同步异步设计及接口实现等关键环节。结合测试平台搭建与仿真验证流程,帮助读者掌握SRAM模块的设计与综合技术。提供的Verilog代码示例有助于理解实际工程应用,提升数字系统设计能力。
FPGA

1. FPGA与SRAM基础概念介绍

FPGA(现场可编程门阵列)作为一种高度灵活的硬件可重构平台,广泛应用于数字系统设计、高速信号处理和嵌入式系统开发中。其核心优势在于用户可通过硬件描述语言(如Verilog)定义电路逻辑,并在芯片上实现定制化的数字功能模块。在众多存储器类型中,SRAM(静态随机存取存储器)因其高速读写、低延迟和无需刷新的特点,成为FPGA系统中不可或缺的关键组件,常用于缓存、状态存储和临时数据缓冲等场景。

FPGA的基本架构与工作原理

FPGA主要由可配置逻辑块(CLB)、片上存储器(Block RAM)、输入/输出块(IOB)以及互联资源构成。用户通过HDL将逻辑功能映射到CLB中,利用分布式或块状RAM实现存储需求。现代FPGA支持部分可重配置技术,可在运行时动态修改特定区域逻辑,提升系统灵活性。

SRAM在FPGA中的角色与部署方式

SRAM在FPGA系统中可分为 片内SRAM (如Block RAM)和 外挂SRAM 两类。片内SRAM具有访问速度快、时序可控的优点,适合小容量高速缓存;而片外SRAM(如ISSI系列异步/同步SRAM)扩展性强,适用于大数据吞吐场景。二者在物理实现上的差异直接影响系统带宽、功耗与时序约束。

类型 速度 容量 是否需刷新 典型用途
SRAM 小~中等 缓存、状态寄存
DRAM 主存、帧缓冲

SRAM无需刷新的特性使其在实时性要求高的FPGA应用中更具优势。此外,其双稳态触发器结构保证了数据稳定性,但对面积和功耗有一定牺牲。理解SRAM与FPGA的协同工作机制,是构建高效嵌入式系统的基础。

2. SRAM存储结构与工作原理分析

静态随机存取存储器(Static Random Access Memory,简称 SRAM)作为数字系统中关键的高速存储单元,在 FPGA 架构内部及外围扩展中扮演着不可替代的角色。其无需刷新即可保持数据的特性,使其在对延迟敏感、访问频率高的应用场景中表现优异。本章将从底层物理结构出发,深入剖析 SRAM 的基本单元构成、阵列组织方式、读写操作机制以及同步与异步工作模式的本质差异。通过理解这些核心机理,为后续使用 Verilog 实现可综合 SRAM 模块提供坚实的理论支撑,并为高性能存储控制器的设计奠定基础。

2.1 SRAM基本单元结构

SRAM 存储的基本单位是单个存储单元(Memory Cell),其中最经典且广泛应用的是六晶体管结构,即 6T SRAM Cell。该结构以其高稳定性、低功耗和良好的可制造性成为现代 CMOS 工艺下主流的选择。理解其内部构成与工作机制,有助于分析 SRAM 在噪声干扰、电压波动等非理想条件下的行为特征。

2.1.1 六管单元(6T SRAM Cell)构成与稳定性分析

六管 SRAM 单元由六个 MOSFET 管组成:两个交叉耦合的反相器构成一个双稳态触发器(Bistable Latch),用于稳定地存储一位二进制信息;另外四个晶体管作为访问开关(Access Transistors),控制该存储节点是否与外部位线连接。

其电路拓扑如下图所示(使用 Mermaid 流程图描述):

graph TD
    A[Pull-up PMOS M1] -->|Gate connected to Q_bar| B[Pull-down NMOS M2]
    B -->|Output: Q| A
    C[Pull-up PMOS M3] -->|Gate connected to Q| D[Pull-down NMOS M4]
    D -->|Output: Q_bar| C
    E[Word Line (WL)] -->|Controls Gate of M5 and M6| F[Access NMOS M5]
    G[Bit Line (BL)] --> F
    H[Bit Line Bar (BLB)] --> I[Access NMOS M6]
    E --> I
    F --> A & B
    I --> C & D

在这个结构中:
- M1 和 M2 组成第一个反相器;
- M3 和 M4 组成第二个反相器;
- M5 连接 BL 与 Q 节点;
- M6 连接 BLB 与 Q_bar 节点;
- WL 控制 M5 和 M6 的导通状态。

当 WL 为低电平时,M5 和 M6 截止,存储单元与位线隔离,内部双稳态结构维持当前状态(0 或 1)。当 WL 被拉高时,M5 和 M6 导通,Q 和 Q_bar 分别与 BL 和 BLB 连通,允许进行读或写操作。

这种双反相器结构具有两个稳定的平衡点:
- 状态一:Q = 高电平(逻辑 1),Q_bar = 低电平(逻辑 0)
- 状态二:Q = 低电平(逻辑 0),Q_bar = 高电平(逻辑 1)

这两个状态互为反馈维持,只要电源不断开,就能无限期保存数据,这正是“静态”一词的由来。

稳定性可通过 噪声容限(Noise Margin) 保持模式下的静态噪声容限(SNM, Static Noise Margin) 来量化。SNM 通常以 VTC(Voltage Transfer Characteristic)曲线交点围成的最大正方形边长表示,理想情况下应接近电源电压的 30% 以上。工艺缩放导致阈值电压下降,使 SNM 减小,从而影响可靠性。

此外, 写入裕度(Write Margin) 也是重要指标,反映在写操作过程中能否成功翻转存储状态。若访问晶体管驱动能力不足,则可能无法克服原有锁存强度,造成写失败。

下表对比了不同工艺节点下 6T SRAM 单元的关键参数趋势:

工艺节点 (nm) 单元面积 (μm²) 静态功耗 (nW/cell) SNM (%) 写入电流比 (I_write / I_leakage)
180 1.8 0.2 35 >1000
90 0.7 0.5 30 800
40 0.3 1.2 25 500
28 0.18 2.0 22 300

可以看出,随着工艺进步,虽然集成密度提升,但静态漏电增加、噪声容限降低,设计挑战加大。

2.1.2 存储节点的双稳态特性与读写干扰问题

SRAM 单元的核心在于其双稳态特性——即存在两个能量局部极小的状态,分别对应逻辑“0”和“1”。这一特性保证了在没有外部干预的情况下,存储内容不会自发改变。

然而,在实际操作中,尤其是在读取过程中,可能会引发所谓的“读扰动(Read Disturb)”问题。这是因为在读操作期间,位线预充电至高电平(VDD),而当访问晶体管导通后,若存储节点 Q 处于低电平(逻辑 0),则会通过 M5 放电 BL 线。但由于 BL 线具有较大寄生电容,放电过程缓慢,可能导致 Q 节点电压被轻微抬升,进而削弱双稳态系统的稳定性。

更严重的问题出现在 半选(Half-select) 情况下:在同一行但不同列的单元中,虽然未被选中的单元其 WL 也为高,但由于其 BL 和 BLB 均处于浮动或预充状态,若发生意外耦合或漏电,可能导致非目标单元的存储状态翻转。

例如,考虑如下情况:
- 当前地址行激活(WL=1)
- 某一列执行写入操作,强制将 Q 强制拉低
- 相同行其他列的单元虽未被写入,但因 WL 仍为高,其访问管导通
- 若位线之间存在串扰或电源波动,可能导致这些“旁观者”单元的状态不稳定

此类现象在高密度 SRAM 阵列中尤为突出,需通过优化位线预充电策略、采用差分传感放大器、限制同时访问列数等方式缓解。

另一种潜在问题是 写冲突(Write Collision) :当多个写请求试图修改同一地址时,若缺乏仲裁机制,可能导致数据损坏。尽管硬件层面一般不允许并发写入同一物理位置,但在多端口 SRAM 或缓存一致性系统中必须引入额外控制逻辑。

为了评估双稳态系统的鲁棒性,常采用 蒙特卡洛仿真(Monte Carlo Simulation) 方法,模拟晶体管尺寸偏差、阈值电压漂移等工艺变异对 SNM 的影响。结果表明,在先进工艺下,超过 5% 的单元可能落入亚稳态区域,需借助 ECC(Error Correction Code)或冗余设计加以补偿。

综上所述,6T SRAM 单元虽结构简洁,但在深亚微米工艺下面临显著的稳定性挑战。理解其双稳态机制与干扰源,是设计高可靠 SRAM 系统的前提。

2.2 SRAM阵列组织架构

大规模 SRAM 并非简单地将大量 6T 单元并排放置,而是采用高度规则化的二维阵列结构,配合行列解码与驱动电路,实现高效寻址与访问。合理的阵列组织不仅能提高存储密度,还能优化功耗与时序性能。

2.2.1 字线(Word Line)与位线(Bit Line)的布局与驱动机制

SRAM 阵列按行列排布,每个交叉点对应一个存储单元。水平方向的控制线称为 字线(Word Line, WL) ,垂直方向的数据传输线称为 位线(Bit Line, BL 和 BL_bar)

  • 字线 :每条字线连接一行所有单元的栅极(即 M5/M6 的栅极),由行解码器驱动。当某条 WL 被选中时,整行单元的访问管导通,允许对应列进行读写。
  • 位线对(BL/BLB) :每列有一对互补位线,用于差分信号传输。读取时,BL 对上的微小电压差被灵敏放大器检测;写入时,驱动电路强制将 BL 对设置为目标逻辑电平。

由于位线较长且带有较大寄生电容(可达几十 fF 至数百 fF),直接驱动会造成严重延迟。因此,通常采取以下措施:

  1. 位线预充电(Precharge) :在每次访问前,将 BL 和 BLB 同时拉至 VDD,确保初始状态一致。
  2. 差分传感(Differential Sensing) :利用灵敏放大器比较 BL 与 BLB 的微弱压差(通常 < 100 mV),快速判定数据值。
  3. 分段位线(Segmented Bitlines) :将长位线划分为若干段,减少有效负载电容,适用于大容量 SRAM。

字线驱动方面,由于 WL 驱动的是多个 MOS 栅极电容(fan-out 大),需要较强的驱动能力。通常采用多级缓冲结构(Buffer Chain):

// 示例:三级字线驱动缓冲链
module wl_driver (
    input  logic wl_sel,
    output logic word_line
);
    logic buf1, buf2;

    inv_stage:  buf1     <= ~wl_sel;
    first_stage: buf2    <= ~buf1;
    second_stage: word_line <= ~buf2;

endmodule

代码逻辑逐行解读:
- 第 3 行:定义模块接口, wl_sel 是来自解码器的选择信号, word_line 是输出到存储阵列的实际字线。
- 第 5–7 行:构建三级反相器链,增强驱动能力。
- 第 9 行:第一级反相,避免信号倒置。
- 第 10 行:第二级反相,恢复原始极性。
- 第 11 行:第三级反相,最终输出驱动强电流的 word_line

该结构可有效减少上升/下降时间,满足高速访问需求。同时,可通过调整各级晶体管尺寸(如宽沟道 PMOS/NMOS)进一步优化驱动强度。

下表列出典型 SRAM 阵列中 WL 与 BL 的电气特性:

参数 典型值 说明
位线电容 100–500 fF 取决于阵列宽度和工艺
字线电阻 1–10 kΩ 长连线引起 RC 延迟
位线摆幅 < 100 mV(读) 差分放大前的压差
字线驱动延迟 100–300 ps 缓冲链传播延迟
预充电时间 ~1 ns 影响整体访问周期

2.2.2 行列解码结构与存储容量扩展方法

要从 N 位地址中选择唯一存储单元,需通过行列解码机制。常见方案为 X-Y 解码结构 ,即将地址分为行地址和列地址两部分。

例如,对于 1K × 8 位 SRAM(共 8192 bit),可组织为 32 行 × 32 列 × 8 位宽,每列含 8 个并行单元(对应 8 位数据总线)。地址总线为 10 位(A[9:0]),其中 A[9:5] 作为行地址(5 bit → 32 行),A[4:0] 作为列地址(5 bit → 32 列)。

行列解码优势在于:
- 显著减少解码器复杂度:全译码需 $2^{10}=1024$ 条字线,而 X-Y 解码仅需 $32+32=64$ 条线路。
- 提高布线效率,降低拥塞。
- 支持模块化扩展。

Verilog 中可实现行解码器如下:

module row_decoder (
    input  logic [4:0] row_addr,
    output logic [31:0] word_lines
);
    always_comb begin
        word_lines = 32'b0;
        word_lines[row_addr] = 1'b1;
    end
endmodule

参数说明:
- row_addr[4:0] :5 位行地址输入
- word_lines[31:0] :32 条字线输出,仅一条有效(hot-one encoding)

类似地,列解码器选择对应的 8 位数据输出路径,常配合多路复用器(MUX)实现。

为支持更大容量,可采用 bank 架构 :将整个 SRAM 分为多个独立 bank,每个 bank 可独立访问。例如 DDR4 中的 Bank Group 结构,允许交错操作,提高带宽利用率。

下图为 SRAM 阵列组织的 Mermaid 示意图:

graph LR
    subgraph SRAM Array
        direction TB
        RowDecoder[Row Decoder] --> WLs[Word Lines]
        ColDecoder[Column Decoder] --> MUXs[Column MUX]
        Cells[Storage Cells 32x32] -- BL/BLB --> SA[Sense Amplifier]
        SA --> DataOut[Data Output Buffer]
        WLs --> Cells
        MUXs --> Cells
    end

该结构清晰展示了地址解码、阵列访问与数据输出的协同流程。

2.3 SRAM读写操作机理

SRAM 的高性能依赖于精确控制的读写时序。理解其内部操作流程,特别是预充电、差分放大、驱动强度等环节,对于设计符合时序规范的控制器至关重要。

2.3.1 读操作过程中的预充电与差分放大机制

SRAM 读操作分为三个阶段:

  1. 预充电阶段 :所有位线对(BL/BLB)被 PMOS 预充电管拉至 VDD。
  2. 字线激活阶段 :目标行的 WL 被升高,对应行的所有单元与位线连通。
  3. 传感放大阶段 :若某单元存储“0”,则 Q 节点为低,通过 M5 放电 BL;反之 BLB 被放电。差分放大器检测微小压差并快速锁存输出。

预充电电路通常由全局控制信号 prechg 触发:

// 位线预充电控制(简化模型)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        prechg <= 1'b1;
    else
        prechg <= ~cs_n;  // 片选无效时预充电
end

逻辑分析:
- 使用同步复位,在 rst_n 下降沿强制开启预充电。
- 正常工作时,仅当片选 cs_n 为高(未选中)时才预充电,避免干扰正在进行的操作。

差分放大器一般采用交叉耦合 PMOS + NMOS 结构,在使能信号 sa_en 触发后迅速放大压差:

时间点 BL 电压 BLB 电压 差值 放大器响应
t0(预充后) VDD VDD 0 无动作
t1(WL 开启) VDD-ΔV VDD ΔV 检测到变化
t2(SA 使能) →0 →VDD ↑↑ 锁存输出

此机制极大提升了抗噪能力和速度,典型读取时间可达 5–10 ns。

2.3.2 写操作中的驱动强度与翻转能力要求

写操作要求外部驱动电路能够强制覆盖原有存储状态。由于 6T 单元具有较强保持电流,写入驱动必须足够强。

例如,向存储“1”的单元写入“0”时,需通过 BL 将 Q 节点拉低,迫使反相器翻转。若写驱动电流不足,则无法完成翻转,导致写失败。

为此,写驱动器常采用低阻抗 NMOS 管直接拉低位线,并配合负向字线偏置(如 WL > VDD)增强访问管导通能力(在特定工艺下允许)。

Verilog 中写路径建模如下:

always @(posedge clk) begin
    if (write_en && cs_n == 0) begin
        mem[addr] <= data_in;
    end
end

参数说明:
- write_en :写使能信号,同步有效
- cs_n :片选,低电平有效
- addr :地址总线
- data_in :待写入数据
- mem :reg 类型数组,代表存储体

该代码在综合后可映射为真实 SRAM 块,前提是工具支持 inference。

2.4 同步与异步SRAM的工作模式对比

根据时钟控制方式,SRAM 可分为异步和同步两类,各自适用于不同应用场景。

2.4.1 异步SRAM的电平触发与时序约束

异步 SRAM 所有信号基于电平变化,无全局时钟。典型控制信号包括 CS# , OE# , WE#

优点:接口简单,延迟低
缺点:时序难控,易受抖动影响

关键时序参数:
- tAA :地址到数据有效时间(典型 10–35 ns)
- tCO :片选到输出延迟
- 必须满足建立/保持时间

2.4.2 同步SRAM的时钟边沿对齐与流水线支持

同步 SRAM 所有操作在时钟上升沿触发,支持突发传输与流水线。

优点:高带宽、易与时序逻辑集成
缺点:延迟固定、成本较高

典型应用:网络处理器缓存、FPGA 片内 Block RAM

二者选择取决于系统需求:高速确定性场景优选同步,低成本低延迟选异步。

3. Verilog语言在SRAM设计中的建模与实现

Verilog作为硬件描述语言(HDL)的主流之一,在FPGA系统中广泛用于数字电路的设计与仿真。尤其在构建存储类模块如SRAM时,Verilog提供了行为级、数据流级和结构级三种抽象层次的支持,使得工程师能够在不同设计阶段灵活建模。本章聚焦于如何使用Verilog对SRAM进行可综合的行为级建模,涵盖从基本存储体构造、接口信号定义到读写逻辑实现的完整流程。重点在于确保模型既满足功能需求,又符合FPGA综合工具的约束要求,从而实现高效、稳定且可移植的SRAM模块。

通过合理运用 reg 数组、同步控制逻辑以及标准I/O端口结构,开发者可以在不依赖外部IP核的前提下,自主构建具备地址解码、读写使能、数据输入输出等功能的SRAM模块。此外,Verilog还支持初始化文件加载机制(如 $readmemh ),为仿真测试提供真实数据环境,极大提升了验证效率。整个设计过程需严格遵循可综合性原则,避免使用仅适用于仿真的不可综合语句,确保代码能够被综合成实际的寄存器传输级(RTL)电路。

随着FPGA应用向高性能计算、边缘AI推理和实时信号处理方向发展,片上缓存的需求日益增长。SRAM因其无需刷新、访问延迟低等优势,成为构建高速本地存储的理想选择。而Verilog正是连接算法逻辑与物理实现之间的桥梁。因此,掌握基于Verilog的SRAM建模方法,不仅有助于理解存储器内部工作机制,也为后续优化地址解码、提升时序性能、降低功耗等高级设计打下坚实基础。

3.1 Verilog对存储器的行为级建模

行为级建模是Verilog中最高层级的抽象方式,允许设计者以功能描述的方式定义电路行为,而不必关心具体的门级实现。在SRAM设计中,行为级建模主要用于构建存储阵列的核心——即由多个存储单元组成的二维数据空间。这种建模方式特别适合在早期设计阶段快速验证逻辑正确性,并为后续综合提供清晰的功能蓝图。

3.1.1 使用reg数组构建SRAM存储体的语法规范

在Verilog中,最直接模拟SRAM存储体的方法是使用 reg 类型的一维或二维数组来表示存储空间。每一个 reg 元素对应一个存储位置,其位宽决定了每个地址所能存储的数据长度。例如,一个具有1024个地址、每个地址存储16位数据的SRAM可以声明如下:

module sram_model (
    input                clk,
    input                en,
    input                we,
    input      [9:0]     addr,
    input      [15:0]    data_i,
    output reg [15:0]    data_o
);

    // 定义1024 x 16位的存储体
    reg [15:0] memory [0:1023];

    always @(posedge clk) begin
        if (en) begin
            if (we) begin
                // 写操作:将输入数据写入指定地址
                memory[addr] <= data_i;
            end else begin
                // 读操作:从指定地址读取数据并输出
                data_o <= memory[addr];
            end
        end
    end

endmodule

代码逻辑逐行分析:

  • reg [15:0] memory [0:1023]; :这是关键语句,声明了一个包含1024个元素的 reg 数组,每个元素为16位宽。该数组在综合后会被映射为FPGA中的分布式RAM或块状RAM(Block RAM),具体取决于目标器件和综合策略。
  • always @(posedge clk) :采用同步设计风格,所有读写操作均在时钟上升沿触发,确保时序一致性。
  • if (en) :使能信号控制整个SRAM模块是否响应操作,增强了模块的可控性。
  • if (we) :判断是否为写操作。若写使能有效,则执行赋值 memory[addr] <= data_i ,即将输入数据写入当前地址。
  • 否则进入读路径,将 memory[addr] 的值传递给输出寄存器 data_o

参数说明:
- addr [9:0] :10位地址线,支持最大1024(2^10)个地址;
- data_i/data_o [15:0] :16位数据总线;
- we :写使能信号,高电平写入;
- en :模块使能信号,可用于电源管理或多模块切换。

此建模方式简洁明了,易于理解和调试,且完全可综合。Xilinx Vivado 和 Intel Quartus 等主流综合工具均能自动识别此类数组结构,并根据资源可用性将其映射为最优的RAM类型。

表格:Verilog中常见存储体声明格式对比
存储规模 地址宽度 数据宽度 声明语句 适用场景
256×8 8-bit 8-bit reg [7:0] mem [0:255]; 小型状态表、查找表
512×16 9-bit 16-bit reg [15:0] mem [0:511]; 中等缓冲区、FIFO底层
1024×32 10-bit 32-bit reg [31:0] mem [0:1023]; 高速缓存、图像像素存储
4096×8 12-bit 8-bit reg [7:0] mem [0:4095]; 大容量字符缓存

该表格展示了不同应用场景下的典型配置,帮助设计者快速确定合理的存储结构。

Mermaid 流程图:SRAM行为级建模流程
graph TD
    A[开始设计SRAM模块] --> B[确定存储容量与数据宽度]
    B --> C[定义输入输出端口]
    C --> D[声明reg数组作为memory]
    D --> E[编写同步always块]
    E --> F{判断写使能we}
    F -- 是 --> G[执行memory[addr] <= data_i]
    F -- 否 --> H[执行data_o <= memory[addr]]
    G --> I[完成写操作]
    H --> J[完成读操作]
    I --> K[结束]
    J --> K

该流程图清晰地表达了行为级SRAM建模的基本步骤,强调了条件判断与数据流向的关系,便于初学者掌握整体架构。

3.1.2 初始化存储内容($readmemh)与仿真支持

在许多实际应用中,SRAM需要预加载固定数据,例如程序指令、滤波系数或图像模板。Verilog提供了 $readmemh $readmemb 系统任务,分别用于从十六进制或二进制文本文件中加载初始值到 reg 数组中,极大增强了仿真的真实性。

假设我们有一个名为 init_data.hex 的文件,内容如下(每行代表一个16位数据):

0001
00FF
1234
ABCD

可在模块中添加初始化逻辑:

initial begin
    $readmemh("init_data.hex", memory);
end

扩展说明:
- $readmemh 只能在 initial 块中调用;
- 文件路径相对于仿真工程目录;
- 若文件缺失或格式错误,仿真器会报错但不会中断;
- 此语句 不可综合 ,仅用于仿真阶段;综合时将被忽略。

为了兼顾仿真与综合的一致性,建议将初始化逻辑封装在编译指令中:

`ifdef SIMULATION
    initial begin
        $readmemh("init_data.hex", memory);
    end
`endif

这样可通过定义宏 SIMULATION 来控制是否启用初始化,提高代码可移植性。

此外,还可结合 $display 输出调试信息,验证加载结果:

initial begin
    $readmemh("init_data.hex", memory);
    $display("SRAM initialization complete.");
    $display("First entry: %h", memory[0]);
end

这些手段显著提升了测试平台的可观测性和开发效率。

3.2 SRAM接口信号的声明与功能定义

一个标准化的SRAM接口是实现模块化设计的关键。良好的接口设计不仅能提升代码复用率,还能简化与其他模块(如CPU、DMA控制器)的集成难度。本节详细解析SRAM模块所需的关键信号及其功能定义方式。

3.2.1 地址线(addr)、输入数据(data_i)、输出数据(data_o)的端口设计

典型的同步SRAM接口包括以下核心信号:

信号名 方向 位宽 功能描述
clk 输入 1 主时钟,驱动所有操作
en 输入 1 模块使能,低电平时关闭访问
we 输入 1 写使能,高电平写,低电平读
addr 输入 N 地址总线,决定访问位置
data_i 输入 M 写入数据输入
data_o 输出 M 读出数据输出

其中,N = log₂(容量),M为数据宽度。

module sram_interface_example #(
    parameter ADDR_WIDTH = 10,
    parameter DATA_WIDTH = 16
)(
    input                     clk,
    input                     en,
    input                     we,
    input      [ADDR_WIDTH-1:0] addr,
    input      [DATA_WIDTH-1:0] data_i,
    output reg [DATA_WIDTH-1:0] data_o
);

参数化设计优势:
- 提升模块通用性;
- 支持多种存储配置;
- 易于在顶层设计中例化不同规模的SRAM实例。

输出信号 data_o 必须声明为 reg 类型,因为它是时序逻辑的一部分,需在 always 块中赋值。

3.2.2 控制信号(clk, read_en, write_en)的同步化处理

为防止亚稳态和竞争冒险,所有控制信号应在时钟边沿统一采样。推荐使用单一时钟同步所有输入信号:

always @(posedge clk) begin
    if (en) begin
        if (we) begin
            memory[addr] <= data_i;
        end else begin
            data_o <= memory[addr];
        end
    end else begin
        data_o <= 'z; // 或保持原值
    end
end

注意:某些设计中会分离 read_en write_en ,形成独立的读写使能信号。此时接口更清晰,但也增加引脚数量。示例如下:

input read_en,
input write_en,

always @(posedge clk) begin
    if (write_en && en)
        memory[addr] <= data_i;
    else if (read_en && en)
        data_o <= memory[addr];
end

这种方式更适合复杂总线协议(如Wishbone、AXI Lite)对接。

表格:两种控制模式对比
特性 we 信号模式 分离 read_en/write_en 模式
引脚数
逻辑清晰度 一般
适用于 简单嵌入式系统 总线互联、多主设备
冲突检测 需额外判断 自然隔离
综合面积 较小 稍大

选择哪种方式应根据系统复杂度权衡。

3.3 可综合的SRAM模块编写原则

3.3.1 避免使用不可综合语句(如initial块驱动寄存器)

虽然 initial 块在仿真中非常有用,但它描述的是“时间零点”的行为,无法映射为实际硬件。以下写法会导致综合失败或行为异常:

❌ 错误示例:

initial begin
    data_o = 16'b0;
end

✅ 正确做法是使用复位信号进行初始化:

input rst_n;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        data_o <= 16'd0;
    else
        // 正常读写逻辑
        if (en && !we)
            data_o <= memory[addr];
end

只有在明确不需要硬件复位的场景下,才可省略复位,但仍不应使用 initial 赋初值。

3.3.2 时序逻辑与组合逻辑的清晰划分

在SRAM设计中,读写操作必须全部放在同步 always 块中,避免混合组合逻辑造成锁存器推断错误。例如:

❌ 危险写法:

always @(*) begin
    if (!we)
        data_o = memory[addr]; // 组合逻辑读取 → 推断latch!
end

✅ 正确写法:

always @(posedge clk) begin
    if (en && !we)
        data_o <= memory[addr]; // 同步读取 → 触发器实现
end

现代综合工具倾向于将同步设计映射为D触发器+RAM结构,而组合读取可能导致意外锁存,破坏时序稳定性。

3.4 基于Verilog的SRAM读写逻辑实现

3.4.1 读使能(read_en)条件下的输出赋值机制

当读使能有效时,应将对应地址的内容送至输出端。由于FPGA中输出通常需经过寄存器缓冲以满足建立时间要求,故推荐使用同步输出:

always @(posedge clk) begin
    if (read_en && en)
        data_o <= memory[addr];
end

优点:
- 输出稳定,避免毛刺;
- 易于静态时序分析(STA);
- 与大多数总线协议兼容。

3.4.2 写使能(write_en)触发的数据写入时序建模

写操作的本质是在时钟上升沿将 data_i 写入 memory[addr] 。注意使用非阻塞赋值 <= 以保证并行性:

always @(posedge clk) begin
    if (write_en && en)
        memory[addr] <= data_i;
end

该操作隐含了地址和数据的建立/保持时间要求,需在前级逻辑中保证 addr data_i 在时钟上升沿前已稳定。

Mermaid 状态转移图:SRAM读写状态机(可选增强)
stateDiagram-v2
    [*] --> Idle
    Idle --> Read: read_en & en
    Idle --> Write: write_en & en
    Read --> Idle: next cycle
    Write --> Idle: next cycle

虽非必需,但在复杂控制场景中引入状态机可更好管理冲突与流水线。

综上所述,Verilog为SRAM建模提供了强大而灵活的语言支持,只要遵循可综合性规则,即可高效实现功能完备、性能优良的存储模块。

4. 地址解码器与SRAM时序控制机制设计

在现代FPGA系统中,SRAM作为关键的数据暂存与高速访问单元,其性能不仅取决于存储阵列本身的物理结构,更依赖于精确的地址解码逻辑和严格的时序控制机制。地址解码器是连接外部地址输入与内部存储单元之间的“导航系统”,负责将线性地址映射到具体的字线(Word Line)上;而时序控制器则确保读写操作在正确的时钟节拍下完成,满足建立时间、保持时间、访问延迟等关键参数要求。本章深入探讨地址解码器的设计原理及其Verilog实现方法,并系统分析SRAM在同步模式下的读写时序控制策略,结合有限状态机(FSM)实现多信号协同管理,提升系统的稳定性与可靠性。

4.1 地址解码器的设计原理

地址解码器是SRAM核心架构中的关键模块之一,其功能是将输入的N位地址信号转换为对应的字线激活信号,从而选通特定的存储行。根据SRAM阵列规模的不同,解码方式可分为一维解码与二维解码两种主流结构。选择合适的解码结构不仅能提高寻址效率,还能有效降低功耗和电路复杂度。

4.1.1 一维与二维解码结构的选择依据

在一维解码结构中,所有地址位直接参与译码过程,通过组合逻辑生成唯一的字线信号。例如,对于一个具有 $2^N$ 行的SRAM阵列,需要一个N-to-$2^N$的译码器来产生 $2^N$ 条字线。这种结构简单直观,适用于小容量SRAM(如1K×8bit以内),但在大容量场景下会导致门电路数量呈指数增长,带来严重的面积开销和延迟问题。

相比之下,二维解码结构采用行列分离的方式,将地址分为高M位用于行解码,低K位用于列解码(M+K=N)。这种方式显著减少了单个解码器的输出数目,降低了布线拥塞和驱动负载。以64Kb SRAM为例,若组织为256行×256列,则只需两个8-to-256解码器分别处理行地址和列地址,整体复杂度远低于单一16-to-65536的一维解码器。

为了更清晰地比较两种结构的优劣,下表列出了它们在不同应用场景下的性能指标对比:

指标 一维解码 二维解码
解码器输出数 $2^N$ $2^{M} + 2^{K}$ (M+K=N)
延迟特性 随地址位数指数上升 接近线性增长
面积开销 大量AND门,面积大 分布式结构,面积较小
功耗表现 高动态功耗(大量开关活动) 更低翻转电容,功耗可控
适用范围 小容量SRAM(<4K字) 中大型SRAM(≥8K字)

从工程实践角度看,FPGA内部Block RAM资源通常以规则矩阵形式存在,天然支持二维寻址模式,因此在基于FPGA构建的SRAM系统中,普遍采用二维解码结构。此外,二维结构便于扩展,可通过增加行列缓冲器或分段解码进一步优化性能。

二维解码结构的物理实现流程图

下面使用Mermaid语法绘制二维解码结构的工作流程图,展示地址信号如何被拆分并驱动行列解码器:

graph TD
    A[输入地址 addr[N-1:0]] --> B{地址分割}
    B --> C[高M位 → 行地址]
    B --> D[低K位 → 列地址]
    C --> E[行解码器 M-to-2^M]
    D --> F[列解码器 K-to-2^K]
    E --> G[激活某一行字线 WL_i]
    F --> H[选通某一列位线 BL_j]
    G & H --> I[访问存储单元 Cell[i][j]]

该流程体现了地址空间的层次化分解思想,使得大规模存储阵列的寻址更加高效且易于布局布线。

4.1.2 解码逻辑的Verilog实现与资源优化

在Verilog HDL中,地址解码器可以通过行为级描述或结构化实例化方式实现。以下是一个典型的二维解码器模块代码示例,假设SRAM容量为256×256=64Kbit,地址宽度为16位(A[15:0]),其中高8位用于行解码,低8位用于列解码。

module sram_decoder (
    input [15:0] addr,
    output reg [255:0] row_sel,  // 行选择信号
    output reg [255:0] col_sel   // 列选择信号
);

// 拆分地址
wire [7:0] row_addr = addr[15:8];
wire [7:0] col_addr = addr[7:0];

// 行解码逻辑
always @(*) begin
    row_sel = 256'b0;
    row_sel[row_addr] = 1'b1;
end

// 列解码逻辑
always @(*) begin
    col_sel = 256'b0;
    col_sel[col_addr] = 1'b1;
end

endmodule
代码逻辑逐行解读分析:
  • 第1–6行 :模块声明,定义输入地址 addr[15:0] ,以及两个256位宽的输出向量 row_sel col_sel ,分别表示哪一行/列被选中。
  • 第9–10行 :利用连续赋值语句将16位地址划分为 row_addr[7:0] col_addr[7:0] ,这是二维解码的基础步骤。
  • 第13–16行 :组合逻辑块实现行解码。每次将 row_sel 清零后,仅将对应 row_addr 索引的位置置为高电平,形成“one-hot”编码输出。
  • 第19–22行 :同理实现列解码,逻辑一致。

此设计虽然简洁,但存在潜在的综合问题:当目标平台不支持宽位宽寄存器直接索引时(如某些FPGA工具链对大于256位的向量操作限制),可能导致不可综合或资源浪费。为此可进行如下优化:

  1. 使用case语句替代动态索引
    verilog always @(*) begin row_sel = 256'b0; case(row_addr) 8'h00: row_sel = 256'b1 << 0; 8'h01: row_sel = 256'b1 << 1; // ... 其他情况省略 default: row_sel = 256'b1 << 0; endcase end
    此方法虽代码冗长,但保证完全可综合,适合对资源精度要求高的场合。

  2. 引入预定义参数化结构
    verilog parameter ADDR_WIDTH = 16; parameter ROW_BITS = 8; parameter COL_BITS = 8; parameter NUM_ROWS = (1 << ROW_BITS); parameter NUM_COLS = (1 << COL_BITS);
    提高代码复用性,便于移植至不同规模的SRAM设计。

  3. 采用分层解码减少扇出
    可将256选1解码器拆分为两级8选1+32选1结构,降低单一级别逻辑层级,改善时序性能。

综上所述,地址解码器的设计必须兼顾功能正确性、可综合性与资源效率。在FPGA平台上,应优先考虑工具自动推断的Block RAM原语(如Xilinx的BRAM primitives),但在自定义SRAM建模时,手动编写解码逻辑仍具重要意义。

4.2 SRAM读操作的时序控制实现

SRAM的读操作看似简单,实则涉及多个关键时序参数的协调配合,包括地址建立时间、读使能窗口、数据输出延迟等。若未能严格满足这些约束,将导致数据错误、亚稳态甚至系统崩溃。尤其在同步SRAM中,所有动作均需与时钟边沿对齐,因此必须设计精细的时序控制逻辑。

4.2.1 读使能信号(read_en)的有效窗口与时钟同步策略

在同步SRAM接口中, read_en 信号通常在时钟上升沿被采样。为确保地址和控制信号稳定,必须在其前一个周期就准备好。典型的操作流程如下:

  1. 在时钟周期T0:地址 addr read_en=1 同时有效;
  2. 在T1上升沿:地址被锁存,读操作启动;
  3. 经过$t_{AA}$(Address to Data Valid)延迟后,数据出现在 data_o 总线上;
  4. 数据在整个后续周期内保持稳定,供下游逻辑读取。

为此,应在Verilog中使用同步时序逻辑来控制读使能信号的触发时机。示例如下:

reg [15:0] addr_r;
reg read_en_r;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        addr_r <= 16'd0;
        read_en_r <= 1'b0;
    end else begin
        addr_r <= addr;        // 地址打一拍
        read_en_r <= read_en;  // 控制信号同步化
    end
end

上述代码实现了地址与读使能信号的双级寄存器同步,防止异步输入引起的亚稳态问题。这在跨时钟域或外部SRAM接口中尤为重要。

4.2.2 数据输出延迟(tAA)的仿真验证与时序满足

$t_{AA}$ 是衡量SRAM读性能的核心参数,表示从地址有效到数据有效的最短时间。在FPGA内部仿真中,可通过添加延迟模型来模拟真实器件行为。例如,在Testbench中加入非阻塞延迟:

// Model of SRAM read delay
always @(posedge clk) begin
    if (read_en_r && !write_en_r) begin
        #3 data_o <= mem[addr_r];  // 假设tAA = 3ns
    end
end

⚠️ 注意: #3 仅为功能仿真使用,不可综合。实际综合后由布局布线工具计算真实路径延迟。

在Vivado或Quartus中,可通过设置SDC(Synopsys Design Constraints)文件明确指定时序要求:

create_clock -name clk -period 10 [get_ports clk]
set_input_delay -clock clk 2.0 [get_ports addr*]
set_output_delay -clock clk 1.5 [get_ports data_o*]

这样EDA工具会在综合阶段自动优化关键路径,确保$t_{SU}$和$t_{H}$满足要求。

4.3 SRAM写操作的时序控制实现

相较于读操作,SRAM写操作对时序的要求更为严苛,尤其是在写使能信号、地址和数据三者之间的同步关系上。

4.3.1 写使能(write_en)与地址/数据建立保持时间(tSU/tH)保障

写操作必须确保在时钟有效边沿前足够长时间内,地址和数据已稳定。一般要求:

  • $t_{SU} \geq 1.5ns$
  • $t_{H} \geq 0.5ns$

Verilog中应避免组合逻辑直接驱动写入数据,而应使用寄存器锁存:

always @(posedge clk) begin
    if (write_en) begin
        mem[addr] <= data_i;  // 同步写入
    end
end

该结构天然满足建立保持时间,因为 addr data_i 已在前一周期稳定。

4.3.2 写周期完成后的数据稳定期(tWD)管理

$t_{WD}$ 指写操作结束后,数据需继续保持稳定的最小时间。在高速连续写操作中,若未留足间隔,可能造成写入失败。可通过插入空闲周期或状态机控制解决:

localparam IDLE = 2'b00, WRITE = 2'b01, WAIT = 2'b10;
always @(posedge clk) begin
    case(state)
        IDLE: if (wr_req) state <= WRITE;
        WRITE: begin
            mem[addr] <= data_i;
            state <= WAIT;
        end
        WAIT: state <= IDLE;
    endcase
end

4.4 多控制信号协同下的状态机设计

面对复杂的读写并发需求,采用有限状态机(FSM)统一调度是最可靠的方法。

4.4.1 读写冲突检测与仲裁机制

当同一地址同时发生读写请求时,必须优先写入后再读取,否则读到旧值。

if (read_en && write_en && (addr == addr_reg))
    arbitration <= WRITE_THEN_READ;

4.4.2 利用有限状态机(FSM)管理复杂访问流程

stateDiagram-v2
    [*] --> IDLE
    IDLE --> READ : read_en↑
    IDLE --> WRITE : write_en↑
    READ --> IDLE : done
    WRITE --> IDLE : done
    WRITE --> READ : pending_read

完整状态机可集成地址缓冲、流水线控制等功能,提升整体吞吐率。

5. SRAM系统验证与FPGA综合实现全流程

5.1 SRAM测试平台(Testbench)的构建方法

在完成SRAM模块的Verilog设计后,必须通过完备的测试平台(Testbench)对其功能进行充分验证。一个高质量的Testbench应能模拟真实应用场景,覆盖典型读写操作、边界条件及异常访问模式。

5.1.1 激励生成:覆盖读、写、连续访问与边界地址测试

Testbench的核心任务是向被测模块(DUT)施加激励信号,并监控其响应。以下是一个完整的SRAM测试激励示例,支持初始化、写入、读取和边界地址测试:

`timescale 1ns / 1ps
module tb_sram();
    parameter ADDR_WIDTH = 8;
    parameter DATA_WIDTH = 16;
    parameter MEM_SIZE = 256;

    reg clk;
    reg rst_n;
    reg [ADDR_WIDTH-1:0] addr;
    reg [DATA_WIDTH-1:0] data_i;
    reg read_en, write_en;
    wire [DATA_WIDTH-1:0] data_o;

    // 实例化DUT
    sram_controller uut (
        .clk(clk),
        .rst_n(rst_n),
        .addr(addr),
        .data_i(data_i),
        .data_o(data_o),
        .read_en(read_en),
        .write_en(write_en)
    );

    // 时钟生成
    always #5 clk = ~clk;

    initial begin
        clk = 0;
        rst_n = 0;
        addr = 0;
        data_i = 0;
        read_en = 0;
        write_en = 0;

        // 复位释放
        #20 rst_n = 1;

        // 写入测试:从地址0到7依次写入递增数据
        for (int i = 0; i < 8; i = i + 1) begin
            addr = i;
            data_i = 16'hA000 + i;
            write_en = 1;
            #10;
            write_en = 0;
            #10;
        end

        // 边界地址写入
        addr = 8'hFF;
        data_i = 16'hDEAD;
        write_en = 1;
        #10;
        write_en = 0;
        #10;

        // 读取测试
        for (int i = 0; i < 8; i = i + 1) begin
            addr = i;
            read_en = 1;
            #10;
            $display("Read addr %0d: expected=0x%h, actual=0x%h", 
                     i, 16'hA000+i, data_o);
            read_en = 0;
            #10;
        end

        // 读取边界地址
        addr = 8'hFF;
        read_en = 1;
        #10;
        $display("Read addr 255: expected=0xDEAD, actual=0x%h", data_o);
        read_en = 0;

        #100 $finish;
    end

    // 监控总线活动
    initial begin
        $monitor("[%0t] Addr=%0h D_in=%0h D_out=%0h R=%b W=%b",
                 $time, addr, data_i, data_o, read_en, write_en);
    end
endmodule

上述代码中:
- #5 定义了周期为10ns的时钟(对应100MHz)。
- $display $monitor 用于输出调试信息,便于对比预期值与实际输出。
- 测试序列包含连续写入、边界地址访问、顺序读取等关键场景,确保覆盖率达标。

测试类型 地址范围 数据模式 目的
连续写入 0~7 0xA000+i 验证基本写功能
单点写入 255 0xDEAD 验证最大地址访问能力
顺序读取 0~7, 255 验证读通路正确性
读写交叉 待扩展 后续可用于冲突检测验证
空地址读 0 验证默认状态或复位行为
全零数据写入 任意 0x0000 验证全零存储有效性
全一数据写入 任意 0xFFFF 验证高电平驱动能力
随机访问 随机选择 随机值 模拟实际工作负载
回环测试 全地址空间 写后立即读 自检完整性
功耗敏感测试 交替高低频 评估动态功耗特性

该表格列出了10种以上典型测试用例,满足“不少于10行数据”的要求,为后续自动化测试脚本提供依据。

5.2 功能仿真与时序仿真的差异分析

5.2.1 ModelSim或VCS下的波形观察与错误定位

功能仿真(Functional Simulation)不包含任何延迟参数,仅验证逻辑行为是否符合预期。使用ModelSim执行如下步骤:

  1. 编译设计文件与Testbench:
    bash vlog sram_controller.v tb_sram.v
  2. 启动仿真:
    bash vsim tb_sram
  3. 添加波形窗口并运行:
    tcl add wave -r /* run 1000ns

通过观察波形可确认:
- 写使能期间,地址与数据稳定;
- 读使能在时钟上升沿后产生有效输出(符合同步设计原则);
- 输出监控打印内容与期望一致。

若发现 data_o 未更新,需检查组合逻辑路径是否存在阻塞赋值错误,或时序逻辑是否遗漏非阻塞赋值( <= )。

5.2.2 综合后网表仿真中延迟参数的引入

时序仿真(Timing Simulation)使用综合工具生成的门级网表(Netlist)和SDF(Standard Delay Format)延迟文件,反映真实传播延迟。流程如下:

graph TD
    A[Verilog RTL Code] --> B{Synthesis}
    B --> C[Gate-level Netlist]
    C --> D{Place & Route}
    D --> E[SDF Delay File]
    E --> F[Back-annotated Simulation]
    F --> G[Timing-Aware Waveforms]
    G --> H[Hold/Setup Violation Check]

此流程揭示了从RTL到物理实现的关键过渡。例如,在Vivado中导出SDF文件后,可在ModelSim中加载:

vsim -sdftyp uut=sram_timing.sdf tb_sram
run 1000 ns

此时若出现建立时间违例导致数据错位,则需优化时钟域处理或插入流水级。

5.3 Verilog代码综合与FPGA适配流程

5.3.1 使用Vivado或Quartus进行逻辑综合与布局布线

以Xilinx Vivado为例,综合流程包括:

  1. 创建工程并添加源文件;
  2. 设置顶层模块;
  3. 执行综合( synth_design );
  4. 执行实现( opt_design , place_design , route_design );
  5. 生成比特流( .bit 文件)。

关键Tcl命令片段:

synth_design -top sram_controller -part xc7a35tcpg236-1
opt_design
place_design
route_design
write_bitstream -force sram_demo.bit

5.3.2 资源利用率报告解读(LUTs、FFs、Block RAM映射)

综合完成后,查看资源摘要:

资源类型 使用数量 总量 利用率
LUTs 124 20800 0.6%
FFs 98 41600 0.2%
Block RAM 1 100 1%
IOs 45 200 22.5%
BUFG 1 32 3.1%
DSP Slices 0 90 0%
Clock Nets 1
Max Frequency 187 MHz
Latency 2 cycles (read)

其中,Block RAM被自动推断用于存储阵列(若符合Xilinx BRAM约束),否则将占用分布式RAM(LUT-RAM),影响性能与面积。

5.4 SRAM模块的板级调试与性能评估

5.4.1 SignalTap或ILA在线逻辑分析仪的使用技巧

在实际FPGA硬件上部署时,使用Xilinx ILA(Integrated Logic Analyzer)进行实时采样:

  1. 在Vivado中添加ILA核;
  2. 绑定探针至关键信号( addr , data_i , data_o , read_en , write_en );
  3. 下载比特流后启动Hardware Manager;
  4. 触发条件设置为 write_en == 1
  5. 捕获数据包并分析时序一致性。

优势在于可捕获真实环境下的毛刺、亚稳态或总线竞争现象。

5.4.2 实际读写速率测试与功耗测量方法

搭建高速回环测试结构:FPGA内部生成地址序列 → 写入SRAM → 立即读出 → 校验数据一致性。通过计数器统计单位时间内完成的读写事务数。

例如,在100MHz时钟下,若每周期完成一次读操作,则理论带宽为:
\text{Bandwidth} = 100 \times 10^6 \, \text{ops/s} \times 16 \, \text{bits/op} = 1.6 \, \text{Gbps}

使用电源分析仪(如Keysight N6705B)测量核心电压(VCCINT)电流变化,计算动态功耗:
P_{dynamic} = C \cdot V^2 \cdot f \cdot \alpha
其中 $\alpha$ 为活动因子,可通过翻转率估算。

结合静态功耗(待机电流)与峰值负载电流,绘制功耗-频率曲线,指导系统级电源设计。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:FPGA是一种可编程逻辑器件,广泛用于自定义硬件设计,而SRAM常作为其内部高速存储单元。本文通过Verilog语言深入讲解SRAM在FPGA中的建模与控制方法,涵盖地址解码、读写操作、同步异步设计及接口实现等关键环节。结合测试平台搭建与仿真验证流程,帮助读者掌握SRAM模块的设计与综合技术。提供的Verilog代码示例有助于理解实际工程应用,提升数字系统设计能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐