Проект

Общее

Профиль

Генерация FM в комплексный сигнал IQ

python:

import numpy as np

# Параметры
fs = 1000000  # 1 МГц
f_mod = 1000  # Тон 1 кГц
dev = 75000  # Девиация 75 кГц
duration = 0.01  # 10 мс записи
samples = int(fs * duration)

t = np.arange(samples) / fs
# FM модуляция: интеграл от синуса - это минус косинус
phase = 2 * np.pi * dev * np.cumsum(np.sin(2 * np.pi * f_mod * t)) / fs
iq = np.exp(1j * phase)  # Комплексный сигнал

# Масштабируем в 24-бит (signed)
scale = (2 ** 23) - 1
i_int = (iq.real * scale).astype(np.int32)
q_int = (iq.imag * scale).astype(np.int32)

# Запись в файл (формат HEX для $readmemh)
with open("input_iq.hex", "w") as f:
    for val_i, val_q in zip(i_int, q_int):
        # Превращаем в 24-битный hex (маскируем для обработки отрицательных чисел)
        f.write(f"{val_i & 0xFFFFFF:06x} {val_q & 0xFFFFFF:06x}\n")

tb iverilog:

`timescale 1ns / 1ps

module fm_demodulator_sin_tb;
    reg clk = 0;
    reg reset = 1;
    reg signed [23:0] i_in, q_in;
    reg ce = 0;

    wire signed [23:0] data_out;
    wire fm_ce;

    // Массив для хранения тестовых данных (глубина должна совпадать с числом сэмплов)
    reg [23:0] test_mem_i [0:9999];
    reg [23:0] test_mem_q [0:9999];
    integer i;

    // Тактовый сигнал
    always #5 clk = ~clk;

    // Подключение модуля
    fm_demodulator #( .SHIFT(23) ) dut (
        .clk(clk), .reset(reset),
        .i_in(i_in), .i_ce(ce),
        .q_in(q_in), .q_ce(ce),
        .data_out(data_out), .fm_ce_out(fm_ce)
    );

    integer out_file;

    initial begin
        // просмотреть демодулированный сигнал можно например в Audacity
        out_file = $fopen("output.raw", "wb"); // Открываем для записи в бинарном режиме
    end

    // Запись происходит каждый раз, когда демодулятор выдает новый сэмпл
    always @(posedge clk) begin
        if (fm_ce) begin
            // Сохраняем 16 или 24 бита результата.
            // Для аудио достаточно 16 бит (старшие разряды data_out)
            $fputc(data_out[15:8],  out_file); // Младший байт (для Little Endian)
            $fputc(data_out[23:16], out_file); // Старший байт
        end
    end

    final begin
        $fclose(out_file);
    end

    initial begin
        // Загрузка данных из файла
        // Внимание: iverilog требует, чтобы в файле I и Q были либо в разных файлах,
        // либо читались хитро. Проще всего считать один файл через $fscanf.
        integer fp;
        fp = $fopen("input_iq.hex", "r");

        $dumpfile("test_sin.vcd"); // Для просмотра в GTKWave
        $dumpvars(0, fm_demodulator_sin_tb);

        #20 reset = 0;

        // Подача данных
        while (!$feof(fp)) begin
            @(posedge clk);
            $fscanf(fp, "%h %h\n", i_in, q_in);
            ce <= 1;
            @(posedge clk);
            ce <= 0;
            repeat(5) @(posedge clk); // Пауза между сэмплами
        end

        $fclose(fp);
        #1000 $finish;
    end
endmodule

Запуск тестов

#!/usr/bin/env sh

python3.11 gen_data.py

if [ -f input_iq.hex ]; then
	iverilog -g2012 -o fm_sin_sim fm_demodulator.v fm_demodulator_sin_tb.v
	if [ -f fm_sin_sim ]; then
		vvp fm_sin_sim
	fi
fi

#gtkwave test_sin.vcd&