`timescale 1ns / 1ps module generator_tb; // === Параметры === localparam DATA_WIDTH = 14; localparam LOGIC_ZERO_LEVEL = 0; // DAC -5V for logic zero localparam VOLTAGE_ZERO_LEVEL = 2**(DATA_WIDTH-1); // DAC 0V for logic zero localparam CLK_PERIOD = 8; parameter string ZERO_LEVEL = "logic"; // "logic" VS "true" // === Сигналы === // Системные сигналы logic clk; logic rst; logic start; // Входные сигналы logic [31:0] pulse_width; // config reg logic [31:0] pulse_period; // config reg logic [DATA_WIDTH-1:0] pulse_height; // config reg logic [15:0] pulse_num; // config reg logic sampler_done; // sampler request for synchronization // Выходные сигналы wire dac_wrt; // DAC wrt singnal wire [DATA_WIDTH-1:0] dac_out; // DAC input logic signal wire generator_done; // generator request for synchronization // === Переменные === int current_zero_level; initial begin if (ZERO_LEVEL == "true") current_zero_level = VOLTAGE_ZERO_LEVEL; else current_zero_level = LOGIC_ZERO_LEVEL; end // DUT generate if (ZERO_LEVEL == "true") begin : gen_dut_true generator #( .DATA_WIDTH(DATA_WIDTH), .ZERO_LEVEL(VOLTAGE_ZERO_LEVEL) ) dut ( .clk_dac(clk), .rst(rst), .start(start), .pulse_width(pulse_width), .pulse_period(pulse_period), .pulse_height(pulse_height), .pulse_num(pulse_num), .dac_wrt(dac_wrt), .dac_out(dac_out), .done(generator_done), .request(sampler_done) ); initial $display("[TB] Generator compiled. ZERO_LEVEL: TRUE"); end else if (ZERO_LEVEL == "logic") begin : gen_dut_logic generator #( .DATA_WIDTH(DATA_WIDTH), .ZERO_LEVEL(LOGIC_ZERO_LEVEL) ) dut ( .clk_dac(clk), .rst(rst), .start(start), .pulse_width(pulse_width), .pulse_period(pulse_period), .pulse_height(pulse_height), .pulse_num(pulse_num), .dac_wrt(dac_wrt), .dac_out(dac_out), .done(generator_done), .request(sampler_done) ); initial $display("[TB] Generator compiled. ZERO_LEVEL: LOGIC"); end else begin : gen_dut_error // защита от дурака initial begin $display("[ERROR] Unknown value ZERO_LEVEL: %s", ZERO_LEVEL); $finish; end end endgenerate // Тактовые сигналы initial begin clk = 0; forever #(CLK_PERIOD/2) clk = ~clk; end // === Таски для тестипрования === // Таска синхронизации, одно рукопожатие task automatic synchronize( input bit sampler_first, // 1 - выставить sampler_done ДО генератора, 0 - ПОСЛЕ input int delay_before_ack, // Если sampler_first=0: задержка ПОСЛЕ gen_done. Если 1: задержка от НАЧАЛА цикла. input int ack_duration // сколько тактов удерживать sampler_done после встречи сигналов ); if (sampler_first) begin // --- сэмплер готов до генератора --- repeat(delay_before_ack) @(posedge clk); sampler_done <= 1; wait(generator_done == 1); repeat(ack_duration) @(posedge clk); sampler_done <= 0; end else begin // --- генератора готов до сэмплер --- wait(generator_done == 1); repeat(delay_before_ack) @(posedge clk); sampler_done <= 1; repeat(ack_duration) @(posedge clk); sampler_done <= 0; end endtask // Таска сброса DUT task automatic reset_dut( input int rst_duration // сколько тактов держать сброс ); rst <= 1; repeat(rst_duration) @(posedge clk); rst <= 0; endtask // Таска запуска DUT task automatic start_dut( input int start_duration // сколько тактов держать импульс ); start <= 1; repeat(start_duration) @(posedge clk); start <= 0; endtask // Таска конфигурации DUT task automatic set_config( input logic [31:0] w, // ширина импульса input logic [31:0] p, // период импульса input logic [15:0] n, // количество импульсов input logic [DATA_WIDTH-1:0] h // высота импульса ); // Задаем конфигурационные регистры @(posedge clk); pulse_width <= w; pulse_period <= p; pulse_num <= n; pulse_height <= h; endtask // Таска проверки устойчивости к долгим управляющим импульсам task automatic check_impulses; // Локальные переменные для хранения случайных параметров int rand_start_duration; int rand_delay; int rand_ack; bit rand_first; int total_impulse_cycles = 0; int pulse_w = 11; int pulse_p = 31; int pulse_n = 5; int pulse_h = 1024; $display("[TB] -check_impulses- Check system stability under random latencies"); // Установка конфигурации set_config( .w(pulse_w), .p(pulse_p), .n(pulse_n), .h(pulse_h) ); reset_dut(5); repeat(2) @(posedge clk); // Старт норме 1 такт. Сделаем случайным от 5 до 25 тактов. rand_start_duration = $urandom_range(5, 25); $display("[TB] Long start: %0d clocks", rand_start_duration); // Фоновый процесс подсчета тактов импульса fork begin : counter_proc forever begin @(posedge dac_wrt); if (dac_out == pulse_h) begin total_impulse_cycles++; end end end join_none // Параллельный запуск длинного старта и обработки синхронизации fork // Поток 1: Удерживаем старт аномально долго begin start_dut(rand_start_duration); end // Поток 2: Обслуживаем n=4 циклов синхронизации со случайными задержками begin repeat(pulse_n) begin // Рандомизируем параметры для каждого из 4-х рукопожатий rand_first = $urandom; // Случайно: Самплер первый (1) или Генератор первый (0) rand_delay = $urandom_range(1, 8); // Случайная задержка ожидания (1..8 тактов) rand_ack = $urandom_range(5, 10); // Аномально долгий удерживаемый импульс sampler_done (10..30 тактов) synchronize( .sampler_first(rand_first), .delay_before_ack(rand_delay), .ack_duration(rand_ack) ); end end join repeat(pulse_p+5) @(posedge clk); disable counter_proc; // Ожидание завершения переходных процессов repeat(10) @(posedge clk); if (total_impulse_cycles == pulse_w*pulse_n) $display("[TB] -check_impulses- Pulse generation CORRECT"); else begin $display("[ERROR] -check_impulses- Pulse generation INCORRECT. Total number of pulses: %d, must be: %d", total_impulse_cycles, pulse_w*pulse_n); $finish; end $display("[TB] -check_impulses- Done"); endtask task automatic run_test_case( input int pulse_w, input int pulse_p, input int pulse_n, input int pulse_h, input bit skip_reset, // skip reset sequence on demand input bit count_level // count ticks of amplitude == pulse_h or amplitude != pulse_h ); int total_impulse_cycles = 0; if (!skip_reset) begin reset_dut(1); @(posedge clk); end set_config( .w(pulse_w), .p(pulse_p), .n(pulse_n), .h(pulse_h) ); @(posedge clk); start_dut(1); // Фоновый процесс подсчета тактов импульса fork begin : counter_proc forever begin @(posedge dac_wrt); if (count_level) begin if (dac_out == pulse_h) begin total_impulse_cycles++; end end else begin if (dac_out != current_zero_level) begin total_impulse_cycles++; end end end end join_none repeat(pulse_n) begin synchronize( .sampler_first(0), .delay_before_ack(1), .ack_duration(2) ); end repeat(pulse_p+5) @(posedge clk); disable counter_proc; repeat(10) @(posedge clk); if (count_level) begin if (total_impulse_cycles == pulse_w*pulse_n) $display("[TB] -run_test_case- Pulse generation CORRECT"); else begin $display("[ERROR] -run_test_case- Pulse generation INCORRECT. Total number of pulses: %d, must be: %d", total_impulse_cycles, pulse_w*pulse_n); $finish; end end else begin if (total_impulse_cycles == 0) $display("[TB] -run_test_case- Pulse generation CORRECT"); else begin $display("[ERROR] -run_test_case- Pulse generation INCORRECT. Total number of pulses: %d, must be: %d", total_impulse_cycles, 0); $finish; end end endtask // --- ОСНОВНОЙ ПРОЦЕСС ТЕСТИРОВАНИЯ --- initial begin $display("[TB] Tests start"); // Инициализация rst = 1; start = 0; pulse_width = 0; pulse_period = 0; pulse_height = 0; pulse_num = 0; sampler_done = 0; $display("[TB] Test 1. Random latency for control signals"); check_impulses(); $display("[TB] Test 1 complete"); $display("[TB] Test 2. Random configs"); for (int i = 0; i < 25; i++) begin int r_w, r_p, r_n, r_h; bit r_skip; // Генерируем параметры r_p = $urandom_range(5, 50); // Период от 5 до 50 r_w = $urandom_range(0, r_p); // Ширина не больше периода r_n = $urandom_range(1, 10); // Количество импульсов r_h = $urandom_range(1, 2**DATA_WIDTH-1); // Высота (для 14 бит) r_skip = $urandom_range(0, 1); // Случайный сброс (0 - сброс, 1 - пропуск) // Защита от "нулевого" импульса. Невозможно проверить длительность. if (r_h == current_zero_level) begin r_h += $urandom_range(1, 10); end $display("[TB] --- Test #%0d (Config: W=%0d, P=%0d, N=%0d, H=%0d, SkipReset=%0b) ---", i+1, r_w, r_p, r_n, r_h, r_skip); run_test_case( .pulse_w(r_w), .pulse_p(r_p), .pulse_n(r_n), .pulse_h(r_h), .skip_reset(r_skip), .count_level(1) ); end $display("[TB] Test 2 complete"); $display("[TB] Test 3. Zero level of pulse height"); run_test_case( .pulse_w(77), .pulse_p(131), .pulse_n(13), .pulse_h(current_zero_level), .skip_reset(0), .count_level(0) ); $display("[TB] Test 3 complete"); $display("[TB] All Tests complete!"); $finish; end endmodule