这一篇在干嘛?
很多 CPU(比如 DSP、Power PC)对外都留一个叫 Local Bus 的总线,可以外挂存储器。这篇笔记带你读懂安路官方参考设计 APUG010:它把这路总线接到 EG4S 器件里的 64Mb SDRAM 上,让 CPU 像读写普通 RAM 一样按字节访问 SDRAM。我们按”总线是什么 → 时序怎么走 → 设计怎么搭 → 工程怎么用”的顺序拆开讲。
先搞懂:Local Bus 是什么总线?
Local Bus 又名 CPU 总线,常见于 DSP、Power PC 等嵌入式 CPU 上。它最大的特点是地址和数据共用一组线,分时复用(这样能省 IO 管脚)。
按写操作的实现方式,Local Bus 分两种模式:
- 同步模式:Master(CPU)额外提供一个时钟给 Slave(本设计里的 FPGA),时钟和数据之间有固定的采样关系。
- 异步模式:不用时钟采样数据(芯片内部仍需系统参考时钟产生时序),而是靠三个低有效信号来把关——片选
cs_l、写使能we_l、读使能oe_l。
本设计选的是异步模式,地址与数据独立分开使用,不需要 lda 复用线(部分 CPU 里 lda 是地址/数据复用信号,用于省 IO)。
先认识一下总线上都有哪些信号(表 2-1):
| 信号 | 方向 | 描述 |
|---|---|---|
| clk | in | 时钟信号,用于同步模式中(异步模式下悬空不用) |
| la[x:0] | in | 地址信号 |
| ld[x:0] | out | 数据信号 |
| lda[x:0] | inout | 数据/地址复用信号 |
| ale_l | in | 地址锁存信号 |
| rdv | out | 应答信号 |
| cs_l | in | 片选信号,低电平有效 |
| we_l | in | 写使能信号,低电平有效 |
| oe_l | in | 读使能信号,低电平有效 |
命名小规律
名字带
_l的信号都是低电平有效:cs_l拉低表示”选中你”,we_l拉低表示”我要写了”,oe_l拉低表示”我要读了”。记住这一点,看时序图就不会晕。
位宽不是固定的
[x:0]表示位宽不固定——不同 CPU 的地址总线宽度不一样,可以根据实际读写范围做适配。本设计里 Local Bus 数据位宽是 8bit,也就是按字节操作。
整体方案:8bit 总线怎么对上 32bit 的 SDRAM?
本方案用异步 Local Bus 对 EG4S 器件内的 64Mb SDRAM 做随机读写,把它当成一块 RAM 来用。这里有一个地址映射的关键换算:
- 64Mb 按字节编码:2048 行 × 4 个 BANK × 每行 256 列,每个单元格是 32bit,所以地址总共 23bit;
- SDRAM 单元格宽 32bit,而 Local Bus 一次只读写 8bit,差 4 倍;
- 解决办法:用 la 地址的最低 2bit 去操作 SDRAM 的 DM(数据掩码),这样每次字节操作都能精确定位到 32bit 里的某一个字节。
这样一来,Local Bus 和 SDRAM 两边都是基于字节方式读写,主机完全感觉不到背后是个 32bit 宽的 SDRAM。
DM 就像"涂改带"
SDRAM 写一个 32bit 单元时,DM 上某一位为有效掩码,对应字节就被保护不写入;最低 2bit 地址翻译成 4 个 DM 位,正好实现”只改 4 个字节中的某一个”。
工程顶层接口(表 3-1)分三组,可以对着上面的思路看:
| 信号 | 方向 | 功能说明 |
|---|---|---|
| SYS_CLK | in | PLL 输入时钟,时钟晶振 25Mhz |
| Local Bus 信号 | ||
| cs_l | in | Local Bus 片选 |
| we_l | in | Local Bus 写使能有效,低电平有效 |
| oe_l | in | Local Bus 读使能有效,低电平有效 |
| La[22:0] | in | Local Bus 读写地址 |
| Ld[7:0] | inout | Local Bus 读写数据 |
| Local_Busy | out | SDRAM 刷新忙指示,高有效 |
| SDRAM 信号 | ||
| SDR_CLK | out | SDRAM 时钟 |
| SDR_CKE | out | SDRAM 的 CKE 时钟使能 |
| SDR_RAS | out | SDARM 行有效 |
| SDR_CAS | out | SDRAM 片选 |
| SDR_WE | out | SDRAM 写使能 |
| SDR_BA[1:0] | out | SDRAM Bank |
| SDR_ADDR[10:0] | out | SDRAM 的操作地址 |
| SDR_DM[3:0] | out | SDRAM 的数据掩码 |
| SDR_DQ[31:0] | inout | SDRAM 数据总线 |
注意 La[22:0] 正好对应前面算出的 23bit 地址。EG4S 器件的 64Mb SDRAM 是 SIP(封装内集成)的,所以顶层只有 Local Bus 引出;如果用其他系列器件,就把 SDRAM 信号引到顶层外挂 SDRAM。
写时序:CPU 怎么把一个字节写进去?
写操作很简单:当 cs_l 片选有效、we_l 写使能有效时,la 地址总线上给出写地址,ld 上给出写数据。看官方时序图(图 3-1):

