ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SRAM Verilog建模与综合仿真:从IP核选型到脚本排错实践

SRAM Verilog建模与综合仿真:从IP核选型到脚本排错实践 简介面向数字电路学习者与 FPGA 开发者的 SRAM Verilog 设计资料包围绕静态随机存储器的建模、读写控制与验证展开重点演示 Verilog 在存储器设计中的实际用法。压缩包共 100 个文件大小约 361KB核心包括 Verilog 源文件.v、Quartus 工程文件.qpf/.qsf、仿真波形.vwf/.cvwf以及综合报告.rpt/.summary等能够完整体现一个 SRAM 从 RTL 设计到工程实现的过程。目前已有 172 人学习下载适合刚接触 SRAM 原理或 Verilog 数字电路设计的读者参考。通过阅读代码和工程文件可以掌握存储单元实例化、地址/数据端口定义、读写时序控制等关键点理解阻塞赋值与非阻塞赋值在读写逻辑中的应用借助仿真波形可验证连续读写、随机访问等场景下的行为报告文件则有助于分析 Quartus 综合与适配结果为后续功能扩展和性能优化提供参考。1. 从一个 SRAM.zip 说起Verilog 建模与打包里最容易踩的三个坑那个标题其实是被搜索引擎拼接出来的三段SRAM.zip、SRAM、verilog。我第一反应不是打开压缩包而是想起过去在搭 verilog 计数器驱动显示缓冲时总是先拍脑袋分配一块二维 reg 数组最后在综合报告里看到它占了 80% 的 LUT。严格说正规 FPGA 项目应当用厂商 SRAM IP 核但下载到的 SRAM.zip 里通常包含行为模型、可综合模型和仿真测试台很多场景下比重新配置 IP 更快——尤其当你是为了验证连接逻辑或者只是想快速跑通一个异步 FIFO / 滑动窗口滤波的原型。下面就从处理这份 zip 的常见路径讲起覆盖模型选择、解压排错、仿真对比。适合正在把别人 SRAM verilog 接入自己工程、又不知道哪些文件能用的人。2. 为什么 SRAM 要用 Verilog 自己写而不是直接调 IP 核2.1 行为模型与可综合模型的边界很多工程师拿到 SRAM.zip 后会直接全选 .v 文件加入工程结果综合报告里 RAM 全部变成 LUT。这不是工具的问题而是把行为模型送进综合器。行为模型的目标是让仿真跑得快写法上允许always (*)、#1、$freadmem这类不可综合语法。行为模型最常见的写法是reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; always (*) begin dout mem[addr]; end这个写法在仿真时地址一变、输出立刻变非常直观但综合工具遇到异步读几乎不会把它推断成块 RAM。真正可综合的 SRAM 模型读路径必须由时钟驱动always (posedge clk) begin if (ren) begin dout mem[addr]; end end判断一份 SRAM verilog 是否可综合最直接的方法是数always (posedge clk)的数量并且确认dout只在时钟沿后改变。拿到 SRAM.zip 之后我一般会在解压目录里执行grep -n always (posedge clk) sram_rtl.v | wc -l grep -n always (*) sram_rtl.v如果第二个 grep 有输出而第一个输出小于端口数量那份文件大概率是行为模型只能留作仿真参考不能加入综合工程。还有一种情况是同一个文件里既写了时钟采样、又在顶层用了assign dout mem[addr]拼接输出这种混写最容易导致综合网表出现长组合路径。2.2 端口结构决定你最终能映射到什么硬件SRAM.zip 里经常同时出现单端口、简单双端口、真双端口三种版本。选型不能只看需求文档还要看目标器件的存储块结构。下表是我在几个项目里按实用性排的优先级SRAM 类型端口组成常见用途综合映射注意点单端口一套时钟、一套地址、一套读写数据缓存、配置寄存器、FIFO 存储体直接映射 BRAM面积最小简单双端口写端口独立、读地址独立图像行缓存、CPU 写 / 逻辑读读端口写端口可不同时钟但地址需分别约束真双端口两套时钟、两套读写端口跨时钟域、多核通信综合工具可能拆成两个 RAM需要额外带宽检查这里的“可综合性”不是指能不能综合而是综合后是否还能保持你想要的时序。简单双端口模型里的读端口和写端口如果异步时钟频率差太大后端时序收敛往往要靠加ASYNC_REG或打拍寄存器。之前我见过一个从 SRAM.zip 提取的双端口模型两个端口用了同一个clk信号但命名不同综合时工具直接报“unable to merge RAM primitives”原因就是时钟域信息在代码里是分开的。2.3 读写冲突行为模型之间最常见的差异同一个 SRAM 地址在同一个时钟沿写入又读出不同模型的行为可能完全相反。标准 IP 核的配置界面里通常有READ_FIRST、WRITE_FIRST和NO_CHANGE三个选项而手写的 SRAM verilog 模型往往只实现其中一种。如果你把下载来的模型直接拿去仿真却没有和最终综合出来的 IP 行为对齐就会在“读写同地址”这种边界用例上发现仿真是错的。处理方式是在模型里加一个参数把两种行为都包进去parameter READ_DURING_WRITE OLD_DATA; generate if (READ_DURING_WRITE OLD_DATA) begin always (posedge clk) begin if (we) mem[addr] wdata; dout mem[addr]; end end else begin always (posedge clk) begin if (we) mem[addr] wdata; else dout mem[addr]; end end endgenerate注意else dout mem[addr]这种写法在写使能时保持输出寄存器不变对应NO_CHANGE行为。不要在一份模型里同时用assign dout mem[addr]和时序块那会让仿真结果既不等于READ_FIRST也不等于WRITE_FIRST。3. 把 SRAM.zip 解开并快速找到可综合顶层3.1 解压前先检查压缩包内容拿到 SRAM.zip 别急着双击解压压缩包损坏或目录结构混乱是后面一连串error read zip archive怎么解决类问题的根源。先用命令行看清单unzip -l SRAM.zip | sed -n 1,40p输出里能看到文件体积、修改时间和完整路径。这时候我会直接过滤顶层文件unzip -l SRAM.zip | grep -E \.(v|sv|vh|hex|mem)$如果文件路径很长多半是 Windows 下压缩的目录嵌套直接在工程里添加文件会影响版本管理。更麻烦的是某些压缩工具会把中文文件名编码成 GBK在 Linux 的 Vivado 或 Quartus 里显示乱码。遇到这种情况先用unzip -O gbk SRAM.zip指定编码或者用7z l查看原始字节不要用改名脚本强行重命名因为 zip 内部目录项和文件内容可能不一致。3.2 修复损坏的 zip 文件而不是重新下载解压时如果出现error: cannot find zipfile directory或者更具体的could not find EOCD说明 zip 的中央目录尾在文件末尾缺失常见原因是下载中断或 FTP 传输截断。先用unzip -t SRAM.zip跑一遍 CRC 校验如果只是末尾尾部损坏zip -FF可以抢救大部分包体zip -FF SRAM.zip --out SRAM_fixed.zip unzip -t SRAM_fixed.zip注意zip -FF修复后文件路径可能会丢失结尾斜杠导致目录层级错乱所以修复完马上重新unzip -l确认。对于带密码的压缩包第一选择是找作者要密码而不是用外部破解工具很多所谓“zip密码移除”工具本身就会被杀毒软件报毒为了一个公开的 SRAM 参考模型不值得冒这个风险。3.3 同名模块去重压缩包里的隐藏雷解压后你常会看到sram.v、sram_top.v、sram_rtl.v三个文件模块名却都叫sram_main。两个文件同时加入工程后仿真器会报redefinition of module sram_main综合工具则会直接终止到 error 阶段。处理办法不是删除某个文件而是先列出所有模块名grep -h ^module *.v | sort | uniq -c拿到重复列表后把多余文件的模块名改成带后缀的别名例如sram_main_arch1、sram_main_arch2。如果两个版本的结构差异很大用ifdef做寄存器级切换更干净module sram_wrapper #( parameter DATA_WIDTH 16, parameter ADDR_WIDTH 8 )( input wire clk, input wire we, input wire [ADDR_WIDTH-1:0] addr, input wire [DATA_WIDTH-1:0] wdata, output wire [DATA_WIDTH-1:0] rdata ); ifdef USE_SYNC_MODEL sram_sync #(.DATA_WIDTH(DATA_WIDTH), .ADDR_WIDTH(ADDR_WIDTH)) u_sync (.clk(clk), .we(we), .addr(addr), .wdata(wdata), .rdata(rdata)); else sram_behavioral #(.DATA_WIDTH(DATA_WIDTH), .ADDR_WIDTH(ADDR_WIDTH)) u_behav (.addr(addr), .dout(rdata)); endif endmodule这样的好处是仿真和综合可以用同样的顶层实例名切换模型时不需要改连线。需要注意ifdef定义的传参路径必须一致尤其当两个子模块的端口名不同时wrapper 里要先统一成标准信号。3.4 把文件整理成可复用的相对路径结构解压后的文件我习惯统一整理成rtl/、sim/、tb/三个目录。rtl只放可综合的 SRAM verilogsim放行为模型和测试向量tb放测试台。无论走 Vivado IP 集成还是 Quartus Tcl 脚本相对路径都比绝对路径耐迁移。mkdir -p rtl sim tb mv *rtl*.v rtl/ mv *behavioral*.v sim/ mv *testbench*.v tb/整理完再写一个简单的filelist.frtl/sram_sync.v sim/sram_behavioral.v tb/tb_sram.v仿真的时候直接用iverilog -c filelist.f或-f filelist.f指定文件列表不再依赖图形界面手动添加。后面的测试台改动也只会影响tb/目录压缩包原始模型不会被误改。4. 把 SRAM Verilog 模型接到读写逻辑上并完整跑一遍仿真4.1 一个带读使能和写使能的单端口 SRAM 模板从 SRAM.zip 里选出的可综合模型最后大概率要经过你自己修改。这里给一个我常用的单端口同步 SRAM 模板带INIT_FILE参数方便用十六进制文件预置内容module sram_sync #( parameter DATA_WIDTH 8, parameter ADDR_WIDTH 10, parameter INIT_FILE )( input wire clk, input wire we, input wire re, input wire [ADDR_WIDTH-1:0] addr, input wire [DATA_WIDTH-1:0] wdata, output reg [DATA_WIDTH-1:0] rdata ); localparam DEPTH 1 ADDR_WIDTH; reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; integer i; initial begin if (INIT_FILE ! ) begin $readmemh(INIT_FILE, mem); end for (i 0; i DEPTH; i i 1) begin mem[i] {DATA_WIDTH{1b0}}; end end always (posedge clk) begin if (we) begin mem[addr] wdata; end if (re) begin rdata mem[addr]; end end endmodule关于这段 SRAM verilog有几个参数需要在使用前确认。DATA_WIDTH决定每个地址能存多少 bitADDR_WIDTH决定存储深度DEPTH 1 ADDR_WIDTH例如ADDR_WIDTH 10对应 1024 个地址。INIT_FILE可以留空直接上电清零也可以给一份.hex文件。读写时序上写操作与读操作共用一个时钟we拉高时数据在下一个时钟沿写入mem[addr]re拉高时rdata在下一个时钟沿更新。若we与re同时拉高这里实现的是读旧数据行为也就是READ_FIRST模型的读旧值分支。4.2 写一个能覆盖读写边界的 testbench只检查理想读地址是不够的SRAM 仿真中常见的错误是地址越界和读写冲突。下边这个测试台会把地址从低到高写一遍再读回来并在每个时钟沿检查rdatamodule tb_sram; reg clk; reg we; reg re; reg [9:0] addr; reg [7:0] wdata; wire [7:0] rdata; sram_sync #( .DATA_WIDTH(8), .ADDR_WIDTH(10) ) u_sram ( .clk (clk), .we (we), .re (re), .addr (addr), .wdata (wdata), .rdata (rdata) ); always #5 clk ~clk; integer i; initial begin clk 0; we 0; re 0; addr 0; wdata 0; repeat (2) (posedge clk); for (i 0; i 1024; i i 1) begin (posedge clk); addr i; wdata i[7:0]; we 1; end we 0; repeat (2) (posedge clk); for (i 0; i 1024; i i 1) begin (posedge clk); addr i; re 1; if (rdata ! i[7:0]) $display(Mismatch at %0d, read %h, expect %h, i, rdata, i[7:0]); end re 0; $finish; end endmodule这段 testbench 里写循环和读循环都等一个时钟沿再更新地址保证addr、wdata、we的建立时间落在时钟上升沿之前。比较rdata时用的也是!方便把未初始化的高阻x也当成错误。运行命令用 Icarus Verilog 即可iverilog -s tb_sram -o tb_sram.vvp sram_sync.sv tb_sram.sv vvp tb_sram.vvp如果用的是 ModelSim / Questa需要先vlog sram_sync.sv tb_sram.sv再vsim -c tb_sram。两种环境里的主要差异只是编译顺序INIT_FILE的相对路径要到启动仿真的工作目录去找不是 .v 文件所在目录。4.3 把读写冲突检测加进模型避免上板才暴露很多 SRAM verilog 模型不会主动报告读写冲突。你可以在always (posedge clk)前加一个always (posedge clk)检测块但更简单的做法是在读写同时为高时打一个打印always (posedge clk) begin if (we re) begin $display(Warning: simultaneous WR/RD at addr %h, addr); end end在大型验证环境中这种打印会非常多建议只在tb_sram这一层做不要放进最终的 RTL 模型里。否则综合时$display会被忽略但仿真时会产生大量冗余系统调用。5. 用双份例化对比 SRAM.zip 里的模型不放过一拍不一致5.1 行为模型和可综合模型同时驱动同一组地址很多 SRAM.zip 里同时存在行为模型和可综合模型与其用两套 testbench 分别测不如把它们放到同一个顶层里做对拍。行为模型负责提供“参考结果”可综合模型负责验证自己是否在每一拍都匹配。这个方式特别适合用iverilog做回归发现任何一拍不匹配都能立刻打印地址和数据。对拍的关键是让两个模型的输出在同一个采样点对齐。若行为模型是异步读、可综合模型是同步读需要把行为模型的dout打一拍再比较reg [7:0] rdata_b_ref; always (posedge clk) begin rdata_b_ref u_behavioral.dout; end always (posedge clk) begin if (re) begin if (rdata_b_ref ! rdata_rtl) begin $display(Mismatch at addr %0h: behaviorel%h rtl%h, addr, rdata_b_ref, rdata_rtl); end end end这里把行为模型的输出放入rdata_b_ref寄存一拍再和可综合模型的rdata_rtl在同一个 posedge 后比较。如果你的可综合模型是“写透”行为而行为模型是“读旧值”这种对拍会在写读同地址时正确暴露差异。5.2 快速测量地址空间覆盖率对拍不只查数据还要看是否把整个地址空间踩满。最简单的地址覆盖率统计是维护一个reg [ADDR_WIDTH-1:0] hit_mask每个被测地址写一次后置位reg [1023:0] hit_mask; always (posedge clk) begin hit_mask[addr] 1b1; end再在 testbench 结尾用$countones(hit_mask)打印命中地址数量。这个统计不消耗任何仿真内存却能在回归时快速发现地址总线高位被固定成 0 的问题。注意hit_mask的宽度必须不小于1 ADDR_WIDTH否则高位地址直接越界。5.3 把所有不匹配收敛成一个失败文件当对拍数据量很大时不要全丢到仿真终端里。在 testbench 里用$fopen打开一个mismatch.log把每个错误写进文件输出格式包含时钟周期数、地址、写数据、两个模型的读数据integer fd; initial fd $fopen(mismatch.log, w); always (posedge clk) begin if (mismatch) begin $fdisplay(fd, %0t: addr%h wdata%h bahave%h rtl%h, $time, addr, wdata, rdata_b_ref, rdata_rtl); end end这样仿真结束后打开mismatch.log就能按地址或时间排序分析。把$fdisplay改成$fwrite也是同样结果但更符合可综合模型的代码习惯。最后一遍跑完若mismatch.log为空说明这份 SRAM.zip 里的 SRAM 模型行为已经与你的可综合 RTL 对齐可以放心进入后续的 FIFO 或缓存控制逻辑开发。本文还有配套的精品资源点击获取
返回列表