FPGA知识基础之--存储器知识点总结以及基于ip核的简单双端口RAM的实现和仿真(附RTL代码和Testbench代码)

目录


前言

笔者在最近的存储器学习时,遇到了一些问题,为此笔者用本篇博客来记录一下有关存储器的一些知识点


一、存储器的分类

在FPGA中,存储器主要分为两类。RAM和ROM,网上对其的分类有很多,比较杂乱,为此笔者做了一张mindmap,希望能使得知识框架更为清晰

笔者主要总结了关于一些主流存储器的功能以及基本功能介绍

二、实验任务

本次笔者将设计一个深度为64,宽度为6的伪双向端口RAM

三 、简单(伪)双端口

关于这方面的介绍,笔者必要参考了xlinx的官方文档pg058,在此笔者给出自己的一些学习经验

除去纠错端口后,其余的端口与单端口一样,为此读者不做过多阐述

重难点!

关于三种工作模式

1、Write First Mode

笔者将xlinx文档资料中关于写优先这一方面的时序找出来了,应该还是很好理解的

写优先即为写入的数据同时放入地址和输出,而原先地址的数据则被覆盖,例如 bb,cc,里面的内容被覆盖为了1111,2222

2、Read First Mode

读优先则会将地址上原先的数据给输出,然后将写入数据放到地址当中。

3、NO Change

这个模式则不管输入的数据,输出数据一直为写使能信号来之前的前一个信号对应的地址

希望读者能理解这三种模式,本次实验我们采取第三种No change模式

四、程序设计

4.1 模块

本次实验需要用到三个模块,读模块,写模块,以及xlinx自带的RAMip模块

三个模块之间的关系如图

4.2 时序分析

需要注意的是,笔者引入了一个flag变量,来消除读写冲突的问题

4.3 RTL代码

ram_wr 写模块

c 复制代码
module ram_wr (
input                        clk,
input                        rst_n,


output       reg             ram_wr_en,
output                       ram_wr_we,
output       [7:0]           ram_wr_data, //ram深度8
output      reg [5:0]        ram_wr_addr,  //ram宽度6
output       reg       [5:0] flag
);



assign     ram_wr_we  =  ram_wr_en; //这是写模块,使能信号拉高之后,即进入写模式

assign ram_wr_data = {2'b0,ram_wr_addr} ;

always @(posedge clk or negedge rst_n) begin  //使能信号拉高
    if(!rst_n)
        ram_wr_en <= 1'b0;
    else
        ram_wr_en <= 1'b1;
end

always @(posedge clk or negedge rst_n) begin  //为ram_wr_addr写入数据
    if(!rst_n)
        ram_wr_addr <= 6'b0;
    else if(ram_wr_en && ram_wr_addr < 6'd63)
        ram_wr_addr <= ram_wr_addr + 1;
    else 
        ram_wr_addr <= 6'b0; 
end

always @(posedge clk or negedge rst_n) begin   //为了避免读写冲突,ram_wr_addr到31之后,进入读模式
    if(!rst_n)
        flag <= 6'd0;
    else if(ram_wr_addr == 6'd31)
        flag <=  6'd1;
    else 
        flag <=  flag;
    
end



    
endmodule

2.ram_rd 写模块

c 复制代码
module ram_rd(
input                   clk,
input                   rst_n,
input    [7:0]          ram_rd_data,
input                   flag,


output                  ram_rd_en,
output    reg [5:0]     ram_rd_addr
);

assign ram_rd_en = flag;  //使能信号和flag信号同步

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        ram_rd_addr <= 6'd0;
    else if(ram_rd_en && ram_rd_addr <= 6'd63)
        ram_rd_addr <= ram_rd_addr + 1; // 地址赋值,在0~63之间循环
    else
        ram_rd_addr <= 0;

end

endmodule

3.top模块

c 复制代码
module ip_2port_ram(
input       sys_clk,
input       sys_rst_n   
);

wire                        ram_wr_en;
wire                        ram_wr_we;
wire          [7:0]         ram_wr_data; 
wire          [5:0]         ram_wr_addr;


wire          [7:0]         ram_rd_data;
wire                        ram_rd_en;
wire          [5:0]         ram_rd_addr;
wire                        flag;

ram_wr  u_ram_wr(
   .clk                 (sys_clk),       
   .rst_n               (sys_rst_n),
   .ram_wr_en           (ram_wr_en),
   .ram_wr_we           (ram_wr_we),
   .ram_wr_data         (ram_wr_data),
   .ram_wr_addr         (ram_wr_addr),
   .flag                (flag)
);

ram_rd   u_ram_rd(
        .clk            (sys_clk),
        .rst_n          (sys_rst_n),
        .ram_rd_data    (ram_rd_data),
        .flag           (flag),
        .ram_rd_en      (ram_rd_en),
        .ram_rd_addr    (ram_rd_addr)
);



blk_mem_gen_0 u_blk_mem_gen_0 (
  .clka                 (sys_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                 (sys_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

在此读者做一下说明,为了使用xlinx自带的RAMip核模块,我们需要提前做一点设置

在vivado的ip库中搜索ram,找到block memory generator,并进行如下设置


其余保持默认

设置好ip核后,可找到xlinx提供的例化模板,根据自己的模块名代入即可

五、仿真

Testbench代码

c 复制代码
`timescale	1ns/1ns //仿真的单位/仿真的精度s
module tb_ip_2port_ram();

parameter CLK_PERIOD = 20;

reg 		sys_clk;  //周期20ns
reg 		sys_rst_n; 


initial begin
	sys_clk <= 1'b0;
	sys_rst_n <=1'b0;
	#200 
	sys_rst_n <= 1'b1;
end

always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
	


 
ip_2port_ram   u_ip_2port_ram (
    .sys_clk     (sys_clk),
	.sys_rst_n   (sys_rst_n) 
 );
 
 
 
 endmodule
 
 

模块仅有两个激励,所以仿真代码还是很好写的

vivado和modelsim联合仿真波形

代码与时序验证成功,结果正确


相关推荐
骁的小小站26 分钟前
Verilator 和 GTKwave联合仿真
开发语言·c++·经验分享·笔记·学习·fpga开发
知识充实人生33 分钟前
时序收敛方法一:控制集优化
stm32·单片机·fpga开发
游戏开发爱好者82 小时前
HTTPS 内容抓取实战 能抓到什么、怎么抓、不可解密时如何定位(面向开发与 iOS 真机排查)
android·网络协议·ios·小程序·https·uni-app·iphone
Black蜡笔小新6 小时前
视频汇聚平台EasyCVR级联播放偶发失败排查:TCP主动模式下的3秒超时响应差
网络·tcp/ip·音视频
Fms_Sa6 小时前
UDP的一次双向通信
网络·网络协议·udp
FPGA_ADDA7 小时前
小尺寸13*13cmRFSOC47DR数模混合信号处理卡
fpga开发·信号处理·射频采集·rfsoc·高速adda·8发8收
keer_zu8 小时前
一个Modbus-TCP传感器数据传输方案
网络·网络协议·tcp/ip
TiAmo8088 小时前
ip生产区
服务器·网络·tcp/ip
代码哈士奇14 小时前
使用仓颉开发一个简单的http服务
网络·网络协议·http·仓颉
Jayyih14 小时前
OSI七层模型和TCP/IP四层模型
网络·tcp/ip·php