FPGA开发中的Verilog POC实战:快速验证概念,避免返工
2026/9/10 2:16:00 网站建设 项目流程

简介:面向数字逻辑与FPGA初学者的Verilog POC概念验证代码包,以最小可运行工程演示顶层CPU的波形模拟验证流程,帮助读者理解双向端口驱动规则与三态门OE使能逻辑,掌握ModelSim/Vivado等工具下的激励设置与边界测试思路。资源共77个文件,压缩包大小仅215KB,其中v/vwf/bsf/bdf为可编辑设计文件,cdb/hdb/qmsg/rpt等为Quartus自动生成的分析、映射与适配数据,便于对照中间过程排查综合与时序问题。配套printer.v与poc.v源码、bsf符号及bdf原理图,清晰展示模块划分与端口连接,可直接复用或二次修改。已有463人学习,适合正在做CPU或总线接口设计、需要参考三态门与inout端口写法的开发者。 做FPGA这行久了,你会发现一个特别常见的场景:产品经理或算法同事拿着一个想法过来,问"这个能不能放在FPGA上跑通?"。你要是二话不说直接开始写完整RTL,那你大概率要浪费两周时间——因为等代码写完、约束做完、上板调完,对面可能告诉你"哦,我们算法又改了"。所以我现在的习惯是:任何新想法、新接口、新算法,落地之前先写一版Verilog POC代码,花一两天把"行不行"这个问题回答掉。这篇就聊聊我做Verilog POC的思路、工具链和实操套路,给同样在搞FPGA、Verilog的朋友做个参考。

1. 先搞清楚:POC代码和正式RTL到底差在哪

POC是Proof of Concept的缩写,概念验证。在FPGA开发里,POC代码就是用来回答"这个想法能不能实现、性能大概什么量级、资源大概吃多少"的实验代码,它和最终交付的正式RTL是完全两种东西。很多人一开始心态就没摆正,写POC的时候非要按正式代码的标准来,结果验证一个接口花了人家三倍时间,这就本末倒置了。

我理解的两者差异,可以看这张表:

维度POC代码正式RTL
核心目标回答"行不行"回答"能不能量产"
代码风格怎么快怎么写可综合、可维护、可复用
时序要求能看到正确波形就行必须约束收敛、跑满目标频率
边界情况覆盖核心路径就够全覆盖,包括异常分支
资源占用无所谓必须精打细算
代码寿命验证完就扔要维护好几年

比如你要验证一个SPI从机能不能收到主机数据,POC阶段你可能只需要验证CPOL/CPHA四种模式下的8位收发,testbench里直接把主机的行为模拟出来,代码可能就几十行,怎么简单怎么来。但如果是正式RTL,你得考虑状态机异常恢复、FIFO溢出保护、跨时钟域处理、甚至复位掉电的时序,这些东西在POC阶段统统可以先不管。

还有一个容易踩的坑:POC代码的目的不是"尽量接近正式代码",而是"用最短时间获得足够可信的结论"。你得先想清楚自己到底要验证什么——是验证协议兼容性?还是验证算法误差在可接受范围?还是验证资源够不够?目标不同,POC代码的写法和精度就完全不同。我见过有人做FIR滤波器的POC,非要在PC上用Python先精确建模,再转化成Verilog,其实FPGA上用定点数仿一下,和浮点模型对比下误差,半天就能出结论了。

2. 搭建POC环境:我现在的轻量工具链和选型理由

做POC最重要的原则是"轻",如果环境搭建本身就要折腾一整天,那就违背了POC的初衷。我现在的标准组合是:VSCode写代码、iverilog做编译仿真、GTKWave看波形。这个组合免费、跨平台、上手快,而且足够应付绝大多数POC场景。热词里有人问"modelsim如何仿真verilog文件",Modelsim当然是好东西,公司里也常用,但个人做POC的时候装license、配环境真的很劝退。相比之下iverilog一条命令就能跑起来。

VSCode方面,装一个Verilog-HDL/SystemVerilog插件就够了,语法高亮、大纲浏览都有,偶尔还能帮你查个括号匹配。折腾过的人可能还知道可以配Verilog Format做格式化,但对POC来说其实无所谓,注释写清楚比格式整齐重要得多。

仿真流程我一般是这样跑的。假设你有一个设计文件counter.v和一个测试文件tb_counter.v:

# 编译设计文件和testbench,生成可执行文件 iverilog -o tb_counter.out counter.v tb_counter.v # 运行仿真,生成VCD波形文件 vvp tb_counter.out # 用GTKWave打开波形 gtkwave tb_counter.vcd

三步走,没有任何多余动作。如果你要验证的工程文件比较多,也可以写个简单的Makefile或者shell脚本,把编译命令固化下来。不过我的经验是:POC阶段文件通常不会超过三五个,直接命令行敲就行,不要过早引入构建工具的复杂度。

关于选择Verilator还是iverilog:Verilator的仿真速度快,但它是把Verilog翻译成C++再编译执行的,对testbench的写法有要求,有些SystemVerilog的测试特性支持不好,学习曲线稍高。iverilog对POC来说足够快,而且GTKWave的集成很自然。等你真的需要跑大规模仿真或者做co-simulation的时候,再上Verilator不迟。

