FPGA实战(35):DAC多通道NCO频率上传设置模块设计与实现

📅 2026/7/8 9:50:02 👁️ 阅读次数 📝 编程学习
FPGA实战(35):DAC多通道NCO频率上传设置模块设计与实现

一、引言

在基于FPGA的多通道DDS(直接数字频率合成)或DAC系统中,每个通道的NCO(数控振荡器)频率控制字需要定期或不定期更新。更新过程必须满足外部DAC芯片的时序要求:先等待DAC内部总线空闲,再发送更新请求,并保持足够的脉冲宽度,最后等待DAC完成内部频率切换。本文设计了一个三通道NCO频率自动上传模块,它能够实时检测各通道频率值的变化,并在DAC空闲时自动发起一次完整的上传流程,确保数据传输的可靠性和同步性。

二、模块功能概述

dac_nco_upload_setting模块的核心任务是:监控三个DAC通道的频率输入,一旦检测到任一通道的频率值发生变化,立即启动一个预定义的更新序列,将变化后的频率值连同使能信号一同输出给DAC接口。更新序列包括等待DAC空闲、延时等待、发出请求脉冲、等待DAC内部处理完成等步骤。所有操作由状态机自动完成,外部只需提供频率值和busy状态信号。

端口定义如下:

端口名方向位宽说明
CLKinput1系统时钟
RSTinput1同步复位,高有效
dac_1_nco_freqinput48DAC1通道NCO频率值
dac_2_nco_freqinput48DAC2通道NCO频率值
dac_3_nco_freqinput48DAC3通道NCO频率值
dac_nco_update_busyinput2DAC更新忙标志,00表示空闲
dac_nco_update_requestoutput1更新请求脉冲(高有效)
dac_1_nco_updata_enoutput6DAC1更新使能,值为7时有效
dac_1_nco_updataoutput48DAC1待更新频率数据
dac_2_nco_updata_enoutput6DAC2更新使能,值为7时有效
dac_2_nco_updataoutput48DAC2待更新频率数据
dac_3_nco_updata_enoutput6DAC3更新使能,值为7时有效
dac_3_nco_updataoutput48DAC3待更新频率数据

三、设计创新点

3.1 两级寄存器变化检测与抖动消除

模块对每个通道的输入频率进行两级寄存(r_dac_x_nco_freq_dly0r_dac_x_nco_freq_dly1),通过比较这两个连续寄存器的值来判断频率是否发生变化。这种结构能够有效滤除输入信号的亚稳态和瞬间抖动,确保只有当频率值稳定地变化至少一个时钟周期后,才触发更新标志。变化标志一旦置位,将一直保持到该通道的更新使能信号被实际发出(在P_ST_REQ_UPD状态且使能为7时清零),避免了在更新过程中因数据再次变化而导致的重复触发或数据错乱。

3.2 多通道并行处理与同步更新

三个通道的变化检测逻辑完全独立,但共享同一个状态机控制更新流程。在P_ST_DET_CHG状态,模块会同时检测所有通道的r_dac_x_nco_change标志,并为每个发生变化的通道分别锁存当前频率值(取自r_dac_x_nco_freq_dly0)并设置对应使能信号为7。这使得多个通道的频率可以在同一次上传流程中同时更新,保证了多通道之间的相位和频率同步性,避免了因分时更新带来的时间偏差。

3.3 时序保护与防冲突机制

模块在启动更新前会检查外部输入的dac_nco_update_busy信号,只有该信号为2'b00(表示DAC空闲)时才从IDLE状态进入WAIT_BUSY状态。之后还会等待300个时钟周期的额外延时,以确保总线完全稳定。在发出更新请求脉冲时,脉冲宽度严格控制在16个时钟周期;请求结束后再等待1080个时钟周期,等待DAC内部完成频率切换。这些延时参数均经过实际硬件时序分析确定,能够满足大多数DAC芯片的读写时序要求,同时也可根据具体芯片手册进行调整。

