[vivado]2FSK非相干信号解调仿真

这篇文章主要记录一下用FPGA做2FSK解调的过程,主要包含了三个步骤:一个是输入信号的处理、带通滤波器、低通滤波器以及抽样判决的设计

输入信号处理部分

 利用MATLAB生成2FSK解调的COE文件,0的载波频率为800KHz,1的载波频率为1MHz,并打印出位宽和深度,深度与位宽用于后续做处理,其中位宽设置为8位,保存形式为无符号,其中需要记住的是采样速率为10MHZ,码元速率为50kbps.【可根据自己的设定自行调整】

clear all; close all; clc;
%% 参数设置
fs = 10e6;           % 采样频率 10MHz
f1 = 800e3;         % 载波1频率 800kHz (表示'1')
f2 = 1e6;           % 载波2频率 1MHz (表示'0')
bit_rate = 5e4;     % 码元速率 50kbps
bit_duration = 1/bit_rate;  % 码元持续时间
samples_per_bit = fs * bit_duration;  % 每个码元的采样点数
%% 生成测试数据序列
data_bits = [1 0 1 0 1 0 1 0 0 1 1];  % 16位测试数据
num_bits = length(data_bits);
% 时间轴
t_bit = 0:1/fs:bit_duration-1/fs;  % 单个码元时间
t_total = 0:1/fs:(num_bits*bit_duration-1/fs);  % 总时间
%% 生成2FSK调制信号
fsk_signal = [];
for i = 1:num_bits
    if data_bits(i) == 1
        % 发送载波1 (800kHz)
        bit_signal = cos(2*pi*f1*t_bit);
    else
        % 发送载波2 (1MHz)
        bit_signal = cos(2*pi*f2*t_bit);
    end
    fsk_signal = [fsk_signal bit_signal];
end
% 量化为8位无符号整数(范围0~255)
signal_min = min(fsk_signal);            % 计算信号最小值
signal_offset = fsk_signal - signal_min;  % 平移信号至非负范围
signal_scaled = signal_offset / max(signal_offset) * (2^8 - 1);  % 缩放到0~255
signal_8bit_unsigned = round(signal_scaled);  % 取整
signal_8bit_unsigned = max(0, min(2^8 - 1, signal_8bit_unsigned));  % 限幅处理
signal_8bit_unsigned = uint8(signal_8bit_unsigned);  % 转换为无符号8位整数
%% 生成2FSK信号COE文件 - 8位无符号版
coe_filename_signal = 'fsk_signal_input_8bit_unsigned.coe';
fid = fopen(coe_filename_signal, 'w');
fprintf(fid, '; 2FSK调制信号输入 COE文件 (8位无符号量化)\n');
fprintf(fid, '; 载波1频率: %.0f kHz (bit 1)\n', f1/1000);
fprintf(fid, '; 载波2频率: %.0f kHz (bit 0)\n', f2/1000);
fprintf(fid, '; 采样频率: %.0f MHz\n', fs/1e6);
fprintf(fid, '; 数据序列: %s\n', num2str(data_bits));
fprintf(fid, '; 每个码元采样点数: %d\n', samples_per_bit);
fprintf(fid, 'memory_initialization_radix=10;\n');
fprintf(fid, 'memory_initialization_vector=\n');
% 写入量化后的数据(无符号)
for i = 1:length(signal_8bit_unsigned)
    if i == length(signal_8bit_unsigned)
        fprintf(fid, '%d;\n', signal_8bit_unsigned(i));
    else
        fprintf(fid, '%d,\n', signal_8bit_unsigned(i));
    end
end
fclose(fid);
%% 打印COE文件的宽度和深度
coe_width = 8;  % 8位无符号
coe_depth = length(signal_8bit_unsigned);  % 数据深度
fprintf('COE文件信息:\n');
fprintf('  文件名: %s\n', coe_filename_signal);
fprintf('  数据宽度: %d 位(无符号)\n', coe_width);
fprintf('  数据深度: %d\n', coe_depth);
fprintf('  每个码元采样点数: %.0f\n', samples_per_bit);
fprintf('  总码元数: %d\n', num_bits);
fprintf('  总采样点数: %d\n', coe_depth);
%% 验证COE文件内容
coe_file = fopen(coe_filename_signal, 'r');
if coe_file == 0
    error('无法打开COE文件进行验证');
