一、RAM资源介绍
RAM的英文全称是Random Access Memory,即随机存取存储器,它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。RAM主要用来存放程序及程序执行过程中产生的中间数据、运算结果等。
单端口:只有一个端口,读写数据不能同时进行,共用数据通道。
伪双端口:拥有两个数据通道,一个用来写一个用来读。
真双端口:拥有两个数据通道,一个用来写一个用来读。
RAM区别于ROM:
RAM(Random Access Memory)和ROM(Read-Only Memory)是计算机中常见的两种存储器件,它们有以下主要区别:
1. 可读写性:
- RAM是一种可读写的内存,用于临时存储数据和程序。数据可以随时被写入、修改和读取,但在断电或重启计算机后数据会消失。
- ROM是一种只读的内存,其中存储了固定的数据或程序,用户无法对其进行修改。即使在断电或重启计算机后,其中的数据也会保持不变。
2. 数据保存:
- RAM用于存储正在运行的程序和临时数据,可以被CPU频繁读写,速度较快,但数据不稳定。
- ROM用于存储固化的系统程序和数据,通常包括启动程序和固件等,数据稳定不易丢失。
3. 容量和成本:
- RAM容量相对较大,通常用于临时存储大量数据和程序,但成本较高。
- ROM容量通常较小,主要用于存储固定的程序和数据,成本相对较低。
综上所述,RAM和ROM在功能、读写性质、数据保存方式以及成本等方面有明显的区别,它们在计算机系统中发挥着不同的作用。
二、RAM IP核使用
1、具体配置
(1)Basic
FPGA中没有ROM资源,ROM资源利用RAM资源模拟生成
(2)Port A Options
使用输出寄存器,数据会延后一个时钟
其他保持默认
2、代码驱动
使用三个计数器分别控制读写切换、写入数据、读写地址
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/02/20 09:25:45
// Design Name:
// Module Name: rw_ram
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module rw_ram(
input clk,
input rst_n,
output ram_en ,
output ram_wea ,
output reg[4:0]ram_addr ,
output reg[7:0]ram_wr_data ,
output reg[7:0]ram_rd_data
);
reg[5:0]rw_cnt;
//读写切换 1写 0读
assign ram_wea=((rw_cnt <= 6'd31)&&(ram_en == 1'b1)) ? 1'b1 : 1'b0;
assign ram_en=rst_n;
//读写状态计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rw_cnt <= 6'b0;
end
else if(rw_cnt < 6'd63)begin
rw_cnt <= rw_cnt + 1'b1;
end
else begin
rw_cnt <= 6'b0;
end
end
//读写数据计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ram_wr_data <= 8'b0;
end
else if(ram_wea == 1'b1)begin
ram_wr_data <= ram_wr_data + 1'b1;
end
else begin
ram_wr_data <= 8'b0;
end
end
//读写地址计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ram_addr <= 5'b0;
end
else if(ram_addr < 5'd31)begin
ram_addr <= ram_addr + 1'b1;
end
else begin
ram_addr <= 5'b0;
end
end
endmodule
3、顶层例化文件
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/02/20 09:22:59
// Design Name:
// Module Name: ip_ram
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module ip_ram(
input sys_clk,
input sys_rst_n
);
wire ram_en ;//RAM使能
wire ram_wea ;//ram读写使能信号,高电平写入,低电平读出
wire [4 : 0] ram_addr ;//ram读写地址
wire [7 : 0] ram_wr_data;//ram写数据
wire [7 : 0] ram_rd_data;//ram读数据
blk_mem_gen_0 u_blk_mem_gen_0 (
.clka(sys_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
);
rw_ram u_rw_ram(
.clk (sys_clk) ,
.rst_n (sys_rst_n) ,
//RAM
.ram_en (ram_en) ,
.ram_wea (ram_wea) ,
.ram_addr (ram_addr) ,
.ram_wr_data (ram_wr_data) ,
.ram_rd_data (ram_rd_data)
);
ila_0 u_ila_0 (
.clk(sys_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
);
endmodule
引脚约束XDC
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
4、综合下载验证
ram_wea 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0;当 ram 地址为 1 时,写入的数据也是 1。
ram_wea(读使能)信号拉低之后, ram_addr 从 0 开始增加,也就是说从 ram 的地址 0 开始读数据; ram中读出的数据 ram_rd_data 在延时一个时钟周期之后, 开始输出数据, 输出的数据为 0, 1, 2。。。。。
三、双口RAM——乒乓操作
1、乒乓操作简介
外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口RAM,FIFO,SDRAM等。在第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数 据流选择单元”将数据写入到“数据缓冲模块2”的同时“输出数据流选择单元”将“数据缓冲模块1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块1”的同时将“数据缓冲模块2”的数据读出。如此反复循环地操作,即为乒乓操作。
乒乓操作的最大特点是通过“输入数据流选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿的,因此非常适合对数据流进行流水线 式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
乒乓操作的第二个特点是可以节约缓存空间,使用双存储单元比单存储单元更节省存储空间,这是很明显的。同时在某些数据处理时,必须要数据达到一定个数才能进行运算,故还可以达到数据缓存的目的。
乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。
2、程序设计
利用状态机,划分四个状态,空闲、写R1、读R1写R2、写R1读R2,从而实现乒乓操作,具体状态转化如下图
IDLE:初始状态,在不工作或复位时就让状态机置为初始状态。
WRAM1:写RAM1状态。该状态我们开始往RAM1中写入数据,此时由于RAM2中并没有写入数据,所以我们不用对RAM2进行读取。
WRAM2_RRAM1:写RAM2读RAM1状态,当第一包数据写入完毕之后,马上跳到该状态,将第二包数据写入到RAM2中的同时读出RAM1中的写入的第一包数据。
WRAM1_RRAM2:写RAM1读RAM2状态。在该状态下我们开始向RAM1中写入第三包数据,此时第三包数据会把第一包数据覆盖,而我们的第一包数据已经读取出来了,并不会使数据丢失。在往RAM1中写入第三包数据的同时,我们读出RAM2中的第二包数据,当读写完成之后,跳回WRAM2_RRAM1状态开始 下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。
(1)RAM IP核配置
(2)驱动代码
读写数据内部,注释部分可单独使用
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/02/20 11:12:22
// Design Name:
// Module Name: rw_ramx2
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module rw_ramx2(
input clk ,
input rstn ,
//R1
output ena ,
output wea ,
output reg [5:0] addra ,
output reg [7:0] dina ,
output reg [7:0] douta ,
//R2
output enb ,
output web ,
output reg [5:0] addrb ,
output reg [7:0] dinb ,
output reg [7:0] doutb ,
output reg [7:0] ram_dout
);
//读写状态计数器
reg [5:0] ram_cnt;
//
reg [5:0] ram_addr;
//
reg [7:0] ram_wr_data;
reg [3:0] state;
//状态
parameter IDLE = 4'b0001; //空闲
parameter WRAM1 = 4'b0010; //写R1
parameter WRAM2_RRAM1 = 4'b0100; //读R1写R2
parameter WRAM1_RRAM2 = 4'b1000; //写R1读R2
//R1读写切换
assign wea = ((ram_cnt <= 6'd31)&&(ena == 1'b1)) ? 1 : 0;
//R2读写切换
assign web = ~wea;
//RAM使能信号
assign ena = rstn;
assign enb = rstn;
//读写状态计数器
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
ram_cnt <= 6'd0;
end
else if(ram_cnt < 6'd63)begin
ram_cnt <= ram_cnt + 1'b1;
end
else begin
ram_cnt <= 6'd0;
end
end
//读写地址计数器
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
ram_addr <= 6'd0;
end
else if(ram_addr < 6'd31)begin
ram_addr <= ram_addr + 1'b1;
end
else begin
ram_addr <= 6'd0;
end
end
//读写数据计数器
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
ram_wr_data <= 8'b0;
end
else if(ram_wr_data < 8'd63)begin
ram_wr_data <= ram_wr_data + 1'b1;
end
else begin
ram_wr_data <= 8'b0;
end
end
//状态跳转
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
state <= IDLE;
end
else begin
case(state)
IDLE:begin //空闲
if (wea == 1'b1) begin
state <= WRAM1;
end
else begin
state <= IDLE;
end
end
WRAM1:begin //写R1
if ((wea == 1'b0)&&(web == 1'b1)) begin
state <= WRAM2_RRAM1;
end
else begin
state <= WRAM1;
end
end
WRAM2_RRAM1:begin //读R1,写R2
if ((wea == 1'b1)&&(web == 1'b0))begin
state <= WRAM1_RRAM2;
end
else begin
state <= WRAM2_RRAM1;
end
end
WRAM1_RRAM2:begin //写R1,读R2
if ((wea == 1'b0)&&(web == 1'b1))begin
state <= WRAM2_RRAM1;
end
else begin
state <= WRAM1_RRAM2;
end
end
default:begin
state <= IDLE;
end
endcase
end
end
//读写数据
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
// state <= IDLE;
end
else begin
case (state)
IDLE:begin
addra <= 5'b0;
dina <= 8'b0;
addrb <= 5'b0;
dinb <= 8'b0;
end
WRAM1:begin
addra <= ram_addr;
dina <= ram_wr_data;
// if(addra < 5'd31) begin
// addra <= addra + 1'b1;
// end
// else begin
// addra <= 5'b0;
// end
// if(dina < 5'd31) begin
// dina <= dina + 1'b1;
// end
// else begin
// dina <= 5'b0;
// end
end
WRAM2_RRAM1:begin
addra <= ram_addr;
addrb <= ram_addr;
dinb <= ram_wr_data;
// if(addrb < 5'd31) begin
// addrb <= addrb + 1'b1;
// end
// else begin
// addrb <= 5'b0;
// end
// if(dinb < 5'd31) begin
// dinb <= dinb + 1'b1;
// end
// else begin
// dinb <= 5'b0;
// end
// if(addra < 5'd31) begin
// addra <= addra + 1'b1;
// end
// else begin
// addra <= 5'b0;
// end
end
WRAM1_RRAM2:begin
addra <= ram_addr;
addrb <= ram_addr;
dina <= ram_wr_data;
// if(addra < 5'd31) begin
// addra <= addra + 1'b1;
// end
// else begin
// addra <= 5'b0;
// end
// if(dina < 5'd31) begin
// dina <= dina + 1'b1;
// end
// else begin
// dina <= 5'b0;
// end
// if(addrb < 5'd31) begin
// addrb <= addrb + 1'b1;
// end
// else begin
// addrb <= 5'b0;
// end
end
default:begin
addra <= addra;
dina <= dina;
addrb <= addrb;
dinb <= dinb;
end
endcase
end
end
endmodule
(3)顶层例化
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/02/20 11:05:52
// Design Name:
// Module Name: ip_ramx2
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module ip_ramx2(
input sys_clk,
input sys_rst_n
);
wire ena ;
wire wea ;
wire [4:0] addra ;
wire [7:0] dina ;
wire [7:0] douta ;
wire enb ;
wire web ;
wire [4:0] addrb ;
wire [7:0] dinb ;
wire [7:0] doutb ;
wire [7:0] ram_dout;
blk_mem_gen_0 u_blk_mem_gen_0 (
//R1
.clka(sys_clk), // input wire clka
.rsta(!sys_rst_n), // input wire rsta
.ena(ena), // input wire ena
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [4 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.douta(douta), // output wire [7 : 0] douta
//R2
.clkb(sys_clk), // input wire clkb
.rstb(!sys_rst_n), // input wire rstb
.enb(enb), // input wire enb
.web(web), // input wire [0 : 0] web
.addrb(addrb), // input wire [4 : 0] addrb
.dinb(dinb), // input wire [7 : 0] dinb
.doutb(doutb) // output wire [7 : 0] doutb
// .rsta_busy(rsta_busy), // output wire rsta_busy
// .rstb_busy(rstb_busy) // output wire rstb_busy
);
rw_ramx2 u_rw_ramx2(
.clk (sys_clk),
.rstn (sys_rst_n),
.ena (ena),
.wea (wea),
.addra (addra),
.dina (dina),
.douta (douta),
.enb (enb),
.web (web),
.addrb (addrb),
.dinb (dinb),
.doutb (doutb),
.ram_dout (ram_dout)
);
ila_0 your_instance_name (
.clk(sys_clk), // input wire clk
.probe0(ena), // input wire [0:0] probe0
.probe1(wea), // input wire [0:0] probe1
.probe2(addra), // input wire [4:0] probe2
.probe3(dina), // input wire [7:0] probe3
.probe4(douta), // input wire [7:0] probe4
.probe5(enb), // input wire [0:0] probe5
.probe6(web), // input wire [0:0] probe6
.probe7(addrb), // input wire [4:0] probe7
.probe8(dinb), // input wire [7:0] probe8
.probe9(doutb), // input wire [7:0] probe9
.probe10(ram_dout) // input wire [7:0] probe10
);
endmodule
引脚约束XDC
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
(4)综合下载验证
四、总结
乒乓操作较为显著的特点是数据的无缝处理,只要大家理解了乒乓操作的处理技巧,那么当我们使用不同的存储单元作为缓冲模块时,操作起来也能得心应手。后续将更新FIFO的乒乓操作。。