FPGA中的嵌入式块存储器RAM:从原理到实现的完整指南

FPGA中的嵌入式块存储器RAM:从原理到实现的完整指南

文章目录

一、引言:为什么需要RAM?

在前一篇文章中,我们深入探讨了FPGA中ROM的原理与应用。然而,在实际的FPGA系统设计中,很多时候我们需要的是可读可写的存储器,这就是RAM(Random Access Memory,随机存取存储器)。无论是用于数据缓存、帧缓冲还是实时数据存储,RAM都是构建高效FPGA系统不可或缺的组成部分。

本文将从RAM的基本原理出发,详细讲解嵌入式块存储器RAM的分类、特性、配置方法以及在实际项目中的应用,特别关注如何通过Vivado工具链高效地使用RAM IP核。

二、RAM的核心特性与应用场景

1.RAM的三大核心特性
与ROM相比,RAM具有以下显著特性:

  • 随机存取:支持对任意地址的读写操作,访问顺序不受限制
  • 非破坏性读取:读取操作不会清除存储内容,数据可多次重复读取
  • 覆盖写入:新数据写入会直接覆盖旧数据,支持动态更新

2.典型应用场景分析
场景一:数据速率匹配缓冲
这是RAM最常见的应用场景之一。考虑以下实际问题:

某ADC以1μs的间隔产生12位数据(速率:1000个/ms),而串口以115200波特率发送数据(每6位数据需要69.4μs)。数据产生速率远快于发送速率。 

解决方案:

// 使用RAM作为数据缓冲器 module data_buffer( input clk, input rst_n, input [11:0] adc_data, input adc_data_valid, output reg [5:0] uart_data, output reg uart_data_valid );// 双端口RAM:端口A用于写入ADC数据,端口B用于读取串口数据// 深度:1000,宽度:12位(写入)和6位(读取)// 写入逻辑 always @(posedge clk) begin if(adc_data_valid) begin ram_wea <=1'b1; ram_addra <= write_addr; ram_dina <= adc_data; write_addr <= write_addr +1; end end // 读取逻辑 always @(posedge clk) begin if(uart_ready) begin ram_addrb <= read_addr; uart_data <= ram_doutb[5:0];// 只取低6位 read_addr <= read_addr +1; end end endmodule 

场景二:图像帧缓冲

在视频处理系统中,RAM常被用作帧缓冲器:

// TFT显示屏图像缓冲 module tft_frame_buffer( input clk_pixel,// 像素时钟 input clk_system,// 系统时钟 input [15:0] pixel_in,// RGB565像素数据 input pixel_valid, output [15:0] pixel_out );// 双端口RAM配置// 端口A:串口写入(系统时钟域)// 端口B:TFT读取(像素时钟域)// 存储需求计算:// 800×480分辨率,16位/像素 → 800×480×16 = 6,144,000 bits// 需要约170个36Kb的BRAM块 endmodule 

三、RAM的类型:SRAM与DRAM详解

在这里插入图片描述
// DDR3控制器接口示例 module ddr3_controller( input clk, input rst_n,// 用户接口 input [31:0] app_addr, input [255:0] app_wdf_data, input app_wdf_wren, output [255:0] app_rd_data, output app_rd_data_valid,// DDR3物理接口 output [13:0] ddr3_addr, output [2:0] ddr3_ba, output ddr3_cas_n, output ddr3_ras_n, output ddr3_we_n );// 使用Xilinx MIG IP核实现 endmodule 

四、Vivado中RAM IP核的详细配置指南

  1. RAM IP核类型选择
    在Vivado的IP Catalog中搜索"Block Memory Generator",可以看到多种RAM类型:

(1)单端口RAM

// 接口信号 module single_port_ram( input clka,// 时钟 input ena,// 使能 input wea,// 写使能(1=写,0=读) input [9:0] addra,// 地址(10位,深度1024) input [15:0] dina,// 数据输入 output [15:0] douta // 数据输出);

特点:

所有操作共享同一组端口

读写不能同时进行

适用于简单的数据存储场景

(2)简单双端口RAM

// 接口信号 module simple_dual_port_ram(// 端口A:只写 input clka, input ena, input wea,// 始终为1(只写) input [9:0] addra, input [15:0] dina,// 端口B:只读 input clkb, input enb, input [9:0] addrb, output [15:0] doutb );

特点:

端口A专用于写,端口B专用于读

可同时进行读写操作

适用于生产者-消费者模型

(3)真双端口RAM

