跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言算法

FPGA 嵌入式块存储器 RAM:原理与实现指南

综述由AI生成FPGA 中嵌入式块存储器 RAM 的原理、类型及应用。首先阐述了 RAM 的随机存取、非破坏性读取等核心特性,并分析了其在数据速率匹配缓冲和图像帧缓冲中的典型应用。接着对比了 SRAM 与 DRAM,重点讲解了 Vivado 中 Block Memory Generator IP 核的配置,包括单端口、双端口及真双端口 RAM 的选择,以及写优先、读优先等工作模式。最后通过基于 RAM 的图像显示系统实战案例,展示了顶层模块设计、数据写入与 TFT 显示控制器的 Verilog 实现,并对资源占用进行了分析,提供了相应的仿真验证代码。

观心发布于 2026/4/5更新于 2026/6/1236 浏览
FPGA 嵌入式块存储器 RAM:原理与实现指南

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

在 FPGA 系统设计中,很多时候我们需要的是可读可写的存储器,这就是 RAM(Random Access Memory,随机存取存储器)。无论是用于数据缓存、帧缓冲还是实时数据存储,RAM 都是构建高效 FPGA 系统不可或缺的组成部分。

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

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

  1. RAM 的三大核心特性 与 ROM 相比,RAM 具有以下显著特性:
  • 随机存取:支持对任意地址的读写操作,访问顺序不受限制
  • 非破坏性读取:读取操作不会清除存储内容,数据可多次重复读取
  • 覆盖写入:新数据写入会直接覆盖旧数据,支持动态更新
  1. 典型应用场景分析 场景一:数据速率匹配缓冲 这是 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 控制器接口示例:

// 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: 不写入任何字节
endmodule

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

  1. 详细实现代码 (1)顶层模块设计 系统架构设计:包含时钟域划分、串口接收、图像数据写入控制、双端口 RAM 实例及 TFT 显示控制器。
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(),
        // 端口 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-1023
            for(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

目录

  1. 一、引言:为什么需要 RAM?
  2. 二、RAM 的核心特性与应用场景
  3. 三、RAM 的类型:SRAM 与 DRAM 详解
  4. 四、Vivado 中 RAM IP 核的详细配置指南
  5. 五、实战案例:基于 RAM 的图像显示系统
  6. 六、仿真验证
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Spring Boot 3 升级至 4 完整迁移指南
  • 多模态大模型垂直微调实战:Qwen3-VL-4B-Thinking 与 Llama Factory
  • 前端练习:使用 Three.js 实现星辰宇宙效果
  • Python 爬虫采集跨境电商数据实战
  • 数据结构基础:串的定义与常见误区
  • Whisper 语音识别库的安装与配置指南
  • LeetCode 25. K 个一组翻转链表 reverse N 核心思路
  • CentOS 7 WinSCP 普通用户上传文件无权限解决方案
  • Flutter与Web混合开发实践
  • llama.cpp Docker 部署:容器化推理服务搭建
  • 基于 OpenClaw 搭建 QQ AI 办公机器人:关键词触发与邮件发送
  • C++ 实现 AVL 平衡二叉搜索树
  • 通义万相 2.1 AIGC 模型 API 调用与图像文本生成实战
  • AIGC 赋能插画创作:技术解析与代码实战
  • 金融风控文本分析:基于 Llama-Factory 训练反欺诈模型
  • 多传感器数据融合算法理论基础
  • VRChat 跨语言交流工具 VRCT 使用指南
  • Python EXE 解包工具实战:py2exe 与 pyinstaller 逆向
  • C++ 智能指针完全指南:从原理到实战
  • AI 产品经理的核心技能、职责与传统产品差异解析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online