1、双端口
我们在单端口 RAM 文档里面讲了,静态 RAM(SRAM)通常分为单端口RAM(SP RAM)、简单双端口RAM(SDP RAM,也称伪双端口RAM)和真双端口RAM(TDP RAM)。
首先我们介绍双端口(也称为伪双端口)RAM,需要注意的是简单双端口 SDP RAM 的一个端口只能写不能读,另一个端口只能读不能写。Gowin 中简单双端口 RAM 的 IP 核是 SDPB IP,其框图如下图所示:
真双端口 RAM 具有两个完全独立的读写端口,每个端口都能同时进行读取和写入操作,其框图如下图所示:
端口介绍引脚定义
端口名 | I/O | 描述 |
---|---|---|
DOA/B | Output | A/B端数据输出信号 |
DIA/B | Input | A/B端数据输入信号 |
ADA/B | Input | A/B端地址输入信号 |
CLKA/B | Input | A/B端时钟输入信号 |
CEA/B | Input | A/B端时钟使能输入信号,高电平有效 |
OCEA/B | Input | 输出时钟使能信号,用于 pipline 模式,对bypass 模式无效 |
WREA/B | Input | A/B端写使能输入信号1:写入 0:读出 |
RESETA/B | Input | A/B端复位输入信号,支持同步复位和异步复位,高电平有效。RESETA 复位寄存器,而不是复位存储器内的值 |
BLKSEL | Input | A/B端GBSRAM 块选择信号, 用于需要多个 BSRAM存储单元级联实现容量扩展 |
注:在伪双端口 SDP RAM 中 CEA 端口只能写不能读,CEB 端口只能读不能写。
2、实战任务
设计一个 FPGA 系统,将一个周期的正弦波波形数据存储在 RAM 中,分成 256 个采样点,并通过 FPGA 输出端口将这些数据传输到 GAO 在线逻辑分析仪上显示。确保在线逻辑分析仪能够实时捕获和显示正弦波的波形特征,以便观察和分析波形,验证 FPGA 设计的正确性(我们上章学的是如何往单端 RAM 里面写数据,然后再读,这章我们将学如何从里面读出数据)。
3、系统框图
根据实战任务分析我们需要实现对 Block Memory 的读控制,按顺序读取出存储在 Block Memory 中的数据。
4、时序图
本次实战任务,我们端口A、B读写模式使用 Bypass (旁路模式)和 Normal (正常模式),我们直接从 Block Memory 里面将数据读出来,此时我们需要提供地址(AD)、时钟(CLK)和写使能(WRE = 0)。时序(B端口)如下图所示(其余未用到的工作模式请参考官方UG285手册,链接:https://cdn.gowinsemi.com.cn/UG285.pdf):
上图是官方的 DPB/DPX9B Normal 写模式 B 写 A 读(Bypass 读模式)时序波形图,参考此图我们来构思一下我们的时序。
从时序图库看出时钟使能输入信号 和 输出时钟使能信号(A/B端)都保持高电平,写使能输入信号(WREA/B),我们设置为 零 ,因为我们只是读取数据,
5、RAM IP 创建
点击 Gowin 软件上方菜单栏中的 Tools -> IP Core Generator 选项或者直接点击快捷启动图标,如下图所示:
弹出的 IP Core Generator 窗口后,我们在 Name 页面的 IP 栏中先展开 Memory ,接着依次展开 Block Memory ,然后双击 DPB ,进入 IP 核配置界面,也可以直接搜索 DPB,如下图所示:
接下来我来介绍一下 DPB 界面,如下所示:
下面对上图所示的几个部分进行简要介绍: 1、Port A\B
address Depth:配置地址深度(Address Depth)。这里我们选择 256。
Data Width :数据宽度(Data Width),这里我们选择 8。
根据 GW2A-18C 的设计要求,地址深度值必须在 2 ~ 753664 之间,数据宽度必须在 1 ~ 828 之间(这个规则我去官方文档里面找了半天没有找到,这边只能展示软件里面截图)如下图所示:
Read Mode:读模式的控制 Bypass 模式(旁路模式)和 Pipeline 模式(流水线读模式),与单端的功能差距差不多。
Write Mode 模式配置,可支持两种写模式:Normal Mode(正常写模式)、Write-Through Mode(通写模式)。而我们的单端 RAM 是支持三种写模式的。
2、Optimization优化模块:
Area :最小其余优化。
speed :为速度优化。
这个模块我找了半天没有找到具体说明,我认为再我们使用大范围的RAM时候可以选择优化区域,这个可能对于数据要求不是特别严格的问题不算太大,而速度优化的话,多半是为了提升读写速度可能有需求。而我们这里选择优化面积。
3、Resources Usage
计算并显示当前容量配置上占用的 Block Ram、 DFF、 LUT、 MUX 的资源情况
4、Reset Mode:配置复位模式,支持同步模式 Synchronous 和异步模式 Asynchronous 。
5、Initialization : 配置初始值,与 pROM 的功能一致。
Dimension Match:设置内存初始化文件在那个端口。
我们这里需要导入一个文件是 doule_rom.mi 的文件,这个文件就是我们前面 pROM 用到是正弦波文件。也可以直接再工程里面找复制这个文件。如果想做可以参考pROM的创建 mi 格式文件的步骤,这里不在讲述了。sine_init数据集
配置完成后的 IP 核界面如下图所示:
配置完成后点击 OK 按钮。
6、程序编写
接下来我们编写 RAM 读模块,用于读取我们存储的信息(doubule_ram.v)代码编写如下:
module doubule_ram(
input sys_clk , //时钟信号
input sys_rst_n , //复位信号,低电平有效
//RAM 写端口操作
output reg [7:0] ram_rd_addr, //ram 读地址
output [7:0] ram_rd_data //ram 读出数据
);
wire [7:0] ram_wr_data; //此demo中无实际意义,仅为了减少报错
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
ram_rd_addr <= 8'd0; // 将读地址复位为0
else if(ram_rd_addr < 9'd256) // 如果当前读地址小于256
ram_rd_addr <= ram_rd_addr + 8'b1; // A端地址加1
else
ram_rd_addr <= 8'd0;
end
Gowin_DPB u_Gowin_DPB(
.douta(ram_rd_data), //output [7:0] douta
.doutb(), //output [7:0] doutb
.clka(sys_clk), //input clka
.ocea(1'b1), //input ocea
.cea(1'b1), //input cea
.reseta(~sys_rst_n), //input reseta
.wrea(1'd0), //input wrea
.clkb(sys_clk), //input clkb
.oceb(1'b0), //input oceb
.ceb(1'b1), //input ceb
.resetb(~sys_rst_n), //input resetb
.wreb(1'd0), //input wreb
.ada(ram_rd_addr), //input [7:0] ada
.dina(ram_wr_data), //input [7:0] dina
.adb(ram_wr_data), //input [7:0] adb
.dinb(ram_wr_data) //input [7:0] dinb
);
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 - 7 行:定义数据的地址和输出端口,主要用于在线逻辑仿真使用。
- 第 10 行:这是定义一个8位宽的信号,用于写入RAM 的数据,由于定义的寄存器没有用,这里我就直接用来绑定 B 端口数据、地址等,因为我把 A 和 B 端的写使能给关了,所以无法写入数据,只能读我们存进去的数据。
- 第 12 - 19 行:用于读取我们存储的信息,一共256个数据。
- 第 22 - 39 行:这里对数据进行绑定。
7、仿真编写
`timescale 10ns/10ns
module doule_ram_mod(); // 定义模块 rom_mod
//reg define
reg sys_clk; // 系统时钟信号
reg sys_rst_n; // 系统复位信号,低有效
//wire define
wire [7:0] ram_rd_addr; //ram 读地址
wire [7:0] ram_rd_data; //ram 读出数据
always #10 sys_clk = ~sys_clk; // 每 10ns 反转一次时钟信号
GSR GSR(.GSRI(1'b1)); //在对 FIFO 或者 SP IP 核等(或其它在底层转换 GSR 时调用了全局复位的 IP/原语)进行仿真时,
//在仿真代码中需要添加如下这一段代码,否则联合仿真时就会报错。
initial begin
sys_clk = 1'b0; // 初始化时钟为低
sys_rst_n = 1'b0; // 初始化复位信号为低电平(表示复位状态)
#200;
sys_rst_n = 1'b1; // 开始执行
#1000;
end
doubule_ram u_doubule_ram(
.sys_clk(sys_clk) , //时钟信号
.sys_rst_n(sys_rst_n) , //复位信号,低电平有效
.ram_rd_addr(ram_rd_addr), //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
程序注释:
- 第 9 - 11 行:定义我们 RAM 的读的地址和数据,这个主要用数据观察需要。
- 第 17 - 24 行:初始一下我们的系统时钟和复位.
- 第 26 - 32 行:将模块进行例化,绑定引脚。
接下来打开 Modelsim 软件对代码进行仿真,需要添加文件如下图所示:
在运行仿真一段时间后,需要将波形放大,仿真的波形如下图所示:
注:我这里添加的时候,只添加的 ram_rd_data 端口进行仿真,其余数据我们这里用不到,而截图中添加了两个没有仿真波形,主要是将 ram_rd_data 波形往下排好观察波形。可通过在信号 ram_rd_data 右键依次选择 Format->Analog(automatic)。
由上图可知,说明仿真和我们想的一致,说明仿真没有问题。
8、I/O引脚绑定
在仿真验证完成后,接下来使用 Gowin 对时钟约束和引脚进行分配并上板验证。本实验中,系统时钟、和复位按键(其余端口仅仅只是为了仿真使用,可以不用绑定)管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | F10 | 复位 | LVCMOS33 |
Gowin 软件中 I/O Constraints 界面如下图所示:
9、在线逻辑分析仪
这里需要创建两个触发信号或者那么可以都创建在一个 Trigger Port 0 中,我这里选择读数据和读地址两个信号。如下图所示:
注:不要忘记了时钟约束哦~
然后点击综合,直到没错,就点击 进入在线逻辑分析仪界面,下载程序,然后点击 运行,抓取波形。观察ram_rd_data[7:0] 数据显示周期性变化,高云的在线逻辑分析仪也可以将数据转换成模拟量进行分析的功能,右击需要观察的信号,选中 Format->Unsigned Line Chart,然后点击 ,观察完整波形,如下图所示。
根据上图的波形观察可以看出和我们仿真一样,也是一个正弦波现状,感兴趣小伙伴们可以和我们导入的数据进行对比看看是否一致。