FPGA学习笔记——RAM

笔记根据正点原子官方教学视频第19讲

时间:2025/4/2

【第一期】手把手教你学领航者&启明星ZYNQ之FPGA开发篇【真人出镜】FPGA教学视频教程_哔哩哔哩_bilibili

 有错误的地方希望可以告诉博主

前言


RAM 介绍 

        RAM(Random Access Memory ):即随机访问存储器,它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。RAM主要用来存放程序及程序执行过程中产生的中间数据、运算结果等。

存储器分类 

​   

特点RAMROM
可写性可以随时写入数据通常只读,某些类型可以写入(如 EEPROM)
易失性易失性,断电后数据丢失非易失性,断电后数据不丢失
访问速度读写速度快读取速度较慢
用途用于存储临时数据和正在运行的程序用于存储固化的程序代码和配置数据

        静态 RAM 和 动态 RAM 的区别如下:

特点DRAMSRAM
刷新需求需要定期刷新(Refresh)不需要刷新
访问速度较慢(需要刷新)较快(不需要刷新)
存储密度高(单位面积存储容量大)低(单位面积存储容量小)
成本低(单位存储成本低)高(单位存储成本高)
功耗较高(刷新操作增加功耗)较低(不需要刷新)
应用场景主存储器(内存条)、显存等大容量存储需求高速缓存(Cache)、寄存器堆等高速存储需求

         

静态 RAM

​         单端口 RAM 在同一个端口进行读写操作。适用场景:没有同时读写需求。

        简单双端口 RAM 虽然有两个端口,但端口只能进行读操作,或者进行写操作。适用场景:有同时读写需求。

        真双端口 RAM 的两个端口都支持读写操作。适用场景:有多个同时读写需求。

单端口 RAM IP 核端口

​         

端口方向作用备注
DINAINPUT数据写入端口A 是端口序号,如果还有一个 DIN 端口,就叫DINB 了,以下端口同理。
ADDRAINPUT地址输入端口用于指定要访问的 RAM 地址。
WEAINPUT读写控制信号端口高电平 1 时,写;低电平 0 时,读。
ENAINPUT使能端口高电平 1 时,RAM 可用。
RSTAINPUT复位端口为高电平时,RAM 的内容被清零。
REGCEAINPUT寄存器使能端口用于控制输出寄存器的使能。低电平 0 时,DOUTA 输出固定值;高电平 1 时,输出输出寄存器中的数据。
CLKAINPUT时钟输入端口用于同步 RAM 的操作。
DOUTAOUTPUT数据输出端口用于从指定地址的存储单元读取数据。

简单(伪)双端口 RAM IP 核端口

         其中 INJECTSBITERR 和 INJECTDBITERR 以及 SBITERR 和 DBITERR 和ECC(纠错码)相关。

端口方向作用备注
DINAINPUT数据写入端口A 是端口序号,如果还有一个 DIN 端口,就叫DINB 了,以下端口同理。
ADDRAINPUT地址输入端口用于指定要访问的 RAM 地址。
WEAINPUT读写控制信号端口高电平 1 时,写;低电平 0 时,不写。
ENAINPUT使能端口高电平 1 时,RAM 可用。
CLKAINPUT时钟输入端口用于同步 RAM 的操作。
ADDRBINPUT地址输入端口用于指定要访问的 RAM 地址。
ENBINPUT使能端口高电平 1 时,RAM 可用。
RSTBINPUT
REGCEBINPUT寄存器使能端口用于控制输出寄存器的使能。低电平 0 时,DOUTA 输出固定值;高电平 1 时,输出输出寄存器中的数据。
CLKBINPUT时钟输入端口用于同步 RAM 的操作。

真双端口 RAM IP 核端口

         参考单端口 RAM IP 核端口。

动态 RAM

        后续补上。

RAM IP核

        Xilinx使用 “Block Memory Generator” IP 核来产生各种各样的 RAM 或 ROM。

基础设置

         Interface Type(接口类型):

可选项作用
Native(常规接口)
AXI4一般用于软核开发,通过软核来控制 RAM;此接口符合 AXI 协议,用于与 AXI 总线连接

        Memory Type(存储器类型):

可选项

Single Port RAM(单端口 RAM)

Simple Dual Port RAM(简单双端口 RAM)

True Dual Port RAM(真正双端口 RAM)

Single Port ROM(单端口 ROM)

Dual Port ROM(双端口 ROM)

        Generate address interface with 32 bits(生成 32 位地址接口):

        默认情况下,Block Memory Generator 生成的地址接口宽度是根据存储器的深度自动计算的。例如,如果存储器的深度为 64,地址宽度为 6 位(因为 2^6=64)。选中这个选项后,地址接口的宽度将固定为 32 位,无论存储器的实际深度是多少。

        Common Clock(公共时钟):这个选项用于配置存储器的时钟信号。选中:所有端口(例如读端口和写端口)共享同一个时钟信号。未选中:每个端口可以使用独立的时钟信号(适用于双端口存储器)。

        ECC Options(错误校正码选项):

