RAM(Random Access Memory )简介
当我们做相关项目时,经常会遇到存储数据的问题,数据量过大时,我们可以将其存储在 FPGA 芯片的外设存储器上,比如sdram、 ddr sdram 等,然而访问这些外设存储器相对比较麻烦,因此当数据量较小时,我们可以直接使用 FPGA 芯片内部自带的 IP 生成的 RAM。 RAM (random access memory)是随机存储器的意思, ram 可以按照所需进行随机读/写,掉电后数据丢失。
配置RAM
打开IP Catalog,配置ROM用的也是这个IP核
配置IP核,有多种模式可选择:单口RAM,简单双口RAM,真双口RAM,单口ROM,双口ROM
单口RAM
summary中检查无误点击OK生成IP核
程序设计
深度为16384,位宽为16bit的RAM,写数据为wr_addr,写完就读,读完就写,并使用testbench进行仿真
`timescale 1ns / 1ps
// 单口RAM
module ram_ctrl(
input clk,
input rst_n,
//input [15:0] wr_data, // 写入数据
output [15:0] rd_data // 读出数据
);
parameter Depth = 16384;
reg wr_en; //高电平时向RAM中写入数据,低电平时从RAM中读出数据
reg [13:0] wr_addr; // 写地址 ---深度为16384
reg [13:0] rd_addr; // 读地址
wire [13:0] addr; // 地址
wire [15:0] wr_data;
assign wr_data = wr_addr ; //写入数据
//----------------- wr_en
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_en <= 1'b1;
else if(rd_addr == Depth - 1) // 读完了
wr_en <= 1'b1;
else if(wr_addr == Depth - 1) // 写完了
wr_en <= 1'b0;
else
wr_en <= wr_en;
end
//----------------- wr_addr
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_addr <= 'b0;
else if(wr_en) begin
if(wr_addr == Depth - 1)
wr_addr <= 'b0;
else
wr_addr <= wr_addr + 1'b1;
end
else
wr_addr <= 'b0;
end
//----------------- rd_addr
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_addr <= 'b0;
else if(!wr_en) begin
if(rd_addr == Depth - 1)
rd_addr <= 'b0;
else
rd_addr <= rd_addr + 1'b1;
end
else
rd_addr <= 'b0;
end
// ------------------ addr
assign addr = wr_en?wr_addr:rd_addr;
adc_data_ram adc_data_ram_inst(
.clka(clk), // 50Mhz
.ena(1'b1),
.wea(wr_en), // 写使能
.addra(addr), // 地址 -------读/写
.dina(wr_data), // 写数据
.douta(rd_data) // 读数据
);
endmodule
testbench
`timescale 1ns / 1ps
module tb_ram_ctrl();
reg clk;
reg rst_n;
wire [15:0] rd_data;
initial begin
clk = 1;
rst_n = 0;
#3000;
rst_n = 1;
end
always#10 clk = ~clk;
ram_ctrl ram_ctrl_inst(
.clk(clk),
.rst_n(rst_n),
.rd_data(rd_data)
);
endmodule
仿真结果
把显示格式改成analog,可以直观地看看
应用举例
fpga通过ADC采样得到波形数据,先存储到RAM中,再通过spi将波形数据发送给stm32,stm32每完成一次接受数据,rd_addr就加1,继续读出下一个数据,再由stm32串口打印数据,这样我们便可以在上位机上直观看到stm32接受到的波形。在这个应用场景中,fpga充当了一个数据采集模块。
ram_ctrl
`timescale 1ns / 1ps
// 单口RAM
module ram_ctrl(
input clk,
input rst_n,
input tx_done,
input [15:0] wr_data, // 写入数据
output [15:0] rd_data // 读出数据
);
parameter Depth = 16384;
reg wr_en; //高电平时向RAM中写入数据,低电平时从RAM中读出数据
reg [13:0] wr_addr; // 写地址 ---深度为16384
reg [13:0] rd_addr; // 读地址
wire [13:0] addr; // 地址
// wire [15:0] wr_data;
// assign wr_data = wr_addr ; //写入数据
//----------------- wr_en
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_en <= 1'b1;
else if(rd_addr == Depth - 1) // 读完了
wr_en <= 1'b1;
else if(wr_addr == Depth - 1) // 写完了
wr_en <= 1'b0;
else
wr_en <= wr_en;
end
//----------------- wr_addr
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_addr <= 'b0;
else if(wr_en) begin
if(wr_addr == Depth - 1)
wr_addr <= 'b0;
else
wr_addr <= wr_addr + 1'b1;
end
else
wr_addr <= 'b0;
end
//----------------- rd_addr
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_addr <= 'b0;
else if(!wr_en) begin
if(rd_addr == Depth - 1)
rd_addr <= 'b0;
else if(tx_done)
rd_addr <= rd_addr + 1'b1;
end
else
rd_addr <= 'b0;
end
// ------------------ addr
assign addr = wr_en?wr_addr:rd_addr;
adc_data_ram adc_data_ram_inst(
.clka(clk), // 50Mhz
.ena(1'b1),
.wea(wr_en), // 写使能
.addra(addr), // 地址 -------读/写
.dina(wr_data), // 写数据
.douta(rd_data) // 读数据
);
endmodule
主程序
`timescale 1ns / 1ps
module adc_top(
input clk,
input rst_n,
input [11:0] adc_data, //采集到的adc数据 --- 12bit ADC
output adc_clk, //adc驱动时钟
input spi_sck,
output spi_miso,
input spi_mosi,
input spi_cs
);
reg [15:0] num;
reg [15:0] cnt;
wire [15:0] adc_data_16;
assign adc_data_16 = {4'b0000, adc_data}; // 数据拼接,便于spi发送16bit数据
reg [15:0] adc_data_16_reg;
wire [15:0] data_input; // fpga从机接受主机发送数据
wire [15:0] rd_data; // RAM/fifo读出的数据
wire tx_done;
wire rst;
assign rst = ~rst_n;
wire clk_20m;
wire clk_10m;
wire clk_5m;
wire locked;
// // 写时钟域的信号
wire wr_clk;
assign wr_clk = adc_clk; //65Mhz
wire rd_clk;
// 寄存器
always @ (posedge adc_clk or negedge rst_n) begin
if (!rst_n)
adc_data_16_reg <= 16'd0;
else
adc_data_16_reg <= adc_data_16;
end
// 16位波形数据存在RAM中,spi发送完成后才重新开始写RAM,
ram_ctrl ram_ctrl_inst(
.clk(clk),
.rst_n(rst_n),
.tx_done(tx_done), // spi发送完成信号,rd_addr加1继续读
.wr_data(adc_data_16_reg),
.rd_data(rd_data)
);
// 感觉用fifo的意义不大,还是用RAM好了
// fifo_ctrl fifo_ctrl_inst(
// .wr_clk(wr_clk),
// .rd_clk(rd_clk),
// .rst_n(rst_n),
// .fifo_data_in(adc_data_16_reg),
// .rd_data(rd_data)
// );
// spi从机
spi_slave spi_slave_inst(
.clk(clk), // 50Mhz
.rst_n(rst_n),
.data_in(rd_data), // fpga发送数据
.data_out(data_input),
.tx_en(1'b1), // fpga 发送使能
.tx_done(tx_done), // 发送完成标志位
.spi_sck(spi_sck),
.spi_miso(spi_miso),
.spi_mosi(spi_mosi)
);
clock_0 clock_0_inst(
.clk_65m(adc_clk),
.clk_5m(clk_5m),
.clk_20m(rd_clk),
.clk_10m(clk_10m),
.resetn(rst_n),
.locked(locked),
.clk_in1(clk)
);
endmodule
简单双口RAM
端口A配置,和端口B配置,均和单口RAM的配置差不多,模仿上面即可。
应用举例
RAM ip核例化
//-----------------------------双口RAM
ram_data ram_data_inst(
.clka(clk_100m), // 写时钟
.ena(clk_en), //
.wea(wr_en), // wr_en
.addra(wr_addr), // 写地址
.dina(wr_data), // 写数据
.clkb(rd_clk), // 读时钟
.enb(1'b1), //
.addrb(rd_addr), //
.doutb(rd_data) //
);
sample模块采样数据存入RAM中,控制wr_addr,wr_data,wr_en等信号
//-----------------------------采样模块
sample sample_inst(
.clk(clk_100m),
.rst_n(rst_n),
.act(act), //启动触发
.clk_en(clk_en), //时钟使能
.channel_sel(channel_sel), //通道选择 channel_sel
.mode_sel(mode_sel), //模式选择
.data_in(data_in), // 10路 信号输入
.wr_addr(wr_addr), //写RAM地址
.wr_data(wr_data), //写RAM数据
.wr_en(wr_en) //RAM写使能
);
显示模块读取RAM数据,控制rd_addr,rd_data,rd_clk等信号
//------------------------------显示驱动
display_ctrl display_ctrl_inst(
.vga_clk(clk_9m),
.rst_n(rst_n),
.display_en(vga_en), //显示使能
.offset(offset), // 显示区域偏移
.marker(marker), //显示垂直基准线
.vcount(vcount), // vga 行扫描计数器
.hcount(hcount), // vga 场扫描计数器
.rd_data(rd_data), // 波形存储器读数据
.rd_clk(rd_clk), // 读数据时钟
.rd_addr(rd_addr), // 读数据地址
.display_data(vga_data_in) // 显示数据
);