IP 核之RAM实验

    技术2026-01-12  6

    目录:

    1.IP核 RAM简介2.实验1: 配置单端口 RAM1)实验任务2)创建工程并添加ram ip3)编写ram_rw.v4)编写顶层文件5)编写激励文件6)仿真测试7)ILA测试 3.实验2: 配置伪双端口 RAM1)创建工程并添加ram ip2)伪双端口RAM的端口定义和时序3)编写测试程序4)编写激励文件5)仿真测试6)ILA测试

    1.IP核 RAM简介

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

    Xilinx 7 系列器件具有嵌入式存储器结构,它由一列列 BRAM(块 RAM)存储器模块组成,通过对这些 BRAM 存储器模块进行配置,可以实现各种存储器的功能,例如:RAM、移位寄存器、ROM 以及 FIFO 缓冲器。

    Vivado 软件自带了 BMG IP 核(Block Memory Generator,块 RAM 生成器),可以配置成 RAM 或者ROM。这两者的区别是 RAM 是一种随机存取存储器,不仅仅可以存储数据,同时支持对存储的数据进行修改;而 ROM 是一种只读存储器,也就是说,在正常工作时只能读出数据,而不能写入数据。

    BRAM 全部是真双端口 RAM(True Dual-Port ram,TDP),这两个端口都可以独立地对 BRAM 进行读/写。但也可以被配置成伪双端口 RAM(Simple Dual-Port ram,SDP)(有两个端口,但是其中一个只能读,另一个只能写)或单端口 RAM(只有一个端口,读/写只能通过这一个端口来进行)。单端口 RAM 只有一组数据总线、地址总线、时钟信号以及其他控制信号,而双端口 RAM 具有两 组数据总线、地址总线、时钟信号以及其他控制信号。

    2.实验1: 配置单端口 RAM

    1)实验任务

    配置一个单端口的 RAM,然后对 RAM 进行读写操作,通过在 Vivado 自带的仿真器中观察波形是否正确,最后将设计下载到 Zynq 开发板中,并使用 ILA 对其进行在线调试观察。

    2)创建工程并添加ram ip

    图1 PICTURE ONE 图2 PICTURE TWO 图3 PICTURE TWO

    3)编写ram_rw.v

    /* * 当计数范围在 0~31 之间时,向 ram 中写入数据; * 当计数范围在 32~63 之间时,从 ram 中读出数据。 */ module ram_rw( input clk, input rst_n, output ram_wea, output ram_en, output reg [4:0] ram_addr, output reg [7:0] ram_wr_data, input [7:0] ram_rd_data ); reg [5:0] cnt; //计数器 范围(0~63) always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 6'd0; else if(cnt == 6'd63) cnt <= 6'd0; else cnt <= cnt + 1'b1; end //使能信号 assign ram_en = rst_n; //读写信号 assign ram_wea = (cnt < 6'd32 && ram_en) ? 1'b1:1'b0; //地址信号 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'b1; end //写信号的数据 always @(posedge clk or negedge rst_n) begin if(!rst_n) ram_wr_data <= 8'd0; else if(ram_addr == 6'd31) ram_wr_data <= 8'd0; else ram_wr_data <= ram_wr_data + 1'b1; end endmodule

    4)编写顶层文件

    module top_singlep_ram_test( input clk, input rst_n ); wire ram_wea; wire ram_en; wire [4:0] ram_addr; wire [7:0] ram_wr_data; wire [7:0] ram_rd_data; // ram_rw 读写模块 ram_rw u_ram_rw( .clk (clk), .rst_n (rst_n), .ram_wea (ram_wea), .ram_en (ram_en), .ram_addr (ram_addr), .ram_wr_data (ram_wr_data), .ram_rd_data (ram_rd_data) ); // ip_ram 核 blk_mem_gen_0 u_blk_mem_gen_0 ( .clka (clk), // input wire clka .ena (ram_en), // input wire ena .wea (ram_wea), // 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

    5)编写激励文件

    `timescale 1ns / 1ps module tb_ip_ram(); reg clk; reg rst_n; initial begin clk = 1'b0; rst_n = 1'b0; #200 rst_n = 1'b1; end always #10 clk = ~clk; top_singlep_ram_test u_top_singlep_ram_test( .clk (clk), .rst_n (rst_n) ); endmodule

    6)仿真测试

    由上图可知,ram_wea 信号拉高,说明此时是对 ram 进行写操作。ram_wea 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0;当 ram 地址为 1 时,写入的数据也是 1,我们总共向 ram 中写入 32 个数据。 由上图可知,ram_wea 信号拉低,说明此时是对 ram 进行读操作。ram_wea 信号拉低之后,ram_addr从 0 开始增加,也就是说从 ram 的地址 0 开始读数据;ram 中读出的数据 ram_rd_data 在延时一个时钟周期之后,开始输出数据,输出的数据为 0,1,2……,和我们写入的值是相等的。

    7)ILA测试

    添加 ILA IP 核,将 ram_en、ram_wea、ram_addr、ram_wr_data 和 ram_rd_data 信号添加至观察列表中。

    图1 PICTURE ONE 图2 PICTURE TWO

    ILA 的时钟和探针信号连接到顶层设计中,例化 ILA IP 核的代码如下:

    ila_0 your_instance_name ( .clk(clk), // input wire clk .probe0(ram_en), // input wire [0:0] probe0 .probe1(ram_wea), // input wire [0:0] probe1 .probe2(ram_addr), // input wire [4:0] probe2 .probe3(ram_wr_data), // input wire [7:0] probe3 .probe4(ram_rd_data) // input wire [7:0] probe4 );

    最后为工程添加 IO 管脚约束,对应的 XDC 约束语句如下所示:

    set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports clk] set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports rst_n]

    生成比特流之后,直接点击“Program”,此时 Vivado 会自动打开 ILA 的调试窗口,如下图所示:

    3.实验2: 配置伪双端口 RAM

    1)创建工程并添加ram ip

    图1 PICTURE ONE 图2 PICTURE TWO 图3 PICTURE ONE 图4 PICTURE TWO

    注意:在Port B Options 栏目下,Primitives Output Register 取消勾选,其功能是在输出数据加上寄存器,可以有效改善时序,但读出的数据会落后地址两个周期。很多情况下,不使能这项功能,保持数据落后地址一个周期。

    完成配置后,点击“Generate”生成 RAM IP。

    2)伪双端口RAM的端口定义和时序

    Simple Dual Port RAM 模块端口的说明如下:

    信号名称方向说明clkain端口A时钟输入weain端口A使能addrain端口A地址输入dinain端口A数据输入clkbin端口B时钟输入addrbin端口B地址输入doutbin端口B数据输出 图1 写时序 图2 读时序

    RAM 的数据写入和读出都是按时钟的上升沿操作的,端口 A 数据写入的时候需要置高wea 信号,同时提供地址和要写入的数据。

    而端口 B 是不能写入数据的,只能从 RAM 中读出数据,只要提供地址就可以了,一般情况下可以在下一个周期采集到有效的数据。

    3)编写测试程序

    /* * 当计数范围在 0~511 之间时,向 ram 中写入数据; * 当计数范围在 512~1023 之间时,从 ram 中读出数据。 */ module ip_simple_port_ram_test( input clk, input rst_n ); wire ram_wea; reg [8:0] ram_wr_addr; reg [15:0] ram_wr_data; reg [8:0] ram_rd_addr; wire [15:0] ram_rd_data; reg [9:0] cnt; //读写使能信号 assign ram_wea = (cnt < 10'd512) ? 1'b1:1'b0; //计数器 范围(0~1023) always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 1'b0; else if(cnt == 10'd1023) cnt <= 1'b0; else cnt <= cnt + 1'b1; end //写信号地址及数据 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin ram_wr_addr <= 1'b0; ram_wr_data <= 1'b0; end else if(ram_wr_addr == 9'd511 && ram_wr_data == 16'd511) begin ram_wr_addr <= 1'b0; ram_wr_data <= 1'b0; end else begin ram_wr_addr <= ram_wr_addr + 1'b1; ram_wr_data <= ram_wr_data + 1'b1; end end //读信号地址 always @(posedge clk or negedge rst_n) begin if(!rst_n) ram_rd_addr <= 1'b0; else if (ram_rd_addr == 9'd511) ram_rd_addr <= 1'b0; else ram_rd_addr <= ram_rd_addr + 1'b1; end //例化 ip ram核 ip_simple_ram u_ip_simple_ram ( .clka(clk), // input wire clka .wea(ram_wea), // input wire [0 : 0] wea .addra(ram_wr_addr), // input wire [8 : 0] addra .dina(ram_wr_data), // input wire [15 : 0] dina .clkb(clk), // input wire clkb .addrb(ram_rd_addr), // input wire [8 : 0] addrb .doutb(ram_rd_data) // output wire [15 : 0] doutb ); endmodule

    4)编写激励文件

    `timescale 1ns / 1ps module tb_ip_simport_ram(); reg clk; reg rst_n; initial begin clk = 1'b0; rst_n = 1'b0; #200 rst_n = 1'b1; end always #10 clk = ~clk; ip_simple_port_ram_test u_ip_simple_port_ram_test( .clk (clk), .rst_n (rst_n) ); endmodule

    5)仿真测试

    6)ILA测试

    添加 ILA IP 核,将 ram_wea、ram_wr_addr、ram_wr_data 、ram_rd_addr和ram_rd_data 信号添加至观察列表中。

    图1 PICTURE ONE 图2 PICTURE TWO

    ILA 的时钟和探针信号连接到顶层设计中,例化 ILA IP 核的代码如下:

    //例化 ILA IP核 ila_0 your_instance_name ( .clk(clk), // input wire clk .probe0(ram_wea), // input wire [0:0] probe0 .probe1(ram_wr_addr), // input wire [8:0] probe1 .probe2(ram_wr_data), // input wire [15:0] probe2 .probe3(ram_rd_addr), // input wire [8:0] probe3 .probe4(ram_rd_data) // input wire [15:0] probe4 );

    最后为工程添加 IO 管脚约束,对应的 XDC 约束语句如下所示:

    set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk] set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

    生成比特流之后,直接点击“Program”,此时 Vivado 会自动打开 ILA 的调试窗口,如下图所示:

    Processed: 0.016, SQL: 9