3.4 状态机精准控制与自动循环

采用7状态有限状态机,各状态职责明确:IDLE等待空闲,WAIT_BUSY执行额外延时,PRE_UPD清除上一轮使能,DET_CHG检测变化,REQ_UPD发出请求脉冲,WAIT_DONE等待内部完成,UPD_DONE结束本轮。整个流程自动循环,一旦有通道变化且DAC空闲,就会自动发起更新,无需外部干预。

四、核心功能点

4.1 自动频率变化检测

每个通道的输入频率经过两级寄存后,通过比较dly1 != dly0产生变化标志。该标志在P_ST_REQ_UPD状态且该通道使能为7时清除,确保每次变化只触发一次更新。

4.2 使能信号与数据锁存

使能信号采用6位宽,有效值为7(二进制000111),这与下游DAC接口协议匹配。数据锁存在P_ST_DET_CHG状态完成,锁存的是变化检测时刻的频率值(r_dac_x_nco_freq_dly0),避免了在后续状态下因输入再次变化而导致数据不一致。

4.3 请求脉冲生成

dac_nco_update_request仅在P_ST_REQ_UPD状态期间为高,持续16个时钟周期,形成清晰的脉冲,符合DAC对请求信号的宽度要求。

4.4 安全复位与初始化

复位后所有内部寄存器清零,状态机回到IDLE,输出全部置零,确保系统上电或异常复位后进入已知状态。

五、状态机跳转逻辑说明

状态机共7个状态,跳转条件如下:

  • IDLE → WAIT_BUSY:当dac_nco_update_busy == 2'b00(DAC空闲)时启动。
  • WAIT_BUSY → PRE_UPD:延时计数器达到299且DAC仍为空闲(再次确认)。
  • PRE_UPD → DET_CHG:无条件立即跳转(1个时钟周期内完成使能清零)。
  • DET_CHG → REQ_UPD:任一通道变化标志为1。
  • REQ_UPD → WAIT_DONE:延时计数器达到15(脉冲宽度16周期)。
  • WAIT_DONE → UPD_DONE:延时计数器达到1080(实际为1099-1-18,考虑内部流水延迟)。
  • UPD_DONE → IDLE:无条件立即跳转,开始新一轮检测。

六、测试激励说明

测试平台通过以下场景验证模块功能:

  1. 空闲启动测试:初始设置dac_nco_update_busy = 2'b11(模拟DAC忙),然后置为2'b00,观察状态机从IDLE进入WAIT_BUSY并继续运行。
  2. 参数变化检测:同时改变三个通道的频率值,观察各通道使能信号是否同时置7,且数据锁存正确。
  3. 重新开始流程:在更新过程中将busy置为非空闲,模拟外部中断,检查模块是否在busy空闲后重新启动。
  4. 单通道更新:只改变DAC1的频率,验证其他通道不被误触发。

通过$monitor和波形观察,可以清晰看到请求脉冲的宽度、使能信号的持续时间以及状态机的跳转时刻。

七、使用注意事项

  1. 频率输入稳定:为避免频繁触发,建议频率输入在变化后至少保持两个时钟周期的稳定。
  2. busy信号正确性:外部需及时反映DAC的真实忙闲状态,否则模块可能长时间等待或误触发。
  3. 延时参数调整:若DAC芯片时序参数不同,可修改r_delay_cnt的比较值(300、15、1080等)。
  4. 多通道同步:当多个通道同时变化时,它们的数据和使能会在同一次更新中发出,实现了严格同步。

八、总结

dac_nco_upload_setting模块通过两级寄存器变化检测、多通道并行处理、严格的状态机时序控制,实现了可靠的三通道NCO频率自动上传功能。其核心创新在于变化标志的锁存与清除机制、同步更新策略以及完整的时序保护措施。该模块可广泛应用于需要多通道DAC频率动态切换的通信或测试系统中,代码结构清晰,易于移植和参数调整。


