目录
一: RAM 简介
1.1 存储器的分类
二: 单端口ram配置
2.1 单端口 RAM 的框图
2.2 RAM IP 核配置
2.3 RAM 读写模块设计
2.4 顶层模块设计
2.5 仿真测试文件代码
2.6 仿真结果
三:伪双端口配置(小梅哥)
3.1 伪双端口框图
3.2详细配置流程图
3.2 激励文件设计代码
3.3 仿真结果
四:伪双端口配置(正点原子)
4.1 RAM 写模块设计
4.2 RAM 读模块设计
4.3 顶层文件设计
4.4 仿真文件
4.5 仿真结果
在了解 RAM IP 核之前,我们先来看下存储器的大致分类,如下图所示:
关于具体每一张图中的具体选项的含义可详见正点原子的《领航者ZYNQ 之 FPGA 开发指南》P542
本次实验写入32个数据所以深度为32,数据位宽为8
至此,ramIP核配置成功。生成ramIP核文件。
本次实验任务是在1~31个地址中写入1~31个数,然后再在1~31个地址中读取这些数据。
读写波形图如下图所示
RAM 读写模块设计代码(代码的输入是RAMIP核的输出,代码的输出是RAMIP核的输入)
module ram_rw(
input clk, //系统时钟,50MHz
input reset_n, //系统复位按键,低电平有效
input [7 : 0] ram_rd_data, //ram读数据
output reg ram_en, //ram端口使能信号,高有效
output wire ram_we, //ram读写使能信号,1为写,0为读
output reg [4 : 0] ram_addr, //ram读写地址
output reg [7 : 0] ram_wr_data //ram写数据
);
reg [5:0] rw_cnt ; //读写控制计数器
always @(posedge clk or negedge reset_n)
if(!reset_n)
ram_en <= 0;
else
ram_en <= 1;
assign ram_we = (rw_cnt <= 6'd31 && ram_en == 1'b1) ? 1'b1 : 1'b0;//组合逻辑实现会和ram_en同步
//时序逻辑am_we实现会使得其不在ram_en信号拉高的同时立马拉高,会晚半拍
/*always @(posedge clk or negedge reset_n)
if(!reset_n)
ram_we <= 0;
else if(ram_en && rw_cnt <= 31)
ram_we <= 1;
else
ram_we <= 0;
*/
//读写控制计数器,计数器范围 0~63
always @(posedge clk or negedge reset_n)
if(!reset_n)
rw_cnt <= 0;
else if(ram_en)begin
if(rw_cnt >= 63 )
rw_cnt <= 0;
else
rw_cnt <= rw_cnt + 1;
end
else
rw_cnt <= 0;
//读写地址信号 范围:0~31
always @(posedge clk or negedge reset_n) begin
if(!reset_n)
ram_addr <= 0;
else if(ram_addr == 5'd31 && ram_en)
ram_addr <= 5'b0;
else if (ram_en)
ram_addr <= ram_addr + 1'b1;
else
ram_addr <= 5'b0;
end
//在 WE 拉高期间产生 RAM 写数据,变化范围是 0~31
always @(posedge clk or negedge reset_n) begin
if(!reset_n)
ram_wr_data <= 8'b0;
else if(ram_wr_data < 8'd31 && ram_we)
ram_wr_data <= ram_wr_data + 1'b1;
else
ram_wr_data <= 8'b0 ;
end
endmodule
设计代码
module zdyz_ip_ram(
input clk,
input reset_n
);
wire ram_en;
wire ram_we;
wire [4 : 0] ram_addr;
wire [7 : 0] ram_wr_data ;
wire [7 : 0] ram_rd_data;
//例化ram 读写模块
ram_rw ram_rw(
. clk(clk), //系统时钟,50MHz
. reset_n(reset_n), //系统复位按键,低电平有效
. ram_rd_data(ram_rd_data), //ram读数据
. ram_en(ram_en), //ram端口使能信号,高有效
. ram_we(ram_we), //ram读写使能信号,1为写,0为读
. ram_addr(ram_addr), //ram读写地址
. ram_wr_data(ram_wr_data) //ram写数据
);
blk_mem_gen_0 blk_mem_gen_0 (
.clka(clk), // input wire clka
.ena(ram_en), // input wire ena
.wea(ram_we), // input wire [0 : 0] wea
.addra(ram_addr), // input wire [4 : 0] addra
.dina(ram_wr_data), // input wire [7 : 0] dina
.douta(ram_rd_data) // output wire [7 : 0] douta
);
endmodule
`timescale 1ns / 1ps
module zdyz_ip_ram_tb();
reg clk;
reg reset_n;
initial begin
clk = 1'b0;
reset_n = 1'b0;
#200
reset_n = 1'b1;
end
//产生时钟
always #20 clk = ~clk;
zdyz_ip_ram zdyz_ip_ram(
.clk(clk),
.reset_n(reset_n)
);
endmodule
仿真通过
关于具体每一张图中的具体选项的含义可详见小梅哥的《基于HDL的FPGA逻辑设计与验证教程》P314
选择 Block Memory Generator 双击鼠标进入到 RAM IP 配置界面。
这里我们选择简单双端口 RAM(Simple Dual Port RAM)
这里选择NO Change(其他选项的具体说明可参考文档教程)
`timescale 1ns / 1ps
module xmg_ram_ip_tb();
reg clka ;
reg clkb ;
reg wea ;
reg [7 : 0] addra ;
reg [7 : 0] dina ;
reg [7 : 0] addrb ;
wire [7 : 0] doutb ;
integer i;//integer类型用于表示整数值。在FPGA设计中,integer类型通常用于计数器、延时器等电路中。作用:用于表示整数。
blk_mem_gen_0 blk_mem_gen_0 (
.clka(clka), // input wire clka
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [7 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.clkb(clkb), // input wire clkb
.addrb(addrb), // input wire [7 : 0] addrb
.doutb(doutb) // output wire [7 : 0] doutb
);
initial clka = 1;
always #10 clka = ~clka;
initial clkb = 1;
always #10 clkb = ~clkb;
initial begin
wea=0;
addra=0;
dina=0;
addrb=0; //255
#201;
wea = 1;
for (i=0;i<=15;i=i+1)begin
dina=255-i;//写入数据
addra = i;//选择地址
#20;
end
wea=0;
#1;
for (i=0;i<=15;i=i+1)begin
addrb=i;//读取相应地址的数据
#40;
end
#200;
$stop;
end
endmodule
模块框图:
模块代码
module zdyz_ram_wr(
input clk , //时钟信号
input reset_n , //复位信号,低电平有效
//RAM 写端口操作
output ram_wr_we , //ram 写使能
output reg ram_wr_en , //端口使能
output reg [5:0] ram_wr_addr , //ram 写地址
output [7:0] ram_wr_data ,//ram 写数据
output reg rd_flag //读启动信号
);
//ram_wr_we 为高电平表示写数据
assign ram_wr_we = ram_wr_en;
//写数据与写地址相同,因位宽不等,所以高位补 0
assign ram_wr_data = {2'b0,ram_wr_addr};
//控制 RAM 使能信号
always @(posedge clk or negedge reset_n)
if(!reset_n)
ram_wr_en <= 1'b0;
else
ram_wr_en <= 1'b1;
//写地址信号 范围:0~63
always @(posedge clk or negedge reset_n)
if(!reset_n)
ram_wr_addr <= 0;
else if(ram_wr_en && ram_wr_addr < 63)
ram_wr_addr <= ram_wr_addr + 1;
else
ram_wr_addr <= 0;
//当写入 32 个数据(0~31)后,拉高读启动信号
always @(posedge clk or negedge reset_n)
if(!reset_n)
rd_flag <= 0;
else if(ram_wr_addr == 31)
rd_flag <= 1;
else
rd_flag <= rd_flag;
endmodule
module zdyz_ram_rd(
input clk , //时钟信号
input reset_n , //复位信号,低电平有效
//RAM 读端口操作
input rd_flag , //读启动标志
input [7:0] ram_rd_data ,//ram 读数据
output wire ram_rd_en , //端口使能
output reg [5:0] ram_rd_addr //ram 读地址
);
assign ram_rd_en = rd_flag;
//读地址信号 范围:0~63
always @(posedge clk or negedge reset_n)
if(!reset_n)
ram_rd_addr <= 0;
else if(rd_flag && ram_rd_addr < 63)
ram_rd_addr <= ram_rd_addr + 1;
else
ram_rd_addr <= 0;
endmodule
module zdyz_ip_2port_ram(
input clk , //系统时钟
input reset_n //系统复位,低电平有效
);
wire ram_wr_we;
wire ram_wr_en;
wire [5:0]ram_wr_addr; //ram 写地址
wire [7:0]ram_wr_data; //ram 写数据
wire [5:0]ram_rd_addr; //ram 读地址
wire [7:0]ram_rd_data; //ram 读数据
wire rd_flag; //读启动标志
wire ram_rd_en;
//RAM 写模块例化
zdyz_ram_wr zdyz_ram_wr(
.clk (clk) , //时钟信号
.reset_n (reset_n) , //复位信号,低电平有效
.ram_wr_we (ram_wr_we) , //ram 写使能
.ram_wr_en (ram_wr_en) , //端口使能
.ram_wr_addr (ram_wr_addr) , //ram 写地址
.ram_wr_data (ram_wr_data) , //ram 写数据
.rd_flag (rd_flag) //读启动信号
);
//RAM 读模块例化
zdyz_ram_rd zdyz_ram_rd(
.clk (clk) ,//时钟信号
.reset_n (reset_n) ,//复位信号,低电平有效
.rd_flag (rd_flag) ,//读启动标志
.ram_rd_data (ram_rd_data) ,//ram 读数据
.ram_rd_en (ram_rd_en) ,//端口使能
.ram_rd_addr (ram_rd_addr) //ram 读地址
);
//RAM IP核例化
zdyz_blk_mem_gen_2 your_instance_name (
.clka(clk), // input wire clka
.ena(ram_wr_en), // input wire ena
.wea(ram_wr_we), // input wire [0 : 0] wea
.addra(ram_wr_addr), // input wire [5 : 0] addra
.dina(ram_wr_data), // input wire [7 : 0] dina
.clkb(clk), // input wire clkb
.enb(ram_rd_en), // input wire enb
.addrb(ram_rd_addr), // input wire [5 : 0] addrb
.doutb(ram_rd_data) // output wire [7 : 0] doutb
);
endmodule
`timescale 1ns / 1ps
module zdyz_ip_2port_ram_tb();
//parameter define
parameter CLK_PERIOD = 20; //时钟周期 20ns
//reg define
reg clk;
reg reset_n;
//信号初始化
initial begin
clk = 1'b0;
reset_n = 1'b0;
#200
reset_n = 1'b1;
end
//产生时钟
always #(CLK_PERIOD/2) clk = ~clk;
zdyz_ip_2port_ram zdyz_ip_2port_ram(
.clk (clk ),
.reset_n(reset_n)
);
endmodule