基于ZYNQ 7010的调制信号解调以及调制参数识别装置的设计(AM、ASK、FM、FSK、PSK信号的解调和参数识别)
一、简介
本设计基于ALINX ZYNQ7010核心板,DAC904和AD9226模块,采用verilog语言实现AM、ASK、FM、FSK、PSK调制信号的解调和对应参数的估计,同时含有部分框图和步骤解答,相信对电赛辅导和指导毕业设计有一定的帮助。本文只粘贴必要代码,不提供源文件,目的为培养大家的自我探知能力,需要源文件需私信我,同时IP核的配置我也是从各大平台自学的,就不一一赘述,本文只贴出配置参数,不含具体配置步骤,不会配置的请借鉴其他帖子经验。
本设计的设计目标:
输入信号分别为AM/FM/ASK/FSK/PSK,所有信号的载频均为1MHz,幅度为10Vpp。设计的具体要求如下:
(1)AM 信号,其正弦调制信号频率 F =1-5kHz。Ma为0.3-0.8,测量并显示该 AM 信号的调幅系数Ma,误差不大于0.1 ;输出解调信号Uo 无明显失真,幅度大于200mV。
(2)FM 信号,其正弦调制信号频率 F =1-5kHz,Mf为2-5,估计并显示该 FM波的调频系数Mf ,误差不大于0.3 ;输出解调信号Uo无明显失真,幅度大于200mV。
(3)ASK/FSK/PSK信号,三者均传输二进制不归零(NRZ)码,码速率 Rc 可为 6kbps、8kbps、10kbps 中的一种;要求装置能够自主识别并显示Um的键控方式,并输出解调后的波形。
总模块框图如下:

二、FPGA的数据类型简介
在 FPGA 设计中,所有数据均以二进制形式流转。数据位宽的定义需综合考量算法动态范围及接口器件的物理限制,比如本设计用到的DAC904为14bit(16384深度),就需要开辟[13:0]的位宽,对应模块的14个数据引脚的输出,而AD9226为12bit(4096深度)则需开辟[11:0]的位宽,对应模块的12个数据引脚的输出。此外,在多模块级联传输过程中,由于不同模块对数据精度的要求不同,必须严格进行位宽匹配管理。这通常涉及截位处理以防止数据溢出,或符号位扩展以保留运算精度。合理的位宽规划与截位策略,是保障系统信噪比(SNR)与运算效率的关键。
三、时钟(Clocking Wizard IP核)
时钟是FPGA系统的核心。为避免亚稳态并确保数据可靠传输,设计上优先保证各模块时钟同源;对于异步时钟域,则采用FIFO进行缓存处理。针对DAC904与AD9226器件间的频率差异,本设计采取降频策略,将DAC904的工作频率调整至与AD9226一致的65MHz,从而实现全局时钟同步。由于开发板仅提供125MHz板载时钟,因此需利用分频器从中提取所需的65MHz工作时钟。
本设计的Clocking Wizard IP核具体配置参数如下:


四、AM/ASK解调模块
1.总模块设计
本设计的AM/ASK解调采用包络解调的方法,具体为:

AM/ASK解调模块的源码为:
module AM_Demodulation(
input wire clk_65m,
input wire rst_n,
input wire signed [11:0] data,
output wire [13:0] AM_ASK_data
);
// ================= 包络提取部分 =================
wire signed [11:0] abs12 = data[11] ? (~data + 1'b1) : data;
wire signed [15:0] abs16 = {abs12, 4'b0000};
wire signed [15:0] lpf_out16;
AM_LPF envelope_filter (
.clk(clk_65m),
.clk_enable(1'b1),
.reset(rst_n),
.filter_in(abs16),
.filter_out(lpf_out16)
);
// assign env_out_16 = lpf_out16;
//================= 输出波形逻辑 =================
reg signed [13:0] AM_ASK_data_reg;
wire signed [13:0] env_for_dac = lpf_out16[15:2];
always @(posedge clk_65m or negedge rst_n) begin
if (!rst_n) begin
AM_ASK_data_reg <= 14'sd0;
end else begin
AM_ASK_data_reg <= env_for_dac;
end
end
wire signed [13:0] AM_gain = 14'd20;
wire signed [13:0] AM_ASK_data_reg_out = AM_ASK_data_reg << 4;
assign AM_ASK_data = {~AM_ASK_data_reg_out[13], AM_ASK_data_reg_out[12:0]}-14'd3300;
//ila_0 AM (
// .clk(clk_65m), // input wire clk
// .probe0(data), // input wire [11:0] probe0
// .probe1(env_out_16) // input wire [15:0] probe1
//);
endmodule
2.整流处理
整流处理其实非常简单,即取平均值然后补高位到滤波器的输入位宽即可。
wire signed [11:0] abs12 = data[11] ? (~data + 1'b1) : data;
wire signed [15:0] abs16 = {abs12, 4'b0000};
3.低通滤波器设计
其中,AM_LPF选用1阶低通IIR巴特沃斯滤波器,采样频率为65000kHz,截止频率为100kHz,具体参数由matlab的fdatool工具生成,具体配置已贴出:

注意:要设置为直接Ⅱ型转置,二阶节,点击设计滤波器,得到滤波器后点击上方编辑->转换结构->Direct-Form Ⅱ Transposed,SOS,直到滤波器结构转换完成,点击左下角设置量化参数,滤波器算法设为定点,最后点击应用,直到量化完成后再点击上方目标->生成HDL...,打开滤波器生成工具,language选verilog,在Global Settings里的Reset asserted level设为Active-low(因为本系统所有复位都是低电平有效),然后选择滤波器生成的位置,找到对应的.v文件即可



五、VIVADO ROM IP核
1.ROM IP核
ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事实上在 FPGA 中通过 IP 核生成的 ROM 或 RAM 调用的都是 FPGA 内部的 RAM 资源,掉电内容都会丢失,用 IP 核生成的 ROM 模块只是提前添加了数据文件(.coe 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化。https://blog.csdn.net/yishuihanq/article/details/130767125
2.波形数据
在基于 DAC904 的正弦波发生架构中,数字域的离散特性决定了模拟输出的台阶效应。由于 DAC904 为 14bit 分辨率,其数字输入码可表示=16384个量化等级。结合其 ±5V 的基准量程(峰峰值 10V),系统的最小量化步长(LSB)计算如下:
这意味着,FPGA 每输出一个递增的数字码,模拟电压仅改变约 0.6mV。一个标准的满幅正弦波,其波峰对应 +5V(数字码 16383),波谷对应 -5V(数字码 0 或补码形式),整个周期内将包含 16384 个离散的电平采样点。
波形数据的产生我用的忘记是谁开发的软件了,如有侵权请联系,用matlab直接生成coe文件也可,需注意数据集是有符号类型还是无符号类型。
链接: https://pan.baidu.com/s/1XYoQWVRFX-wA8fMdy-slvA?pwd=1111 提取码: 1111
3.为什么要用ROM IP核
我们一般用Matlab提前生成需要的波形数据(一般为满幅波形,即与深度相等),存储到coe文件中,在ROM核中进行对应的配置,就可以将波形数据存放到程序之中,之后就可调用了。
4.ROM IP核的配置



六、FM/FSK解调模块
1.总模块设计

该模块采用数字锁相鉴频环路结构,主要包括数控振荡器、鉴相器、低通滤波器以及环路滤波器等部分。
数控振荡器模块:由ROM IP核和相位累加器构成,通过输入的频率控制字和相位控制字控制ROM IP核输出不同频率和相位的余弦参考信号。
鉴相器模块:由乘法器实现,输入信号与本地余弦信号进行相乘运算,产生鉴相器输出,该输出包括低频误差以及两倍频分量,在程序中先对乘法的结果取高位,然后送入滤波器滤去高频部分,只留下相位误差。
低通滤波器模块:依旧1阶低通IIR巴特沃斯滤波器,采样频率为65MHz,截止频率为100kHz。
环路滤波器模块:经过低通滤波后相位误差进入环路滤波器模块,产生频率控制量,此频率控制量同时用于NCO频偏调整以及作为FM/FSK解调结果输出,具体参数(C1、C2)需要根据实际情况进行调节。
module FM_demodulation(
input clk,
input rst_n,
input signed [11:0] data,
output [13:0] FM_FSK_data
);
// 频率控制字定义
// 65MHz采样率下, 1MHz = (1e6/65e6) * 2^32 ≈ 66076419
localparam [31:0] PHASE_STEP_1MHZ = 32'd66076419; // 中心频率 1MHz
localparam [31:0] INITIAL_FREQ = PHASE_STEP_1MHZ; // 初始频率1MHz
// PLL相关信号
wire [11:0] phase_addra;
wire signed [11:0] cos_out;
wire signed [23:0] pd_mult_out; // 鉴相器(乘法器)输出
wire signed [31:0] freq_control; // 环路滤波器输出(频率控制字)
wire signed [15:0] lpf_out_full; // 低通滤波器输出
wire signed [11:0] phase_error; // 截位后的相位误差
// ================= 环路滤波器模块 =================
// 接收鉴相误差,输出频率控制字控制NCO
loop_filter_FM loop_filter_inst (
.clk(clk),
.rst_n(rst_n),
.theta(phase_error), // 输入相位误差
.delta(freq_control) // 输出频率调整量 (此信号即为FM解调结果)
);
// ================= NCO (数控振荡器) =================
PhaseAccumulator nco (
.CLK(clk),
.RSTn(rst_n),
.PhaseOffset(12'd0),
.PhaseStep(INITIAL_FREQ + $unsigned(freq_control)), // 跟踪输入信号频率
.MemAddr(phase_addra)
);
// 产生本地载波 (FM PLL只需要一路余弦或正弦即可完成鉴相)
rom_sin_12bit cos_rom (
.clka(clk),
.addra(phase_addra + 12'd1024), // cos相位偏移
.douta(cos_out)
);
// 将 ROM 输出的无符号偏移二进制码转为有符号二进制补码
wire signed [11:0] cos_out_signed = {~cos_out[11], cos_out[10:0]};
// ================= 鉴相器 (Phase Detector) =================
// 输入FM信号与本地载波相乘,产生包含相位差的高频与低频分量
mult_gen_0 pd_multiplier (
.CLK(clk),
.A(data),
.B(cos_out_signed), // 本地载波
.SCLR(~rst_n), // input wire SCLR
.P(pd_mult_out) // 鉴相输出
);
// ================= 低通滤波器 (LPF) =================
// 滤除乘法器产生的高频分量 (2倍频),保留低频的相位误差分量
wire signed [15:0] lpf_in = pd_mult_out[22:7];
LPF_100k loop_lpf (
.clk(clk),
.clk_enable(1'b1),
.reset(rst_n),
.filter_in(lpf_in),
.filter_out(lpf_out_full)
);
// ================= 相位误差截位 =================
// 将LPF输出截位匹配 loop_filter 的输入位宽 (12bit)
assign phase_error = lpf_out_full[15:4];
// ================= 解调输出 =================
wire signed [11:0] fm_demod = freq_control[22:11];
//assign fm_demod_out = fm_demod;
/**********************************输出波形逻辑*************************************************/
reg signed [13:0] FM_FSK_data_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
FM_FSK_data_reg <= 14'd0;
end else begin
// 注意:这里输入已经是 14-bit (FM_FSK_in),你之前的注释写的是 12->14
// 这里保持你原来的赋值:直接透传 14 位
FM_FSK_data_reg <= fm_demod;
end
end
wire signed [13:0] gain = 14'd6;
wire signed [13:0] FM_FSK_data_reg_out = FM_FSK_data_reg << 2;
assign FM_FSK_data = {~FM_FSK_data_reg_out[13], FM_FSK_data_reg_out[12:0]};
//ila_0 FM (
// .clk(clk), // input wire clk
// .probe0(data), // input wire [11:0] probe0
// .probe1(fm_demod_out) // input wire [11:0] probe1
//);
endmodule
2.分模块说明
相位累加器(数控振荡器)
这里用到了上一章节提到的ROM IP核,与相位累加器相配合可以输出需要的任意频率和相位的波形,由于FM/FSK解调需要先产生本地的参考载波进行相乘,故引入相位累加器PhaseAccumulator模块。
/*
输入信号:
CLK,
RSTn,
PhaseStep: 相位步进字
PhaseOffset: 相位偏移量
输出信号:
MemAddr:存储器地址,
*/
module PhaseAccumulator#(
parameter PhaseAcc_BITS_NUM = 32,
parameter Mem_BITS_NUM = 12,
parameter PhaseOffset_BITS_NUM = 12
)(
CLK,
RSTn,
PhaseStep,
PhaseOffset, // 新增:相位偏移输入
MemAddr
);
input CLK;
input RSTn;
input [PhaseAcc_BITS_NUM-1:0] PhaseStep;
input [PhaseOffset_BITS_NUM-1:0] PhaseOffset; // 新增相位偏移输入
output [Mem_BITS_NUM-1:0] MemAddr;
reg [PhaseAcc_BITS_NUM-1:0] PhaseSum;
wire [Mem_BITS_NUM-1:0] PhaseHighBits; // 相位累加器的高位
wire [Mem_BITS_NUM:0] AddResult; // 带进位的加法结果
always @ ( posedge CLK or negedge RSTn)
if( !RSTn ) begin
PhaseSum <= 32'd0;
end
else
begin
PhaseSum <= PhaseSum + PhaseStep;
end
// 取相位累加器高Mem_BITS_NUM位
assign PhaseHighBits = PhaseSum[PhaseAcc_BITS_NUM-1:PhaseAcc_BITS_NUM-Mem_BITS_NUM];
// 将相位偏移加到相位高位上
assign AddResult = PhaseHighBits + PhaseOffset;
// 取低Mem_BITS_NUM位,允许自动回绕
assign MemAddr = AddResult[Mem_BITS_NUM-1:0];
endmodule
低通滤波器
依旧1阶低通IIR巴特沃斯滤波器,采样频率为65MHz,截止频率为100kHz,见AM/ASK低通滤波器配置部分。
环路滤波器
这个模块具体算法逻辑跟PID类似,公式太多就不放推导了,需要推导的各位可以复制粘贴给AI
module loop_filter_FM #(
// 滤波器参数配置 (根据实际系统需求调整)
parameter signed [31:0] C1 = 32'd80000, // 比例增益 (Proportional Gain)
parameter signed [31:0] C2 = 32'd100 // 积分增益 (Integral Gain)
)(
input wire clk,
input wire rst_n,
input wire signed [11:0] theta, // 鉴相器输出的相位误差
output wire signed [31:0] delta // 环路滤波器输出的控制字 (NCO频率控制字偏移)
);
// 内部寄存器与连线定义
reg signed [31:0] integral_acc; // 积分累加器
reg signed [31:0] delta_reg; // 输出寄存器
// 1. 符号位扩展:将 12-bit 有符号数安全扩展为 32-bit,防止后续运算溢出
wire signed [31:0] theta_ext;
assign theta_ext = {{20{theta[11]}}, theta};
// 2. 乘法运算 (通常综合工具会自动映射为 DSP block)
wire signed [31:0] prop_term;
wire signed [31:0] integ_term;
assign prop_term = theta_ext * C1;
assign integ_term = theta_ext * C2;
// 3. 时序逻辑:积分累加与输出计算
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integral_acc <= 32'd0;
delta_reg <= 32'd0;
end else begin
// 积分路径累加:I[n] = I[n-1] + C2 * theta[n]
integral_acc <= integral_acc + integ_term;
// 比例路径 + 积分路径:为了优化时序,这里直接使用上一拍的积分值
// delta[n] = P[n] + I[n-1]
delta_reg <= prop_term + integral_acc;
end
end
// 4. 驱动输出端口
assign delta = delta_reg;
endmodule
鉴相器(乘法器)
由乘法器实现,输入信号与本地余弦信号进行相乘运算,产生鉴相器输出,该输出包括低频误差以及两倍频分量,在程序中先对乘法的结果取高位,然后送入滤波器滤去高频部分,只留下相位误差。
具体配置如下:


七、PSK解调模块

该模块其实非常简单,我做程序的时候是先实现的PSK的解调又在该解调模块的基础上实现的FM/FSK的解调,大家看这个框图就可以发现与FM/FSK的解调模块存在很多类似的部分。
但是在调试的过程中,我发现当信号源在FSK、FM、PSK几个信号之间轮换的时候会出现锁相环锁不到的情况,故又给加入状态机,使压控振荡器不会跑飞。
具体代码如下:
module PSK_demodulation(
input clk_65m,
input rst_n,
input signed [11:0] data,
output wire PSK_valid,
output signed [13:0] PSK_data_wire
);
// ===== 频率控制字 =====
localparam [31:0] PHASE_STEP_1MHZ = 32'd66076419;
localparam [31:0] INITIAL_FREQ = PHASE_STEP_1MHZ;
// ===== 锁定迟滞阈值 =====
// 上门限:超过此值判定为PSK已锁定
// 下门限:低于此值判定为失锁,触发复位重捕
localparam signed [31:0] LOCK_HIGH = 32'sd10_000;
localparam signed [31:0] LOCK_LOW = 32'sd2_000;
// 复位保持周期(512周期 ≈ 7.9us @65MHz)
localparam [9:0] RESET_HOLD = 10'd512;
// ==============================================================
// ================= 自动状态机 =================================
// ==============================================================
// 状态定义
localparam S_RESET = 2'd0; // 环路复位中(刚上电 / 失锁后)
localparam S_SEARCH = 2'd1; // 搜索中(环路正常运行,等待锁定)
localparam S_LOCKED = 2'd2; // 已锁定
reg [1:0] state;
reg [9:0] reset_cnt;
reg loop_rst_n_r; // 受控的环路复位信号
// psk_lock_acc 前向声明(需要在状态机中读取)
reg signed [31:0] psk_lock_acc;
always @(posedge clk_65m or negedge rst_n) begin
if (!rst_n) begin
state <= S_RESET;
reset_cnt <= RESET_HOLD;
loop_rst_n_r <= 1'b0;
end else begin
case (state)
// ---- 状态0:复位保持 ----
S_RESET: begin
loop_rst_n_r <= 1'b0;
if (reset_cnt > 10'd0) begin
reset_cnt <= reset_cnt - 1'b1;
end else begin
// 复位倒计时结束,进入搜索态
state <= S_SEARCH;
reset_cnt <= RESET_HOLD;
end
end
// ---- 状态1:搜索(自由捕获)----
S_SEARCH: begin
loop_rst_n_r <= 1'b1; // 释放环路
if (psk_lock_acc > LOCK_HIGH) begin
state <= S_LOCKED; // 锁定成功
end
end
// ---- 状态2:已锁定 ----
S_LOCKED: begin
loop_rst_n_r <= 1'b1;
if (psk_lock_acc < LOCK_LOW) begin
// 失锁(切到FSK或信号消失),触发复位重捕
state <= S_RESET;
reset_cnt <= RESET_HOLD;
loop_rst_n_r <= 1'b0;
end
end
default: state <= S_RESET;
endcase
end
end
// 环路滤波器实际复位 = 系统复位 OR 状态机控制复位
wire loop_rst_n_sig = rst_n & loop_rst_n_r;
// 鉴相误差门控:仅在SEARCH/LOCKED状态下向环路滤波器输出有效误差
// RESET状态下输入0,冻结积分器防止乱积
wire signed [11:0] phase_error;
wire signed [11:0] phase_error_gated = loop_rst_n_r ? phase_error : 12'sd0;
// ==============================================================
// ================= Costas 环主体 ==============================
// ==============================================================
wire [11:0] phase_addra;
wire signed [11:0] sin_out, cos_out;
wire signed [23:0] i_mult_out, q_mult_out;
wire signed [31:0] freq_control;
// ===== 环路滤波器 =====
loop_filter_PSK loop_filter_inst (
.clk (clk_65m),
.rst_n (loop_rst_n_sig), // 受状态机控制的复位
.theta (phase_error_gated), // 门控误差输入
.delta (freq_control)
);
// ===== NCO =====
PhaseAccumulator nco (
.CLK (clk_65m),
.RSTn (rst_n),
.PhaseOffset(12'd0),
.PhaseStep (INITIAL_FREQ + $unsigned(freq_control)),
.MemAddr (phase_addra)
);
// ===== 正交载波 =====
rom_sin_12bit sin_rom (
.clka (clk_65m),
.addra(phase_addra),
.douta(sin_out)
);
rom_sin_12bit cos_rom (
.clka (clk_65m),
.addra(phase_addra + 12'd1024),
.douta(cos_out)
);
wire signed [11:0] cos_out_signed = {~cos_out[11], cos_out[10:0]};
wire signed [11:0] sin_out_signed = {~sin_out[11], sin_out[10:0]};
// ===== 混频器 =====
mult_gen_0 i_multiplier (
.CLK (clk_65m),
.A (data),
.B (cos_out_signed),
.SCLR(~rst_n),
.P (i_mult_out)
);
mult_gen_0 q_multiplier (
.CLK (clk_65m),
.A (data),
.B (sin_out_signed),
.SCLR(~rst_n),
.P (q_mult_out)
);
// ===== 低通滤波器 =====
wire signed [15:0] i_filter_in = i_mult_out[23:8];
wire signed [15:0] q_filter_in = q_mult_out[23:8];
wire signed [15:0] i_filter_out_full;
wire signed [15:0] q_filter_out_full;
PSK_LPF i_filter (
.clk (clk_65m),
.clk_enable(1'b1),
.reset (rst_n),
.filter_in (i_filter_in),
.filter_out(i_filter_out_full)
);
PSK_LPF q_filter (
.clk (clk_65m),
.clk_enable(1'b1),
.reset (rst_n),
.filter_in (q_filter_in),
.filter_out(q_filter_out_full)
);
// ===== 鉴相器 =====
wire signed [11:0] i_base = i_filter_out_full[15:4];
wire signed [11:0] q_base = q_filter_out_full[15:4];
assign phase_error = (i_base[11] == 1'b0) ? q_base : -$signed(q_base);
// ===== 解调输出 =====
//assign jietiao = q_base;
// ==============================================================
// ================= 锁定检测器 =================================
// ==============================================================
wire [11:0] abs_i = i_base[11] ? -$signed(i_base) : i_base;
wire [11:0] abs_q = q_base[11] ? -$signed(q_base) : q_base;
wire signed [12:0] lock_diff = $signed({1'b0, abs_q}) - $signed({1'b0, abs_i});
always @(posedge clk_65m or negedge rst_n) begin
if (!rst_n) begin
psk_lock_acc <= 32'sd0;
end else if (!loop_rst_n_r) begin
// 状态机处于RESET状态时,清零累积器
psk_lock_acc <= 32'sd0;
end else begin
// 正常积分(SEARCH和LOCKED状态均有效)
psk_lock_acc <= psk_lock_acc - (psk_lock_acc >>> 14) + lock_diff;
end
end
assign PSK_valid = (state == S_LOCKED);
/************************************输出波形逻辑*****************************************/
reg signed [13:0] PSK_data_reg;
always @(posedge clk_65m or negedge rst_n) begin
if (!rst_n) begin
PSK_data_reg <= 14'd0;
end else begin
// 符号位扩展:把 12 位的最高位复制给 14 位的高两位
PSK_data_reg <= { {2{q_base[11]}}, q_base };
end
end
assign PSK_data_wire = PSK_data_reg;
//// ===== ILA 调试 =====
//ila_0 FM (
// .clk(clk_65m), // input wire clk
// .probe0(data), // input wire [11:0] probe0
// .probe1(jietiao) // input wire [11:0] probe1
//);
endmodule
该模块包括一个自定义的状态机、数控振荡器产生正交载波、I/Q两个混频器、一个低通滤波器、一个相位误差计算电路以及一个锁相检测电路。
与FM/FSK不一样的是鉴相器模块,本模块采用的鉴相方法比较简洁,为下表中的第二种方法,也忘记是从哪篇论文里借鉴的了,如有侵权请联系。

注意:1、该模块的环路滤波器不能沿用FM/FSK中的参数C1、C2,需另调节。
2、低通滤波器同前两模块一样,可以沿用。
3、混频器即为乘法器,可沿用前一模块。
4、正交载波即在正交载波的ROM IP地址上加1/4周期,即4096/4=12‘d1024。
八、调制模式判断模块AMC
调制模式判断说白了就是看解调波形是否为方波或正弦波,也就是看跳变的时间是否陡峭。
本模块在一个滑动窗口计数器window_cnt内计数,抓取特征出现次数进而判断波形类型,具体参数要根据具体ILA抓取波形的数据来确定。
本模块的框图如下:

本模块的代码如下:
module AMC_Classifier(
input wire clk_65m,
input wire rst_n,
input wire [13:0] env_in,
input wire [13:0] freq_in,
input wire psk_locked_in,
// [4]:AM, [3]:ASK, [2]:FM, [1]:FSK, [0]:PSK
output reg [4:0] o_mod_type
);
// ================== 1. 参数 ==================
localparam [16:0] WINDOW_SIZE = 17'd65000;
// 14bit 输入范围:0 ~ 16383
// 这些阈值后面可以按 ILA 实测再微调
localparam [15:0] TH_ENV_ACTIVE = 16'd1500; // 包络变化明显
localparam [15:0] TH_FREQ_ACTIVE = 16'd16300; // 频率变化明显
localparam [15:0] TH_ENV_EDGE_MIN = 16'd100; // ASK 最小绝对跳变
localparam [15:0] TH_FREQ_JUMP_MIN = 16'd1100; // FSK 最小绝对跳变
// Q8 比例阈值:
// env_diff_max / env_range_now > 32 / 256 = 12.5% 判为 ASK
// freq_jump_max / freq_range_now > 32 / 256 = 12.5% 判为 FSK
localparam [7:0] TH_ENV_STEP_RATIO_Q8 = 8'd32;
localparam [7:0] TH_FREQ_STEP_RATIO_Q8 = 8'd16;
// PSK lock 在一个窗口内至少保持 1/8 窗口,才认为有效
localparam [16:0] PSK_LOCK_MIN = WINDOW_SIZE >> 3;
// ================== 2. 寄存器 ==================
reg [16:0] window_cnt;
reg [13:0] env_d1;
reg [13:0] freq_d1;
reg [13:0] freq_delay_line [0:63];
integer i;
reg [13:0] env_max_val;
reg [13:0] env_min_val;
reg [13:0] freq_max_val;
reg [13:0] freq_min_val;
reg [15:0] env_diff_max;
reg [15:0] freq_jump_max;
reg [16:0] psk_lock_cnt;
// ================== 3. 实时特征 ==================
wire [15:0] env_diff =
(env_in >= env_d1) ? ({2'd0, env_in} - {2'd0, env_d1}) :
({2'd0, env_d1} - {2'd0, env_in});
wire [13:0] delayed_freq = freq_delay_line[63];
wire [15:0] freq_jump =
(freq_in >= delayed_freq) ? ({2'd0, freq_in} - {2'd0, delayed_freq}) :
({2'd0, delayed_freq} - {2'd0, freq_in});
wire [15:0] env_range_now =
(env_max_val >= env_min_val) ?
({2'd0, env_max_val} - {2'd0, env_min_val}) :
({2'd0, env_min_val} - {2'd0, env_max_val});
wire [15:0] freq_range_now =
(freq_max_val >= freq_min_val) ?
({2'd0, freq_max_val} - {2'd0, freq_min_val}) :
({2'd0, freq_min_val} - {2'd0, freq_max_val});
// ================== 4. 归一化判决 ==================
wire cond_env_active = (env_range_now > TH_ENV_ACTIVE);
wire cond_freq_active = (freq_range_now < TH_FREQ_ACTIVE);
// wire [31:0] env_step_lhs = ({16'd0, env_diff_max} << 8);
// wire [31:0] env_step_rhs = env_range_now * TH_ENV_STEP_RATIO_Q8;
// wire [31:0] freq_step_lhs = ({16'd0, freq_jump_max} << 8);
// wire [31:0] freq_step_rhs = freq_range_now * TH_FREQ_STEP_RATIO_Q8;
wire cond_env_step =
cond_env_active &&
(env_diff_max > TH_ENV_EDGE_MIN);
wire cond_freq_step =
cond_freq_active &&
(freq_jump_max > TH_FREQ_JUMP_MIN) ;
// &&
// (freq_step_lhs > freq_step_rhs);
wire cond_psk_like = (psk_lock_cnt >= PSK_LOCK_MIN);
// debug_flags[7:0]
// [7] cond_psk_like
// [6] cond_env_active
// [5] cond_env_step
// [4] cond_freq_active
// [3] cond_freq_step
// [2] psk_locked_in
// [1] reserved
// [0] reserved
wire [7:0] debug_flags = {
cond_psk_like,
cond_env_active,
cond_env_step,
cond_freq_active,
cond_freq_step,
psk_locked_in,
1'b0,
1'b0
};
// ================== 5. 主逻辑 ==================
always @(posedge clk_65m or negedge rst_n) begin
if (!rst_n) begin
window_cnt <= 17'd0;
env_d1 <= 14'd0;
freq_d1 <= 14'd0;
env_max_val <= 14'd0;
env_min_val <= 14'h3FFF;
freq_max_val <= 14'd0;
freq_min_val <= 14'h3FFF;
env_diff_max <= 16'd0;
freq_jump_max <= 16'd0;
psk_lock_cnt <= 17'd0;
o_mod_type <= 5'b00000;
for (i = 0; i < 64; i = i + 1) begin
freq_delay_line[i] <= 14'd0;
end
end
else begin
env_d1 <= env_in;
freq_d1 <= freq_in;
freq_delay_line[0] <= freq_in;
for (i = 1; i < 64; i = i + 1) begin
freq_delay_line[i] <= freq_delay_line[i-1];
end
if (window_cnt < WINDOW_SIZE) begin
window_cnt <= window_cnt + 1'b1;
if (env_in > env_max_val)
env_max_val <= env_in;
if (env_in < env_min_val)
env_min_val <= env_in;
if (freq_in > freq_max_val)
freq_max_val <= freq_in;
if (freq_in < freq_min_val)
freq_min_val <= freq_in;
if (env_diff > env_diff_max)
env_diff_max <= env_diff;
if (freq_jump > freq_jump_max)
freq_jump_max <= freq_jump;
if (psk_locked_in && psk_lock_cnt < WINDOW_SIZE)
psk_lock_cnt <= psk_lock_cnt + 1'b1;
end
else begin
window_cnt <= 17'd0;
// ================== 6. 判决优先级 ==================
// PSK:由外部锁定信号优先判断
// ASK/AM:看包络是否明显变化
// FSK/FM:看频率是否明显变化
if (cond_env_active) begin
if (cond_env_step)
o_mod_type <= 5'b01000; // ASK
else
o_mod_type <= 5'b10000; // AM
end
else if (cond_freq_active) begin
if (cond_freq_step)
o_mod_type <= 5'b00010; // FSK
else
o_mod_type <= 5'b00100; // FM
end else if (cond_psk_like) begin
o_mod_type <= 5'b00001; // PSK
end
else begin
o_mod_type <= 5'b00000;
end
// ================== 7. 新窗口初始化 ==================
env_max_val <= env_in;
env_min_val <= env_in;
freq_max_val <= freq_in;
freq_min_val <= freq_in;
env_diff_max <= 16'd0;
freq_jump_max <= 16'd0;
psk_lock_cnt <= 17'd0;
end
end
end
// ================== 8. ILA 调试 ==================
// ila_0 your_instance_name (
// .clk (clk_65m),
// .probe0 (env_in), // [13:0]
// .probe1 (freq_in), // [13:0]
// .probe2 (env_range_now), // [15:0]
// .probe3 (env_diff_max), // [15:0]
// .probe4 (freq_range_now), // [15:0]
// .probe5 (freq_jump_max), // [15:0]
// .probe6 (debug_flags), // [7:0]
// .probe7 (o_mod_type) // [4:0]
// );
endmodule
九、调制参数处理模块
该模块即根据抓取窗口内实际的数值估算出需要测量的参数,都是规定好的公式,得出的参数不同就慢慢调。接收来自AMC的模式识别输入后,输出对应的参数。
`timescale 1ns / 1ps
module data_proc #(
parameter integer CLK_FRE = 65_000_000,
parameter integer WINDOW_SIZE = 65_000,
parameter integer N_WIN = 4,
parameter integer PSK_DEBOUNCE = 32,
// 14bit 解调频率码值满量程对应的频偏 Hz,需要按你的硬件实测校准
parameter integer FREQ_CODE_HZ = 92_000,
// 防止毛刺边沿,最大允许测量频率
parameter integer MAX_MEAS_HZ = 20_000,
// 模式稳定多少拍后才切换
parameter integer MODE_STABLE_N = 200,
// 你前面实测 FM/FSK 是函数发生器的 2 倍,所以默认除 2
parameter integer FM_DIV2 = 1,
parameter integer FSK_DIV2 = 1
)(
input wire clk,
input wire rst_n,
input wire [4:0] i_mode_type,
input wire [13:0] sig_in,
output reg [15:0] ma,
output reg [15:0] mf,
output reg [31:0] bps,
output reg [31:0] fre
);
localparam MODE_AM = 5'b10000;
localparam MODE_ASK = 5'b01000;
localparam MODE_FM = 5'b00100;
localparam MODE_FSK = 5'b00010;
localparam MODE_PSK = 5'b00001;
localparam integer W_CNT = $clog2(WINDOW_SIZE);
localparam integer N_CNT = (N_WIN <= 1) ? 1 : $clog2(N_WIN);
localparam [31:0] ADC_SCALE = 32'd16384;
localparam [13:0] HYST_MIN = 14'd16;
localparam [15:0] MA_GAIN_Q8 = 16'd680;
localparam [31:0] TIMEOUT = CLK_FRE;
localparam [63:0] CLK_FRE_64 = CLK_FRE;
localparam [31:0] MIN_EDGE_GAP_RAW =
(MAX_MEAS_HZ > 0) ? (CLK_FRE / MAX_MEAS_HZ) : 32'd1;
localparam [31:0] MIN_EDGE_GAP =
(MIN_EDGE_GAP_RAW < PSK_DEBOUNCE) ? PSK_DEBOUNCE : MIN_EDGE_GAP_RAW;
// ==================================================
// 模式稳定锁存
// ==================================================
reg [4:0] mode_sample;
reg [4:0] mode_r;
reg [4:0] mode_d;
reg [15:0] mode_stable_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mode_sample <= 5'd0;
mode_r <= 5'd0;
mode_d <= 5'd0;
mode_stable_cnt <= 16'd0;
end else begin
mode_d <= mode_r;
if (i_mode_type != mode_sample) begin
mode_sample <= i_mode_type;
mode_stable_cnt <= 16'd0;
end else if (mode_stable_cnt < MODE_STABLE_N[15:0]) begin
mode_stable_cnt <= mode_stable_cnt + 1'b1;
end else begin
mode_r <= mode_sample;
end
end
end
wire mode_changed = (mode_r != mode_d);
// ==================================================
// 窗口计数
// ==================================================
reg [W_CNT-1:0] win_cnt;
wire win_end = (win_cnt == WINDOW_SIZE - 1);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
win_cnt <= 0;
else if (mode_changed)
win_cnt <= 0;
else if (win_end)
win_cnt <= 0;
else
win_cnt <= win_cnt + 1'b1;
end
reg [N_CNT-1:0] win_acc_cnt;
wire group_end = win_end && (win_acc_cnt == N_WIN - 1);
// ==================================================
// 最大值 / 最小值 / 动态中点
// ==================================================
reg [13:0] x_max_acc;
reg [13:0] x_min_acc;
reg [13:0] x_max_latch;
reg [13:0] x_min_latch;
reg [13:0] zero_mid_r;
reg [13:0] hyst_r;
reg mid_valid;
reg latch_valid;
wire [14:0] x_range_now =
(x_max_acc >= x_min_acc) ?
({1'b0, x_max_acc} - {1'b0, x_min_acc}) : 15'd0;
wire [14:0] x_mid_now =
({1'b0, x_max_acc} + {1'b0, x_min_acc}) >> 1;
wire [13:0] hyst_from_range = {3'd0, x_range_now[14:4]};
wire [13:0] hyst_next =
(hyst_from_range < HYST_MIN) ? HYST_MIN : hyst_from_range;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
win_acc_cnt <= 0;
x_max_acc <= 14'd0;
x_min_acc <= 14'h3FFF;
x_max_latch <= 14'd0;
x_min_latch <= 14'd0;
zero_mid_r <= 14'd8192;
hyst_r <= HYST_MIN;
mid_valid <= 1'b0;
latch_valid <= 1'b0;
end else if (mode_changed) begin
win_acc_cnt <= 0;
x_max_acc <= sig_in;
x_min_acc <= sig_in;
x_max_latch <= 14'd0;
x_min_latch <= 14'd0;
zero_mid_r <= 14'd8192;
hyst_r <= HYST_MIN;
mid_valid <= 1'b0;
latch_valid <= 1'b0;
end else begin
latch_valid <= 1'b0;
if (win_end) begin
if (group_end) begin
x_max_latch <= x_max_acc;
x_min_latch <= x_min_acc;
zero_mid_r <= x_mid_now[13:0];
hyst_r <= hyst_next;
mid_valid <= 1'b1;
latch_valid <= 1'b1;
x_max_acc <= sig_in;
x_min_acc <= sig_in;
win_acc_cnt <= 0;
end else begin
win_acc_cnt <= win_acc_cnt + 1'b1;
end
end else begin
if (sig_in > x_max_acc)
x_max_acc <= sig_in;
if (sig_in < x_min_acc)
x_min_acc <= sig_in;
end
end
end
// ==================================================
// 动态中点滞回比较
// ==================================================
wire [15:0] data_u16 = {2'd0, sig_in};
wire [15:0] mid_u16 = {2'd0, zero_mid_r};
wire [15:0] hyst_u16 = {2'd0, hyst_r};
wire [15:0] cmp_hi = mid_u16 + hyst_u16;
wire [15:0] cmp_lo = (mid_u16 > hyst_u16) ? (mid_u16 - hyst_u16) : 16'd0;
reg level;
reg level_d1;
wire level_rise = level && !level_d1;
wire level_edge = level ^ level_d1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
level <= 1'b0;
level_d1 <= 1'b0;
end else if (mode_changed) begin
level <= (sig_in >= zero_mid_r);
level_d1 <= (sig_in >= zero_mid_r);
end else begin
level_d1 <= level;
if (mid_valid) begin
if (data_u16 > cmp_hi)
level <= 1'b1;
else if (data_u16 < cmp_lo)
level <= 1'b0;
end
end
end
// ==================================================
// AM / FM 频率:上升沿到上升沿平均周期
// ==================================================
reg [31:0] period_timer;
reg period_first;
reg [47:0] period_sum;
reg [23:0] period_cnt;
reg [31:0] period_rate_r;
wire period_mode =
mid_valid &&
((mode_r == MODE_AM) || (mode_r == MODE_FM));
wire [63:0] period_rate_calc =
(period_cnt != 0 && period_sum != 0) ?
((CLK_FRE_64 * period_cnt) / period_sum) : 64'd0;
wire [31:0] period_rate_calc32 =
(period_rate_calc > 64'h0000_0000_FFFF_FFFF) ?
32'hFFFF_FFFF : period_rate_calc[31:0];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
period_timer <= 32'd0;
period_first <= 1'b0;
period_sum <= 48'd0;
period_cnt <= 24'd0;
period_rate_r <= 32'd0;
end else if (mode_changed || !period_mode) begin
period_timer <= 32'd0;
period_first <= 1'b0;
period_sum <= 48'd0;
period_cnt <= 24'd0;
period_rate_r <= 32'd0;
end else if (group_end) begin
period_rate_r <= period_rate_calc32;
period_timer <= 32'd0;
period_first <= 1'b0;
period_sum <= 48'd0;
period_cnt <= 24'd0;
end else begin
if (period_timer >= TIMEOUT) begin
period_timer <= 32'd0;
period_first <= 1'b0;
end else begin
period_timer <= period_timer + 1'b1;
end
if (level_rise && period_timer >= MIN_EDGE_GAP) begin
if (period_first) begin
period_sum <= period_sum + period_timer;
period_cnt <= period_cnt + 1'b1;
end
period_first <= 1'b1;
period_timer <= 32'd0;
end
end
end
// ==================================================
// ASK / FSK / PSK 波特率:按 ASK 同样逻辑,上升沿到上升沿
// ==================================================
reg [31:0] bit_timer;
reg bit_first;
reg [47:0] bit_period_sum;
reg [23:0] bit_period_cnt;
reg [31:0] bit_rate_r;
wire bit_mode =
mid_valid &&
((mode_r == MODE_ASK) ||
(mode_r == MODE_FSK) ||
(mode_r == MODE_PSK));
wire [63:0] bit_rate_calc =
(bit_period_cnt != 0 && bit_period_sum != 0) ?
((CLK_FRE_64 * bit_period_cnt) / bit_period_sum) : 64'd0;
wire [31:0] bit_rate_calc32 =
(bit_rate_calc > 64'h0000_0000_FFFF_FFFF) ?
32'hFFFF_FFFF : bit_rate_calc[31:0];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_timer <= 32'd0;
bit_first <= 1'b0;
bit_period_sum <= 48'd0;
bit_period_cnt <= 24'd0;
bit_rate_r <= 32'd0;
end else if (mode_changed || !bit_mode) begin
bit_timer <= 32'd0;
bit_first <= 1'b0;
bit_period_sum <= 48'd0;
bit_period_cnt <= 24'd0;
bit_rate_r <= 32'd0;
end else if (group_end) begin
bit_rate_r <= bit_rate_calc32;
bit_timer <= 32'd0;
bit_first <= 1'b0;
bit_period_sum <= 48'd0;
bit_period_cnt <= 24'd0;
end else begin
if (bit_timer >= TIMEOUT) begin
bit_timer <= 32'd0;
bit_first <= 1'b0;
end else begin
bit_timer <= bit_timer + 1'b1;
end
if (level_rise && bit_timer >= MIN_EDGE_GAP) begin
if (bit_first) begin
bit_period_sum <= bit_period_sum + bit_timer;
bit_period_cnt <= bit_period_cnt + 1'b1;
end
bit_first <= 1'b1;
bit_timer <= 32'd0;
end
end
end
wire [31:0] fm_fre_out =
(FM_DIV2 != 0) ? (period_rate_r >> 1) : period_rate_r;
wire [31:0] fsk_bps_out =
(FSK_DIV2 != 0) ? (bit_rate_r >> 1) : bit_rate_r;
// ==================================================
// Ma = (max - min) / (max + min),Q8.8,再乘校准系数
// ==================================================
wire [14:0] ma_sum_now =
{1'b0, x_max_acc} + {1'b0, x_min_acc};
wire [14:0] ma_diff_now =
(x_max_acc >= x_min_acc) ?
({1'b0, x_max_acc} - {1'b0, x_min_acc}) : 15'd0;
wire [31:0] ma_q8_raw =
(ma_sum_now != 0) ?
(({17'd0, ma_diff_now} << 8) / ma_sum_now) : 32'd0;
wire [47:0] ma_q8_gain = ma_q8_raw * MA_GAIN_Q8;
wire [31:0] ma_q8_cal = ma_q8_gain[39:8];
wire [15:0] ma_now_sat =
(ma_q8_cal > 32'd256) ? 16'h0100 : ma_q8_cal[15:0];
reg [15:0] ma_r;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
ma_r <= 16'd0;
else if (mode_r != MODE_AM)
ma_r <= 16'd0;
else if (group_end)
ma_r <= ma_now_sat;
end
// ==================================================
// Mf = delta_f / fm,Q8.8
// 注意:FREQ_CODE_HZ 必须按你的 FM 解调比例校准
// ==================================================
wire [14:0] freq_pp_code_now =
(x_max_acc >= x_min_acc) ?
({1'b0, x_max_acc} - {1'b0, x_min_acc}) : 15'd0;
wire [14:0] freq_peak_code = freq_pp_code_now >> 1;
wire [31:0] fm_rate_for_mf =
(FM_DIV2 != 0) ? (period_rate_calc32 >> 1) : period_rate_calc32;
wire [47:0] delta_f_peak_hz =
(({33'd0, freq_peak_code} * FREQ_CODE_HZ) / ADC_SCALE);
wire mf_rate_valid = (fm_rate_for_mf >= 32'd10);
wire [55:0] mf_q8_calc =
(mf_rate_valid) ?
(({8'd0, delta_f_peak_hz} << 8) / fm_rate_for_mf) : 56'd0;
wire [15:0] mf_sat =
(mf_q8_calc > 56'd65535) ? 16'hFFFF : mf_q8_calc[15:0];
reg [15:0] mf_r;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
mf_r <= 16'd0;
else if (mode_r != MODE_FM)
mf_r <= 16'd0;
else if (group_end)
mf_r <= mf_sat;
end
// ==================================================
// 输出
// ==================================================
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ma <= 16'd0;
mf <= 16'd0;
bps <= 32'd0;
fre <= 32'd0;
end else begin
case (mode_r)
MODE_AM: begin
ma <= ma_r;
mf <= 16'd0;
bps <= 32'd0;
fre <= period_rate_r;
end
MODE_ASK: begin
ma <= 16'd0;
mf <= 16'd0;
bps <= bit_rate_r;
fre <= 32'd0;
end
MODE_FM: begin
ma <= 16'd0;
mf <= mf_r;
bps <= 32'd0;
fre <= fm_fre_out;
end
MODE_FSK: begin
ma <= 16'd0;
mf <= 16'd0;
bps <= fsk_bps_out;
fre <= 32'd0;
end
MODE_PSK: begin
ma <= 16'd0;
mf <= 16'd0;
bps <= bit_rate_r;
fre <= 32'd0;
end
default: begin
ma <= 16'd0;
mf <= 16'd0;
bps <= 32'd0;
fre <= 32'd0;
end
endcase
end
end
// ==================================================
// ILA:8 个探针
// ==================================================
wire [7:0] debug_flags = {
mid_valid,
latch_valid,
group_end,
mode_changed,
level,
level_d1,
level_rise,
level_edge
};
wire [15:0] mf_calc_dbg =
(mf_q8_calc > 56'd65535) ? 16'hFFFF : mf_q8_calc[15:0];
ila_0 your_instance_name (
.clk (clk),
.probe0 (sig_in), // [13:0]
.probe1 (mode_r), // [4:0]
.probe2 (zero_mid_r), // [13:0]
.probe3 (x_max_latch), // [13:0]
.probe4 (x_min_latch), // [13:0]
.probe5 (period_rate_r), // [31:0] FM 原始测频,输出 fre 默认除 2
.probe6 (mf_calc_dbg), // [15:0] Mf 计算值
.probe7 (debug_flags) // [7:0]
);
endmodule
十、数据发送模块
该模块的作用是将计算出的调制参数打包用uart协议发出,具体的数据包定义如下:
| 包头 | 调制类型 | 调制信号频率 | Ma | Mf | bps | 包尾 |
| start | AM/ASK/FM/FSK/PSK | Q8.8 | Q8.8 | end |
module uart_packet_sender
#(
parameter CLK_FRE = 65,
parameter BAUD_RATE = 9600
)
(
input i_clk_sys,
input i_rst_n,
input i_send_en,
input [4:0] i_mod_type,
input [31:0] i_freq,
input [15:0] i_Ma, // Q8.8
input [15:0] i_Mf, // Q8.8
input [31:0] i_symbol_rate,
output o_uart_tx,
output reg o_busy
);
localparam BUF_LEN = 96;
// 主状态
localparam S_IDLE = 6'd0;
localparam S_LATCH = 6'd1;
localparam S_HEAD = 6'd2;
localparam S_TYPE = 6'd3;
localparam S_COMMA = 6'd4;
localparam S_DABBLE_INIT = 6'd5;
localparam S_DABBLE_SHIFT = 6'd6;
localparam S_DABBLE_DONE = 6'd7;
localparam S_FIND_FIRST = 6'd8;
localparam S_WRITE_DIGIT = 6'd9;
localparam S_WRITE_Q8_INT = 6'd10;
localparam S_WRITE_Q8_DOT = 6'd11;
localparam S_WRITE_Q8_FRACH = 6'd12;
localparam S_WRITE_Q8_FRACL = 6'd13;
localparam S_TAIL = 6'd14;
localparam S_SEND_LOAD = 6'd15;
localparam S_SEND_WAIT = 6'd16;
localparam S_WAIT_GAP = 6'd17;
// 字段
localparam FIELD_FREQ = 3'd0;
localparam FIELD_MA = 3'd1;
localparam FIELD_MF = 3'd2;
localparam FIELD_BPS = 3'd3;
localparam FIELD_DONE = 3'd4;
reg [5:0] r_state;
// 锁存输入
reg [4:0] l_mod_type;
reg [31:0] l_freq, l_symbol_rate;
reg [15:0] l_Ma, l_Mf;
// type 字符串
reg [7:0] r_type_buf [0:4];
reg [2:0] r_type_len;
// 填充 buffer
reg [7:0] r_buf [0:BUF_LEN-1];
reg [6:0] r_fill_idx;
reg [6:0] r_buf_len;
reg [6:0] r_byte_cnt;
reg [2:0] r_cur_field;
reg [2:0] r_wr_digit_idx;
reg [7:0] r_tx_data;
reg r_tx_valid;
wire w_tx_done;
reg [19:0] r_wait_gap_cnt;
// ---------- Double Dabble(只给 freq / bps 用) ----------
localparam BCD_W = 40; // 支持到 10 位十进制以内
reg [BCD_W-1:0] r_bcd;
reg [31:0] r_bin_shift;
reg [5:0] r_dabble_cnt;
reg [5:0] r_dabble_total;
reg [7:0] r_digit_buf [0:9];
function [3:0] add3;
input [3:0] x;
begin
add3 = (x >= 4'd5) ? (x + 4'd3) : x;
end
endfunction
wire [BCD_W-1:0] w_bcd_next;
assign w_bcd_next = {
add3(r_bcd[39:36]),
add3(r_bcd[35:32]),
add3(r_bcd[31:28]),
add3(r_bcd[27:24]),
add3(r_bcd[23:20]),
add3(r_bcd[19:16]),
add3(r_bcd[15:12]),
add3(r_bcd[11:8]),
add3(r_bcd[7:4]),
add3(r_bcd[3:0])
};
// ---------- Q8.8 直接拆分 ----------
wire [7:0] w_q8_int = (r_cur_field == FIELD_MA) ? l_Ma[15:8] : l_Mf[15:8];
// 先扩位再乘100,避免位宽截断
wire [15:0] w_ma_frac_mul = {8'd0, l_Ma[7:0]} * 16'd100;
wire [15:0] w_mf_frac_mul = {8'd0, l_Mf[7:0]} * 16'd100;
wire [7:0] w_ma_frac = w_ma_frac_mul[15:8];
wire [7:0] w_mf_frac = w_mf_frac_mul[15:8];
wire [7:0] w_q8_frac = (r_cur_field == FIELD_MA) ? w_ma_frac : w_mf_frac;
wire [7:0] w_q8_hund = w_q8_int / 8'd100;
wire [7:0] w_q8_tens = (w_q8_int % 8'd100) / 8'd10;
wire [7:0] w_q8_ones = w_q8_int % 8'd10;
wire [7:0] w_q8_frac_tens = w_q8_frac / 8'd10;
wire [7:0] w_q8_frac_ones = w_q8_frac % 8'd10;
wire [1:0] w_q8_digits = (w_q8_int >= 8'd100) ? 2'd3 :
(w_q8_int >= 8'd10 ) ? 2'd2 : 2'd1;
// ============================================================
// 主状态机
// ============================================================
always @(posedge i_clk_sys or negedge i_rst_n) begin
if (!i_rst_n) begin
r_state <= S_IDLE;
o_busy <= 1'b0;
r_fill_idx <= 7'd0;
r_buf_len <= 7'd0;
r_byte_cnt <= 7'd0;
r_wr_digit_idx <= 3'd0;
r_cur_field <= FIELD_FREQ;
r_tx_data <= 8'd0;
r_tx_valid <= 1'b0;
r_wait_gap_cnt <= 20'd0;
l_mod_type <= 5'd0;
l_freq <= 32'd0;
l_symbol_rate <= 32'd0;
l_Ma <= 16'd0;
l_Mf <= 16'd0;
r_type_len <= 3'd0;
r_bcd <= {BCD_W{1'b0}};
r_bin_shift <= 32'd0;
r_dabble_cnt <= 6'd0;
r_dabble_total <= 6'd0;
end else begin
r_tx_valid <= 1'b0;
case (r_state)
// ------------------------------------------------
S_IDLE: begin
o_busy <= 1'b0;
r_fill_idx <= 7'd0;
r_byte_cnt <= 7'd0;
r_wr_digit_idx <= 3'd0;
r_wait_gap_cnt <= 20'd0;
if (i_send_en) begin
o_busy <= 1'b1;
r_state <= S_LATCH;
end
end
// ------------------------------------------------
S_LATCH: begin
l_mod_type <= i_mod_type;
l_freq <= i_freq;
l_Ma <= i_Ma;
l_Mf <= i_Mf;
l_symbol_rate <= i_symbol_rate;
case (i_mod_type)
5'b10000: begin
r_type_buf[0] <= "A";
r_type_buf[1] <= "M";
r_type_len <= 3'd2;
end
5'b01000: begin
r_type_buf[0] <= "A";
r_type_buf[1] <= "S";
r_type_buf[2] <= "K";
r_type_len <= 3'd3;
end
5'b00100: begin
r_type_buf[0] <= "F";
r_type_buf[1] <= "M";
r_type_len <= 3'd2;
end
5'b00010: begin
r_type_buf[0] <= "F";
r_type_buf[1] <= "S";
r_type_buf[2] <= "K";
r_type_len <= 3'd3;
end
5'b00001: begin
r_type_buf[0] <= "P";
r_type_buf[1] <= "S";
r_type_buf[2] <= "K";
r_type_len <= 3'd3;
end
default: begin
r_type_buf[0] <= "U";
r_type_buf[1] <= "N";
r_type_buf[2] <= "K";
r_type_len <= 3'd3;
end
endcase
r_wr_digit_idx <= 3'd0;
r_state <= S_HEAD;
end
// ------------------------------------------------
S_HEAD: begin
case (r_wr_digit_idx)
3'd0: r_buf[r_fill_idx] <= "s";
3'd1: r_buf[r_fill_idx] <= "t";
3'd2: r_buf[r_fill_idx] <= "a";
3'd3: r_buf[r_fill_idx] <= "r";
3'd4: r_buf[r_fill_idx] <= "t";
3'd5: r_buf[r_fill_idx] <= ",";
default: r_buf[r_fill_idx] <= ",";
endcase
r_fill_idx <= r_fill_idx + 1'b1;
if (r_wr_digit_idx == 3'd5) begin
r_wr_digit_idx <= 3'd0;
r_state <= S_TYPE;
end else begin
r_wr_digit_idx <= r_wr_digit_idx + 1'b1;
end
end
// ------------------------------------------------
S_TYPE: begin
r_buf[r_fill_idx] <= r_type_buf[r_wr_digit_idx];
r_fill_idx <= r_fill_idx + 1'b1;
if (r_wr_digit_idx == r_type_len - 1'b1) begin
r_wr_digit_idx <= 3'd0;
r_cur_field <= FIELD_FREQ;
r_state <= S_COMMA;
end else begin
r_wr_digit_idx <= r_wr_digit_idx + 1'b1;
end
end
// ------------------------------------------------
S_COMMA: begin
r_buf[r_fill_idx] <= ",";
r_fill_idx <= r_fill_idx + 1'b1;
case (r_cur_field)
FIELD_FREQ: begin
r_bin_shift <= l_freq;
r_dabble_total <= 6'd32;
r_state <= S_DABBLE_INIT;
end
FIELD_MA: begin
r_wr_digit_idx <= 3'd0;
r_state <= S_WRITE_Q8_INT;
end
FIELD_MF: begin
r_wr_digit_idx <= 3'd0;
r_state <= S_WRITE_Q8_INT;
end
FIELD_BPS: begin
r_bin_shift <= l_symbol_rate;
r_dabble_total <= 6'd32;
r_state <= S_DABBLE_INIT;
end
default: begin
r_wr_digit_idx <= 3'd0;
r_state <= S_TAIL;
end
endcase
end
// ------------------------------------------------
// 只给 freq / bps 用
S_DABBLE_INIT: begin
r_bcd <= {BCD_W{1'b0}};
r_dabble_cnt <= 6'd0;
r_state <= S_DABBLE_SHIFT;
end
S_DABBLE_SHIFT: begin
r_bcd <= {w_bcd_next[BCD_W-2:0], r_bin_shift[31]};
r_bin_shift <= r_bin_shift << 1;
r_dabble_cnt <= r_dabble_cnt + 1'b1;
if (r_dabble_cnt == r_dabble_total - 1'b1)
r_state <= S_DABBLE_DONE;
end
S_DABBLE_DONE: begin
r_digit_buf[0] <= r_bcd[3:0] + 8'h30;
r_digit_buf[1] <= r_bcd[7:4] + 8'h30;
r_digit_buf[2] <= r_bcd[11:8] + 8'h30;
r_digit_buf[3] <= r_bcd[15:12] + 8'h30;
r_digit_buf[4] <= r_bcd[19:16] + 8'h30;
r_digit_buf[5] <= r_bcd[23:20] + 8'h30;
r_digit_buf[6] <= r_bcd[27:24] + 8'h30;
r_digit_buf[7] <= r_bcd[31:28] + 8'h30;
r_digit_buf[8] <= r_bcd[35:32] + 8'h30;
r_digit_buf[9] <= r_bcd[39:36] + 8'h30;
r_state <= S_FIND_FIRST;
end
S_FIND_FIRST: begin
if (r_digit_buf[9] != "0") r_wr_digit_idx <= 3'd9;
else if (r_digit_buf[8] != "0") r_wr_digit_idx <= 3'd8;
else if (r_digit_buf[7] != "0") r_wr_digit_idx <= 3'd7;
else if (r_digit_buf[6] != "0") r_wr_digit_idx <= 3'd6;
else if (r_digit_buf[5] != "0") r_wr_digit_idx <= 3'd5;
else if (r_digit_buf[4] != "0") r_wr_digit_idx <= 3'd4;
else if (r_digit_buf[3] != "0") r_wr_digit_idx <= 3'd3;
else if (r_digit_buf[2] != "0") r_wr_digit_idx <= 3'd2;
else if (r_digit_buf[1] != "0") r_wr_digit_idx <= 3'd1;
else r_wr_digit_idx <= 3'd0;
r_state <= S_WRITE_DIGIT;
end
S_WRITE_DIGIT: begin
r_buf[r_fill_idx] <= r_digit_buf[r_wr_digit_idx];
r_fill_idx <= r_fill_idx + 1'b1;
if (r_wr_digit_idx == 3'd0) begin
if (r_cur_field == FIELD_FREQ) begin
r_cur_field <= FIELD_MA;
r_state <= S_COMMA;
end else begin
r_cur_field <= FIELD_DONE;
r_state <= S_TAIL;
end
end else begin
r_wr_digit_idx <= r_wr_digit_idx - 1'b1;
end
end
// ------------------------------------------------
// Ma / Mf 直接按 Q8.8 打印
S_WRITE_Q8_INT: begin
case (w_q8_digits)
2'd3: begin
case (r_wr_digit_idx)
3'd0: r_buf[r_fill_idx] <= "0" + w_q8_hund;
3'd1: r_buf[r_fill_idx] <= "0" + w_q8_tens;
default: r_buf[r_fill_idx] <= "0" + w_q8_ones;
endcase
end
2'd2: begin
case (r_wr_digit_idx)
3'd0: r_buf[r_fill_idx] <= "0" + w_q8_tens;
default: r_buf[r_fill_idx] <= "0" + w_q8_ones;
endcase
end
default: begin
r_buf[r_fill_idx] <= "0" + w_q8_ones;
end
endcase
r_fill_idx <= r_fill_idx + 1'b1;
if (r_wr_digit_idx == w_q8_digits - 1'b1) begin
r_wr_digit_idx <= 3'd0;
r_state <= S_WRITE_Q8_DOT;
end else begin
r_wr_digit_idx <= r_wr_digit_idx + 1'b1;
end
end
S_WRITE_Q8_DOT: begin
r_buf[r_fill_idx] <= ".";
r_fill_idx <= r_fill_idx + 1'b1;
r_state <= S_WRITE_Q8_FRACH;
end
S_WRITE_Q8_FRACH: begin
r_buf[r_fill_idx] <= "0" + w_q8_frac_tens;
r_fill_idx <= r_fill_idx + 1'b1;
r_state <= S_WRITE_Q8_FRACL;
end
S_WRITE_Q8_FRACL: begin
r_buf[r_fill_idx] <= "0" + w_q8_frac_ones;
r_fill_idx <= r_fill_idx + 1'b1;
if (r_cur_field == FIELD_MA) begin
r_cur_field <= FIELD_MF;
r_state <= S_COMMA;
end else begin
r_cur_field <= FIELD_BPS;
r_state <= S_COMMA;
end
end
// ------------------------------------------------
S_TAIL: begin
case (r_wr_digit_idx)
3'd0: r_buf[r_fill_idx] <= ",";
3'd1: r_buf[r_fill_idx] <= "e";
3'd2: r_buf[r_fill_idx] <= "n";
3'd3: r_buf[r_fill_idx] <= "d";
3'd4: r_buf[r_fill_idx] <= 8'h0D; // \r
3'd5: r_buf[r_fill_idx] <= 8'h0A; // \n
default: r_buf[r_fill_idx] <= 8'h0A;
endcase
r_fill_idx <= r_fill_idx + 1'b1;
if (r_wr_digit_idx == 3'd5) begin
r_buf_len <= r_fill_idx + 1'b1;
r_byte_cnt <= 7'd0;
r_state <= S_SEND_LOAD;
end else begin
r_wr_digit_idx <= r_wr_digit_idx + 1'b1;
end
end
// ------------------------------------------------
S_SEND_LOAD: begin
r_tx_data <= r_buf[r_byte_cnt];
r_tx_valid <= 1'b1;
r_state <= S_SEND_WAIT;
end
S_SEND_WAIT: begin
if (w_tx_done) begin
r_wait_gap_cnt <= 20'd0;
r_state <= S_WAIT_GAP;
end
end
S_WAIT_GAP: begin
if (r_wait_gap_cnt < 20'd62500) begin
r_wait_gap_cnt <= r_wait_gap_cnt + 1'b1;
end else begin
if (r_byte_cnt == r_buf_len - 1'b1) begin
o_busy <= 1'b0;
r_state <= S_IDLE;
end else begin
r_byte_cnt <= r_byte_cnt + 1'b1;
r_state <= S_SEND_LOAD;
end
end
end
default: r_state <= S_IDLE;
endcase
end
end
uart_tx #(
.CLK_FRE (CLK_FRE),
.BAUD_RATE (BAUD_RATE)
) u_uart_tx (
.i_clk_sys (i_clk_sys),
.i_rst_n (i_rst_n),
.i_data_tx (r_tx_data),
.i_data_valid (r_tx_valid),
.o_uart_tx (o_uart_tx),
.o_tx_done (w_tx_done)
);
endmodule
STM32的接收数据处理逻辑:
void uart_proc(void)
{
uint32_t len = 0;
__disable_irq();
if (usart_rb.itemCount == 0)
{
__enable_irq();
return;
}
len = usart_rb.itemCount;
if (len >= BUUFER_SIZE)
len = BUUFER_SIZE - 1;
if (ringbuffer_read(&usart_rb, usart_read_buffer, len) != 0)
{
__enable_irq();
return;
}
__enable_irq();
usart_read_buffer[len] = '\0';
printf("ringbuffer data: %s\r\n", usart_read_buffer);
// 3. 初始化 strtok,并寻找包头 "start"
char *token = strtok((char *)usart_read_buffer, ",");
if (token != NULL && strcmp(token, "start") == 0)
{
// --- 提取第 2 段:调制类型 ---
token = strtok(NULL, ",");
if(token != NULL) {
if(strcmp(token, "AM") == 0) g_mod_params.mod_type = 0;
else if(strcmp(token, "ASK") == 0) g_mod_params.mod_type = 1;
else if(strcmp(token, "FM") == 0) g_mod_params.mod_type = 2;
else if(strcmp(token, "FSK") == 0) g_mod_params.mod_type = 3;
else if(strcmp(token, "PSK") == 0) g_mod_params.mod_type = 4;
else g_mod_params.mod_type = 5; // 其他
}
// --- 提取第 3 段:调制信号频率 ---
token = strtok(NULL, ",");
if(token != NULL) g_mod_params.mod_freq = atoi(token);
// --- 提取第 4 段:Ma ---
token = strtok(NULL, ",");
if(token != NULL) g_mod_params.Ma = atof(token);
// --- 提取第 5 段:Mf ---
token = strtok(NULL, ",");
if(token != NULL) g_mod_params.Mf = atof(token);
// --- 提取第 6 段:码元速率 ---
token = strtok(NULL, ",");
if(token != NULL) g_mod_params.symbol_rate = atoi(token);
// --- 提取第 7 段:包尾校验 ---
token = strtok(NULL, ",");
if (token != NULL && (strcmp(token, "end") == 0))
{
printf("Parse Success! Type:%d, Freq:%d\n", g_mod_params.mod_type, g_mod_params.mod_freq);
// 解析成功后可以触发 OLED 更新
}
}
// 4. 清空缓冲区,准备下次接收
memset(usart_read_buffer, 0, BUUFER_SIZE);
}
void OLED_Proc(void)
{
// 1. 静态变量记录上次数据
static ModParams_t last_params;
// 2. 数据变化检查
if (g_mod_params.mod_type == last_params.mod_type &&
g_mod_params.mod_freq == last_params.mod_freq &&
g_mod_params.Ma == last_params.Ma &&
g_mod_params.Mf == last_params.Mf &&
g_mod_params.symbol_rate == last_params.symbol_rate)
{
return;
}
// --- 开始刷新流程 ---
char *xinhao[] = {"AM ", "ASK", "FM ", "FSK", "PSK", "..."};
// 第一行:始终显示模式
OLED_ShowString(0, 0, "Mode:", 16, 0);
uint8_t type = (g_mod_params.mod_type > 5) ? 5 : g_mod_params.mod_type;
OLED_ShowString(48, 0, xinhao[type], 16, 0);
// --- 第二行:频率逻辑处理 ---
if (g_mod_params.mod_type == 1 || g_mod_params.mod_type == 3 || g_mod_params.mod_type == 4)
{
// 如果是 ASK(1), FSK(3), PSK(4),清空第二行,不显示 Freq
OLED_ShowString(0, 2, " ", 16, 0);
}
else
{
// AM 和 FM 模式显示频率
OLED_ShowString(0, 2, "Freq:", 16, 0);
OLED_ShowNum(48, 2, g_mod_params.mod_freq, 5, 16, 0);
OLED_ShowString(48 + 40, 2, "Hz", 16, 0);
}
// --- 第三行:参数逻辑处理 ---
// 先清空第三行
OLED_ShowString(0, 4, " ", 16, 0);
if (g_mod_params.mod_type == 0)
{
// AM 显示 Ma
OLED_ShowString(0, 4, "Ma:", 16, 0);
OLED_Showdecimal(32, 4, g_mod_params.Ma, 1, 2, 16, 0);
}
else if (g_mod_params.mod_type == 2)
{
// FM 显示 Mf
OLED_ShowString(0, 4, "Mf:", 16, 0);
OLED_Showdecimal(32, 4, g_mod_params.Mf, 1, 2, 16, 0);
}
else if (g_mod_params.mod_type == 1 || g_mod_params.mod_type == 3 || g_mod_params.mod_type == 4)
{
// ASK, FSK, PSK 显示 Rate
OLED_ShowString(0, 4, "Rate:", 16, 0);
OLED_ShowNum(48, 4, g_mod_params.symbol_rate, 5, 16, 0);
OLED_ShowString(48 + 40, 4, "Baud", 16, 0); // 码元速率单位通常也是Hz或Baud
}
else
{
OLED_ShowString(0, 4, "Wait Data...", 16, 0);
}
// 更新备份
last_params = g_mod_params;
}
十一、总结
本设计在1-3kHz的FM波形的Mf估计中,由于解调出的FM波形幅度非常小,故抓不到频率,后续还可改善,基本可以实现五路信号的模式自动识别,AM/ASK/PSK参数较为准确,如果PSK还是出现锁不住的情况,此时可改变一下PSK的波特率,调大或调小后更容易锁住。
当用函数发生器输入信号的时候,要加一个测试端口,拿示波器打一下波形是否为10Vpp满幅信号,看一下输入阻抗是否为50Ω,如果输入阻抗为1MΩ则示波器会显示峰峰值为设定的一半。

还有最重要的ILA IP核配置没有放,大家可以去网上公开资料里学习配置方法。程序的编写工作占一成,仿真工作占两成,调试工作占七成。FPGA虽数据处理能力非常迅速,但是跟shi一样的编译速度非常让人头疼,特别是到后面程序特别多的时候,编译一次要五分钟还多,一天编译不了几次,在编译的空闲时间里我甚至将权力的游戏给刷完了。
更多推荐


所有评论(0)