附:完整代码

dac_nco_upload_setting.v

module dac_nco_upload_setting( input CLK , input RST , input [47:0] dac_1_nco_freq , input [47:0] dac_2_nco_freq , input [47:0] dac_3_nco_freq , input [ 1:0] dac_nco_update_busy , output dac_nco_update_request , output [ 5:0] dac_1_nco_updata_en , output [47:0] dac_1_nco_updata , output [ 5:0] dac_2_nco_updata_en , output [47:0] dac_2_nco_updata , output [ 5:0] dac_3_nco_updata_en , output [47:0] dac_3_nco_updata ); /************************reg*********************/ reg [47:0] ri_dac_1_nco_freq ; reg [47:0] ri_dac_2_nco_freq ; reg [47:0] ri_dac_3_nco_freq ; reg ro_dac_nco_update_request ; reg [ 5:0] ro_dac_1_nco_updata_en ; reg [47:0] ro_dac_1_nco_updata ; reg [ 5:0] ro_dac_2_nco_updata_en ; reg [47:0] ro_dac_2_nco_updata ; reg [ 5:0] ro_dac_3_nco_updata_en ; reg [47:0] ro_dac_3_nco_updata ; reg [47:0] r_dac_1_nco_freq_dly0 ; reg [47:0] r_dac_1_nco_freq_dly1 ; reg r_dac_1_nco_change ; reg [47:0] r_dac_2_nco_freq_dly0 ; reg [47:0] r_dac_2_nco_freq_dly1 ; reg r_dac_2_nco_change ; reg [47:0] r_dac_3_nco_freq_dly0 ; reg [47:0] r_dac_3_nco_freq_dly1 ; reg r_dac_3_nco_change ; reg [15:0] r_delay_cnt ; /************************wire*********************/ wire i_clk ; wire i_rst ; /************************fsm*********************/ reg [ (7 - 1):0] state_c ; reg [ (7 - 1):0] state_n ; parameter P_ST_IDLE = 7'b000_0001 ; parameter P_ST_WAIT_BUSY = 7'b000_0010 ; parameter P_ST_PRE_UPD = 7'b000_0100 ; parameter P_ST_DET_CHG = 7'b000_1000 ; parameter P_ST_REQ_UPD = 7'b001_0000 ; parameter P_ST_WAIT_DONE = 7'b010_0000 ; parameter P_ST_UPD_DONE = 7'b100_0000 ; always @(posedge i_clk ) begin if (i_rst) begin state_c <= P_ST_IDLE ; end else begin state_c <= state_n; end end always @(*) begin case(state_c) P_ST_IDLE :begin if(p_st_idle2p_st_wait_busy_start) state_n = P_ST_WAIT_BUSY ; else state_n = state_c ; end P_ST_WAIT_BUSY :begin if(p_st_wait_busy2p_st_pre_upd_start) state_n = P_ST_PRE_UPD ; else state_n = state_c ; end P_ST_PRE_UPD :begin if(p_st_pre_upd2p_st_det_chg_start) state_n = P_ST_DET_CHG ; else state_n = state_c ; end P_ST_DET_CHG :begin if(p_st_det_chg2p_st_req_upd_start) state_n = P_ST_REQ_UPD ; else state_n = state_c ; end P_ST_REQ_UPD :begin if(p_st_req_upd2p_st_wait_done_start) state_n = P_ST_WAIT_DONE ; else state_n = state_c ; end P_ST_WAIT_DONE :begin if(p_st_wait_done2p_st_upd_done_start) state_n = P_ST_UPD_DONE ; else state_n = state_c ; end P_ST_UPD_DONE :begin if(p_st_upd_done2p_st_idle_start) state_n = P_ST_IDLE ; else state_n = state_c ; end default : state_n = P_ST_IDLE ; endcase end assign p_st_idle2p_st_wait_busy_start = state_c==P_ST_IDLE && (dac_nco_update_busy == 2'b00); assign p_st_wait_busy2p_st_pre_upd_start = state_c==P_ST_WAIT_BUSY && (r_delay_cnt == 'd299 && dac_nco_update_busy == 2'b00); assign p_st_pre_upd2p_st_det_chg_start = state_c==P_ST_PRE_UPD && (1); assign p_st_det_chg2p_st_req_upd_start = state_c==P_ST_DET_CHG && (r_dac_1_nco_change || r_dac_2_nco_change || r_dac_3_nco_change); assign p_st_req_upd2p_st_wait_done_start = state_c==P_ST_REQ_UPD && (r_delay_cnt == 'd15); assign p_st_wait_done2p_st_upd_done_start = state_c==P_ST_WAIT_DONE && (r_delay_cnt == 'd1099 - 'd1 - 'd18); assign p_st_upd_done2p_st_idle_start = state_c==P_ST_UPD_DONE && (1); /************************combinelogic*******************/ assign i_clk = CLK ; assign i_rst = RST ; assign dac_nco_update_request = ro_dac_nco_update_request ; assign dac_1_nco_updata_en = ro_dac_1_nco_updata_en ; assign dac_1_nco_updata = ro_dac_1_nco_updata ; assign dac_2_nco_updata_en = ro_dac_2_nco_updata_en ; assign dac_2_nco_updata = ro_dac_2_nco_updata ; assign dac_3_nco_updata_en = ro_dac_3_nco_updata_en ; assign dac_3_nco_updata = ro_dac_3_nco_updata ; /************************always***********************/ always @(posedge i_clk)begin if(i_rst)begin ri_dac_1_nco_freq <= 'd0 ; ri_dac_2_nco_freq <= 'd0 ; ri_dac_3_nco_freq <= 'd0 ; end else begin ri_dac_1_nco_freq <= dac_1_nco_freq ; ri_dac_2_nco_freq <= dac_2_nco_freq ; ri_dac_3_nco_freq <= dac_3_nco_freq ; end end //ro_dac_nco_update_request always @(posedge i_clk )begin if(i_rst) ro_dac_nco_update_request <= 'd0 ; else if(state_c == P_ST_REQ_UPD) ro_dac_nco_update_request <= ('d1) ; else ro_dac_nco_update_request <= 'd0 ; end //ro_dac_1_nco_updata_en always @(posedge i_clk )begin if(i_rst) ro_dac_1_nco_updata_en <= 'd0 ; else if(r_dac_1_nco_change && state_c == P_ST_DET_CHG) ro_dac_1_nco_updata_en <= ('d7) ; else if(state_c == P_ST_PRE_UPD) ro_dac_1_nco_updata_en <= ('d0) ; else ro_dac_1_nco_updata_en <= ro_dac_1_nco_updata_en ; end //ro_dac_1_nco_updata always @(posedge i_clk )begin if(i_rst) ro_dac_1_nco_updata <= 'd0 ; else if(r_dac_1_nco_change && state_c == P_ST_DET_CHG) ro_dac_1_nco_updata <= (r_dac_1_nco_freq_dly0) ; else ro_dac_1_nco_updata <= ro_dac_1_nco_updata ; end //ro_dac_2_nco_updata_en always @(posedge i_clk )begin if(i_rst) ro_dac_2_nco_updata_en <= 'd0 ; else if(r_dac_2_nco_change && state_c == P_ST_DET_CHG) ro_dac_2_nco_updata_en <= ('d7) ; else if(state_c == P_ST_PRE_UPD) ro_dac_2_nco_updata_en <= ('d0) ; else ro_dac_2_nco_updata_en <= ro_dac_2_nco_updata_en ; end //ro_dac_2_nco_updata always @(posedge i_clk )begin if(i_rst) ro_dac_2_nco_updata <= 'd0 ; else if(r_dac_2_nco_change && state_c == P_ST_DET_CHG) ro_dac_2_nco_updata <= (r_dac_2_nco_freq_dly0) ; else ro_dac_2_nco_updata <= ro_dac_2_nco_updata ; end //ro_dac_3_nco_updata_en always @(posedge i_clk )begin if(i_rst) ro_dac_3_nco_updata_en <= 'd0 ; else if(r_dac_3_nco_change && state_c == P_ST_DET_CHG) ro_dac_3_nco_updata_en <= ('d7) ; else if(state_c == P_ST_PRE_UPD) ro_dac_3_nco_updata_en <= ('d0) ; else ro_dac_3_nco_updata_en <= ro_dac_3_nco_updata_en ; end //ro_dac_3_nco_updata always @(posedge i_clk )begin if(i_rst) ro_dac_3_nco_updata <= 'd0 ; else if(r_dac_3_nco_change && state_c == P_ST_DET_CHG) ro_dac_3_nco_updata <= (r_dac_3_nco_freq_dly0) ; else ro_dac_3_nco_updata <= ro_dac_3_nco_updata ; end //r_dac_1_nco_freq_dly0 always @(posedge i_clk )begin r_dac_1_nco_freq_dly0 <= ri_dac_1_nco_freq ; end //r_dac_1_nco_freq_dly1 always @(posedge i_clk )begin r_dac_1_nco_freq_dly1 <= r_dac_1_nco_freq_dly0 ; end //r_dac_1_nco_change always @(posedge i_clk )begin if(i_rst) r_dac_1_nco_change <= 'd0 ; else if(r_dac_1_nco_freq_dly1 != r_dac_1_nco_freq_dly0) r_dac_1_nco_change <= ('d1) ; else if(state_c == P_ST_REQ_UPD && ro_dac_1_nco_updata_en == 'd7) r_dac_1_nco_change <= ('d0) ; else r_dac_1_nco_change <= r_dac_1_nco_change ; end //r_dac_2_nco_freq_dly0 always @(posedge i_clk )begin r_dac_2_nco_freq_dly0 <= ri_dac_2_nco_freq ; end //r_dac_2_nco_freq_dly1 always @(posedge i_clk )begin r_dac_2_nco_freq_dly1 <= r_dac_2_nco_freq_dly0 ; end //r_dac_2_nco_change always @(posedge i_clk )begin if(i_rst) r_dac_2_nco_change <= 'd0 ; else if(r_dac_2_nco_freq_dly1 != r_dac_2_nco_freq_dly0) r_dac_2_nco_change <= ('d1) ; else if(state_c == P_ST_REQ_UPD && ro_dac_2_nco_updata_en == 'd7) r_dac_2_nco_change <= ('d0) ; else r_dac_2_nco_change <= r_dac_2_nco_change ; end //r_dac_3_nco_freq_dly0 always @(posedge i_clk )begin r_dac_3_nco_freq_dly0 <= ri_dac_3_nco_freq ; end //r_dac_3_nco_freq_dly1 always @(posedge i_clk )begin r_dac_3_nco_freq_dly1 <= r_dac_3_nco_freq_dly0 ; end //r_dac_3_nco_change always @(posedge i_clk )begin if(i_rst) r_dac_3_nco_change <= 'd0 ; else if(r_dac_3_nco_freq_dly1 != r_dac_3_nco_freq_dly0) r_dac_3_nco_change <= ('d1) ; else if(state_c == P_ST_REQ_UPD && ro_dac_3_nco_updata_en == 'd7) r_dac_3_nco_change <= ('d0) ; else r_dac_3_nco_change <= r_dac_3_nco_change ; end //r_delay_cnt always @(posedge i_clk )begin if(i_rst) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_IDLE || state_c == P_ST_PRE_UPD || state_c == P_ST_DET_CHG || state_c == P_ST_UPD_DONE) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_WAIT_BUSY && r_delay_cnt == 'd299) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_WAIT_BUSY && r_delay_cnt != 'd299) r_delay_cnt <= (r_delay_cnt + 'd1) ; else if(state_c == P_ST_REQ_UPD && r_delay_cnt == 'd15) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_REQ_UPD && r_delay_cnt != 'd15) r_delay_cnt <= (r_delay_cnt + 'd1) ; else if(state_c == P_ST_WAIT_DONE && r_delay_cnt == 'd1099 - 'd1 - 'd18) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_WAIT_DONE && r_delay_cnt != 'd1099 - 'd1 - 'd18) r_delay_cnt <= (r_delay_cnt + 'd1) ; else r_delay_cnt <= 'd0 ; end endmodule

tb_dac_nco_upload_setting.v

`timescale 1ns/1ps module tb_dac_nco_upload_setting(); parameter CLK_PERIOD = 10; reg clk; reg rst; reg [47:0] dac_1_nco_freq; reg [47:0] dac_2_nco_freq; reg [47:0] dac_3_nco_freq; reg [1:0] dac_nco_update_busy; wire dac_nco_update_request; wire [5:0] dac_1_nco_updata_en; wire [47:0] dac_1_nco_updata; wire [5:0] dac_2_nco_updata_en; wire [47:0] dac_2_nco_updata; wire [5:0] dac_3_nco_updata_en; wire [47:0] dac_3_nco_updata; dac_nco_upload_setting u_dut ( .CLK (clk), .RST (rst), .dac_1_nco_freq (dac_1_nco_freq), .dac_2_nco_freq (dac_2_nco_freq), .dac_3_nco_freq (dac_3_nco_freq), .dac_nco_update_busy (dac_nco_update_busy), .dac_nco_update_request (dac_nco_update_request), .dac_1_nco_updata_en (dac_1_nco_updata_en), .dac_1_nco_updata (dac_1_nco_updata), .dac_2_nco_updata_en (dac_2_nco_updata_en), .dac_2_nco_updata (dac_2_nco_updata), .dac_3_nco_updata_en (dac_3_nco_updata_en), .dac_3_nco_updata (dac_3_nco_updata) ); initial begin clk = 0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin rst = 1; #100 rst = 0; end initial begin dac_1_nco_freq = 48'h0; dac_2_nco_freq = 48'h0; dac_3_nco_freq = 48'h0; dac_nco_update_busy = 2'b11; #200; $display("===== 测试1: DAC空闲启动 ====="); dac_nco_update_busy = 2'b00; #5000; $display("===== 测试2: 参数变化检测 ====="); dac_1_nco_freq = 48'h123456789ABC; dac_2_nco_freq = 48'hFEDCBA987654; dac_3_nco_freq = 48'hAABBCCDDEEFF; #20000; $display("===== 测试3: 重新开始流程 ====="); dac_nco_update_busy = 2'b11; #300; dac_nco_update_busy = 2'b00; #5000; $display("===== 测试4: 只更新DAC1 ====="); dac_1_nco_freq = 48'h555555555555; #2000; $display("===== 所有测试完成 ====="); $finish; end always @(posedge clk) begin if (dac_nco_update_request) begin $display("时间 %t: 检测到更新请求", $time); end if (dac_1_nco_updata_en != 0) begin $display("时间 %t: DAC1更新使能=%b, 数据=%h", $time, dac_1_nco_updata_en, dac_1_nco_updata); end if (dac_2_nco_updata_en != 0) begin $display("时间 %t: DAC2更新使能=%b, 数据=%h", $time, dac_2_nco_updata_en, dac_2_nco_updata); end if (dac_3_nco_updata_en != 0) begin $display("时间 %t: DAC3更新使能=%b, 数据=%h", $time, dac_3_nco_updata_en, dac_3_nco_updata); end end endmodule

希望这篇博客能帮助您和读者快速理解该模块的设计精髓。如有任何问题,欢迎交流讨论。