FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

Verilog中ROM与RAM的RTL级实现:读与写时序设计与验证指南

二牛学FPGA二牛学FPGA
技术分享
4小时前
0
0
3

Quick Start

准备一个 Vivado 或 Quartus 工程,目标器件选 Xilinx Artix-7 或 Intel Cyclone IV。创建一个顶层模块 mem_top.v,例化一个单端口 RAM 和一个同步 ROM。编写 RAM 模块:使用 reg [DATA_WIDTH-1:0] mem [0:DEPTH-1] 声明存储阵列,写操作在时钟上升沿触发,读操作可为组合逻辑或同步。编写 ROM 模块:使用 initial 块或 $readmemh 加载初始化数据,读操作为组合逻辑。编写 testbench 对 RAM 进行写后读验证,对 ROM 进行地址遍历读验证。在 Vivado 中运行行为仿真,观察写数据与读数据波形是否对齐时钟沿。综合后查看资源报告,确认 RAM 被推断为 Block RAM 或分布式 RAM,ROM 被推断为 LUT ROM。验收标准:仿真波形中 RAM 写操作后下一拍读出正确数据;ROM 输出与初始化数据一致。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T主流 FPGA 平台Intel Cyclone IV / Lattice iCE40
EDA 版本Vivado 2022.2支持 Block RAM 推断Quartus Prime 20.1 / ModelSim
仿真器Vivado Simulator (xsim)集成于 VivadoModelSim / VCS / Icarus Verilog
时钟/复位100 MHz 时钟,高电平有效异步复位标准时序50 MHz / 同步复位
接口依赖地址总线宽度 8 位 (深度 256)便于演示16 位 / 深度 65536
约束文件XDC 中定义时钟周期 10 ns,无额外 RAM 约束基本时序约束SDC 文件 / 综合选项

目标与验收标准

  • 功能点:RAM 支持写使能控制的同步写,读操作可选择同步或组合输出;ROM 支持组合读。
  • 性能指标:RAM 读延迟为 1 个时钟周期(同步读),Fmax ≥ 200 MHz(Artix-7 速度等级 -1)。
  • 资源消耗:深度 256、宽度 8 的 RAM 使用 1 个 Block RAM (18Kb) 或 256 个 LUT(分布式)。
  • 验收方式:仿真波形中写使能有效时数据写入,下一拍地址对应输出更新;ROM 输出与初始化文件一致。

实施步骤

工程结构

创建工程目录:src/ 放 RTL 文件,sim/ 放 testbench,constr/ 放约束文件。顶层模块 mem_top.v 例化 single_port_ramsync_rom

关键模块:单端口 RAM

module single_port_ram #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 8
) (
    input clk,
    input rst_n,
    input we,                // 写使能,高有效
    input [ADDR_WIDTH-1:0] addr,
    input [DATA_WIDTH-1:0] din,
    output reg [DATA_WIDTH-1:0] dout
);

    reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1];

    // 写操作:时钟上升沿触发,受 we 控制
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // 复位时不清空存储内容,仅复位输出
            dout <= {DATA_WIDTH{1'b0}};
        end else begin
            if (we) begin
                mem[addr] <= din;
            end
            // 同步读:读操作与写操作共享同一时钟沿
            dout <= mem[addr];
        end
    end

endmodule

机制分析:上述代码中,读操作在写操作之后执行,因此当 we 有效时,写入的新数据不会立即出现在 dout 上,而是下一拍才可见。这是同步 RAM 的标准行为,保证了读写的确定性,但引入了 1 个时钟周期的读延迟。若需要组合读(零延迟),可将 dout 赋值移到组合逻辑中,但会降低时序性能,且综合工具可能推断为分布式 RAM 而非 Block RAM。

关键模块:同步 ROM

module sync_rom #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 8
) (
    input clk,
    input [ADDR_WIDTH-1:0] addr,
    output reg [DATA_WIDTH-1:0] dout
);

    reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1];

    // 初始化:使用 $readmemh 加载数据
    initial begin
        $readmemh("rom_init.hex", mem);
    end

    // 同步读:时钟上升沿捕获地址
    always @(posedge clk) begin
        dout <= mem[addr];
    end

endmodule

机制分析:ROM 的初始化通过 $readmemh 在仿真开始前完成,综合工具会将此实现为 LUT ROM 或 Block ROM(取决于器件)。同步读版本引入 1 个时钟周期延迟,适合高速设计;若改为组合读(assign dout = mem[addr];),则延迟为零,但可能无法达到高 Fmax。实际应用中,ROM 内容固定,因此无需写使能逻辑,资源消耗更低。

Testbench 示例

