小梅哥Xilinx FPGA学习笔记21——IP核之RAM实验

发布时间:2024年01月05日

目录

一: 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 简介

1.1 存储器的分类

在了解 RAM IP 核之前,我们先来看下存储器的大致分类,如下图所示:

? ? ? ? ?? ??

?????? 由上图可知,存储器包括随机存储器和只读存储器,随机存储器包括静态 RAM 动态 RAM 。静态 RAM只要有供电,它保存的数据就不会丢失;而动态 RAM 在供电的情况下,还需要根据其要求的时间来对存储 的数据进行刷新,才能保持存储的数据不会丢失。
?????? 静态 RAM 一般包括 单端口 RAM 简单双端口RAM 真双端口 RAM。 静态 RAM 的特点是存储容量相对不是很大,但是读写速度非常高,
?????? 动态 RAM 一般包括 SDRAM DDR SDRAM 。目前 DDR SDRAM 已经从 DDR1 代发展到 DDR5 代了, DDR3 DDR4 SDRAM 是目前非常主流的存储器, 其特 点是存储容量非常大、但是读写速度相比于静态 RAM 会稍低一些
?????? 只读存储器 一般包括 PROM EPROM EEPROM 等,是非易失性的存储器。目前使用率较高的是 EEPROM ,其特点是容量相对较小,存储的一般是器件的配置参数信息,
本次我们学习的 RAM 属于 静态 RAM ,我们重点看下几种静态 RAM 的特性与区别:

不同的特性决定不同的应用场景,在 RAM 的实际应用中,我们一般根据功能需求和带宽需求来选择合适的 RAM 类型,

二: 单端口ram配置

????????Vivado 软件自带的 Block Memory Generator IP ,可以用来配置生成 RAM 或者 ROM RAM 是一种随机存取存储器,不仅可以读出存储的数据,同时还支持对存储 的数据进行修改,而 ROM 是一种只读存储器,也就是说,在工作时只能读出数据,而不能写入数据。需要 注意的是,配置生成的 RAM 或者 ROM 使用的都是 FPGA 内部的 BRAM 资源( Block RAM ,即块随机存 储器,是 FPGA 厂商在逻辑资源之外,给 FPGA 加入的专用 RAM 块资源),只不过配置成 ROM 时只用到 了嵌入式 BRAM 的读数据端口。本章我们主要介绍如何将 BMG IP 核配置成 RAM

2.1 单端口 RAM 的框图

各个端口的功能描述如下:

2.2 RAM IP 核配置

关于具体每一张图中的具体选项的含义可详见正点原子的《领航者ZYNQ 之 FPGA 开发指南》P542

本次实验写入32个数据所以深度为32,数据位宽为8

?

至此,ramIP核配置成功。生成ramIP核文件。

2.3 RAM 读写模块设计

本次实验任务是在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

2.4 顶层模块设计

设计代码

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

2.5 仿真测试文件代码

`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

2.6 仿真结果

仿真通过

三:伪双端口配置(小梅哥)

关于具体每一张图中的具体选项的含义可详见小梅哥的《基于HDL的FPGA逻辑设计与验证教程》P314

3.1 伪双端口框图

与单端口 RAM 不同的是,伪双端口 RAM 输入有两路时钟信号 CLKA/CLKB;独立的两组地址信号ADDRA/ADDRB;Port A 仅提供 DINA 写数据总线,作为数据的写入口;Port B 仅提供数据读的功能,读出的数据为 DOUTB。 关于各个引脚说明可参考小梅哥或者正点原子文档教程。

3.2详细配置流程图

选择 Block Memory Generator 双击鼠标进入到 RAM IP 配置界面。

端口类型的选择,Xilinx 的很多 IP 一般都有提供两种接口,一种是常规接口,一种是 AXI 接口,这里选择选择常规接口 Native

这里我们选择简单双端口 RAMSimple Dual Port RAM

ECC 全称是 Error Correction Capability,是在简单双端口 RAM 类型下的一种纠错功能,具体该功能的详细说明,可以查看 IP 手册,这里选择 NO ECC

写数据字节使能,如果勾选,写使能信号会根据写数据的字节数生成对应的 bit 数据,1 个字节对应 1bit 写使能,这里字节的大小可以设置为 8 9,当这里选择后,输入输出的数据的位宽就必须是 8 9 的整数倍,这里我们需要一个位宽为 8bit RAM,这里勾选 Write Enable 并设置字节大小为 8bit

这里我们保持默认的最小面积选项即可。

RAM 数据位宽和深度设置,这个根据实际应用需求进行设置,这里设置数据位宽 8bit,深度 256

这里选择NO Change(其他选项的具体说明可参考文档教程)

端口使能信号类型设置,一个是一直使能,一个是通过一个 ENA 信号管脚控制,这里选择 Always Enable

由于我们前面选择的是简单双端口 RAM,对于端口 A 只能进行数据的写入,没有数据的输出,所有关于端口 A 的数据输出的相关配置是不可配置的。

端口 B 数据位宽和内存深度的设置,这里设置位宽为8,深度会自动根据你选择的位宽进行设置。

端口 B 操作模式不可设置,由于在简单双端口 RAM 下端口 B 只能进行读 操作,不能进行写操作,所以这里不可设置,在真双单口 RAM 下,这里是可进 行设置的。端口使能就设置为 Always Enable ,让端口 B 一直使能。
??????? ??端口 B 输出寄存器配置,这里可以看下 RAM 内部结构图,可以很清楚的看到 Primitives Output Register 是结构中的 1 处的寄存器, Core Output Register 是结 构图中 2 出的寄存器。 REGCEB Pin 是寄存器使能管脚,如果勾选,会有一个寄 存器使能控制管脚用于控制寄存器的使能,如果不勾选寄存器就一直使能状态, 这里就不勾选。要得到更好的性能,将这里的两个寄存器都勾选。

端口 B 输出置位/复位设置,这里不创建置位/复位端口,需注意这里置位/复位并不复位 RAM 中的数据而是只复位寄存器上的值。

其他设置,这里不对 RAM 进行初始化, 关于仿真设置就保持默认即可。

3.2 激励文件设计代码

?????? 为了测试简单双端口 RAM ,可以通过实际写入一些数据再读取部分数据的 方式来验证双端口 RAM 读写是否正常。添加并新建 tb 文件命名为 ram_tb.v 。编 tb 代码,具体 tb 代码实现的是在地址从 0~16 上写入数据为从 255 减至 240 延时一段时间后读地址为 0~16 上的数据。
`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

3.3 仿真结果

至此伪双端口RAM配置以及设计仿真完毕。

四:伪双端口配置(正点原子)

由于伪双端口配置较为常用,所以再次再次重复编写一下伪双端口实例。
详细配置方案可参考正点原子文档教程《 领航者ZYNQ 之 FPGA 开发指南》P564。
本节任务是在0~63个地址依次写入0~63个数,然后在写到一半数据(32个数据)时就开始从地址0~63依次读数据

4.1 RAM 写模块设计

模块框图:

模块代码

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

4.2 RAM 读模块设计

模块框图:
模块代码
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

4.3 顶层文件设计

代码设计
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

4.4 仿真文件

`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

4.5 仿真结果

至此RAM IP核配置以及仿真验证已经结束。
下一章节使用RAM实现串口收发与存储双口 RAM 简易应用系统 (将会使模块化设计应用到极致)。
文章来源:https://blog.csdn.net/m0_51430584/article/details/135407183
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。