这一篇在干嘛?
写 Verilog 时想存点数据,你可以自己用寄存器搭一个数组——但那样又费资源又慢。FPGA 厂商早在芯片里埋好了一块块现成的”内存砖”,安路 EG4S20 里叫 ERAM(Embedded Block RAM)。这一篇带你把官方手册里的规格、模式、坑点全部过一遍,看完你就能熟练地在 TD 软件里配出一块自己想要的 RAM。
先认识两块”砖”:ERAM9K 和 ERAM32K
EG4 系列器件里有两类嵌入式存储器模块(ERAM):
- ERAM9K:每块 9K bits(精确说是 9216 bits),是个多面手,能做单口 RAM、双口 RAM、简单双口 RAM(也叫伪双口),甚至内嵌了硬件 FIFO 控制器,可以直接当 FIFO 用。
- ERAM32K:每块 32K bits,主打”大容量真双口”,能做单口 RAM 和双口 RAM。
ERAM9K 的深度×位宽可以这么切(总容量都是 9K):
| 深度 × 位宽 | 说明 |
|---|---|
| 8192 × 1 | 最窄最深的”面条” |
| 4096 × 2 | |
| 2048 × 4 | |
| 1024 × 8 或 1024 × 9 | 9 位就是带 1 个奇偶校验位(8+1) |
| 512 × 16 或 512 × 18 | 18 位带 2 个校验位(16+2) |
它还支持这些功能特性:
- A/B 口时钟完全独立
- A/B 口位宽可单独配置:真双口支持 x1 到 x9,简单双口(一写一读)支持 x18
- 9 或 18 位写操作带字节使能(Byte Enable)
- 输出锁存器可选,相当于支持 1 级流水线
- RAM/ROM 模式都支持数据初始化(配置芯片时写入初值)
- 三种写操作模式:只写(Normal)、先读后写(Read Before Write)、写穿通(Write Through)
一块 ERAM9K 长什么样:端口信号
先看结构图,对整体有个印象:

A 口和 B 口的控制信号完全独立,每个口都有这些输入:
- 片选 CS(ChipSelect,3 位)
- 时钟使能 CE(Clock Enable)
- 复位 RST(输入/输出寄存器复位)
- 读写控制 WE(1 写 0 读)
- 输出寄存器锁存使能 OCE(只有用了输出寄存器才有效)
- 字节使能 Byte Enable[1:0]
把它们拼起来就是一次操作的”开关条件”:
| 操作 | CLK | CS | ClockEnable | RST | WE |
|---|---|---|---|---|---|
| 写操作 | 上升沿 | 1 | 1 | 0 | 1 |
| 读操作 | 上升沿 | 1 | 1 | 0 | 0 |
| IDLE | x | 1 | 0 | 0 | x |
| 省电 | x | 0 | 0 | 0 | x |
几个端口细节值得注意:
- 数据线
dia[8:0]、doa[8:0]都是 9 位宽;18 位模式时 A 口的 dia/doa 拼低 9 位,B 口的 dib/dob 拼高 9 位。 - 地址线
addra[12:0]共 13 位:[12:4]永远是字地址,[3:0]在不同位宽模式下含义不同。18 位模式时 addra[1:0] 复用为字节使能 Byte Enable[1:0]。 - 复位 rsta 默认高有效,可以配置成同步复位或异步复位。
- 片选
csa[2:0]是 3 位、每位都可独立设置是否反向,3 位全 1 才选中。
3 位片选的妙用
3 位片选信号每位都能配置反向,这样不用外加任何逻辑就能做地址译码,方便地对 2~8 块 RAM 做深度扩展——把高位地址接成片选组合就完事了。
最重要的坑:读写同一地址会怎样(Read-during-Write)
这是本篇最需要吃透的知识点。在单口 RAM 或真双口 RAM 模式下,你可能在写一个地址的同时又读这个地址,这时输出端口给什么数据?EG4 的 ERAM9K 支持三种行为:
| 模式 | 同地址读写时输出 | TD 里叫法 |
|---|---|---|
| Normal(默认) | 输出保持不变(No change) | No Change |
| Read Before Write | 输出旧数据(写之前的值) | Read First(读优先) |
| Write Through | 输出新数据(正要写入的值) | Write First(写优先) |
看波形最直观,先看默认的 No change:

Write Through 是”边写边把新值透传到输出”:

Read Before Write 则是”先把旧值读出来,再写入新值”:

别想当然
不加设置时默认是 Normal(No change),很多从其他平台转来的同学以为”写入时读出来的是新值”,结果仿真和上板行为对不上。如果你的设计依赖”读写同地址拿到某个确定值”,一定要显式指定写模式。
三种常用 RAM 模式
单口模式(Single-Port Mode)
一个口既读又写,适合不需要”一边写一边读”的场景。因为 ERAM9K 内部有 A/B 两套读写控制逻辑,一块 ERAM9K 甚至可以同时实现两个单口 RAM。
位宽配置方面:8192×1、4096×2、2048×4、1024×8、1024×9 都可以由独立的 A 口或 B 口实现;512×16、512×18 则需要 A 口 B 口联合实现。

