当前位置: 首页 > news >正文

FPGA实战(21):基于Verilog的可配置扫频信号发生器设计与验证

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验证,确保了模块在各种边界条件下的正确性。该模块可作为频率合成、信号发生器或测试仪器的核心部件,具有较高的工程应用价值。

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


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

http://www.jsqmd.com/news/1024338/

相关文章:

  • 2026年东莞押出机厂家联系电话:高效精密押出机与电线电缆押出机源头工厂实力之选 - 企业推荐官【官方】
  • AI最大的误解:LLM实际上并不会调用工具
  • 终极指南:LiveSplit如何成为速度跑者的专业计时利器
  • 苏州吴中区专业管道疏通 2026 真实评测最新综合排行榜 - 居顺联家政疏通
  • Minio RELEASE.2024-03升级踩坑实录:从文件丢失到SDK连接超时,我的完整修复方案
  • 北京利康鸿运搬家|日式精细化打包服务标杆,全程无需业主动手 - 热点速览
  • 2026年永州汽车贴膜门店服务细节横向观察 - 国麟测评
  • 告别网络限制:5分钟上手B站视频下载工具BiliTools
  • 香奈儿/LV/古驰均可收|佛山包包回收店铺盘点,地址报价干货分享 - 名奢变现站
  • 2026电销机器人系统推荐排行 全场景适配品牌评测 - 极欧测评
  • 自制 USB HUB
  • Python socket编程核心模式
  • 企业职业认证考核,智能考务系统赋能 AI 智能考试 - 936品牌测评网
  • 2026年贵阳高考志愿填报咨询机构深度评测:从分数至上到就业导向的升学规划革命 - 优质企业观察收录
  • 2026湖州婚纱礼服馆实测:5家口碑品牌甑选 - 江湖评测
  • 3大核心技术深度解析:如何通过逆向工程实现《鸣潮》游戏模组定制
  • Dify批量运行实战:从API调用到自动化调度全解析
  • 海口江东新区8家回收横评,紫罗兰翡翠结算速度比拼 - 逸程
  • 永续盘存法在交通运输业资本存量核算中的应用与实操
  • 武汉线束定制:从源头解决电气连接难题 - 资讯报道
  • 2026杭州装修公司推荐:从资质到口碑的五大靠谱装企横向比较 - 品牌评测研究中心
  • 5分钟上手暗黑破坏神2存档编辑器:可视化编辑你的游戏角色数据
  • 城关区豪兴宇:深耕西北二十余年的专业户外用品供应商 - 奔跑123
  • 2026年郑州泳池温泉水处理设备厂家推荐:5大品牌深度横评与选购指南 - 年度推荐企业名录
  • MSC8112 DSI接口配置与调试实战:从原理到性能优化
  • ControlNet-v1-1 FP16架构设计:Stable Diffusion 1.5高性能控制网络优化实战
  • .NET统计API设计:告别后端画图,构建前后端解耦的数据可视化方案
  • BiliTools终极指南:跨平台哔哩哔哩工具箱全面解析
  • 2026 怀化防水补漏公司口碑排行榜推荐:全屋暗管漏水检测、厨卫渗水免砸砖处理、楼顶外墙渗漏、飘窗阳台漏水、地下室防水、瓷砖空鼓修缮专业测评 - 泛家庭维修
  • 2026保姆级指南:录音转文字软件教程,免费在线/电脑手机专业工具全覆盖 - 办公小帮手