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_dly0和r_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信号只有该信号为2b00表示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 2b00DAC空闲时启动。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无条件立即跳转开始新一轮检测。六、测试激励说明测试平台通过以下场景验证模块功能空闲启动测试初始设置dac_nco_update_busy 2b11模拟DAC忙然后置为2b00观察状态机从IDLE进入WAIT_BUSY并继续运行。参数变化检测同时改变三个通道的频率值观察各通道使能信号是否同时置7且数据锁存正确。重新开始流程在更新过程中将busy置为非空闲模拟外部中断检查模块是否在busy空闲后重新启动。单通道更新只改变DAC1的频率验证其他通道不被误触发。通过$monitor和波形观察可以清晰看到请求脉冲的宽度、使能信号的持续时间以及状态机的跳转时刻。七、使用注意事项频率输入稳定为避免频繁触发建议频率输入在变化后至少保持两个时钟周期的稳定。busy信号正确性外部需及时反映DAC的真实忙闲状态否则模块可能长时间等待或误触发。延时参数调整若DAC芯片时序参数不同可修改r_delay_cnt的比较值300、15、1080等。多通道同步当多个通道同时变化时它们的数据和使能会在同一次更新中发出实现了严格同步。八、总结dac_nco_upload_setting模块通过两级寄存器变化检测、多通道并行处理、严格的状态机时序控制实现了可靠的三通道NCO频率自动上传功能。其核心创新在于变化标志的锁存与清除机制、同步更新策略以及完整的时序保护措施。该模块可广泛应用于需要多通道DAC频率动态切换的通信或测试系统中代码结构清晰易于移植和参数调整。附完整代码dac_nco_upload_setting.vmodule 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 7b000_0001 ; parameter P_ST_WAIT_BUSY 7b000_0010 ; parameter P_ST_PRE_UPD 7b000_0100 ; parameter P_ST_DET_CHG 7b000_1000 ; parameter P_ST_REQ_UPD 7b001_0000 ; parameter P_ST_WAIT_DONE 7b010_0000 ; parameter P_ST_UPD_DONE 7b100_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_cP_ST_IDLE (dac_nco_update_busy 2b00); assign p_st_wait_busy2p_st_pre_upd_start state_cP_ST_WAIT_BUSY (r_delay_cnt d299 dac_nco_update_busy 2b00); assign p_st_pre_upd2p_st_det_chg_start state_cP_ST_PRE_UPD (1); assign p_st_det_chg2p_st_req_upd_start state_cP_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_cP_ST_REQ_UPD (r_delay_cnt d15); assign p_st_wait_done2p_st_upd_done_start state_cP_ST_WAIT_DONE (r_delay_cnt d1099 - d1 - d18); assign p_st_upd_done2p_st_idle_start state_cP_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 endmoduletb_dac_nco_upload_setting.vtimescale 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 48h0; dac_2_nco_freq 48h0; dac_3_nco_freq 48h0; dac_nco_update_busy 2b11; #200; $display( 测试1: DAC空闲启动 ); dac_nco_update_busy 2b00; #5000; $display( 测试2: 参数变化检测 ); dac_1_nco_freq 48h123456789ABC; dac_2_nco_freq 48hFEDCBA987654; dac_3_nco_freq 48hAABBCCDDEEFF; #20000; $display( 测试3: 重新开始流程 ); dac_nco_update_busy 2b11; #300; dac_nco_update_busy 2b00; #5000; $display( 测试4: 只更新DAC1 ); dac_1_nco_freq 48h555555555555; #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希望这篇博客能帮助您和读者快速理解该模块的设计精髓。如有任何问题欢迎交流讨论。