简单双口模式(Simple Dual-Port Mode)
一写一读,A 口管写、B 口管读,是跨时钟域缓冲最常用的形态。注意一个限制:18 位写入或 18 位读出时不支持真双口模式,只能做单口和简单双口。
18 位写入时:DIB[8:0] 是高 9 位、DIA[8:0] 是低 9 位;18 位读出时:DOB[8:0] 是高 9 位、DOA[8:0] 是低 9 位。18 位写 18 位读的连接长这样:

读写位宽还可以不同(比如写 9 位、读 18 位),官方给出了支持的混合位宽矩阵:
| 读端口 \ 写端口 | 8Kx1 | 4Kx2 | 2Kx4 | 1Kx8 | 512x16 | 1Kx9 | 512x18 |
|---|---|---|---|---|---|---|---|
| 8Kx1 | √ | √ | √ | √ | √ | ||
| 4Kx2 | √ | √ | √ | √ | √ | ||
| 2Kx4 | √ | √ | √ | √ | √ | ||
| 1Kx8 | √ | √ | √ | √ | √ | ||
| 512x16 | √ | √ | √ | √ | √ | ||
| 1Kx9 | √ | √ | |||||
| 512x18 | √ | √ |
看得出规律:9 位家族(带校验位)和 8 位家族(不带校验)不能混着读写。
8/16 位宽的禁忌
使用 8/16 位宽时,禁止使用 DIA[9]、DIB[9]、DOA[9]、DOB[9],否则读写位宽不同会造成内部数据映射失配——简单说就是读出来的数据错位了。
真双口模式(True Dual-Port Mode)
A/B 两口完全独立,支持所有读写组合:两读、两写、一读一写。位宽只支持到 9 位,混合位宽矩阵如下:
| 读端口 \ 写端口 | 8Kx1 | 4Kx2 | 2Kx4 | 1Kx8 | 1Kx9 |
|---|---|---|---|---|---|
| 8Kx1 | √ | √ | √ | √ | |
| 4Kx2 | √ | √ | √ | √ | |
| 2Kx4 | √ | √ | √ | √ | |
| 1Kx8 | √ | √ | √ | √ | |
| 1Kx9 | √ |

怎么选模式?
只需要一个读写口 → 单口;典型的跨时钟域”一端写、一端读” → 简单双口;两个处理器或模块都要随机读写同一块内存 → 真双口。模式越高配,能用到的资源路径越多,按需选就行。
ROM 模式:把 RAM 当只读表用
ERAM9K 也支持 ROM 模式:内容保存在初始化文件里,芯片编程下载时直接写进 ERAM。初始化值可以在生成 IP 时用 MIF 文件设置,ROM 输出可选带寄存器或不带寄存器锁存,读时序和单口 RAM 的读操作完全一样。做正弦查找表、字符库这类固定数据,用它比用逻辑搭省资源得多。
FIFO 模式:硬件自带 FIFO 控制器
ERAM9K 内部集成了 FIFO 控制器,硬件直接支持同步/异步 FIFO,位宽设置和简单双口 RAM 相同,最高 18 bit 进、18 bit 出。常用端口一目了然:
- 写侧:写时钟 clkw、写使能 we、写片选 csw[2:0]、复位 rst
- 读侧:读时钟 clkr、读使能 re、读片选 csr[2:0]、读指针复位 rprst
- 标志位:empty_flag(读空)、aempty_flag(几乎空)、full_flag(写满)、afull_flag(几乎满),空类标志与 clkr 同步、满类标志与 clkw 同步
空满标志的阈值可以在软件里设置:
| FIFO 属性 | 含义 | 设置范围 |
|---|---|---|
| FF | Full flag,满标志 | 1 to Max |
| AF | Almost full,几乎满 | 1 to Full-1 |
| AE | Almost empty,几乎空 | 1 to Full-1 |
| EF | Empty,空 | 固定为 0 |
防指针溢出的硬件技巧
FIFO 写满后继续写、读空后继续读都会让指针溢出。官方推荐的做法:把满信号反向后接到 csw 端、空信号反向后接到 csr 端,反向逻辑直接用 csw/csr 内部的反向与逻辑实现,不用额外搭门电路。
ERAM32K:大容量选项
需要更大容量时上 ERAM32K,每块 32K bits,可配置为 2K×16 或 4K×8,A/B 口时钟独立、8/16 位两种位宽可分别配置,输出锁存器同样可选。它只支持两种写模式:Normal 和 Write Through(没有 Read Before Write),也不支持 FIFO 模式。