// 接口信号 module true_dual_port_ram(// 端口A:可读写 input clka, input ena, input wea, input [9:0] addra, input [15:0] dina, output [15:0] douta,// 端口B:可读写 input clkb, input enb, input web, input [9:0] addrb, input [15:0] dinb, output [15:0] doutb );

特点:

两个端口都可独立读写

需要处理读写冲突

适用于复杂的数据共享场景

  1. 关键配置参数详解
    (1)存储容量计算
总存储容量 = 数据位宽 × 深度 单位:bits 示例: 数据位宽:16 bits 深度:1024 总容量:16 × 1024=16,384 bits =16 Kb 

(2)BRAM资源使用
Xilinx 7系列FPGA的BRAM配置:

每个BRAM块:36 Kb
可配置为:

1个36Kb RAM
2个独立的18Kb RAM

常见配置模式:
32K × 1
16K × 2
8K × 4
4K × 9
2K × 18
1K × 36
512 × 72
(3)工作模式选择
在"Port A Options"或"Port B Options"中:
Write First Mode(写优先模式):

// 当读写同一地址时,写入的数据会立即出现在输出 always @(posedge clk) begin if(wea) begin mem[addr]<= din; dout <= din;// 写优先 end else begin dout <= mem[addr]; end end 

Read First Mode(读优先模式):

// 当读写同一地址时,先读取旧数据,再写入新数据 always @(posedge clk) begin dout <= mem[addr];// 先读if(wea) begin mem[addr]<= din;// 后写 end end 

No Change Mode(无变化模式):

// 当读写同一地址时,输出保持不变 always @(posedge clk) begin if(wea) begin mem[addr]<= din; end else begin dout <= mem[addr]; end end 
  1. 字节写使能功能
    字节写使能允许按字节粒度控制数据写入,特别适用于处理不同位宽的数据:
// 24位数据,按字节写入控制 module byte_write_ram( input clk, input ena, input [2:0] wea,// 3位写使能,控制3个字节 input [9:0] addra, input [23:0] dina,// 24位输入数据 output [23:0] douta // 24位输出数据);// wea[2:0]控制:// wea = 3'b111: 写入全部3个字节// wea = 3'b011: 只写入低2个字节(dina[15:0])// wea = 3'b001: 只写入最低字节(dina[7:0])// wea = 3'b000: 不写入任何字节

五、实战案例:基于RAM的图像显示系统

  1. 详细实现代码
    (1)顶层模块设计

系统架构设计

