FPGA单端口RAM——IP核


前言

环境:
1、Quartus18.1
2、vscode
3、板子型号:原子哥开拓者2(EP4CE10F17C8)
要求:
调用RAM IP核进行单端口RAM的读写数据。


一、RAM简介

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

1、随机存储器IP核分类

在Cyclone IV 器件中,具有嵌入式存储器结构,嵌入式存储器结构由一系列M9K存储器模块进行配置,可以实现各种存储器功能,例如:RAM、移位寄存器、ROM以及FIFO缓冲器。

1、RAM IP核

RAM 是一种随机存取存储器,不仅仅可以存储数据,同时支持对存储的数据进行修改。

2、ROM IP核

是一种只读存储器,也就是说,在正常工作时只能读出数据,而不能写入数据。

  • 注意:

这两种存储器使用的资源都是 FPGA 的内部嵌入式 RAM 块,只不过 ROM IP 核只用到了嵌入式 RAM 块的读数据端口。

2、RAM IP核

Altera 推出的 RAM IP 核分为两种类型:单端口 RAM双端口 RAM。单端口 RAM 只有一组地址线,这组地址线控制着写数据端口和读数据端口,而双端口 RAM 具有两组地址线,这两组地址线分别控制着写数据端口和读数据端口。

在这里插入图片描述

  • 端口描述:

data:RAM 写数据端口;
address:RAM 读写地址端口,对于单口 RAM 来说,读地址和写地址共用同一组地址;
wren:写使能信号,高电平有效;
byteena:字节使能控制,该功能屏蔽了输入数据,这样仅写入数据中指定字节,未被写入的字节保留之前写入的值。当写入数据的位宽为 16 位、18 位、32 位和 36 位时,M9K 模块将支持字节使能,wren 信号以及字节 byteena 信号一起控制 RAM 模块的写操作。byteena 信号在 RAM IP 核创建过程中是可选的,可选择是否使用字节使能控制功能。
addressstall:地址时钟使能控制,当 addressstall 信号为高电平时,有效地址时钟使能就会保持之前的地址。addressstall 信号在 RAM IP 核创建过程中是可选的,可选择是否使用地址使能控制功能。
clockena:时钟使能控制,高电平有效;
rden:读使能信号,高电平有效;
aclr:异步复位信号,高电平有效;
q:从 RAM 中读出的数据;

输入与输出时钟模式下,输入时钟控制存储器模块的所有输入寄存器,输出时钟控制存储器模块的所有输出寄存器。只有一个时钟信号的情况下,一个时钟控制所有寄存器。

二、IP核配置步骤

  • 添加IP核:
    在这里插入图片描述
  • 双击后弹出:
    在这里插入图片描述

写入IP名、路径、语言等信息。

  • 参数配置:
    在这里插入图片描述

1、“How wide should the ‘q’ output bus be?”:用于指定输出数据端口的位宽,我们这里保持默认,选择 8bit;
2、“How many 8-bit words of memory?”:用于指定存储器的容量大小,我们这里选择存储容量为32words;
3、“What should the memory block type be?”:用于指定实现存储器使用的存储块类型,具体可选值与使用的 FPGA 芯片型号有关,一般选择默认 AUTO 就可以了;
4、“What clocking method would you like to use?”:用于指定使用的时钟模式,可选择单时钟和双时钟,一般对于单口 ram 选择单时钟就可以了。点击next。

  • next过后:
    在这里插入图片描述
  • next:
    在这里插入图片描述
  • next:
    在这里插入图片描述
  • next:
    在这里插入图片描述
  • next:
    在这里插入图片描述
  • 点击finish过后点击yes生成IP并加入到工程:

在这里插入图片描述

  • 效果:
    在这里插入图片描述

IP核简介及PLL_IP核的调用这是我的第一篇有关IP核的写的比较详细,还写了如何重新对IP核进行修改的操作。

三、源码

我们虽然配置了IP核但我们仍然需要写驱动文件、顶层文件对IP核进行调用。

1、ram_rw驱动文件

