这一篇在干嘛?

FPGA 只能输出 0 和 1,怎么让它产生一条平滑的正弦波?答案是 DAC(数模转换器)。本实验在 FPGA 内部用 ROM 预先存好一个周期的正弦表,地址计数器不断递增、逐点读出数据,送给板上的 12 位 DA902 转换芯片还原成模拟电压——接上示波器,就能看到一条连续的正弦曲线。这是「DDS 直接数字频率合成」最朴素的雏形。

实验目标与原理

实验目的

制定 ROM 数据,通过 12bit DA 产生正弦信号发生器。

数字量如何变成模拟量

DAC 的任务很单纯:输入一个 N 位二进制数,输出一个和它成正比的电压。以 12 位 DAC 为例,输入范围是 0~4095,对应输出电压的 0~满量程,每变化 1 个码值,输出电压跳变「满量程 ÷ 4096」。只要输入码值变化得足够快、足够规律,经过外围的平滑,输出的阶梯波看上去就是连续曲线。

于是产生正弦波的思路就清晰了:正弦函数 y = sin(x) 的值是连续的,但我们可以把一个周期均分成很多份「采样点」,把每个点的幅值预先算好、量化成 12 位整数,存进 FPGA 的 ROM(查找表)里。运行时让地址计数器一遍遍从 0 扫到表尾,ROM 就源源不断地把正弦幅值送出去——地址扫得越快,输出正弦波的频率就越高。这就是 DDS(直接数字频率合成)的基本思想。

本设计的实现结构

在 FPGA 内部定制一个正弦信号发生的数据,详见此例 SIN.COE 图 24.1,再送给 DA 转换成模拟信号,通过示波器观察。

24.1 生成的 coe 正弦波文件。

  1. RTL 图文件如下 24.2:

24.2 RTL 原理图

数据通路可以概括为三步:

  1. 时钟生成:板载 50M 时钟经过锁相环,分别输出两路信号——一路倍频 100M,作为 DA 的输入时钟;一路分频输出 1MHz 和读取 ROM 地址控制器信号。
  2. 查表:ROM 定制输入端是 9 位地址输入,内部嵌入定制好的 sina.coe 文件。9 位地址对应 512 个点,正好把一个正弦周期均分成 512 份。根据每个地址的输入,读取 sina 里对应的数据。
  3. 转换:数据通过 12 位输出口输出,输出口接外围的 DA902,转换成模拟信号输出,接示波器观察波形。

细算一下输出频率:地址计数器按 1MHz 节拍递增,扫完 512 个地址输出一个完整周期,输出正弦波频率约为 1MHz ÷ 512 ≈ 1.95kHz;而 DA 芯片本身用 100MHz 时钟驱动,保证每个新数据都能被及时转换,波形阶梯更细密。想改输出频率,只需要改地址递增的节拍或步进——这正是 DDS「频率控制字」的作用。

COE 文件是什么?

COE(coefficient)是 FPGA 工具定制 ROM 时常用的初始化文件格式,里面按行列出每个地址对应的初值。本实验的 sina.coe / SIN.COE 就是把一个周期正弦波量化成 12 位整数后的 512 行数据。生成方法很简单:用 MATLAB 或 Python 算出 round(2047.5 × sin(2πn/512)) + 偏置,逐行写进文件即可。ROM 内容错了,示波器上看到的波形会「缺一块」或畸变,这是本实验最常见的故障点。

输出幅度与量化误差

两个在调板上很有用的数量关系:

  • 幅度分辨率:12 位 DA 把满量程分成 4096 级。示波器上若量出波形峰峰值为 Vpp,则每个码值对应的电压约 Vpp/4096。ROM 里数据的最大值与最小值之差,直接决定输出波形的峰峰值。
  • 直流偏置:DA 输出通常单极性,ROM 数据一般带一个中点偏置(如 2048 附近为 0 电平),让正弦波以满量程中点为中心摆动。如果偏置取错,波形会被「顶」到满量程一端削平,示波器上表现为正弦波上半周削顶。

量化带来的台阶在示波器低时基下可能看得见毛刺,外围的滤波/示波器带宽限制会把它平滑掉;采样点越多(ROM 越长)、DA 位数越高,台阶越小,波形越接近理想正弦。

从 COE 到波形:一整套参数链