ERAM32K 的字节级写控制通过 wbyte_ena 和 bytea 实现:16 位模式下 wbyte_ena=1 启用 8 位写,bytea=1 写高 8 位、=0 写低 8 位;8 位模式时 bytea 当最低位地址用、wbyte_ena 接 0。
ERAM32K 和 Byte Enable 不搭
开启 byte enable 功能时不建议用 ERAM32K——当 ERAM 深度比较小时会浪费很多内存。要按字节写,优先考虑 ERAM9K。
动手调用:两种方式把 ERAM 用起来
方式一:IP Generate(图形界面,推荐新手)
在 TD 软件里走 Tools → IP Generate → IP core → BlockMemory-EMB → RAM:

界面里关键参数的含义:
| 参数 | 配置项 | 默认值 |
|---|---|---|
| Implement | EMB9K(area opt)、EMB9K(Timing opt)、EMB32K | EMB9K(area opt) |
| Memory Type | Single Port RAM、Dual Port RAM、Simple Dual Port RAM | Single Port RAM |
| Byte Write Enable | None、8、9 | None |
| Operating Mode | Write First、Read First、No Change | No Change |
| Output Register | None、Register without Ocea、Register with Ocea | None |
| Memory Initialization | 选择 MIF 文件 | 不勾选 |
其中 Implement 的取舍:EMB9K 面积优化为主,EMB9K(fast) 时序优化为主。另外勾选 “Debug Enable” 后 TD 默认用 Single Port RAM 模式,端口 B 被占用、端口 A 数据可回读,方便用 ERAMEditor 在线调试。
初始化文件:mif 和 dat 两种格式
想让 RAM 上电就有内容(做 ROM 或预置表),要准备初始化文件,TD 支持两种格式:
- mif 格式:必须定义内存深度和宽度,数据和地址可用二进制 BIN、十六进制 HEX、八进制 OCT、无符号十进制 UNS 等格式,数值必须与声明的格式匹配。
- dat 格式:Verilog 存储空间初始化风格,十六进制地址用
@标注;文件很大时可以省略地址行。
生成 IP 时勾选 “Load Init File” 再选文件即可:

方式二:直接例化(写代码,灵活)
熟悉了以后可以直接在代码里例化,常用参数一览:
| 参数 | 含义 | 默认值 |
|---|---|---|
| DATA_WIDTH_A / B、ADDR_WIDTH_A / B、DATA_DEPTH_A / B | A/B 口位宽、地址位宽、深度 | 可配置 |
| MODE | DP(真双口)、SP(单口)、PDPW(简单双口)、FIFO | ”DP” |
| REGMODE_A | NOREG / OUTREG(输出寄存器) | “NOREG” |
| WRITEMODE_A | NORMAL / READBEFOREWRITE / WRITETHROUGH | ”NORMAL” |
| RESETMODE | SYNC / ASYNC | ”SYNC” |
| IMPLEMENT | 9K、9K(FAST)、32K | ”9K” |
| DEBUGGABLE / PACKABLE | 调试使能 / 可压缩 | ”NO” |
| INIT_FILE、FILL_ALL | 初始化文件、填充值 | ”NONE” |

最佳实践
- 输出寄存器(OUTREG)能改善时序、加一级流水,对时序紧张的设计值得开
- 复位模式默认 SYNC;异步逻辑多的场合可改 ASYNC,但要确认复位行为符合预期
- 大容量优先 EMB32K,小而多、要 FIFO/字节写的用 EMB9K
- 时序不达标时试试把 Implement 从 area opt 换成 9K(FAST)
看懂时序:读数据晚一拍
ERAM 是同步 RAM,地址和使能打进去后,数据在下一个时钟上升沿才出现。单口模式的参考时序如下:

流水线级数影响读延迟
默认 NOREG 时读延迟是 1 拍;开了输出寄存器(OUTREG)后变 2 拍。写状态机或读侧对齐数据时,别把延迟算错了。
收个尾
把这篇的内容浓缩成四句话:
- 容量:ERAM9K 每块 9K bits(可切 8K×1 到 512×18),ERAM32K 每块 32K bits(2K×16 或 4K×8)。
- 模式:单口、简单双口(一写一读)、真双口(两口全独立)按需选;9 位带校验家族和 8 位家族不能混合读写。
- 冲突:同地址读写有 Normal / Write Through / Read Before Write 三种行为,默认输出不变,别想当然。
- 上手:IP Generate 适合新手,直接例化适合熟手;ROM 初值用 mif/dat 文件在下载时写入。
自测 1:想跨时钟域缓存 18 位数据,一端写一端读,选哪种模式?位宽有什么限制?
应选简单双口模式(A 口写、B 口读,时钟可各自独立)。18 位模式只有 512×18 一种,且此时不支持真双口模式;读写位宽只能在同一个家族内混搭(9 位带校验家族内部、或 8 位家族内部)。
自测 2:默认配置下,写某地址的同一拍读同地址,输出是什么?想要”读到旧值”该怎么设? 默认 Normal(No Change),输出保持不变。想要旧值需把写模式设为 Read Before Write(Read First / 读优先)。