可选项作用

Soft ECC(软件 ECC)

使用软核逻辑实现 ECC 功能。

BuiltIn ECC(内置 ECC)

使用 FPGA 内置的 ECC 功能。

No ECC(无 ECC)

不启用任何错误校正功能。

        Byte Write Enable:启用字节写使能。通过图示讲解:

         可以看到,这里固定在地址0000来写入数据,写入24位数据 DIN,其中 WE 是字节写使能,当 WE 为011时,可以看到 FF EE DD 只有低2字节被写入(通过 RAM Contents 查看)。后续同理。需要注意的是:启用该使能后 WE 的位宽会发生变化,具体变化后的位宽取决于一次写入数据的字节数(若是16位,则是2字节,WE 的位宽变为2)。具体来说,WE 控制的字节是从低位开始计算的,如18位写入数据,[7:0]为第一字节,[8:15]为第二字节。

        Algorithm Options(算法选项):

        Algorithm:选择存储器的算法:

可选项作用

Minimum Area

优化存储器的面积。
Maximum Performance优化存储器的性能。

Fixed Primitives(固定原语)

使用固定大小的原语(Primitive)来实现存储器。

        Primitive:选择存储器的原语。例如:8kx2的意思就是每个原语具有8192个存储单元,每个存储单元的宽度为2位(换句话说,可以用于存储 8k 个 2 位的数据)。

端口设置

        Memory Size(存储器大小)

可选项作用
Write Width(写宽度)定义存储器的写数据位宽
Read Width(读宽度)定义存储器的读数据位宽
Write Depth(写深度)存储器的总容量 = 数据位宽 × 地址深度
Read Depth(读深度)

        Operating Mode(操作模式)

        在做写操作的时候,读端口 DOUT 的数据如何变化。

可选项
Write First(先写模式)
Read First(先读模式)
No Change (不改变模式)

        写优先模式:输入数据同时写入内存并驱动数据输出。

        读优先模式:之前存储在写地址的数据会出现在数据输出端,同时输入数据被存储在内存中。

        不改变模式:输出锁存器在写操作期间保持不变。如图3-11所示,数据输出仍然是之前的读数据,并且不受同一端口写操作的影响。

        Enable Port Type(使能端口类型)

可选项作用
Use ENA Pin启动 EN 引脚来控制 RAM 使能
Always EnabledRAM 持续使能

        Port A Optional Output Registers(端口A可选输出寄存器)

可选项作用

Primitives Output Register(原语输出寄存器)

Core Output Register(核心输出寄存器)
SoftECC Input Register(软ECC输入寄存器)
REGCEA Pin(REGCEA引脚)

        Port A Output Reset Options(端口A输出复位选项)

        注意:这里的复位并非重置了 RAM 中所有地址的值。

可选项作用
RSTA Pin(复位引脚)是否使用复位引脚
Output Reset Value(Hex)(输出复位值)定义复位后输出引脚输出的值
Reset Memory Latch(复位存储锁存器)确定是否在复位相应端口的嵌入式基本输出寄存器的同时,也复位内存锁存
Reset Priority(复位优先级)定时钟使能和复位的优先级关系,即相应端口的时钟使能是否优先于复位,或者复位是否优先于时钟使能

         READ Address Change A(读地址变化A)

         当读取地址变化时,输出数据会立即更新为新地址对应的数据(无额外延迟)。若启用输出寄存器,地址变化后需等待时钟沿才能看到新数据。

        具体 IP 核参数使用请参照手册(Block Memory Generator v8.4 Product Guide (PG058) • 查看器 • AMD 技术信息门户网站) ,查看手册是必要的能力,这里不再对更多具体参数进行详细解释。

简单项目练手 

单端口 RAM 实验任务

        本节的实验任务是使用Vivado软件生成一个单端口的RAM并对其进行读写操作,然后通过仿真观察波形是否正确,最后通过在线调试工具对实验结果进行验证。如:将RAM设置的深度和宽度分别为32和8进行读写测试。

程序框图

        从上述单端口 RAM 的图示中可以看到(这个图示在手册中同样存在),单端口 RAM 一共有8个引脚,但并不是每一个引脚都是我们需要的。

时序

        ram_we:高电平写,低电平表示读。这里计数器的作用是方便向指定地址写入指定数据,如计数器为 1 时,向地址 1 写入数据 1.

代码

         RAM_RW 模块的代码如下:

module ram_rw(
    input                   clk,
    input                   rst_n,
    input        [7:0]      ram_rd_data,    // RAM 读出的数据
                 
    output  reg             ram_en,         // RAM 使能信号,高电平有效
    output                  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;
assign ram_we = (rw_cnt <= 6'd31 && ram_en == 1'b1) ? 1'b1 : 1'b0;

// 对 RAM 使能信号赋值,结束复位后拉高
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        ram_en <= 1'b0;
    else
        ram_en <= 1'b1;
end

// 对 rw_cnt 进行赋值
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        rw_cnt <= 6'b0;
    else if (ram_en)
        rw_cnt <= rw_cnt + 6'b1;
    else if (ram_en && rw_cnt == 6'd63)
        rw_cnt <= 6'b0;
    else if (!ram_en)  
        rw_cnt <= 6'b0;
    else
        rw_cnt <= rw_cnt;
end

// 对 RAM 地址进行赋值,变化范围是0-31
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        ram_addr <= 5'b0;
    else if (ram_en && ram_addr == 5'd31)
        ram_addr <= 5'b0;
    else if (ram_en)
        ram_addr <= ram_addr + 5'b1;
    else if (!ram_en)
        ram_addr <= 5'b0;
    else
        ram_addr <= ram_addr;
end

// 对 RAM 写数据进行赋值,变化范围是0-31
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        ram_wr_data <= 8'b0;
    else if (ram_we && ram_wr_data < 8'd31)
        ram_wr_data <= ram_wr_data +8'b1;
    else
        ram_wr_data <= 8'b0;
        
end
endmodule

        顶层模块的代码如下:

module ip_1port_ram(
    input   sys_clk,
    input   sys_rst_n
    );

wire    [7:0]  ram_rd_data ;
wire           ram_en      ;
wire           ram_we      ;
wire    [4:0]  ram_addr    ;
wire    [7:0]  ram_wr_data ;

ram_rw u_ram_rw(
    .clk            (sys_clk    ),
    .rst_n          (sys_rst_n  ),
    .ram_rd_data    (ram_rd_data),
    .ram_en         (ram_en),
    .ram_we         (ram_we),
    .ram_addr       (ram_addr),
    .ram_wr_data    (ram_wr_data)
    );

blk_mem_gen_0 your_instance_name (
  .clka     (sys_clk),   
  .ena      (ram_en),     
  .wea      (ram_we),     
  .addra    (ram_addr), 
  .dina     (ram_wr_data),   
  .douta    (ram_rd_data)  
);  

endmodule

双端口 RAM 实验任务

        本节的实验任务是使用Vivado软件生成一个简单双端口的RAM并对其进行读写操作,然后通过仿真观察波形是否正确,最后通过在线调试工具对实验结果进行验证。如:将RAM设置的深度和宽度分别为64和8进行读写测试。

程序框图

时序

        写模块时序如下:

        这里加入 rd_flag 是为了保证读模块在写入操作完成后才开始读数据,以确保读出有效数据。

        读模块时序如下:

代码

        读模块代码如下:

module ram_rd(
    input            clk        ,   //时钟信号
    input            rst_n      ,   //复位信号,低电平有效
                                    
    //RAM读端口操作                 
    input            rd_flag    ,   //读启动标志
    input      [7:0] ram_rd_data,   //ram读数据
    output           ram_rd_en  ,   //端口使能
    output reg [5:0] ram_rd_addr    //ram读地址       
);


//控制RAM使能信号
assign ram_rd_en = rd_flag;      

//读地址信号 范围:0~63        
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)        
        ram_rd_addr <= 6'd0;
    else if(ram_rd_addr < 6'd63 && ram_rd_en)
        ram_rd_addr <= ram_rd_addr + 1'b1;
    else
        ram_rd_addr <= 6'd0;
end

endmodule

        写模块代码如下:

module ip_2port_ram(
    input       sys_clk    ,  //系统时钟
    input       sys_rst_n     //系统复位,低电平有效
    );

//wire define
wire             ram_wr_en  ; //端口A使能
wire             ram_wr_we  ; //ram端口A写使能
wire             ram_rd_en  ; //端口B使能
wire             rd_flag    ; //读启动标志
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读数据
   

//RAM写模块
ram_wr u_ram_wr(
    .clk           (sys_clk    ),
    .rst_n         (sys_rst_n  ),

    .rd_flag       (rd_flag    ),
    .ram_wr_en     (ram_wr_en  ),
    .ram_wr_we     (ram_wr_we  ),
    .ram_wr_addr   (ram_wr_addr),
    .ram_wr_data   (ram_wr_data)
    );

//简单双端口RAM
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
);

//RAM读模块    
ram_rd u_ram_rd(
    .clk           (sys_clk    ),
    .rst_n         (sys_rst_n  ),

    .rd_flag       (rd_flag    ),
    .ram_rd_en     (ram_rd_en  ),
    .ram_rd_addr   (ram_rd_addr),
    .ram_rd_data   (ram_rd_data)
    ); 
   
ila_0 u_ila_0 (
    .clk    (sys_clk    ), // input wire clk
    
    .probe0 (ram_wr_en  ), // input wire [0:0]  probe0  
    .probe1 (ram_wr_we  ), // input wire [0:0]  probe1 
    .probe2 (ram_rd_en  ), // input wire [0:0]  probe2 
    .probe3 (rd_flag    ), // input wire [0:0]  probe3 
    .probe4 (ram_wr_addr), // input wire [5:0]  probe4 
    .probe5 (ram_wr_data), // input wire [7:0]  probe5 
    .probe6 (ram_rd_addr), // input wire [5:0]  probe6 
    .probe7 (ram_rd_data)  // input wire [7:0]  probe7
);

endmodule

        顶层模块代码如下:

module ip_2port_ram(
    input       sys_clk    ,  //系统时钟
    input       sys_rst_n     //系统复位,低电平有效
    );

//wire define
wire             ram_wr_en  ; //端口A使能
wire             ram_wr_we  ; //ram端口A写使能
wire             ram_rd_en  ; //端口B使能
wire             rd_flag    ; //读启动标志
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读数据
   

//RAM写模块
ram_wr u_ram_wr(
    .clk           (sys_clk    ),
    .rst_n         (sys_rst_n  ),

    .rd_flag       (rd_flag    ),
    .ram_wr_en     (ram_wr_en  ),
    .ram_wr_we     (ram_wr_we  ),
    .ram_wr_addr   (ram_wr_addr),
    .ram_wr_data   (ram_wr_data)
    );

//简单双端口RAM
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
);

//RAM读模块    
ram_rd u_ram_rd(
    .clk           (sys_clk    ),
    .rst_n         (sys_rst_n  ),

    .rd_flag       (rd_flag    ),
    .ram_rd_en     (ram_rd_en  ),
    .ram_rd_addr   (ram_rd_addr),
    .ram_rd_data   (ram_rd_data)
    ); 
   
ila_0 u_ila_0 (
    .clk    (sys_clk    ), // input wire clk
    
    .probe0 (ram_wr_en  ), // input wire [0:0]  probe0  
    .probe1 (ram_wr_we  ), // input wire [0:0]  probe1 
    .probe2 (ram_rd_en  ), // input wire [0:0]  probe2 
    .probe3 (rd_flag    ), // input wire [0:0]  probe3 
    .probe4 (ram_wr_addr), // input wire [5:0]  probe4 
    .probe5 (ram_wr_data), // input wire [7:0]  probe5 
    .probe6 (ram_rd_addr), // input wire [5:0]  probe6 
    .probe7 (ram_rd_data)  // input wire [7:0]  probe7
);

endmodule

FPGA自学笔记——设计与验证JMB FPGA(可编程逻辑门阵列)是一种可编程的硬件平台,可以实现各种数字电路的设计与验证。本文将简要介绍使用FPGA自学设计与验证JMB(低功耗、高效能、集成度高的多媒体芯片)的过程。 首先,我们需要了解JMB的功能和特性。JMB是一种面向多媒体应用的芯片,具备低功耗、高效能和高集成度的优势。我们需要详细研究JMB的硬件架构和内部模块,包括处理器核、存储器模块、图像和音频处理模块等。 接下来,我们可以使用FPGA开发板来设计和验证JMB。首先,我们需要熟悉FPGA设计工具,例如Vivado或Quartus等。这些工具提供了图形化界面和硬件描述语言(HDL)等设计方法。我们可以使用HDL编写JMB的功能模块,并将其综合为FPGA可执行的位流文件。 在设计完成后,我们需要验证JMB的功能和性能。我们可以使用仿真工具(例如ModelSim或ISE Simulator)来模拟JMB在不同情况下的行为。通过设计测试程序并运行仿真,我们可以验证JMB的各个模块是否正确地工作,是否满足设计要求。 在验证完成后,我们可以将位流文件下载到FPGA开发板中进行智能芯片的物理实现和测试。通过与外部设备的连接以及相关测试程序的运行,我们可以验证JMB在实际硬件中的功能和性能。 总结起来,学习FPGA设计与验证JMB,我们需要熟悉JMB的硬件架构和内部模块,并使用FPGA开发工具进行设计与验证。通过仿真和物理实现测试,我们可以验证JMB的功能和性能。这些过程需要理论知识和实践经验的结合,希望这些笔记能够给你提供一些参考和指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值