把整个系统的参数串起来推一遍,能帮你建立「数字合成模拟波形」的量感:

  1. ROM 地址 9 位 → 一个周期 512 个采样点;
  2. 地址节拍 1MHz → 每秒扫 1MHz ÷ 512 ≈ 1953 个周期,即输出正弦约 1.95kHz;
  3. DA 时钟 100MHz → 每个正弦周期内 DA 转换 100MHz × (1/1.95kHz) ≈ 51200 次,远远细于 512 点的查表步进,输出非常平滑;
  4. 12 位幅度分辨率 → 满量程 4096 级,正弦波每个台阶的电压变化只有满量程的约万分之二点四。

想改变输出频率有三条路:改地址计数器的分频节拍(最直接)、让地址每次递增不止 1( DDS 的频率控制字,精度最高)、或改变 ROM 点数。这三条路都不需要动 DAC 和外围电路——这正是 DDS 优于模拟振荡器的地方:频率切换是纯数字的、瞬时且相位连续的。

da 逻辑分析仪波形

图 24-3 是逻辑分析仪抓到的实时波形,可以看到地址计数器递增、ROM 数据按正弦规律起伏的过程:

24-3 逻辑分析仪实时波形

常见问题排查

上板看到波形不对时,按信号流向从前往后查最省时间:

  • 没有输出:先量 da_clk 有没有时钟(K15),再查扩展板是否插紧 J1 口——时钟和数据都没到 DA 芯片,示波器自然是平线;
  • 波形畸变、缺块:多半是 ROM 内容与地址位数不符,检查 COE 文件深度是否为 512 点、位宽是否为 12 位;
  • 波形上下削顶:ROM 数据的偏置或最大幅值越界,超出了 DA 满量程,重新生成 COE 即可;
  • 频率不对:核对地址计数器的分频系数是否与设计一致,别忘了锁相环两路输出各司其职。

硬件准备

先认识一下 AD/DA 扩展板。图 24.4 上端是并行采样速率 50MHz 的 12 位 AD807 处理芯片,红色皮套是专用模拟信号采用端口,标有 AD_IN 是针式采用端口,AGND 是接地;图下端是并行转换速率 165MHz 的 12 位 DA902 处理芯片,红色皮套是专用模拟信号输出端口,标有 DA_IN 是针式输出端口,AGND 是接地。

图 24.4 是 AD807 芯片的引脚图,图 24.5 是 DA902 芯片的引脚图,图 24.6、24.7 分别是 AD807 和 DA902 40 芯接口原理图。AD_D0~AD_D11、AD_CLK 和 DA_D0~DA_D11、DA_CLK 分别是 AD807 和 DA902 数据脚和时钟输入脚。

24-4

图 24-5 AD807 芯片原理图

图 24-6 DA902 芯片原理图

图 24-7 AD807 40 芯接口引脚原理图 图 24-8 DA902 40 芯接口引脚原理图

本实验只用到 DA 侧:FPGA 输出 12 位数据线 da_data[11:0] 和时钟线 da_clk 接到 DA902;DA902 的并行转换速率高达 165MHz,远高于本设计 100MHz 的驱动时钟,余量充足。

引脚分配

IO 管脚约束:

set_pin_assignment { clk50mhz } { LOCATION = R7; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment { da_clk } { LOCATION = K15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[0] } { LOCATION = R15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[10] } { LOCATION = H15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[11] } { LOCATION = J16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[1] } { LOCATION = P15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[2] } { LOCATION = P16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[3] } { LOCATION = N14; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[4] } { LOCATION = N16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[5] } { LOCATION = L16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[6] } { LOCATION = M15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[7] } { LOCATION = M16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[8] } { LOCATION = K16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { da_data[9] } { LOCATION = H16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }

信号含义:clk50mhz(R7)是 50MHz 板载时钟;da_data[11:0] 是 12 位并行数据;da_clk(K15)是送给 DA902 的转换时钟。

硬件连接

实验结果

内部 ROM 描点产生的正弦信号如下,示波器上可以看到 DA902 输出的完整正弦波形:

24-7 示波器显示 DA902 芯片显示的内容

回顾整个链路:COE 文件提供「波形基因」→ 锁相环提供两路时钟 → 地址计数器扫表输出 12 位数据 → DA902 以 100MHz 时钟并行转换 → 示波器还原出正弦波。把正弦表换成三角波、方波、锯齿波的 COE 文件,输出波形就随之改变——这也是下一篇双通道波形发生器的思想基础。

自测