end
% 跳过文件头
skip_header = 1;
coe_data = [];
while ~feof(coe_file)
    line = fgetl(coe_file);
    if isempty(line), continue; end
    
    % 跳过注释行和头信息
    if skip_header
        if contains(line, 'memory_initialization_vector=')
            skip_header = 0;
        end
        continue;
    end
    
    % 提取数据
    data = textscan(line, '%d,', 'Delimiter', ',;');
    if ~isempty(data{1})
        coe_data = [coe_data; data{1}];
    end
end
fclose(coe_file);
% 验证宽度和深度
if length(coe_data) == coe_depth
    fprintf('验证通过: COE文件深度与计算值一致\n');
else
    warning('验证警告: COE文件深度与计算值不一致');
    fprintf('  计算深度: %d\n', coe_depth);
    fprintf('  文件深度: %d\n', length(coe_data));
end

 COE导入到IP核

会生成在左边图片上的COE文件,可以利用COE文件导入到vivodo。

打开vivodo软件之后,首先创建信号源的IP核,如下图找到IP,catalog,点击他右边会弹出页面,搜索ROM,选择BLOCK&ROMs&BRAM,双击他。

弹出来的界面,在Basic界面如下图所示进行配置,配置完之后,点击Port A options进行配置。

跟着下面进行操作。

导入到IP核之后,需要进行实例化,才可以调用,相当于一个函数,生成右面的IP核之后找到veo文件,将这个复制到顶层模块可以进行实例化。

800KHz滤波器设计

在matlab命令行中输入fdatool,具体选择如下图所示,其中采样频率为10Mhz,选择40阶的滤波器,其中要对量化的位宽以及输入的信号做出限制,都如下图所示:

导入至IP核,直接搜索fir就可以,选自FIR COmpiler

直接点击OK就可以了,最后也会弹出来一个generate的界面,也需要进行点击最终才可以生成。生成了IP核也需要在顶层模块中进行实例化,相当于函数声明,同样需要找到它的veo文件。

1Mhz的滤波器设计

这一部分就稍微讲简单一点,我们输入的位宽同样是8位,频率为10Mhz,所以对比于800Khz的滤波器只需要更改matlab端,在新生成一个IP核那里更改coe的地址既可以,其他参数按照800Khz保持不变

一点要点击应用,,输入小数长度为0,vivado的ip核部分按照上面的步骤进行配置就可以了。

低通滤波器进行设计

同样只需要在matlab端更改生成COE文件,然后更改路径就可以了,其余的配置都按照800Khz的IP核进行配置。

最终会有4个IP核,两个带通、1个低通,因为低通的设置都为一样的就可以只设置一个,一个fsk的信号源。

顶层模块的编写

顶层模块的编写包含了生成的滤波器的实例化、抽样判决模块的实例化,代码如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/06/29 14:36:17
// Design Name: 
// Module Name: fsk_demod
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module fsk_demod(
    input clk,
    input rst_n,
    output data_out

   );
  //输入信号IP核
  parameter FSK_MEM_DEPTH = 2200;   // 存储器深度
  reg [11:0]addra;
  wire [7:0]fsk_out;
  wire [15:0]sub;
  wire sample_pulse;
//带通滤波信号800K
wire s_axis_data_8tready=1'b1;
wire m_axis_800_tvalid;
wire [31:0]fi800_out;
//带通滤波信号1M
wire s_axis_data_1tready=1'b1;
wire m_axis_1m_tvalid;
wire [31:0]fi1m_out;
//800K低通
wire s_axis_data_low0tready=1'b1;
wire m_axis_low_pass0;
wire [31:0]low0out;
//1M低通
wire s_axis_data_low1tready=1'b1;
wire m_axis_low_pass1;
wire [31:0]low1out;
//低通滤波的输入
wire [7:0]low0in;
wire [7:0]low1in;
assign low0in=fi800_out[31:24];
assign low1in=fi1m_out[31:24];
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin               // 复位时地址清零
            addra <= 12'd0;
        end else begin
            if (addra >= FSK_MEM_DEPTH - 1) begin // 达到深度后循环
                addra <= 12'd0;
            end else begin                          // 地址递增
                addra <= addra + 1'b1;
            end
        end
    end
 //输入信号IP核
