拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FPGA+R-2R实现正弦波DAC:Verilog设计、仿真与实测详解

简介面向数字信号处理与现场可编程门阵列学习者这是一套基于Verilog语言的数模转换器设计工程以Xilinx Vivado项目形式完整呈现演示数字量到模拟电压的转换逻辑。压缩包共九十九个文件整体大小约六百一十八千字节源码、约束、报告与工程配置已按源文件、综合实现、仿真等目录清晰划分主要包含Verilog源码、约束文件、Tcl脚本、综合报告、工程配置及比特流文件同时带有仿真波形数据。目前已有一百九十五人学习下载。通过这套工程读者可以理解数模转换器分辨率决定输出等级的原理例如八位转换器对应二百五十六个模拟电压档位可以学习用Verilog编写转换控制逻辑和接口设计还能借助Vivado完成从综合、实现到生成比特流的完整流程并通过报告分析资源占用和时序收敛情况。整个工程结构完整直接打开项目即可复现设计。对有数字电路基础、正在入门现场可编程门阵列或想移植数模转换模块的开发者都很有参考价值。1. 项目分析与整体方案接手 dac.zip_VFT_dac 这个工程的时候我习惯先解压看目录结构确认拿到的是不是那种网上随手打包、缺胳膊少腿的半成品。解压之后发现事情比想象中靠谱里面是一整套 Vivado 工程RTL 代码用的是 Verilog顶层模块就叫 VFT_DAC工程里还保留着设计者的仿真波形截图。说白了这个工程做的事情就是用 FPGA 的普通 IO 口接一个 R-2R 电阻网络通过 Verilog 里的计数器、相位累加器、ROM 查表在输出端得到频率可控的正弦波并且用 Vivado 仿真的前后波形变化把整条链路验证了一遍。这类设计在数字电路学习里几乎绕不开因为它把数字域的计数器、地址映射和模拟域的电阻分压、低通滤波完整串在了一条链路上。如果你正准备入门 FPGA想搞明白 DAC 前后波形到底差在哪或者项目里正好需要一路不依赖专用芯片的模拟输出这个工程都值得拆一遍。下面我会按方案选型、Verilog 实现、Vivado 实操、问题排查这条线来讲透。1.1 DAC实现方案的取舍FPGA 输出模拟信号常见路子有三条外挂 DAC 芯片、用 IO 口拼 R-2R 电阻网络、用 PWM 加低通滤波。三条路我都在项目里试过差异其实非常明显。方案成本精度速度上限调试难度适用场景并行/SPI DAC芯片高高12bit以上高中高速波形、通信系统R-2R电阻网络低中8~10bit受IO速率限制低学习验证、低频波形PWMRC低通最低低等效6~8bit低低调光、调压、简单信号这个工程选 R-2R 而不是现成 DAC 芯片我认为是刻意为之。一方面 R-2R 网络只需要两种阻值的电阻和 FPGA 板载端接电阻的套路一脉相承原理上比 PWM 滤波更接近真 DAC的分压模型另一方面没有 SPI 或并行接口时序的干扰可以把注意力完全放在波形生成算法上。代价是精度主要靠电阻精度撑着1% 精度的电阻做到 8bit 没问题想上 10bit 就得选 0.1% 精密电阻了。还要提醒一句R-2R 网络的输出阻抗是固定值 R如果直接用示波器探头去测阻抗不匹配会拉低电压。工程里如果要求严格输出端要加一级运放电压跟随器我用的是 LM358 做的跟随简单够用。1.2 数字侧的整体数据流从顶层往下看这个工程的数字链路是典型的 DDS直接数字频率合成结构参考时钟进来到相位累加器累加器的高位当作 ROM 地址ROM 存着波形表查出来的数据经过输出寄存器送到 IO 口IO 口外面挂 R-2R 电阻网络。为什么用相位累加器而不是简单的计数器去扫 ROM 地址这是这个工程里最值得想明白的一点。计数器扫地址只能输出固定频率波形想换频率就得改系统时钟或者改计数器上限频率一变相位连续性就会出问题。相位累加器不存在这个问题每次累加一个步长频率由步长决定相位始终是连续累积的改步长就能瞬间改频波形不会出现跳变。工程里的数据位宽分配也比较典型32 位相位累加器取高 8 位做 ROM 地址ROM 深度 256输出 8bit 数据。这个组合在精度和资源占用之间取了平衡。后面我会把这个位宽设计背后的计算过程完整过一遍。2. Verilog核心设计与实现细节2.1 R-2R网络与输出关系要理解代码在算什么先得清楚 R-2R 网络的电压关系。R-2R 网络是个梯形结构每个 bit 对应的权重是二进制的最高位贡献 Vref/2次高位贡献 Vref/4依此类推。8bit 数据 D 对应的输出电压是Vout Vref × (D / 256)这个公式非常直观逻辑电平是 3.3V 时最低位LSB对应的电压就是 3.3 / 256 ≈ 12.9mV。这个精度做波形发生器够了做高精度电压源就吃力了。代码里不需要做任何模拟计算FPGA 只需要把数字码送到引脚上电阻网络自然会按权重分压。但这里有个关键细节IO 口的驱动能力和输出阻抗会影响分压精度。我在约束文件里会把输出引脚驱动强度设置成合适的档位一般是 LVCMOS33 标准、驱动电流 8mA 或者 12mA具体看引脚手册。如果驱动能力不足波形顶部会被削平。2.2 相位累加器参数计算DDS 的核心公式是频率字步长的计算。设参考时钟为 Fclk相位累加器位宽为 N目标输出频率为 Fout则每个时钟周期需要累加的步长为M Fout × 2^N / Fclk这个工程如果跑在 50MHz 时钟下要输出 1kHz 正弦波N 取 32那么 M 1000 × 2^32 / 50,000,000 ≈ 85899.35取整 85899实际输出频率就是 85899 × 50,000,000 / 4,294,967,296 ≈ 999.994Hz误差不到千分之一。频率分辨率则由 2^32 位宽决定50MHz / 2^32 ≈ 0.0116Hz也就是说只要步长加 1输出频率变化 0.0116Hz做精细调频完全够用。为什么取高 8 位当 ROM 地址而不是全部 32 位都接过去因为 ROM 深度不可能做 43 亿点取高 8 位相当于把相位量化成 256 等份每个等份 360/256 1.406 度。这个相位量化带来的杂散很小在 8bit 输出精度的场景下基本看不出差异。如果后面要升级到 12bit 输出ROM 深度可以提到 1024 或者 4096对应低 8 位留着做相位抖动处理那是后话。2.3 关键代码参考工程里最核心的一段是相位累加器代码非常简洁parameter N 32; localparam FREQ_WORD 32d85899; // 50MHz时钟下输出1kHz reg [N-1:0] phase_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc {N{1b0}}; else phase_acc phase_acc FREQ_WORD; end wire [7:0] rom_addr phase_acc[N-1:N-8]; // 取高8位做ROM地址ROM 表的实现有几种做法工程里用的是 case 语句组合逻辑查表。我用过 $readmemh 加载 .mem 文件的写法好处是换波形不用改 RTL坏处是多一个工程文件要管理。case 写法在小型工程里反而直观reg [7:0] dout; always (*) begin case (rom_addr) 8d0: dout 8d128; 8d1: dout 8d131; // 后面按正弦表填充256个点 default: dout 8d128; endcase end输出寄存器必须加这是消除组合逻辑输出毛刺的关键。ROM 查表本质是组合逻辑数据变化瞬间会有竞争冒险直接接引脚会在模拟波形上出现尖峰这个工程在输出前打了一拍寄存器把数据变化对齐到时钟沿波形干净很多。reg [7:0] dac_out_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) dac_out_reg 8d0; else dac_out_reg dout; end3. Vivado实操与波形对比验证3.1 Testbench与仿真配置这个工程最值得看的部分其实是它的仿真验证环境。作者写 testbench 的时候不只是把数字信号拉出来看还在仿真里模拟了 R-2R 网络的输出电压这样就能对比 DAC 前后的波形差异。testbench 里模拟输出电压不复杂用 Verilog 的 real 类型就能算real analog_out; wire [7:0] dac_out; always (*) begin analog_out 3.3 * dout / 256.0; end在 Vivado 自带的 xsim 里加 analog_out 这个信号到波形窗口选 analog 显示模式就能直接在仿真波形里看到类似示波器的正弦曲线。这个技巧我强烈建议用上否则纯看数字码流很难直观判断波形质量。仿真配置上Vivado 默认会对所有信号做优化有时波形窗口看不到内部信号。要么在综合属性里给模块加 mark_debug要么在 testbench 里把 key 信号用 $display 打出来。我一般两种都做仿真初期看波形回归时打日志效率最高。3.2 DAC前后波形到底差在哪工程里那个 dac.zip 之所以叫 VFT我理解原作者说的就是功能测试——重点验证 DAC 前后的波形变化。实际仿真跑完你会看到三个层面的差异第一层是数字码流本身dout 从 0 到 255 再回到 0呈现的是典型阶梯状正弦第二层是 real 类型模拟出的 R-2R 输出电压也是阶梯状但每个台阶对应一个输出数据第三层是理想正弦曲线和数字化之后的楼梯波叠加对比能明显看到量化误差。8bit 的量化台阶落后平滑正弦和楼梯波之间的最大偏差约半个 LSB也就是 6.5mV 左右这符合预期。这种对比要盯的不是像不像而是三个关键指标台阶是否完整、有没有异常毛刺、频率是否准确。毛刺通常出在数据从 127 跳到 128 这种多 bit 同时翻转的时刻因为引脚电平翻转有先后中间会产生不希望的中间值。解决办法就是之前说的输出打一拍再加上 IO 驱动强度别开太大。3.3 约束与上板实测上板之前约束文件要写清楚除了引脚分配IO 标准也别忘了。8bit 数据输出加时钟一共 9 根线要分配引脚。约束大概是这样的set_property PACKAGE_PIN E3 [get_ports dac_out[0]] set_property IOSTANDARD LVCMOS33 [get_ports dac_out[0]] set_property DRIVE 12 [get_ports dac_out[0]]实测的时候示波器探头要接地线夹尽量短。R-2R 网络输出端如果不加低通滤波示波器看到的就是带台阶的波形这是正常的。要得到平滑正弦在输出端加一级 RC 低通截止频率取信号频率的 10 到 50 倍之间。比如信号 1kHz取截止 20kHzR 用 1kΩ那 C 1 / (2π × 1000 × 20000) ≈ 8nF取标称 10nF 就好。这个低通会去掉高频量化噪声但也会造成一点点幅度衰减实测时心里有数就行。4. 常见问题与排查记录4.1 Vivado环境与仿真问题用 Vivado 跑这种工程最容易翻车的不是 RTL 代码而是环境本身。仿真闪退这个问题我至少遇到三次现象是 point 到某个波形时间点直接退出或者 xsim 突然无响应。后来发现基本是工程路径有中文或空格、缓存目录被塞满或者同时开了太多波形窗口。处理方式很简单工程目录用纯英文路径定期清理 .cache .runs 目录仿真别一次开几十个窗口。还有一种常见问题是波形窗口显示不了 real 类型信号。xsim 的波形窗口默认不把 real 类型当成模拟信号看要在 Waveform 窗口的设置里手动把信号添加成模拟显示模式否则只能看到数字量 0。这个我第一次搞的时候懵了很久一度以为代码算错了。4.2 波形异常速查表把工程调试过程中踩过的坑整理成一张表遇到对应现象可以直接对着查。现象可能原因解决办法波形台阶异常、幅度不对IO驱动能力不足或引脚没分配检查约束文件调整DRIVE档位波形跳变处有尖峰毛刺组合逻辑输出未打拍输出加一级寄存器波形底部直流偏置偏高输出端没有接负载电阻R-2R输出加下拉或运放跟随频率和预期差很多相位累加器位宽截取错误确认ROM地址取的位段仿真波形是粗锯齿状real类型信号没切换显示模式波形窗口转analog模式输出噪声大、不稳定地线过长或数字模拟共地隔离数字模拟地、缩短地线夹4.3 关于地线那些事上板实测最容易忽略的是地回路。R-2R 网络接到示波器如果只接信号线不接地线波形会飘得厉害。更讲究的做法是数字部分和模拟部分单点接地FPGA 板的数字地和 R-2R 网络的模拟地走线先分开在电源入口处汇合。板子不大的话这个工程直接共地也能工作但波形的高频噪声会明显一些。另外输出线要短我最早测试时用了一根二十厘米的杜邦线连接 R-2R 网络和示波器波形上全是高频振铃。换成同轴线直接接振铃立刻消失。这个细节对 R-2R 这种高输出阻抗的电路影响特别大。5. 后续扩展方向与个人体会5.1 从R-2R换到真正的DAC芯片这个工程的框架不止能驱动 R-2R 网络。把 dout 的 8bit 并行输出改成一个 SPI 接口是半天的工作量加一个 spi_dac 模块把 dout 按字节拆成 16bit 的命令字按时序发出去。我后来在另一个项目里用 AD9744 替代了 R-2R只改了输出级DDS 部分基本原封没动频率动态范围直接从低频做到了几兆赫兹波形质量也上了两个档次。想进阶的读者完全可以把这当成下一步练手目标。5.2 一点个人心得做这个类型的工程我的体会是先建模、再写码、最后才是调板子。把 DDS 的频率字算明白、把 R-2R 的分压关系写在纸上比急着打开 Vivado 敲代码重要得多。这个 dac.zip 工程之所以质量高不是因为它用了多高级的电路结构而是设计者在每个环节都留了验证手段——仿真里有模拟波形对比代码里有输出寄存器约束里有驱动档位设置。这种每个中间结果都可检查的习惯才是这个工程真正值得学的东西。最后再分享一个小技巧调试 R-2R 波形的时候把示波器探头衰减系数调到 1x先别急着看是否平滑先确认峰值电压和计算值是否对得上。峰值电压对了说明数字链路、电阻网络、基准电压都在正常范围内这时候再去处理波形细节才不会越调越乱。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门