Генерация 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&