
简介面向数字逻辑与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还是iverilogVerilator的仿真速度快但它是把Verilog翻译成C再编译执行的对testbench的写法有要求有些SystemVerilog的测试特性支持不好学习曲线稍高。iverilog对POC来说足够快而且GTKWave的集成很自然。等你真的需要跑大规模仿真或者做co-simulation的时候再上Verilator不迟。这里提个很多人忽略的细节仿真时一定要在testbench里把信号dump成VCD文件$dumpfile和$dumpvars否则波形看不了。我见过不止一次代码写完跑仿真console里打印一堆信号值说看起来对但波形文件没生成最后还得回头改testbench重跑。POC阶段波形图和打印输出缺一不可打印是快速判断波形是定位问题。3. POC代码骨架时钟、复位、计数器与testbenchPOC代码写得多了你会发现很多验证工作本质上都围绕几个最基本的构件展开时钟、复位、计数器、状态机。其中计数器又是最常用的——接口时序需要它分频需要它数据采样也需要它。所以我常说如果你的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 1b0; 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要回答的问题是在CPOL0/CPHA0这种模式下主机发一个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 8b0; end else begin shift_reg {shift_reg[6:0], mosi}; bit_cnt bit_cnt 1b1; end end always (posedge sclk) begin if (cs_n (bit_cnt 3d7)) rx_valid 1b1; else rx_valid 1b0; end always (posedge sclk) begin if (cs_n (bit_cnt 3d7)) rx_data {shift_reg[6:0], mosi}; end endmodulePOC阶段你不需要把这个从机做到多完整能收到数据并在接收完成时拉一个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从机模式支持CPOL0 CPHA08位数据带接收完成标志再写一个对应的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.vfinal_v2.v这样的文件最后谁都不知道里面是什么。至少要有清晰的模块名、文件头注释、关键信号的说明这些都是POC阶段少花不了几分钟、后面能省好几小时的事。做POC的心态应该是轻量、快速、准确但该有的记录一点都不能省。本文还有配套的精品资源点击获取