FPGA实战(21):基于Verilog的可配置扫频信号发生器设计与验证
2026/6/16 16:14:49 网站建设 项目流程

1. 项目概述

在数字信号处理、频率合成器测试及通信系统中,常常需要产生一个频率随时间线性变化的信号(即扫频信号)。本设计实现了一个参数可配置的扫频频率发生器,通过状态机控制输出频率按设定步进递增,并支持单次扫描连续循环扫描两种模式。模块采用Verilog HDL编写,具有结构清晰、可复用性强等特点,并配套完整的Testbench进行功能验证。


2. 模块端口说明

信号名方向位宽功能描述
clkI1系统时钟(上升沿有效)
rst_nI1异步复位,低电平有效
SweepModeI1扫频使能:高电平启动/连续模式
StartFreqI32扫频起始频率
StopFreqI32扫频截止频率
StepFreqI32频率步进间隔
TimeDelayI32每步频点保持的时钟周期数
DACFreqO32当前输出的频率值

3. 设计核心创新点

  • 灵活的参数配置:起始/截止频率、步进值、驻留时间均可通过输入端口实时设置,适应不同应用场景。
  • 单次与连续双模式:仅通过SweepMode信号电平控制。脉冲式高电平触发单次扫描;持续高电平实现自动循环扫描,无需额外控制逻辑。
  • 鲁棒的复位处理:异步复位后所有内部寄存器清零,输出频率归零;复位释放后进入空闲状态,等待下次触发。
  • 防死锁状态机:状态转移条件明确,并处理了起始频率≥截止频率的边界情况(直接停留在起始值)。

4. 内部结构及状态机设计

4.1 主要寄存器

  • ri_SweepMode,ri_StartFreq, … : 输入信号锁存,保证在扫频过程中参数稳定。
  • ro_DACFreq:输出频率寄存器。
  • r_delay_cnt:延迟计数器,用于计时频点驻留时间。

4.2 状态机(3状态)

localparam P_ST_IDLE = 3'b001; localparam P_ST_DELAY = 3'b010; localparam P_ST_STEP = 3'b100;
  • IDLE:空闲态。若SweepMode为高,则加载起始频率到输出,并跳转至DELAY
  • DELAY:延迟态。计数器从0递增至TimeDelay-1,完成后跳转至STEP
  • STEP:步进态。若当前频率 < 停止频率,则增加一个步进并返回DELAY;否则回到IDLE(完成一次扫描)。

连续扫频的实现:当SweepMode持续为高时,IDLE状态会立即重新触发,从而自动开始下一轮扫描,形成连续循环。


5. 关键代码解析

5.1 状态转移条件

assign p_st_idle2p_st_delay_start = state_c==P_ST_IDLE && (ri_SweepMode ); assign p_st_delay2p_st_step_start = state_c==P_ST_DELAY && (r_delay_cnt >= ri_TimeDelay ); assign p_st_step2p_st_idle_start = state_c==P_ST_STEP && (ro_DACFreq >= ri_StopFreq ); assign p_st_step2p_st_delay_start = state_c==P_ST_STEP && (ro_DACFreq < ri_StopFreq );

5.2 输出频率更新逻辑

always @(posedge i_clk )begin if(i_rst) ro_DACFreq <= 'd0 ; else if(state_c == P_ST_IDLE) ro_DACFreq <= (ri_StartFreq) ; // 空闲时预置起始频率 else if(state_c == P_ST_STEP && ro_DACFreq >= ri_StopFreq) ro_DACFreq <= (ri_StartFreq) ; // 扫频结束,重置为起始 else if(state_c == P_ST_STEP && ro_DACFreq < ri_StopFreq) ro_DACFreq <= (ro_DACFreq + ri_StepFreq) ; else ro_DACFreq <= ro_DACFreq ; end

5.3 延迟计数器