fsk fsk (
  .clka(clk),    // input wire clka
  .addra(addra),  // input wire [12 : 0] addra
  .douta(fsk_out)  // output wire [7 : 0] douta
);
//带通滤波800K实例化
fir_compiler_0 f800 (
  .aclk(clk),                              // input wire aclk
  .s_axis_data_tvalid(1'b1),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(s_axis_data_8tready),  // output wire s_axis_data_tready
  .s_axis_data_tdata(fsk_out),    // input wire [7 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(m_axis_800_tvalid),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(fi800_out)    // output wire [31 : 0] m_axis_data_tdata
);
//带通滤波1M实例化
fir_1m your_instance_name (
  .aclk(clk),                              // input wire aclk
  .s_axis_data_tvalid(1'b1),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(s_axis_data_1tready),  // output wire s_axis_data_tready
  .s_axis_data_tdata(fsk_out),    // input wire [7 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(m_axis_1m_tvalid),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(fi1m_out)    // output wire [31 : 0] m_axis_data_tdata
);
//实例化低通
fir_compiler_1 low0band (
  .aclk(clk),                              // input wire aclk
  .s_axis_data_tvalid(1'b1),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(s_axis_data_low0tready),  // output wire s_axis_data_tready
  .s_axis_data_tdata(low0in),    // input wire [7 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(m_axis_low_pass0),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(low0out)    // output wire [31 : 0] m_axis_data_tdata
);
fir_compiler_1 low1band (
  .aclk(clk),                              // input wire aclk
  .s_axis_data_tvalid(1'b1),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(s_axis_data_low1tready),  // output wire s_axis_data_tready
  .s_axis_data_tdata(low1in),    // input wire [7 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(m_axis_low_pass1),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(low1out)    // output wire [31 : 0] m_axis_data_tdata
);
judge judgeuut(
    . clk(clk),          // 系统时钟(5MHz)
    . rst_n(rst_n),        // 异步复位,低有效
    .lowpass0(low0out),     // 低通滤波器输出(对应800KHz载波)
    .lowpass1(low1out),     // 低通滤波器输出(对应1MHz载波)
    .data_out(data_out),     // 判决输出比特(0:800K,1:1M)
    .sub(sub),
    .sample_pulse(sample_pulse)  // 抽样脉冲(高电平表示判决时刻)
);
endmodule

其中生成的IP核的部分是模块名加上你的名字,比如说 judge judgeuut,judge是模块名 judgeuut是自己在顶层模块用于调用自己取得名字

抽样判决模块编写

module judge(
    input        clk,          // 系统时钟(10MHz)
    input        rst_n,        // 异步复位,低有效
    input [31:0] lowpass0,     // 低通滤波器输出(对应800KHz载波)
    input [31:0] lowpass1,     // 低通滤波器输出(对应1MHz载波)
    output reg   data_out,     // 判决输出比特(0:800K,1:1M)
    output wire [15:0] sub,
    output reg   sample_pulse  // 抽样脉冲(高电平表示判决时刻)
    );
    
    reg [15:0] dout;

    // 无符号减法(带饱和处理)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            dout <= 16'd0;
        end else begin
            if (lowpass0[31:24] >= lowpass1[31:24]) begin
                dout <= lowpass0[31:16] - lowpass1[31:16];
            end else begin
                dout <= 16'd0;  // 下溢时输出0
            end
        end
    end
    
    assign sub = dout;

    // 基于10MHz时钟的参数设置
    // 码元速率为50Kbps,则每个码元持续时间为20us
    // 在10MHz时钟下,20us = 200个时钟周期
    parameter SAMPLE_CYCLE = 200;           // 码元周期(10MHz时钟下的周期数)
    parameter HALF_CYCLE   = SAMPLE_CYCLE/2; // 100(码元中间位置)
    parameter DELAY_POINTS = 0;            // 延迟点数
    // 内部计数器和状态变量
    reg [15:0] bit_counter;          // 比特周期计数器
    reg [15:0] sampled_diff;         // 抽样时刻的差值
   reg [15:0] sample_point;         // 抽样点计数器
    // 时钟分频和抽样脉冲生成
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bit_counter  <= 16'd0;
            sample_pulse <= 1'b0;
            sample_point <= 16'd0;
        end else begin
            // 比特周期计数器(200个时钟周期为1个码元)
            if (bit_counter >= SAMPLE_CYCLE - 1) begin
                bit_counter <= 16'd0;
            end else begin
                bit_counter <= bit_counter + 1'b1;
            end
            
            // 生成抽样脉冲(在每个比特周期的中间位置100)
            if (bit_counter == HALF_CYCLE) begin
                sample_pulse <= 1'b1;
                sample_point <= bit_counter;  // 记录抽样点位置
            end else begin
                sample_pulse <= 1'b0;
            end
        end
    end

    // 抽样判决逻辑(保持不变)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sampled_diff <= 16'd0;
            data_out     <= 1'b0;
        end else begin
            // 在抽样脉冲有效时锁存当前差值
            if (sample_pulse) begin
                sampled_diff <= dout;
            end
            
            // 判决逻辑:根据两路信号的强度比较
            if (sample_pulse) begin
                if (lowpass0[31:0] > lowpass1[31:0]) begin
                    data_out <= 1'b0;  // 800kHz信号更强,判为0
                end else begin
                    data_out <= 1'b1;  // 1MHz信号更强,判为1
                end
            end
        end
    end

endmodule

TB仿真激励文件编写

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/06/29 21:51:08
// Design Name: 
// Module Name: tb_fsk
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module tb_fsk();
    reg clk;
    reg rst;
    wire [31:0] fir800_out;
    wire [31:0] fir1m_out;
    wire [7:0]  fsk_signal;
    wire [31:0] lowpass0out;
    wire [31:0] lowpass1out;
    wire        data_out;
    wire        sample_pulse;
    parameter CLK_PERIOD = 100; 
      fsk_demod uut (
        .clk        (clk),
        .rst_n      (rst),
        .data_out  (data_out)
    );
    assign fsk_signal = uut.fsk_out;
    assign fir800_out=uut.fi800_out;
    assign fir1m_out=uut.fi1m_out;
    assign lowpass0out = uut.low0out;  // 新增:连接低通输出
    assign lowpass1out = uut.low1out;  // 新增:连接低通输出
    assign sample_pulse = uut.sample_pulse; // 新增:连接抽样脉冲
    always #(CLK_PERIOD/2) clk = ~clk;
    // 测试序列
 initial begin
        // 初始化信号
        clk = 1'b0;
        rst = 1'b0;
        
        // 复位操作
        #100;               // 等待50ns
        rst = 1'b1;      // 释放复位
        
        // 运行测试
        #50000;            // 运行10us (1000个时钟周期)
        
        // 结束仿真
        $display("FSK解调测试完成");
        $finish;
    end
 initial begin
        $monitor("Time: %0t ns | rst: %b | FSK: %0d | 800k: %0d | 1M: %0d | LP0: %0d | LP1: %0d | Decision: %b | Sample: %b",
                 $time, rst, fsk_signal, fir800_out, fir1m_out, lowpass0out, lowpass1out, data_out, sample_pulse);
        #0;  // 立即执行一次监控
    end
    //波形记录
    initial begin
        $dumpfile("fsk_demod.vcd");
        $dumpvars(0, tb_fsk);
    end
endmodule

整个仿真就包含了两个module和一个仿真激励文件,module fsk_demod为顶层,module judge为子模块,ip核也相当于子模块,子模块要在顶层进行调用就需要实例化,代码已经全部给出,可以参照自己的进行修改就可以,整个的时钟频率仿真设置为10Mhz,输入位宽都固定为8位。

出来的效果图如下:

如果有更多的建议请批评指正

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stupdf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值