信号保持多久有讲究
这些信号持续的时间,CPU 可以根据实际情况调整长短——拉得越长越稳,但读写带宽效率就越低。这是个”稳”和”快”的权衡。
读时序:为什么读一个字节要等 24 个时钟?
读操作多了一个”等待”环节:cs_l 有效、oe_l 有效后,la 上给出读地址;经过一段时间后,Slave(FPGA)必须把读数据放到 ld 上还给 Master。这段时间 Master 有一定容限范围,但 Slave 当然是越快越好。

为什么快不起来?因为 SDRAM 从收到地址到读出数据再送出,用的是 SDRAM 自己的主频时钟,本方案大约需要 24 个时钟、约 200ns 才给出有效数据。所以 Master 里的 Local Bus 需要设置采样读数据的有效窗口,窗口必须盖住这 200ns。
还有一个容易忽视的细节:Slave 通过 ld 总线把数据反馈给 Master 后,要在约定的时间内释放总线,否则会妨碍 Master 下一次发起通信。释放时间由双方事先约定。
读数据别急着采样
如果 Master 在
oe_l有效后立刻采样,读到的还是总线释放前的旧值或者三态值。一定要等满约 200ns 的有效窗口,这也是移植时最容易踩的坑(后面第 7 节官方注意事项也专门强调了这一点)。
SDRAM 的”保命机制”:刷新
SDRAM 靠电容存数据,不刷新就会丢失,所以设计里专门有刷新模块,支持两种方式,默认开启自刷新:
- 自刷新:控制器自己对时钟计数、定时发刷新命令。计数用到的参数在
global_def.v文件里配置,用户必须明确当前控制器的时钟工作频率来计算周期——比如工作在 150Mhz,时钟周期就是 1000000000/150000000。其他频率自行更改。最终把SELF_REFRESH_INTERVAL的计算结果带入代码做自动刷新。 - 手动刷新:把
sdr_init_ref模块的self_refresh_open参数传入 0,然后由用户自己控制app_ref_req信号(至少拉高 1 个时钟周期);发出刷新命令后如果收到sdr_ref_ack,就表示这次刷新有效。手动刷新可以由用户自己通过 Local Bus 做协议转换去控制。
刷新保持有 2 个原则:
- 数据内容在部分 SDRAM 里如 64ms 内不断重复读写(比如图像数据),可以选择用户自刷新场景,此时用户不用去做刷新;
- 如果不刷新,数据内容会丢失。
刷新期间绝对不要读写!
SDRAM 刷新时会给出
Local_Busy高电平,Master 此时不要对 SDRAM 进行读写操作,误操作会影响刷新以及数据的正常读写。上板前记得把这个信号接好、查好。
资源占用与频率怎么选?
官方在 EG4S-MINI-DEV3 Demo 板上、用 TD 软件版本 TD5.6.2 71036 验证的结果:
| 资源 | 类型 | 数值 |
|---|---|---|
| 最大频率 | c0 | 375Mhz |
| 使用资源 | LUT | 306 |
| 使用资源 | REG | 132 |
| 使用资源 | PLL | 1 |
| 使用资源 | IO | 10 |
| 使用资源 | BRAM | N/A |
| 使用资源 | DSP | N/A |
| 使用资源 | SERDES | N/A |
| 适用器件 | Anlogic 全系器件(除 EG4S 外,需要外挂 SDRAM) |
工程逻辑本身能跑比较高的频率,但目前 SDRAM 最高只支持 200Mhz。而且 SDRAM 的带宽远大于 Local Bus 总线,为了快速采样 Local Bus 总线信号,建议 SDRAM 工作主频设在 150Mhz。
工程怎么用:调试、仿真与文件构成
这套参考设计提供了完整的 Test_bench 和 IP Model,语言是 Verilog/VHDL,目标平台是 EG4S20-MINI-DEV3,仿真环境为 Modelsim。
上板调试:工程中已经添加了调试的 CWC 文件,初期使用时把下载文件烧到开发板,即可直接抓波形。官方给的实测波形里能看到:Local Bus 写入的数据 slave_rx_dat 是 0x01 往上的递增数,读出来的数据 Local_rd_dout 同样是 0x01 往上的递增数——写入和读出对上了,链路就是通的。
仿真:因为工程没有真正的 CPU 主控制器,Local Bus 总线部分是用模拟的方式产生的。仿真数据是 0x01~0xFF 的递增数:先写入 SDRAM,再从 SDRAM 读出,检查数据是否正确。在 Modelsim 里只需执行 do 文件:
vlog -work work +incdir+$INCLUDE_PATH +define+SIMULATION ../source_code/rtl/*.v
vlog -incr +incdir+$INCLUDE_PATH ../source_code/model/*.v
vlog -incr +incdir+$INCLUDE_PATH +define+SIMULATION ../source_code/tb/*.v
vlog -incr +incdir+$INCLUDE_PATH +define+SIMULATION ../prj/lvds_2_sdr/al_ip/*_sim.v逐行看这个 do 文件:
- 第一行:编译
source_code/rtl/下所有 Verilog 源码,加+define+SIMULATION宏——这是设计里的核心逻辑(Local Bus 转 SDRAM 控制); - 第二行:编译
source_code/model/下的模型文件(不带仿真宏),也就是 SDRAM 的行为模型; - 第三行:编译
source_code/tb/下的 Test_bench——它既用于仿真,也可以直接综合(负责模拟 Local Bus 主机产生激励); - 第四行:编译
prj/lvds_2_sdr/al_ip/下的 IP 仿真模型。
四类文件各司其职:你的 RTL、对方的模型、主机的激励、官方 IP,拼起来就是一个完整的闭环仿真环境。
移植与避坑清单
官方第 7 章给了 5 条注意事项,每条都值得画重点:
移植时的 5 个坑
- 换同步 Local Bus:要按对应协议修改 Local Bus 部分,把接收的数据送入工程的
sdr_wrrd模块;- 读时序余量:注意 Local Bus 读地址到读数据的时间——SDRAM 收到地址到读出数据送出用的是 SDRAM 主频时钟,本方案大约要 24 个时钟,Master 的采样窗口必须覆盖它;
- 外挂 SDRAM:先把 SDRAM 总线连接到工程顶层,再分配相应的管脚;
- 刷新方式:必须确认使用何种刷新方式,保证数据内容在长期没有操作时不会发生变化;
- 地址位宽:SDRAM 地址位宽可在
global_def文件中更改,因此必须把该文件 include 到目标工程中,否则编译都过不了。
最佳实践小结
- SDRAM 主频按官方建议设 150Mhz,兼顾带宽与 Local Bus 采样裕量;
Local_Busy拉高期间停止读写,保护刷新;- 每次改时钟频率,同步更新
global_def.v里的刷新计数参数。
自测
- Local Bus 的同步模式和异步模式,靠什么信号对数据进行采样?本设计选了哪种?
- Master 在
oe_l有效后为什么要等约 200ns 才能采样读数据?这段时间里 SDRAM 侧在做什么?如果刷新正在进行,Master 应该怎么处理?
参考文件
- AN_EG4_DataSheet_DS300
- AN_EG4S20_DataSheet_DS301