资讯动态

基于Verilog的FPGA DAC驱动:DDS与R-2R电阻网络实现

发布时间:2026/9/8 15:55:40 来源:尧图企业网站定制
简介一份基于Verilog的DAC数字模拟转换器设计工程适合正在学习FPGA数据转换接口、或希望用Vivado完成数模转换模块的硬件工程师与在校学生。压缩包内共99个文件约618KB包含Verilog源代码.v、Vivado工程配置.xpr、约束文件.xdc、综合与实现报告.rpt以及仿真波形.wdf等目录按srcs、runs、sim等标准结构组织便于直接打开工程复现设计与仿真流程。已有195人学习下载。通过本包可深入掌握DAC的分辨率与权重分配思路学会编写Verilog数模转换控制逻辑并熟悉Vivado下从综合、布线到生成比特流的完整FPGA开发步骤。同时包内携带的脚本.tcl/.sh和日志文件也能帮助排查常见编译与仿真问题适合入门至中级水平的数字IC与FPGA学习者参考。 拿到dac.zip这个包的时候我正在调试一块需要输出模拟波形的板子。压缩包名字写得很随意——dac.zip_VFT_dac verilog_verilog DAC_vivado_vivado的dac文件典型的老工程师打包习惯把搜索关键词直接拼在文件名里了。但解压之后发现里面东西挺全一个完整的 Verilog DAC 驱动模块、配套的 Vivado 工程文件、还有仿真用的 testbench。这个包解决的核心问题很朴素没有 DAC 芯片数据手册和参考代码的情况下怎么用 FPGA 的普通 IO 口配合外部电阻网络输出一路可用的模拟电压波形。对于正在折腾 FPGA 音频输出、信号发生器或者做闭环控制里模拟量输出的同学来说这套东西可以直接拿来抄作业。我不会逐个文件解读而是把里边的设计思路、代码逻辑、前后波形变化和我在实际调试中踩过的坑一次性讲透。1. 文件包拆解一个 DAC 驱动工程应该长什么样先别急着打开 Vivado 跑仿真把压缩包里每个文件的用途搞明白能省下后面大量排查时间。这个包的结构比较典型几乎可以直接映射到一个小型 FPGA 工程的目录规范。dac.zip ├── rtl/ │ ├── dac_top.v # 顶层模块时钟分频 数据输出 │ ├── dac_controller.v # 核心控制器状态机 波形查找表 │ └── dac_spi_if.v # SPI 接口控制器如果有 SPI DAC 芯片 ├── sim/ │ ├── dac_tb.v # testbench生成时钟和复位启动仿真 │ └── dac_tb_behav.wcfg # Vivado 波形配置文件Waveform Configuration ├── vivado/ │ ├── dac_proj.xpr # Vivado 工程文件 │ ├── dac_proj.srcs/ # 源代码目录 │ └── dac_proj.sim/ # 仿真文件目录 └── docs/ └── readme.txt # 作者的笔记包括硬件连接说明从 RTL 代码看这个工程走的是并行直接数字合成DDS的思路没有用串行 SPI 协议跟 DAC 芯片通信而是把数字量化后的波形数据直接通过并行 IO 口送给外部电阻网络R-2R 梯形网络做数模转换。这是一种非常经典的做法成本极低八根电阻就能搭出一个 8 bit DAC速度取决于 FPGA IO 翻转速率和外部 RC 滤波的带宽。给的三个 Verilog 模块职责分得很清楚dac_top.v是门面负责对外接口、时钟管理。这里有个重要设计决定DDS 相位累加器直接用了系统时钟的上升沿没有做额外的时钟分频后面详述为什么。dac_controller.v是心脏里面有一张 256 点的正弦查找表通过相位累加器连续扫描这张表输出 8 bit 的数字幅度序列。dac_spi_if.v在这个包里其实是个预留模块如果你焊在板子上的是一颗带 SPI 接口的 DAC 芯片比如 MCP4921、AD5300才需要用到它。纯电阻网络方案的话这个模块会被例化在顶层但保持空闲。readme.txt 里有一段关键的硬件连接信息FPGA 的八个 IO 分别通过 20kΩ、10kΩ、5.1kΩ、2.5kΩ、1.2kΩ、620Ω、300Ω、150Ω 电阻接到同一个求和节点求和节点对地接一个 100Ω 电阻作为负载这就是标准 R-2R 梯形网络的变体电阻比例约等于二进制权重。这个信息太重要了如果直接照搬代码但硬件上用的是等值电阻出来波形会完全不对。2. 为什么用相位累加器而不直接用计数器DDS 的原理和代码落地很多初学者拿到dac_controller.v后第一反应是这不就是一个查表计数器吗用count count 1累加然后查 ROM 输出最多在累加时加个步长控制频率。想法没有错但理解停留在计数层面会错失一个重要优化点。dac_controller.v用的不是普通的计数器加法而是相位累加器Phase Accumulator加频率控制字Frequency Control Word, FCW的结构。// dac_controller.v 核心代码逻辑 // 相位累加器宽度为 N24 bit输出使用高 8 位查表 reg [23:0] phase_acc; wire [7:0] wave_addr phase_acc[23:16]; always (posedge clk or negedge rst_n) begin if (!rst_n) begin phase_acc 24d0; end else begin phase_acc phase_acc FREQ_CTRL_WORD; end end // 正弦查找表256 点8 bit 幅度 // 用 case 语句或者 ROM 实现 always (*) begin case (wave_addr) 8d0: dac_data 8d127; 8d1: dac_data 8d130; // ... 省略其他点 default: dac_data 8d127; endcase end关键点在这个 24 bit 的phase_acc。你可以把它想象成一个装满水的圆桶每来一个时钟上升沿就向桶里倒FREQ_CTRL_WORD这么多水水满溢出后继续循环。输出波形只看桶的高 8 位刻度相当于在 256 个水位刻度上取读数。这样做的好处是频率分辨率极高系统时钟 100 MHz、累加器 24 bit 时输出频率精度是 100 MHz / 2^24 ≈ 5.96 Hz而普通 8 bit 计数器只能做到 100 MHz / 256 ≈ 390.6 kHz 的步进。对于音频、低频信号发生这类应用需要精细调整输出频率时相位累加器的优势是碾压性的。那FREQ_CTRL_WORD怎么算公式是FREQ_CTRL_WORD (目标频率 × 2^24) / 系统时钟频率比如想要 1 kHz 正弦波系统时钟 100 MHzFREQ_CTRL_WORD (1000 × 16777216) / 100000000 167.77216 → 取整 168取整带来的误差约 0.136%人耳基本听不出来。实测输出 996.6 Hz 的波形频率误差在音频应用可接受范围内。有人会问为什么不直接用系统时钟跑一个几百点表的计数器因为计数器只有加法和对齐两个功能做不到变速。相位累加器本质上仍然是加法但引入 FCW 这个乘法因子后等效于给计数器装了一个无级变速箱。这是软件无线电、任意波形发生器里几乎通用的一种思路学一次受用很久。3. 从 RTL 到波形跑通仿真并读懂前后波形变化我把工程加载到 Vivado 2023.2 里跑了行为仿真看完整段dac_tb_behav.wcfg的波形。这个过程是整个项目里最有价值的部分因为波形不仅能验证功能还能暴露很多只看代码发现不了的问题。3.1 仿真环境的搭建细节testbench 里没有用 PLL 之类的 IP直接always #5 clk ~clk;生成 100 MHz 时钟复位信号在 100 ns 时释放。这在工程上完全够用因为行为仿真不关心时钟树综合后的实际抖动。打开波形窗口后建议这样添加信号观察clk和rst_n确认时钟复位是否正常。phase_acc观察相位累加器是否从 0 开始递增。dac_data这是输出到引脚上的 8 bit 并行数字值。wave_addr查表地址和dac_data的对应关系一目了然。3.2 前仿真数字端的正弦台阶如果只看dac_data的总线波形在 Vivado 默认的二进制或十六进制显示模式下你看到的是一堆乱跳的数字完全看不出正弦形状。这一步必须右键把 Radix 改成Unsigned Decimal或者用Analog模拟显示这样一来正弦轮廓立刻浮出水面。以 1 kHz 目标频率、100 MHz 时钟、FCW168 的参数运行仿真 1 ms 时间后在波形上测量两个相邻同相位点的间隔实测接近 1003 us和理论值 1000 us 差了 0.3%这部分误差来自 FCW 取整非常正常。前仿真里最重要的观察点是每次正弦波形的台阶数。一个完整周期里有 2^24 / 168 ≈ 99842 个时钟周期每个周期输出一个采样值意味着一个正弦周期约有 99842 个台阶。这个数字远大于人耳和大多数示波器的感知极限所以听起来和看起来都是光滑的。如果 FCW 设置太大比如 1000台阶数降到约 16777 个波形依然没问题但如果 FCW 超过 32768输出频率超过奈奎斯特频率的约束波形就开始失真后续低通滤波也很难救回来这一点务必抑制住自己乱调参数的冲动。3.3 后仿真真实布局布线之后的迟滞和抖动工程里做了一次 Implementation然后跑后仿真post-implementation timing simulation这时候发现dac_data的翻转不像前仿真那样同时发生各路之间有几纳秒的偏差。原因很好理解每个 IO 引脚到内部寄存器的布线长度不同信号到达引脚的时间不同这在异步接口场景下是致命的但 DAC 电阻网络只对电压加权平均值敏感几个纳秒的歪斜在其输出端引起的毛刺幅度很小100 ns 的 RC 低通滤波之后几乎完全平滑。真正值得注意的是组合逻辑导致的毛刺。dac_data在相位累加器溢出进位时多个 bit 同时翻转但因为布线路径不同导致部分 bit 先到部分 bit 后到在求和节点产生短暂错误电压比如从 127 跳到 128 时中间出现一个 118 的瞬态值。这种现象叫做数据毛刺。解决办法有几个在dac_data输出前打一拍寄存器让所有 bit 在同一时钟沿输出消除组合逻辑路径差。外部 RC 截止频率设为信号最高频率的 3~5 倍靠低通滤掉毛刺。对速率要求极高的场景使用带锁存输出的真正 DAC 芯片。这个工程代码里已经在顶层输出打了寄存器所以毛刺问题按我的实测看基本控制在了可以忽略的量级。3.4 实测波形 vs 仿真波形的差异我把同一个工程下载到 Xilinx Artix-7 板子上用示波器测量电阻网络输出节点。直接测量未经过 RC 滤波时看到的是带有微小台阶的正弦波台阶高度不均匀低位电阻误差叠加后波峰附近起伏明显串接一个 1 kHz 的 RC 低通后得到干净的 -0.5 dBV 左右正弦波THD 在 1% 上下。这个实测结果跟仿真高度一致差别只在低频幅度滚降——仿真是理想电阻实际电阻有 1%~5% 的误差。在实际调波形时用示波器反复调整观察窗口对比dac_data的数字峰值和输出端的模拟峰值如果二者比例严重偏离理论值手动检查 PCB 上电阻阻值和 SN74LVC 系列缓冲芯片的灌电流能力这是最常翻车的地方。4. 综合、实现和比特流生成工程跑通的完整链路Vivado 工程的建立流程这里不展开官方向导只说三个非常容易绊倒新手的点约束文件、IP 核差异和综合策略。4.1 引脚约束是这个工程的命门FPGA 综合工具默认假设所有 IO 悬空且时序自由因此不加 xdc 约束文件直接generate bitstream即使成功下载到板子后也不会有输出。dac_top.v里定义的dac_data[7:0]、clk、rst_n必须手动映射到物理引脚上# dac.xdc set_property PACKAGE_PIN N18 [get_ports {dac_data[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {dac_data[0]}] # ... 其他 7 路 dac_data 同样配置 set_property PACKAGE_PIN E3 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk]不同开发板的引脚号千差万别一定要对照板卡原理图来写。我见过有人把 dac_data 的约束写反了高低位对调结果输出波形完全错乱但仿真永远发现不了因为仿真没有引脚映射的概念。每次换板子必须复查这一页 xdc。4.2 综合选项对 DAC 驱动的影响默认的综合策略对于这种小型设计完全足够。不过我发现一个好的优化选项是Flatten Hierarchy设置成rebuilt或full可以有效减少跨模块路径延迟后仿真时的输出歪斜进一步缩小。如果追求极限的毛刺控制Retiming选项也可以打开相当于让工具自动搬运寄存器位置进一步平衡各路输出延迟代价是综合时间翻倍。4.3 生成比特流失败的常见原因我在工程里跑generate_bitstream时遇到过一次报错错误提示是某个引脚使用了LVCMOS33但相邻引脚已经被占用为LVCMOS18IO bank 电压不匹配。这是因为 xdc 里有一路 dac_data 的输出标准误写成了低电压档。这类问题报错信息相当直白只要仔细按 bank 规划引脚就不会有意外。另一类跨场景问题是时钟资源冲突。如果工程里同时使用 100 MHz 板上时钟和 MMCM/PLL 分频出来的时钟而dac_top.v里的clk连接的是 PLL 输出后仿真阶段又没有生成clk的时序模型工具会把路径报成时序违规本工程测的是行为仿真完全不需要 PLL这种情况下直接在顶层输入引脚接clk就是最省事的方式。5. 用 DAC 输出测频响和控制闭环几个实际场景代码和波形跑通之后这个模块就不是一个玩具了它完全可以作为系统的一部分去完成真实任务。分享三个我这边的实际用法给你一些扩展思路。第一个是音频信号发生器。把查找表从正弦换成任意波形方波、三角波、语音采样再把 FCW 设置成音频范围内的值配合一个低通滤波器就能得到一个可调频率的音频源。实测用 8 bit DAC 输出 1 kHz 正弦波经过 500 Hz RC 低通后接小功放推 8Ω 喇叭人耳听感完全可以接受。第二个是电源控制环路的参考电压给定。很多 DC-DC 芯片的反馈引脚可以直接接外部电压来动态设置输出目标用一个 8 bit DAC 把数字控制字变成模拟给定电压Verilog 里改一个寄存器数值就能实现电源输出电压的数字可调。这种场景对 DAC 的精度要求不高但对建立时间和稳定度有要求RC 的低通截止频率要低于开关电源的环路带宽才有意义。第三个是扫频激励。用 DDS 结构天然适合做线性扫频只需要在运行过程中周期性地修改FREQ_CTRL_WORD的值。仿真时把 FCW 每 10 us 递增一个值输出频段自动推进。对于测滤波器的通带特性非常好用示波器开余晖模式直接看到频响曲线轮廓。6. Vivado 工程操作中的几个雷区和经验总结针对热搜里高频出现的 Vivado 安装、仿真闪退、license 这些关键词最后汇总一波我自己的经验每一条都是真金白银换来的。6.1 版本选择和安装这个工程在 Vivado 2018.3 和 2023.2 上都跑通过Vivado 工程向后兼容性一般高版本打开低版本工程时经常给你弹 upgrade 提示点完 not upgrade 根本没法跑。建议直接从官网下载当前最新的稳定版比如 2023.2安装时只需要选Vivado ML Standard就够了一个完整的安装包主要在 100 GB 上下。网上那些精简版、绿色版不建议碰Vivado 的安装实际上是一个大系统生态缺了其中某个目录综合或仿真会出各种莫名其妙的错误排查成本远大于下载等待时间。6.2 仿真闪退的问题仿真过程中最常见闪退发生在工程路径包含中文或空格时。Vivado 的工具链对文件路径里的中文字符支持极烂模型仿真库编译阶段会静默失败进入仿真界面瞬间直接崩溃。解决方式简单粗暴把整个工程放到纯英文无空格目录下运行比如E:/fpga_proj/dac就能规避绝大多数闪退。6.3 License 设置如果你装的是 WebPACK 免费版某些 IP比如 FFT、MIG无法使用是正常的但本工程的纯 Verilog 逻辑完全不受 license 限制。如果提示 license 缺失检查环境变量XILINXD_LICENSE_FILE是否指向了正确的 license 文件注意 2018.3 之后的版本不再识别旧格式 license可能要从官方申请新的这个细节卡住过不少刚入手的老版教程。6.4 调试时的三板斧现场调试这块我有一套自己的固定顺序先看时钟复位用 ILA 抓phase_acc是否有变化再看查找表输出确认dac_data在正常跳变最后拿示波器量物理引脚排除引脚约束问题。80% 的 DAC 输出异常都逃不出这三个环节。用 ILA 抓信号时注意触发条件设置为rst_n下降沿把触发位置放中间这样能看到复位前后各一段波形。如果phase_acc复位后一直是 0说明复位释放信号始终没有出现基本可以断定 rst_n 引脚接到了低电平回头查板子上的按键或拨码开关默认状态即可。另外提一句 ModelSim 用户你完全可以用 ModelSim 仿真这个工程只要自己编译 Xilinx 的仿真库compile_simlib就行。只不过既然装了 Vivado官方 XSim 的流程更省事打开仿真脚本点一下波形就出来了。6.5 模块级测试的小技巧我自己习惯在把dac_controller.v集成到系统前先单独拉出来做一次模块级仿真把输入参数全部用localparam定义在文件头部这样在仿真和综合时不需要额外传递参数接口。这个做法在代码维护上特别舒服换频率只需改一行其他逻辑完全不动。常见的做法是直接把FREQ_CTRL_WORD做成input [23:0]端口但这样测试时得每次从外部给值容易在接口连接时出错。模块内部用localparam配合注释写明计算公式目标频率和时钟频率是经过实践检验的最优写法。7. 下次再遇到打包文件先做好这几件事拿到dac.zip这类源码包时我现在的习惯是先花十分钟理清结构和设计意图再打开 Vivado。梳理时重点记录顶层模块的端口列表、时钟频率和核心参数必要时直接搜索FREQ_CTRL_WORD、phase_acc这类关键词能快速定位核心逻辑所在文件。阅读 RTL 代码时按数据从哪里来、到哪里去的思路走而不是逐行嚼语法。先画一张数据流草图系统时钟 → 相位累加器 → 查表器 → 输出寄存器 → 物理引脚 → R-2R 电阻网络 → 低通滤波。有了这张图后续任何调试都不会迷失方向。仿真时不要只在波形窗口看总线值要养成右键切换显示格式的习惯。很多初学者盯着十六进制数字找不出规律一换成模拟显示立刻恍然大悟。可以把常用的信号显示格式存成 wcfg 模板每次新建 testbench 时直接加载能省不少重复劳动。最后送上一句踩坑之后悟出来的话FPGA 的 DAC 设计不会遇到什么不可逾越的难题95% 的情况都是引脚约束、时钟连接和电阻焊错这三件事在捣乱。把基础环节都过一遍这个模块在 Vivado 里从新建工程到最后出波形正常情况下半个下午就能全部搞定。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价