在这里插入图片描述
module image_display_system( input clk_100m,// 100MHz系统时钟 input clk_pixel,// 像素时钟(25MHz for 800x480@60Hz) input rst_n,// 串口接口 input uart_rx, output uart_tx,// TFT显示接口 output [15:0] tft_data, output tft_hsync, output tft_vsync, output tft_de );// 时钟域划分 wire clk_sys = clk_100m; wire clk_disp = clk_pixel;// 串口接收模块 wire [7:0] uart_rx_data; wire uart_rx_valid; uart_receiver u_uart_rx(.clk(clk_sys),.rst_n(rst_n),.rx(uart_rx),.data(uart_rx_data),.valid(uart_rx_valid));// 图像数据写入控制 wire [15:0] ram_wdata; wire [16:0] ram_waddr;// 800*480=384000,需要19位地址 wire ram_wen; image_writer u_writer(.clk(clk_sys),.rst_n(rst_n),.uart_data(uart_rx_data),.uart_valid(uart_rx_valid),.ram_wdata(ram_wdata),.ram_waddr(ram_waddr),.ram_wen(ram_wen));// 双端口RAM实例 wire [15:0] ram_rdata; wire [16:0] ram_raddr; blk_mem_gen_0 u_ram(// 端口A:写端口(串口数据写入).clka(clk_sys),.ena(1'b1),.wea(ram_wen),.addra(ram_waddr[16:0]),.dina(ram_wdata),.douta(),// 端口A不读取// 端口B:读端口(TFT显示读取).clkb(clk_disp),.enb(1'b1),.addrb(ram_raddr[16:0]),.doutb(ram_rdata));// TFT显示控制器 tft_controller u_tft(.clk(clk_disp),.rst_n(rst_n),.pixel_data(ram_rdata),.pixel_addr(ram_raddr),.tft_data(tft_data),.tft_hsync(tft_hsync),.tft_vsync(tft_vsync),.tft_de(tft_de)); endmodule 

(2)图像数据写入模块

module image_writer( input clk, input rst_n, input [7:0] uart_data, input uart_valid, output reg [15:0] ram_wdata, output reg [16:0] ram_waddr, output reg ram_wen ); reg [1:0] byte_cnt;// 字节计数器(0-1,两个字节组成一个16位像素) reg [7:0] pixel_low;// 像素低字节 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin byte_cnt <=2'd0; pixel_low <=8'd0; ram_waddr <=17'd0; ram_wen <=1'b0; end elseif(uart_valid) begin case(byte_cnt)2'd0: begin pixel_low <= uart_data;// 保存低字节 byte_cnt <=2'd1; ram_wen <=1'b0; end 2'd1: begin ram_wdata <={uart_data, pixel_low};// 拼接高字节和低字节 ram_waddr <= ram_waddr +1'b1; ram_wen <=1'b1; byte_cnt <=2'd0; end endcase end else begin ram_wen <=1'b0; end end endmodule 

(3)TFT显示控制器

module tft_controller( input clk,// 像素时钟 input rst_n, input [15:0] pixel_data,// 从RAM读取的像素数据 output reg [16:0] pixel_addr,// 读取地址 output reg [15:0] tft_data, output reg tft_hsync, output reg tft_vsync, output reg tft_de );// 显示时序参数(800x480@60Hz) parameter H_ACTIVE =800;// 水平有效像素 parameter H_FP =40;// 水平前沿 parameter H_SYNC =128;// 水平同步脉冲 parameter H_BP =88;// 水平后沿 parameter H_TOTAL = H_ACTIVE + H_FP + H_SYNC + H_BP; parameter V_ACTIVE =480;// 垂直有效行 parameter V_FP =13;// 垂直前沿 parameter V_SYNC =2;// 垂直同步脉冲 parameter V_BP =33;// 垂直后沿 parameter V_TOTAL = V_ACTIVE + V_FP + V_SYNC + V_BP;// 行计数器和列计数器 reg [10:0] h_cnt;// 水平计数器(0-1047) reg [9:0] v_cnt;// 垂直计数器(0-527) always @(posedge clk or negedge rst_n) begin if(!rst_n) begin h_cnt <=11'd0; v_cnt <=10'd0; pixel_addr <=17'd0; tft_hsync <=1'b0; tft_vsync <=1'b0; tft_de <=1'b0; tft_data <=16'd0; end else begin // 水平计数器递增if(h_cnt == H_TOTAL -1) begin h_cnt <=11'd0;// 垂直计数器递增if(v_cnt == V_TOTAL -1) begin v_cnt <=10'd0; pixel_addr <=17'd0;// 帧结束,复位地址 end else begin v_cnt <= v_cnt +1'b1; end end else begin h_cnt <= h_cnt +1'b1; end // 生成同步信号 tft_hsync <=(h_cnt >= H_ACTIVE + H_FP)&&(h_cnt < H_ACTIVE + H_FP + H_SYNC); tft_vsync <=(v_cnt >= V_ACTIVE + V_FP)&&(v_cnt < V_ACTIVE + V_FP + V_SYNC);// 生成数据使能信号 tft_de <=(h_cnt < H_ACTIVE)&&(v_cnt < V_ACTIVE);// 在有效显示区域内读取像素数据if(tft_de) begin tft_data <= pixel_data; pixel_addr <= pixel_addr +1'b1; end else begin tft_data <=16'd0; end end end endmodule 
  1. RAM资源需求分析
    对于800×480分辨率的TFT显示,使用RGB565格式(16位/像素):
总像素数:800 × 480=384,000 像素 每像素数据:16 bits 总存储需求:384,000 × 16=6,144,000 bits Xilinx XC7Z015芯片资源: - 每个BRAM块:36 Kb - 总BRAM数量:95个 - 总BRAM容量:95 × 36 Kb =3,420 Kb 结论:无法存储完整一帧图像 

六、仿真验证

测试平台设计

module ram_tb; reg clk; reg rst_n;// 时钟生成(100MHz) always #5 clk =~clk;// 测试序列 initial begin // 初始化 clk =0; rst_n =0;// 复位释放 #100 rst_n =1;// 测试1:顺序写入然后读取test_sequential();// 测试2:随机地址访问test_random();// 测试3:读写冲突测试test_collision(); $finish; end task test_sequential; integer i; begin $display("=== 顺序读写测试开始 ===");// 写入0-1023for(i =0; i <1024; i = i +1) begin @(posedge clk); ram_wen =1; ram_addr = i; ram_wdata = i *2; end @(posedge clk); ram_wen =0;// 验证读取for(i =0; i <1024; i = i +1) begin @(posedge clk); ram_addr = i; @(posedge clk);if(ram_rdata !== i *2) begin $display("错误:地址 %d,期望值 %d,实际值 %d", i, i*2, ram_rdata); end end $display("=== 顺序读写测试完成 ==="); end endtask task test_random; integer i, addr; begin $display("=== 随机访问测试开始 ===");for(i =0; i <100; i = i +1) begin addr = $random %1024;// 写入随机数据 @(posedge clk); ram_wen =1; ram_addr = addr; ram_wdata = $random; test_data[addr]= ram_wdata;// 等待写入完成 @(posedge clk); ram_wen =0;// 延迟2个周期后读取验证repeat(2) @(posedge clk); ram_addr = addr; @(posedge clk);if(ram_rdata !== test_data[addr]) begin $display("随机测试错误:地址 %d", addr); end end $display("=== 随机访问测试完成 ==="); end endtask endmodule 

Read more

OpenClaw 飞书机器人搭建流程

OpenClaw 飞书机器人搭建流程

OpenClaw 飞书机器人搭建流程 手把手教你搭建属于自己的飞书 AI 机器人! 一、创建企业自建应用 首先进入飞书开发者后台: 👉 https://open.feishu.cn/app 填写应用名称和描述,直接点击创建即可。 创建完成后,会自动生成 App ID 和 App Secret,这两个凭证后面配置 OpenClaw 时会用到,先记下来。 二、添加机器人能力 在应用详情页左侧菜单找到「机器人」,点击添加。 添加成功后,机器人就可以在飞书中被搜索和使用了。 三、开通消息权限 进入「权限管理」,找到 im: 相关权限,全部勾选。 ⚠️ 注意:以下这个权限建议不要勾选: 获取群组中所有消息(im:message.group_msg) 否则群里所有消息机器人都会收到并响应,会造成不必要的干扰。

从零开始“养龙虾”:OpenClaw 本地极简部署与 QQ 机器人接入全保姆级教程

从零开始“养龙虾”:OpenClaw 本地极简部署与 QQ 机器人接入全保姆级教程

文章目录 * 引言 * 什么是 OpenClaw? * 为什么选择 OpenClaw? * 一、基础环境准备 * 1. 安装 Node.js (v22及以上) * 2.安装 Git * 3. 解决 npm 被拦截(没报错跳过) * 二、一键部署与唤醒“龙虾” * 1.全自动拉取与组装 * 2.醒龙虾与配置“大脑” * 三、接入官方 QQ 机器人(可选) * 1. 领取官方机器人的“身份证” * 2. 本地安装专属通信插件 * 3. 结果展示 * 总结 引言 什么是 OpenClaw? 最近开源界有一只“红皮小龙虾”非常火,它就是 OpenClaw。

AI绘画新手入门到进阶全攻略:提示词+工具+实战,看完就能出图

AI绘画新手入门到进阶全攻略:提示词+工具+实战,看完就能出图

AI绘画已成为自媒体、电商运营、设计师的必备高效工具,但很多新手刚接触时会陷入“关键词堆了一堆,出来的图却杂乱无章”的困境。本文结合主流AI绘画工具(稿定AI绘画、Stable Diffusion WebUI),从工具选型、核心提示词技巧,到3个高频场景实战,再到常见问题解决,全程干货无废话,新手跟着操作就能快速产出高质量配图。 一、新手必看:2款主流AI绘画工具选型(附优缺点对比) 新手无需盲目追求复杂工具,优先根据需求选择适配工具可大幅提升效率。以下2款工具覆盖“零门槛上手”和“开源可定制”核心需求,按需挑选即可: 1.1 稿定AI绘画(新手首选,零配置) 核心优势:零配置,浏览器/APP直接使用;内置国潮、治愈系等海量风格模板;支持参考图风格迁移,新手3分钟出图。适用场景:自媒体配图、PPT插图、电商主图快速制作。缺点:自定义模型等高级功能需付费,个性化定制度较低。

MCAP :机器人数据容器的全面实践指南

Outline: MCAP 已形成完整工具链生态: * Foxglove Studio:可视化分析工具 * mcap-cli:跨平台命令行工具 * AWS RoboMaker:原生云存储支持 随着 IEEE 正在制定的 P3196 机器人数据标准,MCAP 正在演进为行业基础架构的重要组成。其设计哲学启示我们:优秀的数据格式应该在存储效率与读取便利间找到平衡,这正是 MCAP 在机器人革命中脱颖而出的关键。 参考资料: 1. https://juejin.cn/post/7508575831791812658 https://getiot.tech/fileformat/mcap/ MCAP :机器人数据容器的全面实践指南 在机器人和自动驾驶系统开发中,高效存储和处理传感器数据是核心挑战之一。传统的 ROS bag 格式在面对大规模、多类型数据时逐渐暴露出性能瓶颈,而 MCAP(Modular Container for Asynchronous