always @(posedge i_clk )begin if(i_rst) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_DELAY && r_delay_cnt < ri_TimeDelay) r_delay_cnt <= (r_delay_cnt + 'd1 ) ; else r_delay_cnt <= 'd0 ; end

6. Testbench设计与测试用例

为了充分验证模块功能,我们编写了包含任务封装的Testbench,覆盖以下场景:

测试用例描述
TC1:基本扫频起始100,停止500,步进100,延迟10个周期,触发单次扫描,验证结束回初始值
TC2:连续扫频起始1000,停止2000,步进200,延迟5,保持SweepMode为高,观察循环
TC3:边界条件起始≥停止(500→100),验证状态机正确处理
TC4:复位干扰扫描过程中拉低复位,检查输出清零,释放复位后恢复到起始值

6.1 任务封装示例

task set_sweep_config; input [31:0] i_start, i_stop, i_step, i_delay; begin StartFreq = i_start; StopFreq = i_stop; StepFreq = i_step; TimeDelay = i_delay; end endtask task trigger_sweep; begin @(posedge clk); SweepMode = 1; @(posedge clk); SweepMode = 0; end endtask task check_freq; input [31:0] expected; input [255:0] msg; begin if (DACFreq !== expected) $display("[ERROR] ..."); else $display("[PASS] ..."); end endtask

6.2 仿真结果预期

仿真运行后,控制台输出如下(示例):

[INFO] Reset Released at time 50 ========== TEST CASE 1: Basic Sweep ========== [CONFIG] Start=100, Stop=500, Step=100, Delay=10 [TRIG] Sweep Triggered at time 110 ... [PASS] Time=610: Freq=100 OK (Sweep Finished, back to StartFreq) ========== TEST CASE 2: Continuous Sweep ========== ... [PASS] Continuous sweep loop detected correctly. ========== TEST CASE 3: Boundary Test ========== ... [PASS] Time=...: Freq=500 OK (Boundary Case: Start > Stop, stay at StartFreq) ========== TEST CASE 4: Reset During Sweep ========== ... [PASS] Time=...: Freq=0 OK (Output should be 0 during Reset) [PASS] Time=...: Freq=100 OK (Output reset to StartFreq after Reset release)

波形上可见频率值按节拍递增,延迟周期内保持不变,连续模式下周期重复。


7. 完整代码

7.1 顶层模块sweep_freq.v

module sweep_freq( input clk , input rst_n , input SweepMode ,//扫频模式使能 input [31:0] StartFreq ,//扫频起始频率 input [31:0] StopFreq ,//扫频截止频率 input [31:0] StepFreq ,//扫频频率步进间隔 input [31:0] TimeDelay ,//扫频频率时间间隔 output [31:0] DACFreq ); /************************parameter***********************/ /************************REG*********************/ reg ri_SweepMode ; reg [31:0] ri_StartFreq ; reg [31:0] ri_StopFreq ; reg [31:0] ri_StepFreq ; reg [31:0] ri_TimeDelay ; reg [ 31: 0] ro_DACFreq ; reg [ 31: 0] r_delay_cnt ; /************************WIRE*********************/ wire i_clk ; wire i_rst ; /************************FSM*********************/ reg [ (3 - 1):0] state_c ; reg [ (3 - 1):0] state_n ; localparam P_ST_IDLE = 3'b001 ; localparam P_ST_DELAY = 3'b010 ; localparam P_ST_STEP = 3'b100 ; 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_delay_start) state_n = P_ST_DELAY ; else state_n = state_c ; end P_ST_DELAY :begin if(p_st_delay2p_st_step_start) state_n = P_ST_STEP ; else state_n = state_c ; end P_ST_STEP :begin if(p_st_step2p_st_idle_start) state_n = P_ST_IDLE ; else if(p_st_step2p_st_delay_start) state_n = P_ST_DELAY ; else state_n = state_c ; end default : state_n = P_ST_IDLE ; endcase end assign p_st_idle2p_st_delay_start = state_c==P_ST_IDLE && (ri_SweepMode ); assign p_st_delay2p_st_step_start = state_c==P_ST_DELAY && (r_delay_cnt >= ri_TimeDelay ); assign p_st_step2p_st_idle_start = state_c==P_ST_STEP && (ro_DACFreq >= ri_StopFreq ); assign p_st_step2p_st_delay_start = state_c==P_ST_STEP && (ro_DACFreq < ri_StopFreq ); /************************CombineLogic*******************/ assign i_clk = clk ; assign i_rst = ~rst_n ; assign DACFreq = ro_DACFreq ; /************************Inist***********************/ /************************Process***********************/ always @(posedge i_clk )begin if(i_rst)begin ri_SweepMode <= 'd0 ; ri_StartFreq <= 'd0 ; ri_StopFreq <= 'd0 ; ri_StepFreq <= 'd0 ; ri_TimeDelay <= 'd0 ; end else begin ri_SweepMode <= SweepMode ; ri_StartFreq <= StartFreq ; ri_StopFreq <= StopFreq ; ri_StepFreq <= StepFreq ; ri_TimeDelay <= TimeDelay ; end end always @(posedge i_clk )begin if(i_rst) ro_DACFreq <= 'd0 ; else if(state_c == P_ST_IDLE) ro_DACFreq <= (ri_StartFreq) ; else if(state_c == P_ST_STEP && ro_DACFreq >= ri_StopFreq) ro_DACFreq <= (ri_StartFreq) ; else if(state_c == P_ST_STEP && ro_DACFreq < ri_StopFreq) ro_DACFreq <= (ro_DACFreq + ri_StepFreq) ; else ro_DACFreq <= ro_DACFreq ; end always @(posedge i_clk )begin if(i_rst) r_delay_cnt <= 'd0 ; else if(state_c == P_ST_DELAY && r_delay_cnt < ri_TimeDelay) r_delay_cnt <= (r_delay_cnt + 'd1 ) ; else r_delay_cnt <= 'd0 ; end endmodule

7.2 Testbenchtb_sweep_freq.v

`timescale 1ns/1ps module tb_sweep_freq; //========================================================== // Parameter Definitions //========================================================== parameter CLK_PERIOD = 10; // 100MHz parameter SIM_TIME = 20000; // 仿真总时长 //========================================================== // Interface Signals //========================================================== reg clk; reg rst_n; reg SweepMode; reg [31:0] StartFreq; reg [31:0] StopFreq; reg [31:0] StepFreq; reg [31:0] TimeDelay; wire [31:0] DACFreq; // State Monitor (Hierarchical Reference) wire [2:0] current_state; assign current_state = u_sweep_freq.state_c; //========================================================== // DUT Instantiation //========================================================== sweep_freq u_sweep_freq ( .clk (clk), .rst_n (rst_n), .SweepMode (SweepMode), .StartFreq (StartFreq), .StopFreq (StopFreq), .StepFreq (StepFreq), .TimeDelay (TimeDelay), .DACFreq (DACFreq) ); //========================================================== // Clock & Reset Generation //========================================================== initial begin clk = 0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin rst_n = 0; repeat(5) @(posedge clk); rst_n = 1; $display("[INFO] Reset Released at time %0t", $time); end //========================================================== // Tasks: Encapsulation for Stimulus //========================================================== task set_sweep_config; input [31:0] i_start; input [31:0] i_stop; input [31:0] i_step; input [31:0] i_delay; begin StartFreq = i_start; StopFreq = i_stop; StepFreq = i_step; TimeDelay = i_delay; $display("[CONFIG] Start=%0d, Stop=%0d, Step=%0d, Delay=%0d", i_start, i_stop, i_step, i_delay); end endtask task trigger_sweep; begin @(posedge clk); SweepMode = 1; @(posedge clk); SweepMode = 0; $display("[TRIG] Sweep Triggered at time %0t", $time); end endtask task check_freq; input [31:0] expected; input [255:0] msg; begin if (DACFreq !== expected) begin $display("[ERROR] Time=%0t: Mismatch! Expected=%0d, Actual=%0d (%s)", $time, expected, DACFreq, msg); end else begin $display("[PASS] Time=%0t: Freq=%0d OK (%s)", $time, DACFreq, msg); end end endtask //========================================================== // Main Test Process //========================================================== initial begin // 1. Initialization SweepMode = 0; StartFreq = 0; StopFreq = 0; StepFreq = 0; TimeDelay = 0; // Wait for reset @(posedge rst_n); repeat(2) @(posedge clk); //------------------------------------------------------ // Test Case 1: Basic Frequency Sweep //------------------------------------------------------ $display("\n========== TEST CASE 1: Basic Sweep =========="); set_sweep_config(100, 500, 100, 10); // 100 -> 500, step 100, delay 10 cycles trigger_sweep(); // 等待足够时间观察一次扫频过程 // Delay=10, 共需扫频 100->200->300->400->500 // 预估时间:(10 delay cycles + 2 step cycles) * 5 steps approx repeat(80) @(posedge clk); // 验证扫频结束后是否回到起始频率并停止 // 逻辑:扫频完成后应停在 StartFreq (100) check_freq(100, "Sweep Finished, back to StartFreq"); //------------------------------------------------------ // Test Case 2: Continuous Sweep Mode //------------------------------------------------------ $display("\n========== TEST CASE 2: Continuous Sweep =========="); set_sweep_config(1000, 2000, 200, 5); @(posedge clk); SweepMode = 1; // 持续保持高电平 $display("[TRIG] Continuous Mode ON"); // 观察一个完整的循环周期:1000 -> ... -> 2000 -> 1000 // 等待频率从 1000 变到 2000,再变回 1000 // 由于延迟为 5,粗略等待 60 个周期观察一次循环 repeat(60) @(posedge clk); if (DACFreq == 1000) $display("[PASS] Continuous sweep loop detected correctly."); else $display("[WARN] Freq=%0d, waiting for cycle restart...", DACFreq); // 再等待一段时间确认仍在循环 repeat(40) @(posedge clk); if (DACFreq > 1000 && DACFreq <= 2000) $display("[PASS] Still sweeping in loop. Freq=%0d", DACFreq); SweepMode = 0; // 停止扫频 @(posedge clk); //------------------------------------------------------ // Test Case 3: Boundary Condition (Start >= Stop) //------------------------------------------------------ $display("\n========== TEST CASE 3: Boundary Test =========="); set_sweep_config(500, 100, 100, 5); // Start > Stop trigger_sweep(); // 此时 StartFreq >= StopFreq,状态机应立即判断完成并回到IDLE repeat(10) @(posedge clk); check_freq(500, "Boundary Case: Start > Stop, stay at StartFreq"); //------------------------------------------------------ // Test Case 4: Reset During Operation //------------------------------------------------------ $display("\n========== TEST CASE 4: Reset During Sweep =========="); set_sweep_config(100, 1000, 100, 20); trigger_sweep(); repeat(15) @(posedge clk); // 运行一半 $display("[INFO] Asserting Reset during sweep..."); rst_n = 0; // 拉低复位 repeat(3) @(posedge clk); check_freq(0, "Output should be 0 during Reset"); rst_n = 1; // 释放复位 repeat(2) @(posedge clk); check_freq(100, "Output reset to StartFreq after Reset release"); //------------------------------------------------------ // Simulation End //------------------------------------------------------ $display("\n========== ALL TESTS COMPLETED =========="); #100; $stop; end endmodule

8. 仿真环境与工具

  • 仿真器:Modelsim / Vivado Simulator 均可
  • 时钟频率:100MHz(周期10ns),可根据需要调整
  • 观察方式:打印输出 + 波形查看(DACFreq、状态等)

9. 总结

本设计实现了一个功能完备、参数可调的扫频频率发生器,其状态机结构简单且易于理解,覆盖了单次和连续两种工作模式。通过全面的Testbench验证,确保了模块在各种边界条件下的正确性。该模块可作为频率合成、信号发生器或测试仪器的核心部件,具有较高的工程应用价值。

如需进一步扩展,可增加频率字输出幅度控制、非均匀步进或扫频方向控制等功能,有兴趣的读者可在此基础上进行二次开发。


欢迎留言交流,若需工程文件可私信获取。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询