这一篇在干嘛?

写 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 × 99 位就是带 1 个奇偶校验位(8+1)
512 × 16 或 512 × 1818 位带 2 个校验位(16+2)

它还支持这些功能特性:

  • A/B 口时钟完全独立
  • A/B 口位宽可单独配置:真双口支持 x1 到 x9,简单双口(一写一读)支持 x18
  • 9 或 18 位写操作带字节使能(Byte Enable)
  • 输出锁存器可选,相当于支持 1 级流水线
  • RAM/ROM 模式都支持数据初始化(配置芯片时写入初值)
  • 三种写操作模式:只写(Normal)、先读后写(Read Before Write)、写穿通(Write Through)

一块 ERAM9K 长什么样:端口信号

先看结构图,对整体有个印象:

EG4 ERAM9K 模块结构框图,展示 A/B 两组对称的地址、数据与控制端口

A 口和 B 口的控制信号完全独立,每个口都有这些输入:

  • 片选 CS(ChipSelect,3 位)
  • 时钟使能 CE(Clock Enable)
  • 复位 RST(输入/输出寄存器复位)
  • 读写控制 WE(1 写 0 读)
  • 输出寄存器锁存使能 OCE(只有用了输出寄存器才有效)
  • 字节使能 Byte Enable[1:0]

把它们拼起来就是一次操作的”开关条件”:

操作CLKCSClockEnableRSTWE
写操作上升沿1101
读操作上升沿1100
IDLEx100x
省电x000x

几个端口细节值得注意:

  • 数据线 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:

No change 模式时序波形,写操作时输出数据保持不变

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

Write Through 模式时序波形,写数据的同时新数据直接出现在输出端

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

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 口联合实现。

利用 A 口实现的 9 位宽及以下单口 RAM 连接示意

简单双口模式(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 位读的连接长这样:

简单双口 18 位写 18 位读的端口连接示意

读写位宽还可以不同(比如写 9 位、读 18 位),官方给出了支持的混合位宽矩阵:

读端口 \ 写端口8Kx14Kx22Kx41Kx8512x161Kx9512x18
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 位,混合位宽矩阵如下:

读端口 \ 写端口8Kx14Kx22Kx41Kx81Kx9
8Kx1
4Kx2
2Kx4
1Kx8
1Kx9

位宽小于等于 9 位时的 A/B 双口 RAM 连接示意

怎么选模式?

只需要一个读写口 → 单口;典型的跨时钟域”一端写、一端读” → 简单双口;两个处理器或模块都要随机读写同一块内存 → 真双口。模式越高配,能用到的资源路径越多,按需选就行。

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 属性含义设置范围
FFFull flag,满标志1 to Max
AFAlmost full,几乎满1 to Full-1
AEAlmost empty,几乎空1 to Full-1
EFEmpty,空固定为 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 模式。

EG4 ERAM32K 模块结构框图,展示 16 位数据与 11 位地址端口

ERAM32K 的字节级写控制通过 wbyte_enabytea 实现: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:

TD 软件中 IP Generate 生成 ERAM 模块的菜单入口

界面里关键参数的含义:

参数配置项默认值
ImplementEMB9K(area opt)、EMB9K(Timing opt)、EMB32KEMB9K(area opt)
Memory TypeSingle Port RAM、Dual Port RAM、Simple Dual Port RAMSingle Port RAM
Byte Write EnableNone、8、9None
Operating ModeWrite First、Read First、No ChangeNo Change
Output RegisterNone、Register without Ocea、Register with OceaNone
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” 再选文件即可:

mif 文件与 dat 文件的格式范例

方式二:直接例化(写代码,灵活)

熟悉了以后可以直接在代码里例化,常用参数一览:

参数含义默认值
DATA_WIDTH_A / B、ADDR_WIDTH_A / B、DATA_DEPTH_A / BA/B 口位宽、地址位宽、深度可配置
MODEDP(真双口)、SP(单口)、PDPW(简单双口)、FIFO”DP”
REGMODE_ANOREG / OUTREG(输出寄存器)“NOREG”
WRITEMODE_ANORMAL / READBEFOREWRITE / WRITETHROUGH”NORMAL”
RESETMODESYNC / ASYNC”SYNC”
IMPLEMENT9K、9K(FAST)、32K”9K”
DEBUGGABLE / PACKABLE调试使能 / 可压缩”NO”
INIT_FILE、FILL_ALL初始化文件、填充值”NONE”

在 Verilog 代码中直接例化 ERAM 的写法示例

最佳实践

  • 输出寄存器(OUTREG)能改善时序、加一级流水,对时序紧张的设计值得开
  • 复位模式默认 SYNC;异步逻辑多的场合可改 ASYNC,但要确认复位行为符合预期
  • 大容量优先 EMB32K,小而多、要 FIFO/字节写的用 EMB9K
  • 时序不达标时试试把 Implement 从 area opt 换成 9K(FAST)

看懂时序:读数据晚一拍

ERAM 是同步 RAM,地址和使能打进去后,数据在下一个时钟上升沿才出现。单口模式的参考时序如下:

单口 RAM 模式的参考时序图,展示地址、使能与数据输出的先后关系

流水线级数影响读延迟

默认 NOREG 时读延迟是 1 拍;开了输出寄存器(OUTREG)后变 2 拍。写状态机或读侧对齐数据时,别把延迟算错了。

收个尾

把这篇的内容浓缩成四句话:

  1. 容量:ERAM9K 每块 9K bits(可切 8K×1 到 512×18),ERAM32K 每块 32K bits(2K×16 或 4K×8)。
  2. 模式:单口、简单双口(一写一读)、真双口(两口全独立)按需选;9 位带校验家族和 8 位家族不能混合读写。
  3. 冲突:同地址读写有 Normal / Write Through / Read Before Write 三种行为,默认输出不变,别想当然。
  4. 上手:IP Generate 适合新手,直接例化适合熟手;ROM 初值用 mif/dat 文件在下载时写入。