Генератор белого шума. Много шума из ничего. Verilog.

Генератор белого шума. Так случается иногда с каждым. Место утонченного и тщательно структурированного порядка занимает беспросветный хаос. Причем чем плотнее распределение мощности в спектре тем лучше. Не буду долго ходить вокруг да около объясняя почему это Вам просто жизненно необходимо, приведу пример кода. Большого и очень большого.

Но объяснить кое-что все же придется. Экспертное сообщество считает что лучшим алгоритмом для реализации белого шума есть алгоритм генератора Мерсенна-Твистера. Но это если у Вас очень большая микросхема FPGA, с 2-мя ручками для переноски и есть настоящий друг который поможет ее переносить. Значит Вам очень сильно необходим Генератор белого шума.

Итак, код в студию:

noise_generator.v

module noise_generator (
    input wire clk,
    input wire clk_en,
    input wire rst,
    output reg [15:0] noise_out
);
    // Параметры генератора Mersenne Twister
    reg [31:0] mt [0:623];
    integer i;
    integer index;
    reg [31:0] y;
    reg [31:0] seed;

    initial begin
        seed = 32'h12345678; // начальное значение семени
        mt[0] = seed;
        for (i = 1; i < 624; i = i + 1) begin
            mt[i] = 32'h6c078965 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i;
        end
        index = 624;
    end

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            index <= 624;
        end else if (clk_en) begin
            if (index >= 624) begin
                for (i = 0; i < 624; i = i + 1) begin
                    y = (mt[i] & 32'h80000000) + (mt[(i + 1) % 624] & 32'h7fffffff);
                    mt[i] = mt[(i + 397) % 624] ^ (y >> 1);
                    if (y % 2 != 0) begin
                        mt[i] = mt[i] ^ 32'h9908b0df;
                    end
                end
                index = 0;
            end
            y = mt[index];
            y = y ^ (y >> 11);
            y = y ^ ((y << 7) & 32'h9d2c5680);
            y = y ^ ((y << 15) & 32'hefc60000);
            y = y ^ (y >> 18);
            
            // Преобразование в дополнительный код
            noise_out <= y[15:0];
            index = index + 1;
        end
    end
endmodule

tb_noise_generator.v

`timescale 1ns/1ps

module tb_noise_generator;

    reg clk;
    reg clk_en;
    reg rst;
    wire [15:0] noise_out;
    integer file;

    noise_generator uut (
        .clk(clk),
        .clk_en(clk_en),
        .rst(rst),
        .noise_out(noise_out)
    );

    initial begin
        $dumpfile("noise_generator.vcd"); // Имя VCD файла
        $dumpvars(0, tb_noise_generator); // Имя модуля тестбенча

        clk = 0;
        clk_en = 1;
        rst = 1;
        file = $fopen("noise_output.bin", "wb");
        if (file == 0) begin
            $display("Error opening file!");
            $finish;
        end
        #10 rst = 0;
        #1000000 $stop;
    end

    always #5 clk = ~clk;

    always @(posedge clk) begin
        if (!rst) begin
            $fwrite(file, "%c%c", noise_out[7:0], noise_out[15:8]);
        end
    end

    initial begin
        #1000000 $fclose(file);
    end

endmodule

Компиляция и симуляция генератора белого шума

Компиляция Verilog файлов с помощью iverilog:

iverilog -o noise_sim tb_noise_generator.v noise_generator.v

Запуск симуляции:

vvp noise_sim

Визуализация генератора белого шума с помощью Python

Создайте файл plot_spectrogram.py со следующим содержимым:

import numpy as np
import matplotlib.pyplot as plt
from vcdvcd import VCDVCD

# Открытие VCD файла
vcd_path = 'noise_generator.vcd'
vcd = VCDVCD(vcd_path)

# Находим правильный путь к сигналу
signal_path = 'tb_noise_generator.uut.noise_out[15:0]'
if signal_path not in vcd.signals:
    raise ValueError(f'Signal {signal_path} not found in VCD file.')

signal_data = vcd[signal_path]

# Извлекаем временные метки и значения
times = []
values = []
for tv in signal_data.tv:
    # Игнорируем неопределенные значения 'x'
    if tv[1] != 'x':
        times.append(tv[0])
        values.append(int(tv[1], 2))

# Преобразование значений к signed 16-bit integer
values = np.array(values, dtype=np.int32)
values = np.where(values > 32767, values - 65536, values)

# Убедимся, что данные не пусты
if len(values) == 0:
    raise ValueError('No data found in the specified signal.')

# Построение спектрограммы
plt.figure()
plt.specgram(values, Fs=1e6)
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [s]')
plt.title('Spectrogram of Noise Signal from VCD')
plt.colorbar(label='Intensity [dB]')

# Чтение бинарного файла
bin_file_path = 'noise_output.bin'
with open(bin_file_path, 'rb') as f:
    bin_data = f.read()

# Преобразование бинарных данных в массив numpy
bin_values = np.frombuffer(bin_data, dtype=np.int16)

# Убедимся, что данные не пусты
if len(bin_values) == 0:
    raise ValueError('No data found in the binary file.')

# Построение спектрограммы для данных из бинарного файла
plt.figure()
plt.specgram(bin_values, Fs=1e6)
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [s]')
plt.title('Spectrogram of Noise Signal from Binary File')
plt.colorbar(label='Intensity [dB]')

plt.show()

Запустите скрипт Python для визуализации спектрограммы генератора белого шума:

python3 plot_spectrogram.py

Будут ли ошибки при компиляции в Вашем случае? Скорее всего да. Дорогу осилит идущий. Продолжение следует, и следующий раз будет младший брат этого генератора белого шума, занимающий мало места и весьма неплохо живущий.

Чтобы создать генератор шума высокого качества, который поместится в не более чем 1000 логических ячеек FPGA и использует алгоритм, аналогичный Mersenne Twister, можно рассмотреть более простые, но все же эффективные методы, такие как комбинированные линейные конгруэнтные генераторы (Combined Linear Congruential Generators, CLCG). Этот метод требует меньше ресурсов, но обеспечивает достаточно качественный случайный шум. Но это конечно тоже не решение вопроса судя по результату.

Генерация гауссовского шума с помощью алгоритма, который можно реализовать на FPGA с использованием ограниченного количества логических ячеек, может быть выполнена с помощью метода Бокса-Мюллера (Box-Muller). Этот метод позволяет генерировать гауссовский шум из двух независимых равномерно распределенных случайных чисел.

Для реализации на Verilog с учетом ограниченного количества логических ячеек, нам нужно использовать эффективные способы генерации случайных чисел и вычисления логарифмических и тригонометрических функций. Возможно применить генератор случайных чисел LFSR и метод Бокса-Мюллера для генерации гауссовского шума.

Генератор белого шума на 16 LFSR :

При частоте дискретизации 38 Мгц или 19 мгц спектр шума должен быть линеен по уровню 3 Дб до частоты 2,5 Мгц

Для получения шума с требуемыми характеристиками спектра, необходимо применить несколько улучшений в генераторе шума. Основные шаги включают увеличение количества LFSR и добавление фильтра для улучшения спектральных характеристик. Цель — получить шум, который будет линейным по уровню до частоты 2,5 МГц при частоте дискретизации 38 МГц или 19 МГц.

noise_generator.v

module noise_generator (
    input wire clk,
    input wire clk_en,
    input wire rst,
    output reg [15:0] noise_out
);
    // Параметры LFSR
    reg [31:0] lfsr [0:15];
    wire [31:0] lfsr_feedback [0:15];
    integer i;

    // Преобразование значений к signed 16-bit integer в двухкомплементарный код
    wire signed [15:0] noise_twos_complement;
    reg signed [31:0] sum;
    reg signed [31:0] filtered_noise;

    assign noise_twos_complement = sum[15:0];

    generate
        genvar j;
        for (j = 0; j < 16; j = j + 1) begin : lfsr_gen
            assign lfsr_feedback[j] = (lfsr[j] >> 1) ^ (-(lfsr[j] & 1) & 32'h80000057);
        end
    endgenerate

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            lfsr[0] <= 32'h12345678;
            lfsr[1] <= 32'h87654321;
            lfsr[2] <= 32'h56781234;
            lfsr[3] <= 32'h43218765;
            lfsr[4] <= 32'habcdef01;
            lfsr[5] <= 32'hfedcba98;
            lfsr[6] <= 32'h0f1e2d3c;
            lfsr[7] <= 32'h4b5a6978;
            lfsr[8] <= 32'h78912345;
            lfsr[9] <= 32'h98765432;
            lfsr[10] <= 32'h23456789;
            lfsr[11] <= 32'h34567890;
            lfsr[12] <= 32'h45678901;
            lfsr[13] <= 32'h56789012;
            lfsr[14] <= 32'h67890123;
            lfsr[15] <= 32'h78901234;
            sum <= 0;
            filtered_noise <= 0;
        end else if (clk_en) begin
            for (i = 0; i < 16; i = i + 1) begin
                lfsr[i] <= lfsr_feedback[i];
            end

            // Суммирование значений для генерации Gaussian шума
            sum <= lfsr[0][15:0] + lfsr[1][15:0] + lfsr[2][15:0] + lfsr[3][15:0] +
                   lfsr[4][15:0] + lfsr[5][15:0] + lfsr[6][15:0] + lfsr[7][15:0] +
                   lfsr[8][15:0] + lfsr[9][15:0] + lfsr[10][15:0] + lfsr[11][15:0] +
                   lfsr[12][15:0] + lfsr[13][15:0] + lfsr[14][15:0] + lfsr[15][15:0];

            // Корректирующий фильтр для улучшения спектра шума
            filtered_noise <= (filtered_noise + sum) >> 1;

            noise_out <= filtered_noise[15:0];
        end
    end
endmodule

Этот код включает простейший корректирующий фильтр (цифровой фильтр первого порядка), который помогает сгладить спектр шума и уменьшить завал нижних частот.

tb_noise_generator.v

module tb_noise_generator;
    reg clk;
    reg clk_en;
    reg rst;
    wire [15:0] noise_out;

    noise_generator uut (
        .clk(clk),
        .clk_en(clk_en),
        .rst(rst),
        .noise_out(noise_out)
    );

    // Генерация тактового сигнала
    initial begin
        clk = 0;
        forever begin
            #5 clk = ~clk; // 100 MHz clock
        end
    end

    integer file;
    initial begin
        // Открытие файла для бинарного вывода
        file = $fopen("noise_output.bin", "wb");

        // Инициализация входов
        clk_en = 1;
        rst = 1;
        #10;
        rst = 0;

        // Запуск симуляции на заданное время
        #1000000; // 1ms

        // Запись шума в файл
        forever begin
            @(posedge clk);
            if (clk_en && !rst) begin
                // Запись данных в файл в формате 16-bit little-endian
                $fwrite(file, "%c%c", noise_out[7:0], noise_out[15:8]);
            end
        end

        // Закрытие файла
        $fclose(file);

        $stop;
    end

    // Генерация VCD файла
    initial begin
        $dumpfile("noise_generator.vcd");
        $dumpvars(0, tb_noise_generator);
    end
endmodule


Оценка количества логических ячеек

Используйте Yosys для оценки количества логических ячеек:

yosys -p "synth_altera -top noise_generator"

После этого вы можете использовать следующий командный файл для оценки:

read_verilog noise_generator.v
synth_altera -top noise_generator
stat

Генератор белого шума. Симуляция и тестирование

Запустите симуляцию, чтобы убедиться, что спектр шума соответствует требованиям. Если завал нижних частот остается, можно дополнительно подкорректировать фильтр или параметры LFSR.