module ram_rw (
    input               clk,
    input               rst_n,
    input  [7:0]        ram_rd_data,//读数据

    output              ram_rw_en,//写使能
    output              ram_rd_en,//读使能
    output  reg [4:0]   ram_addr,//读写地址
    output  reg [7:0]   ram_wr_data//写数据
);
reg [5:0] rw_cnt;//读写控制器
//高电平操作
//rw_cnt 计数范围在 0~31,ram_wr_en 为高电平;32~63 时,ram_wr_en 为低电平
assign ram_rw_en = ((rw_cnt >= 6'd0) && (rw_cnt <= 6'd31) && rst_n) ? 1'b1 :1'b0;
//rw_cnt 计数范围在 32~63,ram_rd_en 为高电平;0~31 时,ram_rd_en 为低电平
assign ram_rd_en = ((rw_cnt >= 6'd32) && (rw_cnt <= 6'd63)) ? 1'b1 :1'b0;
//0~63的计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rw_cnt <= 6'd0;
    else if(rw_cnt == 6'd63)
        rw_cnt <= 6'd0;
    else 
        rw_cnt <= rw_cnt +1'b1;
end
//读写控制器计数范围:0~31 产生 ram 写使能信号和写数据信号,
//读使能信号也已产生,读数据信号由input信号引入
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        ram_wr_data <= 8'd0;
    else if(rw_cnt >= 6'd0 && rw_cnt <= 6'd31)
        ram_wr_data <= ram_wr_data +8'd1;
    else 
        ram_wr_data <= 8'd0;
end
//控制读写地址范围
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        ram_addr <= 5'd0;
    else if(ram_addr == 5'd31)
        ram_addr <= 5'd0;
    else
        ram_addr <= ram_addr + 1'd1;
end
endmodule

2、ip_1port_ram顶层文件

module ip_1port_ram(
    input       sys_clk ,
    input       sys_rst_n

);
wire        ram_wr_en       ;//写使能
wire        ram_rd_en       ;//读使能
wire [4:0]  ram_addr        ;//读写地址
wire [7:0]  ram_wr_data     ;//ram写数据

wire [7:0]  ram_rd_data     ;//ram读数据
//ram读写控制模块
ram_rw ram_rw_inst(
    .clk            (sys_clk),
    .rst_n          (sys_rst_n),
    .ram_rd_data    (ram_rd_data),//读数据

    .ram_rw_en      (ram_wr_en  ),//写使能
    .ram_rd_en      (ram_rd_en  ),//读使能
    .ram_addr       (ram_addr   ),//读写地址
    .ram_wr_data    (ram_wr_data)//写数据
);   
ram ram_inst(
    .address    (ram_addr),
	.clock      (sys_clk),
	.data       (ram_wr_data),
	.rden       (ram_rd_en),
	.wren       (ram_wr_en),
	.q          (ram_rd_data)
);   
endmodule

3、仿真文件

`timescale 1ns/1ns
module ip_1port_ram_tb();

parameter T = 20;

reg   sys_clk;
reg   sys_rst_n;

initial begin
    sys_clk   = 1'b0;
    sys_rst_n = 1'b0;
    #(T+1)
    sys_rst_n = 1'b1;
    #(3000)
    $stop;
end

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

ip_1port_ram ip_1port_ram_tb_inst(
    .sys_clk        (sys_clk),
    .sys_rst_n      (sys_rst_n)
);

endmodule

4、仿真波形

  • 写状态的波形:
    在这里插入图片描述
  • 分析:

ram_wr_en 信号拉高,ram_rd_en 信号拉低,说明此时是对 ram 进行写操作。
ram_wr_en 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 >时,写入的数据也是 0;当 ram 地址为 1 时,写入的数据也是 1,我们总共向 ram >中写入 32 个数据。

  • 读状态波形:
    在这里插入图片描述
  • 分析:

ram_rd_en 信号拉高,ram_wr_en 信号拉低,说明此时是对 ram 进行读操作。
ram_rd_en(读使能)信号拉高之后,ram_addr 从 0 开始增加,也就是说从 ram的>地址 0 开始读数据;ram中读出的数据 ram_rd_data 在延时一个时钟周期之后,开始输出数据,输出的数据为 0,1,2……结果正确

四、SignalTap II在线调试

具体调试流程看:SignalTap II 软件使用步骤

  • 读状态:
    在这里插入图片描述

在读数据的状态下,读的数据都是延后一个时钟周期的数据,与仿真一致.

  • 写状态:
    在这里插入图片描述

写状态下,ram_wr_en 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 >时,写入的数据也是 0;当 ram 地址为 1 时,写入的数据也是 1.与仿真一致.实验结果正确.


五、总结

IP核的配置总体来说并不难,但需要我们一步步的细心配置,代码部分只需要对IP的操作为主。就是读数据哪里的波形,为什么我们的写数据没有时钟周期的延时,反而在读数据的时候会有一个时钟的延时。有没有懂佬解惑下?

六、参考资料

以上资料均来自正点原子的教学视频或开拓者2开发教程:
原子官方

猜你喜欢

转载自blog.csdn.net/qq_52215423/article/details/131772483