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

2026技术展望】Python与AI的深度融合:从“能用”到“好用”的质变之年

2026技术展望】Python与AI的深度融合:从“能用”到“好用”的质变之年

🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或缺! 前言 站在2026年的春天回望,Python与AI的这段“联姻”已经走过了近十年的高光时刻。如果说过去五年我们关注的是“大模型还能多大”、“算力还能多强”,那么2026年,整个技术圈的风向已经发生了根本性的转变。 2026年,是AI Agent(智能体)全面落地的一年,是模型从“重训练”转向“轻推理”的一年,更是Python生态从“胶水语言”蜕变为“AI原生操作系统”的一年。 今天,我们不聊虚的,直接深入2026年Python+AI的技术腹地,看看这一年的热点到底在哪里,以及作为开发者,我们该如何抓住这波红利。 热点一:AI Agent 框架的“百团大战”与 Python

AI 爬虫高手养成:Openclaw+Scrapling 手动部署 + 采集策略(以Walmart 电商平台为例)

AI 爬虫高手养成:Openclaw+Scrapling 手动部署 + 采集策略(以Walmart 电商平台为例)

安装与使用 让ai自动安装的方法可以用以下官方提示词: Curl https://lobehub.com/skills/openclaw-skills-scrapling-mcp/skill.md, then follow the instructions to set up LobeHub Skills Marketplace and install the skill. Once installed, read the SKILL.md file in the installed directory and follow its instructions to complete the task. 自动安装虽然方便,但是没必要耗token,而且需要python环境安装库或模块,那么如果ai只按md文件严格执行就会安装或使用全局python来安装库或模块,一旦安装过多python相关的项目或skills就容易库或模块的版本依赖冲突(ai也许最终能解决但得不偿失),所以决定自己手动管理

让 AI 记住一切:OpenClaw 自我进化实录

> 从 70% Token 自动压缩到"每日三省吾身",打造一个真正会学习的 AI 助手 --- ## 背景 用 OpenClaw 一段时间后,发现两个痛点: 1. **会话太长,Token 爆满** — 聊着聊着就忘了前面的内容 2. **每次重启都是白纸** — 知识没有沉淀,重复问同样的问题 能不能让 AI 自己管理记忆,像人一样"三省吾身"? 折腾了一天,终于搞定了。 --- ## 一、Token 自动压缩:70% 就动手 ### 问题 OpenClaw 默认的 auto-compaction 是在 context window 接近满载时才触发。但这时候已经太晚了—

AI资源白嫖——Trae国际版一周年福利,免费用一个月600次快速请求

AI资源白嫖——Trae国际版一周年福利,免费用一个月600次快速请求

AI资源白嫖——Trae国际版一周年福利,免费用一个月600次快速请求 作为字节跳动推出的全球首款AI原生IDE,Trae自上线以来就凭借强大的代码生成、多模型适配能力圈粉无数开发者。恰逢Trae国际版上线一周年(1月20日周年庆),官方开启了重磅福利大放送,无论你是免费用户还是Pro用户,都能领到专属快速请求额度,堪称AI编程党的“羊毛盛宴”。今天就带大家吃透这份福利,手把手教你领取、使用,顺带解锁Trae国际版的隐藏玩法。 一、福利核心详情:谁能领?领多少? 本次一周年福利面向Trae国际版全量用户,无门槛覆盖免费用户与Pro用户,额度与有效期根据用户类型差异化配置,具体如下: * 免费用户:直接赠送600次Fast Request(快速请求),有效期至北京时间2026年2月14日10:00,足足一个月的使用期限,日常开发需求完全够用。 * Pro用户:升级赠送800次Fast Request,有效期延长至北京时间2026年3月14日10:00,适配专业开发者高频使用场景。 福利适用范围覆盖Trae国际版IDE全场景,包括Chat模式、Builder模式、SOLO模