今天的主线任务就是了解一下ROM和RAM相关IP核的使用
1、创建工程
在创建工程的时候,要选取下面这个信息
Series (系列) 芯片的产品系列,决定架构和性能等级。 GW5A(高云5A系列)
Device (器件) 系列下的具体型号,决定逻辑资源规模。 GW5A-25
Speed (速度等级) 芯片的性能等级,等级越高,时序性能越好。 ES(工程样片)
2、地址宽度和数据宽度区别
数据宽度 (Data Width) 每个存储单元存放的数据的位数(即ROM每个地址输出的数据位宽) 决定了你一次能读出多少位数据
地址宽度 (Address Depth) 存储单元的数量(寻址范围) 决定了你的ROM能存多少个数据
举个例子:
假设你要用ROM存储一个简单的4色调色板,颜色值为:3’b000(黑), 3’b001(红), 3’b010(绿), 3’b011(蓝)。
分析:每个颜色值有 3位。共有 4个 颜色值。
选择参数:
数据宽度 (Data Width):每个颜色值是3位,所以设为 3。
地址深度 (Address Depth):有4个颜色值,所以至少需要4个地址。2^2=4,所以地址宽度设为 2 位刚好满足。如果你想预留空间,可以设更大(如3位,地址深度8)。
图片中显示的ROM16符号确实只有 addr[0..0](1位地址线),但这只是因为您当前配置的 地址深度(Address Depth)只有2(只需要1位地址就能索引2个位置:0和1)。
核心原理:地址宽度是动态的
ROM16模块的地址端口宽度不是固定的,它会根据您在配置界面中设置的 “Address Depth” 的值自动调整!
存储器总容量 (bits) = 数据宽度 (bits) × 地址深度 (寻址单元数)
地址深度 = 2 ^ (地址宽度)
sine为正弦波的意思
3、PROM的输入输出端口
PROM端口作用:ad输入地址,clk同步时钟,oce控制输出更新,ce使能芯片,reset复位,dout输出数据。
首先来看一下oce端口的作用:只有oce使能了,才能更新得输出数据。只有 oce信号使能(为高电平 1)时,ROM 的输出数据 dout才会在时钟上升沿被更新。
①当oce为高电平(1)时,允许ROM在时钟沿更新输出数据,新数据覆盖原值,dout随地址变化。
②当oce为低电平(0)时,ROM的输出数据被冻结并保持最后一个值,内部逻辑继续正常工作。
PROM(ROM)的 bypass模式 和 pipeline模式:
bypass模式 和 pipeline模式 的核心区别在于数据输出与时钟的时序关系。
Bypass模式(旁路模式):数据输出无时钟延迟,地址变化后数据立即更新(异步读取)。
Pipeline模式(流水线/寄存器模式):数据输出有1个时钟周期的延迟,在时钟上升沿锁存输出(同步读取)。
ROM时序波形图(Bypass模式):
Bypass模式:clk上升沿读取地址,下降沿读出数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| +-----------------------------------------------------------------------+ | | | CLK: __|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__ | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | | | | CE: ________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ | | | | AD: [ ad_0 ] [ ad_1 ] [ ad_2 ] [ ad_3 ] | | | | DO: [MEM(ad_0)] [MEM(ad_1)] [MEM(ad_2)] [MEM(ad_3)] | | | | | | 特征:DO 随 AD 变化立即更新,无时钟延迟 | +-----------------------------------------------------------------------+
|
Pipeline模式:上升沿读取地址,下个下降沿读取数据,慢一拍
ROM时序波形图(Pipeline模式):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| +-----------------------------------------------------------------------+ | | | CLK: __|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__ | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | | | | CE: ________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ | | | | AD: [ ad_0 ] [ ad_1 ] [ ad_2 ] [ ad_3 ] | | | | DO: ________ [MEM(ad_0)] [MEM(ad_1)] [MEM(ad_2)] | | | | | | 特征:DO 较 AD 延迟1个时钟周期更新 | +-----------------------------------------------------------------------+
|
模式选择建议:
1、追求性能和数据稳定性:选择 Pipeline模式。这是最常用的模式,能保证数据在稳定时被捕获,避免系统因异步读取而产生冒险。
2、需要立即获取数据:仅在极少数对延迟极其敏感且能处理潜在毛刺的场景下,考虑 Bypass模式。
复 位 模 式 配 置 , 支 持 同 步 复 位 (Synchronous) 和 异 步 复 位(Asynchronous),reset 信号复位锁存器和输出寄存器,因此当设置reset 信号有效时,不管用户使用的是寄存器输出模式还是旁路输出模式,端口都输出 0。同步复位有效时,DO 在 CLK 上升沿复位为 0,异步复位有效时,DO 随之复位为 0,不需要等到 CLK 上升沿。
配置初始值,初始值以二进制、十六进制或带地址十六进制的格式写在初始化文件中。“Memory Initialization File”选取的初始化文件可通过手动或者在IDE菜单栏中依次点击“File->New->Memory Initialization File”获取。
4、PROM使用代码
直接使用Gowin_pROM进行实例化即可
IP核代码
1 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
| module Gowin_pROM (dout, clk, oce, ce, reset, ad);
output [7:0] dout; input clk; input oce; input ce; input reset; input [7:0] ad;
wire [23:0] prom_inst_0_dout_w; wire gw_gnd;
assign gw_gnd = 1'b0;
pROM prom_inst_0 ( .DO({prom_inst_0_dout_w[23:0],dout[7:0]}), .CLK(clk), .OCE(oce), .CE(ce), .RESET(reset), .AD({gw_gnd,gw_gnd,gw_gnd,ad[7:0],gw_gnd,gw_gnd,gw_gnd}) );
defparam prom_inst_0.READ_MODE = 1'b0; defparam prom_inst_0.BIT_WIDTH = 8; defparam prom_inst_0.RESET_MODE = "SYNC"; defparam prom_inst_0.INIT_RAM_00 = 256'hD7D5D3D0CECBC9C6C4C1BEBCB9B6B3B0ADAAA7A5A29E9B9895928F8C89868380; defparam prom_inst_0.INIT_RAM_01 = 256'hFFFFFFFEFEFEFDFDFCFBFAFAF9F8F6F5F4F3F1F0EEEDEBEAE8E6E4E2E0DEDCDA; defparam prom_inst_0.INIT_RAM_02 = 256'hDCDEE0E2E4E6E8EAEBEDEEF0F1F3F4F5F6F8F9FAFAFBFCFDFDFEFEFEFFFFFFFF; defparam prom_inst_0.INIT_RAM_03 = 256'h8386898C8F9295989B9EA2A5A7AAADB0B3B6B9BCBEC1C4C6C9CBCED0D3D5D7DA; defparam prom_inst_0.INIT_RAM_04 = 256'h282A2C2F313436393B3E414346494C4F5255585A5D6164676A6D707376797C80; defparam prom_inst_0.INIT_RAM_05 = 256'h0000000101010202030405050607090A0B0C0E0F1112141517191B1D1F212325; defparam prom_inst_0.INIT_RAM_06 = 256'h23211F1D1B1917151412110F0E0C0B0A09070605050403020201010100000000; defparam prom_inst_0.INIT_RAM_07 = 256'h7C797673706D6A6764615D5A5855524F4C494643413E3B393634312F2C2A2825;
endmodule
|
实例化代码:
1 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
| module rom_top( clk, reset_n, dout ); input clk; input reset_n; output wire [7:0]dout; reg [9:0]addr; always@(posedge clk or negedge reset_n) if(!reset_n) addr<=0; else addr<=addr+1'b1; Gowin_pROM Gowin_pROM( .dout(dout), .clk(clk), .oce(1'b1), .ce(1'b1), .reset(~reset_n), .ad(addr) ); endmodule
|
5、RAM代码
具有两个地址,写地址WAD 和读地址 RAD,这两个地址端口是异步的。WRE 为高电平时进行写操作,此时会在 CLK 的上升沿将数据加载到存储器对应写地址。读操作则由读地址确
定输出 RAM 对应位置的数据,其时序波形图如下所示。:

上面这个图片,时钟沿一来,同时写入地址和读数据
1 2 3 4 5 6 7 8 9 10
|
Gowin_RAM16SDP your_instance_name( .dout(dout_o), .wre(wre_i), .wad(wad_i), .di(di_i), .rad(rad_i), .clk(clk_i) );
|
测试文件:
具体 tb代码实现的是在地址从 016上写入数据为从 255 减至 240。延时一段时间后读地址为 016 上的数据。
1 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 57 58 59 60 61 62 63 64 65 66 67
| `timescale 1ns/1ns `define CLK_PERIOD 20
module ram_tb(); reg clk; reg wea; reg [7:0] addra; reg [7:0] dina; reg [7:0] addrb; wire [7:0] doutb; integer i;
GSR GSR(.GSRI(1'b1));
Gowin_RAM16SDP your_instance_name( .dout(doutb), .wre(wea), .wad(addra), .di(dina), .rad(addrb), .clk(clk) );
initial clk = 1'b1; always #(`CLK_PERIOD/2) clk = ~clk;
initial begin wea = 0; addra = 0; dina = 0; addrb = 255;
#(`CLK_PERIOD*10 + 1);
wea = 1; for (i = 0; i <= 15; i = i + 1) begin dina = 255 - i; addra = i; #`CLK_PERIOD; end wea = 0; #1;
for (i = 0; i <= 15; i = i + 1) begin addrb = i; #`CLK_PERIOD; end
#200; $stop; end endmodule
|