module tb_mem;

    reg clk;
    reg rst_n;
    reg we;
    reg [7:0] addr;
    reg [7:0] din;
    wire [7:0] dout_ram;
    wire [7:0] dout_rom;

    single_port_ram #(.DATA_WIDTH(8), .ADDR_WIDTH(8)) u_ram (
        .clk(clk),
        .rst_n(rst_n),
        .we(we),
        .addr(addr),
        .din(din),
        .dout(dout_ram)
    );

    sync_rom #(.DATA_WIDTH(8), .ADDR_WIDTH(8)) u_rom (
        .clk(clk),
        .addr(addr),
        .dout(dout_rom)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 100 MHz
    end

    initial begin
        rst_n = 0;
        we = 0;
        addr = 0;
        din = 0;
        #20 rst_n = 1;

        // RAM 写后读验证
        @(posedge clk); we = 1; addr = 8'hA5; din = 8'h3C;
        @(posedge clk); we = 0; addr = 8'hA5;
        @(posedge clk); // 此时 dout_ram 应显示 8'h3C

        // ROM 地址遍历读验证
        for (int i = 0; i < 256; i++) begin
            @(posedge clk); addr = i;
            // 检查 dout_rom 是否与 rom_init.hex 一致(需在仿真器中比较)
        end

        #100 $finish;
    end

endmodule

验证结果

运行行为仿真后,观察波形:

  • RAM 写操作:当 we 为高时,在时钟上升沿写入数据;下一拍地址不变时,dout 更新为写入值。
  • ROM 读操作:每个时钟上升沿,dout 输出对应地址的初始化数据。

综合后查看资源报告:若 RAM 深度 ≤ 256,宽度 ≤ 8,Vivado 默认推断为分布式 RAM(LUT);深度 ≥ 512 时自动使用 Block RAM。ROM 通常推断为 LUT ROM,除非使用 ram_style 属性强制指定。

排障指南

  • RAM 读数据不正确:检查地址是否在写操作后保持稳定;确认 we 信号在时钟沿前满足建立时间;若使用组合读,注意竞争风险。
  • ROM 输出全 X 或全 Z:确认 rom_init.hex 文件路径正确,且格式为每行一个十六进制数;检查地址是否越界。
  • 综合后资源异常:若 RAM 被推断为大量 LUT,尝试添加 (* ram_style = "block" *) 属性;若 ROM 占用过多 LUT,考虑使用 Block ROM 或增加深度。

扩展应用

本指南中的单端口 RAM 可扩展为双端口 RAM(读写端口独立),或添加字节使能支持部分写入。ROM 可结合状态机实现查表运算,如正弦波生成或 CRC 计算。对于更高性能需求,可引入流水线寄存器或使用 Xilinx 原语(如 RAMB18E1)直接例化。

参考资源

  • Xilinx UG901: Vivado Design Suite User Guide - Synthesis
  • Intel Quartus Prime Handbook: Recommended HDL Coding Styles
  • IEEE Std 1364-2005: Verilog Hardware Description Language

附录:初始化文件格式示例

rom_init.hex 文件内容示例(每行一个十六进制数,地址从 0 开始):

00
01
02
...
FF

注意:文件行数应与 ROM 深度一致,否则未定义地址输出为 X。

标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/37354.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
59917.50W3.93W3.67W
分享:
成电国芯FPGA赛事课即将上线
Vivado仿真中Testbench编写技巧:从简单激励到自检环境
Vivado仿真中Testbench编写技巧:从简单激励到自检环境上一篇
FPGA 状态机编码方式对比:二进制、格雷码与独热码设计与选择指南下一篇
FPGA 状态机编码方式对比:二进制、格雷码与独热码设计与选择指南
相关文章
总数:646
FPGA学习四大误区深度解析——金牌培训师教你避坑突围

FPGA学习四大误区深度解析——金牌培训师教你避坑突围

误区一:盲目敲代码,却对FPGA底层架构视而不见症状表现:…
技术分享, 行业资讯
1年前
0
0
408
0
FPGA在边缘AI的落地:从TensorFlow Lite到FPGA推理引擎的部署流程

FPGA在边缘AI的落地:从TensorFlow Lite到FPGA推理引擎的部署流程

本文旨在为工程师提供一套从TensorFlowLite模型到FPGA推…
技术分享
5天前
0
0
12
0
FPGA片上系统(SoC)设计入门:基于MicroBlaze/Zynq的软硬件协同

FPGA片上系统(SoC)设计入门:基于MicroBlaze/Zynq的软硬件协同

QuickStart步骤1:下载并安装Vivado(2020.1或更新…
技术分享
1天前
0
0
5
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容