这里提个很多人忽略的细节:仿真时一定要在testbench里把信号dump成VCD文件($dumpfile$dumpvars),否则波形看不了。我见过不止一次,代码写完跑仿真,console里打印一堆信号值说"看起来对",但波形文件没生成,最后还得回头改testbench重跑。POC阶段波形图和打印输出缺一不可,打印是快速判断,波形是定位问题。

3. POC代码骨架:时钟、复位、计数器与testbench

POC代码写得多了你会发现,很多验证工作本质上都围绕几个最基本的构件展开:时钟、复位、计数器、状态机。其中计数器又是最常用的——接口时序需要它,分频需要它,数据采样也需要它。所以我常说,如果你的POC代码里连一个计数器都没有,那大概率你没在干FPGA的活。

热词里有个"verilog语言二分频代码",其实就是计数器最经典的应用之一。一个简单的偶数分频器,比如10MHz时钟分成5MHz,本质就是计数器翻转一位:

module clk_div2 ( input wire clk_in, input wire rst_n, output reg clk_out ); always @(posedge clk_in or negedge rst_n) begin if (!rst_n) clk_out <= 1'b0; else clk_out <= ~clk_out; end endmodule

这段代码作为POC,要验证的就是一件事:clk_out的频率是否精确是clk_in的一半,占空比是不是50%。你用testbench跑200ns,数一下翻转次数就知道了。

配套的testbench是所有POC验证里最值得花时间写的东西。我总结了一个模板,基本可以应付所有中小规模验证:

`timescale 1ns / 1ps module tb_clk_div2; reg clk_in; reg rst_n; wire clk_out; // 1. 生成时钟,周期10ns,即100MHz initial clk_in = 0; always #5 clk_in = ~clk_in; // 2. 生成复位 initial begin rst_n = 0; #20; rst_n = 1; end // 3. 控制仿真时长并在结束前dump波形 initial begin $dumpfile("tb_clk_div2.vcd"); $dumpvars(0, tb_clk_div2); #200; $finish; end // 4. 被测模块实例化 clk_div2 uut ( .clk_in (clk_in), .rst_n (rst_n), .clk_out(clk_out) ); endmodule

这个模板里的四个部分,每部分都对应一个必须回答的问题:时钟从哪来?复位怎么拉?什么时候结束?被测模块接上没?写testbench的时候把这些问题过一遍,基本不会漏东西。我还习惯在关键节点加$display打印,比如"check counter value = 3 at time 50ns",这样一跑仿真就能立刻看到结果是否符合预期,不用每次都打开波形图数格子。

POC阶段的testbench还有一个作用:它是你验证思路的书面表达。你写testbench的过程,其实就是逼自己把"这个模块应该怎么工作"这个问题想清楚的过程。所以我的建议是:POC阶段宁可testbench多写一点,设计代码少写一点,也不要把顺序搞反。

4. 两类最常见的POC场景:接口验证与算法验证

FPGA的POC场景虽然五花八门,但总结下来主要是两类:验证接口协议是否走得通,验证算法实现是否靠谱。我分别说说这两类的套路。

接口类POC,最典型的就是SPI、I2C、UART这些低速协议。拿SPI从机来说,POC要回答的问题是:在CPOL=0/CPHA=0这种模式下,主机发一个8位字节0xA5,从机能不能完整收到。写POC代码的时候,你最需要关注的不是完整协议栈,而是采样点和移位时序。

下面是一个极简SPI slave接收逻辑的骨架:

module spi_slave_poc ( input wire sclk, input wire cs_n, input wire mosi, output reg [7:0] rx_data, output reg rx_valid ); reg [2:0] bit_cnt; reg [7:0] shift_reg; always @(posedge sclk or negedge cs_n) begin if (!cs_n) begin bit_cnt <= 0; shift_reg <= 8'b0; end else begin shift_reg <= {shift_reg[6:0], mosi}; bit_cnt <= bit_cnt + 1'b1; end end always @(posedge sclk) begin if (cs_n && (bit_cnt == 3'd7)) rx_valid <= 1'b1; else rx_valid <= 1'b0; end always @(posedge sclk) begin if (cs_n && (bit_cnt == 3'd7)) rx_data <= {shift_reg[6:0], mosi}; end endmodule

POC阶段,你不需要把这个从机做到多完整,能收到数据并在接收完成时拉一个valid脉冲就够了。testbench里模拟主机行为更是简单:手动翻转sclk、逐位给mosi赋值,然后检查rx_data是否等于0xA5。在真正的接口验证里,我还建议你把SCLK的周期做小一点,或者干脆不规则翻转,模拟真实主机的时序抖动,这样才能暴露采样点设置的问题。不过这是后话,POC阶段先跑通理想情况就行,别一上来就搞压力测试。

算法类POC就更看重数值和精度的验证了。拿热词里的"滑动窗口滤波verilog"举例,滑动平均的本质是连续N个采样点求和再取平均,在FPGA里用移位寄存器存窗口数据、用累加器做求和就行。POC阶段要回答的核心问题是:窗口大小N取多少才能达到你想要的信噪比改善,以及数据位宽选择多少才能不溢出。

这类POC我建议你走"三步走":先在PC上用Python或MATLAB把算法浮点模型跑通,得到理想输出;再用Verilog写定点实现,把同样的输入激励喂进去;最后把两者的输出做差,看误差是否在容忍范围内。很多刚上手的朋友喜欢直接写Verilog,然后盯着波形图看"好像差不多",这是不对的。波形图看不出量化误差,必须有数值对比。

我最近做一个8点滑动平均滤波的POC,输入数据是12位ADC采样值,输出保留12位。用Python模型和Verilog仿真对比了10000个随机点,最大误差不到1LSB,这个结论就可以支撑后续正式开发的决策。你在POC阶段把这个对比跑完,后面写正式代码的时候心里是完全有底的。

5. 用AI Agent写Verilog POC:效率翻倍,但要有底线的经验

最近圈子里聊得很多的是用AI Agent写Verilog代码,我自己也试了,说实话在POC阶段效率提升是实打实的。接口模块的骨架、分频器、FIFO读写控制这些相对标准化的代码,AI写出来基本能直接跑。热词里有人搜"claude code写verilog代码""ai agent verilog代码",说明大家都开始尝试了。

我现在的做法是:把POC需求拆成小任务,一个模块一个模块让AI生成,然后自己检查、仿真、再让AI修bug。比如我会这样描述需求:

"写一个Verilog模块,SPI从机模式,支持CPOL=0 CPHA=0,8位数据,带接收完成标志;再写一个对应的testbench,随机产生主机发送序列,dump VCD波形。"

这样一句话,AI通常能给出一个像模像样的初版。但我要泼三盆冷水:

第一,AI生成的代码仿真通过,不代表综合能过。我接过好几次AI写的代码,仿真波形完美,但里面有给寄存器用initial赋值、把for循环当软件用、甚至使用不可综合的运算符。这些在仿真器里都能跑,但综合器直接报错。POC阶段虽然不追求综合,但如果你后续打算转正式RTL,最好让AI生成的时候就带一句"可综合风格"。

第二,AI对时序的理解会出错。最典型的是跨时钟域信号没有打拍同步、异步复位的释放时序不对、边沿检测漏掉一种情况。这些bug在简单的testbench里看不出来,但恰恰是FPGA上板后最容易炸的地方。所以AI生成的代码,我每次都会重点检查三件事:时钟信号有没有混用、复位是异步还是同步、跨时钟域信号有没有同步器。

第三,AI不会主动问你需求细节。你如果说"写一个UART模块",它默认给你写9600波特率、8N1,但其实你要的可能是一个奇偶校验可配置的版本。所以在让AI干活之前,你得把接口定义、时序要求、位宽都说清楚,越具体越好。

不过我也得承认,AI最适合干的活就是POC这种"一次性代码":生成完了、验证完了、结论拿到了,代码直接扔了也不心疼。这比让AI直接写正式RTL要安全得多,因为它写那些"只验证思路"的代码,即使有瑕疵,也不至于埋雷到产品里。

6. 仿真通过只是第一步:下一步我还会做什么

很多人的POC止步于"仿真波形对了",然后拿着波形图就去汇报了。我记得第一次吃这个亏:一个FIR滤波器POC,仿真非常完美,但估算资源的时候发现要用快200个DSP48,小板子根本放不下。从那以后我就记住了:仿真通过只是POC的最低标准,你还得补上几个关键结论。

第一件事是资源估算。综合工具跑一遍,看看LE/LUT、FF、DSP、BRAM的消耗量级,判断目标芯片选型是否合适。POC阶段可以不做严格的时序约束,但资源占用必须看,因为这是影响项目成本的关键因素。

第二件事是简单上板冒烟测试,有条件的话在FPGA开发板上把POC代码跑起来,用ILA抓一下真实信号。仿真和真实电路的区别在于:仿真环境是理想化的,信号毛刺、时钟抖动、上下电行为都模拟不了。我遇到过UART POC仿真完美但上板收不到数据的案例,最后定位到是按键复位时间太短,板级RC复位电路没配合好,这个在仿真里根本不会出现。

第三件事是把POC阶段的关键参数记录下来。比如SPI的SCLK最高能跑到多少MHz、FIR滤波器的工作时钟和延迟是几个周期、误差范围是多少。这些数据是项目选型和后续正式开发最重要的输入。我一般会在POC代码的comments里直接写清楚结论,再把典型波形截图存到一个"POC结果"文件夹里,方便后面写设计文档时引用。

最后再说一个我踩过几次坑之后的教训:POC代码命名和结构要清晰。就算第二天就要丢掉,你也要保证别人(或者两周后的你)能看懂当时做了什么。我见过太多"test1.v""final_v2.v"这样的文件,最后谁都不知道里面是什么。至少要有清晰的模块名、文件头注释、关键信号的说明,这些都是POC阶段少花不了几分钟、后面能省好几小时的事。做POC的心态应该是:轻量、快速、准确,但该有的记录一点都不能省。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询