1、RAM 简介
RAM 的英文全称是Random Access Memory,即随机存取存储器,简称随机存储器。它允许在任意时刻将数据写入或从任意指定地址的存储单元中读取数据,并且其读写速度受到时钟频率的影响。
RAM 分为静态 RAM 、动态 RAM 和只读存储器(ROM):
静态 RAM(SRAM)通常分为单端口RAM(SP RAM)、简单双端口RAM(SDP RAM,也称伪双端口RAM)和真双端口RAM(TDP RAM)。尽管其存储容量通常较小,但具有非常快的读写速度。在 FPGA 或 ASIC 设计中,静态 RAM 扮演着重要角色,常用于构建查找表、寄存器、组合逻辑等核心数字电路组件,显示了其在数字电路体系中的重要性。
动态 RAM(DRAM)主要包括 SDRAM 和 DDR SDRAM。DDR SDRAM 已经发展到 DDR5 代,DDR3 和 DDR4 SDRAM 目前是流行的存储器标准,广泛应用于电脑、嵌入式系统和FPGA板卡,具有大容量但相对较低的读写速度,特别在小数据量情况下差异明显。
同步 DRAM (SDRAM ) 是一种同步动态随机存取存储器,是动态 RAM 的一种。它的工作速度比传统的异步 DRAM 更快,因为它采用了与系统时钟同步的方式进行数据传输,与 SRAM 相比,速度相对较慢但更经济,适合大容量存储如主内存。
2、实战任务
用单端 ARM IP 核生成执行代码,进行读写操作并通过仿真验证波形,然后将设计下载到FPGA开发板并使用在线调试工具验证实验结果。
3、系统框图
根据实战任务分析我们需要实现对 Block Memory 的读写控制,按顺序将数据写入指定地址后,再读取出存储在 Block Memory 中的数据。
4、时序图
本次实战任务,我们读写模式使用 Bypass (旁路模式)和 Normal (正常模式),当 WRE 拉高时,表示我们正在写数据,此时如在 WRE 为高电平期间读 DO 上的数据都保持为最后一次读出的数据。而我们再Bypass (旁路模式)下读数据的输出会比地址晚一个时钟周期。时序如下图所示(其余未用到的工作模式请参考官方UG285手册,链接在本章节,往下寻找):
5、IP核创建
5.1、 高云 IP 核 RAM 资源介绍
如上图所示,Gowin 软件自带的 RAM 的 IP 核分为 Block Memory(块状静态随机存储器 BSRAM )与 Shadow Memory(分布式静态随机存储器 SSRAM )。
Block Memory 是块状静态随机存储器,具有静态存取功能。根据 BSRAM 的特性建立软件模型, 可分为单端口模式(SP/SPX9)、双端口模式(DPB/DPX9B)、伪双端口模式(SDPB/SDPX9B)和只读模式(pROM/pROMX9)。
单端口 RAM 示意图如下所示:
端口的功能描述如下:
端口名 | I/O | 描述 |
---|---|---|
DO | Output | 数据输出信号 |
DI | Input | 数据输入信号 |
AD | Input | 地址输入信号 |
CLK | Input | 时钟输入信号 |
CE | Input | 时钟使能输入信号,高电平有效 |
OCE | Input | 输出时钟使能信号,用于 pipline 模式,对bypass 模式无效 |
WRE | Input | 写使能输入信号1:写入 0:读出 |
RESET | Input | 复位输入信号,支持同步复位和异步复位,高电平有效。 |
BLKSEL | Input | GBSRAM 块选择信号, 用于需要多个 BSRAM存储单元级联实现容量扩展 |
5.2、IP 核之 SP 配置
点击 Gowin 软件上方菜单栏中的 Tools -> IP Core Generator 选项或者直接点击快捷启动图标,如下图所示:
双击即可进入 SP 配置界面,如下所示:
下面对上图所示的几个部分进行简要介绍:
1、Address Depth:配置地址深度(Address Depth)。这里我们选择 50。
Data Width :数据宽度(Data Width),这里我们选择 8。
根据 GW2A-18C 的设计要求,地址深度值必须在 2 ~ 753664 之间,数据宽度必须在 1 ~ 1656 之间,并且地址深度 * 数据宽度必须小于或者等于 847872。(这个规则我去官方文档里面找了半天没有找到,这边只能展示软件里面截图)如下图所示:
2、Read/Write Mode:读写模式配置。
Read Mode 模式配置,可支持二种读模式:Bypass 模式(旁路模式)和 Pipeline 模式(流水线读模式)。在 Pipeline 模式中同步写入存储器时,使用输出寄存器(此模式的数据宽度最大 36 位,如下图所示。 Bypass 模式是不使用输出寄存器,数据保留在存储器( Memory Array )的输出。
Write Mode 模式配置,可支持三种写模式:Normal Mode(正常写模式)、Write-Through Mode(通写模式),Readbefore-Write Mode(先读后写模式)。
3、资源利用情况,当配置完成的时候,可以点击 Calculate 计算 LUT、DFF 等利用情况。
4、 复位模式配置,支持同步复位(Synchronous)和异步复位(Asynchronous)。
配置完成后的 IP 核界面如下图所示:
配置完成后点击 OK 按钮。
6、程序编写
6.1、RAM 读写模块
接下来我们编写 RAM 读写模块,用于处理 RAM 的读写操作,(single_ram_rw.v)代码编写如下:
module single_ram_rw(
input ram_clk, //ram端时钟
input ram_rst_n, //ram端复位信号,低电平有效
output reg ram_rw_en, //ram 读写使能
output reg [5:0] ram_addr, //ram 地址
output reg [7:0] ram_wr_data //ram 写数据
);
parameter RAM_RW_COUNT_MAX = 7'd100; //读 + 写 范围最大值
reg [6:0] ram_rw_count; //读写控制计数器
//计数器范围 0 - 99
always @(posedge ram_clk or negedge ram_rst_n) begin
if(!ram_rst_n)
ram_rw_count <= 7'd0;
else if(ram_rw_count < (RAM_RW_COUNT_MAX - 7'd1))
ram_rw_count <= ram_rw_count + 7'd1;
else
ram_rw_count <= 7'd0;
end
//ram 端口读写使能,1:写数据 0:读数据
always @(*) begin
if(!ram_rst_n)
ram_rw_en <= 1'd0;
else if(ram_rw_count <= ((RAM_RW_COUNT_MAX / 2) - 7'd1))
ram_rw_en <= 1'd1;
else
ram_rw_en<= 1'd0;
end
//RAM 写数据
always @(posedge ram_clk or negedge ram_rst_n) begin
if(!ram_rst_n)
ram_wr_data <= 8'd0;
else if( ram_rw_count < (RAM_RW_COUNT_MAX / 2) - 7'd1 ) //在ram_rw_count 0 - 49 的计数范围内开始写数据
ram_wr_data <= ram_wr_data + 8'd1;
else
ram_wr_data <= 8'd0;
end
//地址信号 范围:0 - 49
always @(posedge ram_clk or negedge ram_rst_n) begin
if(!ram_rst_n)
ram_addr <= 6'd0;
else if(ram_addr < (RAM_RW_COUNT_MAX / 2) - 7'd1)
ram_addr <= ram_addr + 6'd1;
else
ram_addr <= 6'd0;
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
代码解析:
第 17 - 24 行:用于 写数据 和 读数据 的计数,当 ram_rw_count 为 0 - 49 时,向 Block Memory 里面写入数据,当 ram_rw_count 为 50 - 99 时,向 Block Memory 里面读出数据。
第 27 - 34 行:当 ram_rw_count 为 0 - 49 时,写入数据,当 ram_rw_count 为 50 - 99 时,读出数据 。
第 36 - 44 行:当 ram_rw_en 向 Block Memory 里面写入数据。
第 46 - 54 行:让 RAM 写地址累依次加,用于我们数据依次写入和读出测试。
6.2、顶层模块
在实际工程项目中,我们模块需要做分离,一个模块对应一个功能(方便调试),所以我们这边需要创建一个顶层模块来让 RAM 读写模块 和 IP 核模块进行数据交互,这个数据交互则需要靠顶层文件来实现 ,我们将顶层模块命名为 single_ram_top.v 代码如下:
module single_ram_top(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低电平有效
output ram_rw_en , //ram 读写使能
output [5:0] ram_addr, //ram 地址
output [7:0] ram_wr_data, //ram 写数据
output [7:0] ram_rd_data //ram 读数据
);
//单端口 RAM IP 核
Gowin_SP u_Gowin_SP(
.dout(ram_rd_data), //output [7:0] dout
.clk(sys_clk), //input clk
.oce(1'd1), //input oce
.ce(1'd1), //input ce
.reset(~sys_rst_n), //input reset
.wre(ram_rw_en), //input wre
.ad(ram_addr), //input [5:0] ad
.din(ram_wr_data) //input [7:0] din
);
//RAM 模块
single_ram_rd u_single_ram_rd(
.ram_clk(sys_clk), //ram端时钟
.ram_rst_n(sys_rst_n), //ram端复位信号,低电平有效
.ram_rw_en(ram_rw_en), //ram 读写使能
.ram_addr(ram_addr), //ram 地址
.ram_wr_data(ram_wr_data) //ram 写数据
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
代码解析:
第 11 - 21 行:这里是例化了单端口 RAM IP 核。
第 24 - 33 行:单端口 RAM IP 核读/写所需的全部数据、地址和使能信号。
7、仿真编写
接下来我们编写仿真代码,因为本章实验我们只有系统时钟和系统复位这两个输入信号,所以仿真文件也只需要这两个信号即可,signle_ram_mod.v 代码编写如下:
`timescale 10ns/10ns
module signle_ram_mod();
//在对 FIFO 或者 SP IP 核等(或其它在底层转换 GSR 时调用了全局复位的 IP/原语)进行仿真时,在仿真代码中需要添加如下这一段代码,否则联合仿真时就会报错。
GSR GSR(.GSRI(1'b1));
//reg define
reg sys_clk;
reg sys_rst_n;
//wire define
wire ram_rw_en; //ram 读写使能
wire [5:0] ram_addr; //ram 地址
wire [7:0] ram_wr_data; //ram 写数据
wire [7:0] ram_rd_data; //ram 读数据
always #10 sys_clk = ~sys_clk;
initial begin
sys_clk = 1'd0;
sys_rst_n = 1'd0;
#200;
sys_rst_n = 1'd1;
end
single_ram_top u_single_ram_top(
.sys_clk(sys_clk), //系统时钟
.sys_rst_n(sys_rst_n) , //系统复位,低电平有效
.ram_rw_en(ram_rw_en), //ram 读写使能
.ram_addr(ram_addr), //ram 地址
.ram_wr_data(ram_wr_data), //ram 写数据
.ram_rd_data(ram_rd_data) //ram 读数据
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
程序注释:
第 6 行:加了一个 GTP_GRS 模块,在对 FIFO 或者 SP IP 核等(或其它在底层转换 GSR 时调用了全局复位的 IP/原语)进行仿真时,在仿真代码中需要添加如下这一段代码,否则联合仿真时就会报错。
第 32 - 40 行:和 single_ram_top 将读/写数据、地址和使能信号绑定。
接下来打开 Modelsim 软件对代码进行仿真,需要添加文件如下图所示:
在运行仿真一段时间后,需要将波形放大,仿真的波形如下图所示:
由上图写数据知,ram_rw_en 被拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0,此时写入数据和地址都是0,和我们所画的时序图一致。
由上图可知,ram_rw_en 被拉低之后,地址从 0 开始增加,开始 ram 的地址 0 开始读数据,通过仿真图可看出读数据的输出比读地址晚一个时钟周期;ram_rd_data 读出的数据为 0、1、2 ...,和我们写入的值是相同的,从我们仿真的结果来看和我们所想一致。
8、I/O 引脚绑定
在仿真验证完成后,接下来使用 Gowin 对时钟约束和引脚进行分配并上板验证。本实验中,系统时钟、和复位按键(其余端口仅仅只是为了仿真使用,可以不用绑定)管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | F10 | 复位 | LVCMOS33 |
Gowin 软件中 I/O Constraints 界面如下图所示:
7、在线逻辑分析仪
这里需要创建四个触发信号或者那么可以都创建在一个 Trigger Port 0 中,我这里选择 读写数据、地址和读写使能四个信号。如下图所示:
然后点击综合,直到没错,就点击 进入在线逻辑分析仪界面,下载程序,然后点击 运行,抓取波形:
由上图写数据知,ram_rw_en 被拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0,此时写入数据和地址都是0,和我们仿真波形一致。
由上图可知,ram_rw_en 被拉低之后,地址从 0 开始增加,开始 ram 的地址 0 开始读数据,通过仿真图可看出读数据的输出比读地址晚一个时钟周期;ram_rd_data 读出的数据为 0、1、2 ...。至此写数据和写数据都和我们仿真的波形一样。证明我们的 IP 核 RAM 实验没有问题。