`timescale 1ns / 1ps `include "interfaces.svh" // `define DEBUG `define MEASURE_CLK(clk, period) \ begin \ realtime t1, t2; \ @(posedge clk); \ t1 = $realtime; \ @(posedge clk); \ t2 = $realtime; \ period = t2 - t1; \ end `define ERR_CHECK \ total_tests++; \ if (result_flag) begin \ total_failed_tests++; \ $error("Test #%0d failed. Err code: %0d", total_tests, result_flag); \ end \ module reflectometer_tb; //------------------------------------------------------------ // Параметры //------------------------------------------------------------ localparam int unsigned DAC_DATA_WIDTH = 14; localparam int unsigned ADC_DATA_WIDTH = 12; localparam LOGIC_ZERO_LEVEL = 0; // DAC -5V for logic zero localparam VOLTAGE_ZERO_LEVEL = 2**(DAC_DATA_WIDTH-1); // DAC 0V for logic zero localparam PACK_FACTOR = 1; // not used in TB localparam PROCESS_MODE = 0; // 0 - uint, 1 - int. Current accumulator don't support signed sum localparam ACCUM_WIDTH = 32; // accumulator number bit witdth localparam N_MAX = 4096; // max value of windows to average by experiments localparam PACKET_SIZE = 1024; // bytes per UDP packet localparam int REQUEST_TIMEOUT = 3 * PACKET_SIZE; // timeout for packet receiving from accumulator localparam int TEST_NUM = 100; // number of random tests localparam real PEARSON_THRESHOLD = 0.99; localparam real NRMSE_THRESHOLD = 0.1; localparam ZERO_LEVEL = LOGIC_ZERO_LEVEL; // "logic" VS "voltage" localparam CLK_ETH_PHY_PERIOD = 8.000; // 125 MHz localparam CLK_REF_PERIOD = 5.000; // 200 MHz //------------------------------------------------------------ // Глобальные перменные //------------------------------------------------------------ realtime CLK_ADC_PERIOD; realtime CLK_DAC_PERIOD; //------------------------------------------------------------ // Тактовые сигналы и сброс //------------------------------------------------------------ logic clk_ref = 1'b0; // 200 MHz logic clk_eth_phy = 1'b0; // common for RX & TX logic rst_n = 1'b0; //------------------------------------------------------------ // Управление и конфиг DUT //------------------------------------------------------------ logic [31:0] window_size_port; // AXI-S интерфейс для управления axis_if axis_control_if ( .clk(clk_eth_phy), .rst_n(rst_n) ); //------------------------------------------------------------ // Входы DUT //------------------------------------------------------------ // ADC интерфейс wire clk_adc; wire adc_otr; wire [ADC_DATA_WIDTH-1:0] adc_data; //------------------------------------------------------------ // Выходы //------------------------------------------------------------ // Статусы wire mmcm_locked; wire workflow_done; wire processing_done; // DAC интерфейс wire clk_dac; wire dac_wrt; wire [DAC_DATA_WIDTH-1:0] dac_data; // AXI-S интерфейс для данных axis_if axis_accumulator_if ( .clk(clk_eth_phy), .rst_n(rst_n) ); //------------------------------------------------------------ // Внутренние сигналы тестбенча //------------------------------------------------------------ // Интерфейс хендшейка с MAC-PHY wire send_request; logic request_ready; // Сигнал между ЦАП и АЦП real signal_voltage; //------------------------------------------------------------ // Virtual DAC //------------------------------------------------------------ virtual_dac_model #( // default voltage range is +/- 5V .DAC_DATA_WIDTH(DAC_DATA_WIDTH) // ,.VOLTAGE_GAIN(2) ) virtual_dac ( .clk_i(clk_dac), .wrt_i(dac_wrt), .data_i(dac_data), .voltage_o(signal_voltage) ); //------------------------------------------------------------ // Virtual ADC //------------------------------------------------------------ virtual_adc_model #( // default voltage range is +/- 5V .ADC_DATA_WIDTH(ADC_DATA_WIDTH) ) virtual_adc ( .clk_i(clk_adc), .voltage_i(signal_voltage), .otr_o(adc_otr), .data_o(adc_data) ); //------------------------------------------------------------ // Statistics processing //------------------------------------------------------------ //------------------------------------------------------------ // Config handler //------------------------------------------------------------ //------------------------------------------------------------ // DUT //------------------------------------------------------------ reflectometer_top #( .DAC_DATA_WIDTH(DAC_DATA_WIDTH), .ADC_DATA_WIDTH(ADC_DATA_WIDTH), .PACK_FACTOR(PACK_FACTOR), .PROCESS_MODE(PROCESS_MODE), .ZERO_LEVEL(ZERO_LEVEL), .ACCUM_WIDTH(ACCUM_WIDTH), .N_MAX(N_MAX), .PACKET_SIZE(PACKET_SIZE) ) DUT ( .clk_in(clk_ref), .rst_n(rst_n), // Status .locked(mmcm_locked), .workflow_done(workflow_done), .processing_done(processing_done), // Accumulator AXI-S bus .clk_axis_accumulator(clk_eth_phy), // GMII PHY RX clock .axis_accumulator(axis_accumulator_if.master), // Control AXI-S bus .clk_axis_control(clk_eth_phy), // GMII PHY TX clock .axis_control(axis_control_if.slave), .window_size(window_size_port), // direct signal crutch (old controller) // RTL-MAC handshake .request_ready(request_ready), .send_request(send_request), // DAC .dac_clk_o(clk_dac), .dac_data(dac_data), .dac_wrt(dac_wrt), // ADC .adc_clk_o(clk_adc), .adc_data(adc_data), .adc_otr(adc_otr) ); //------------------------------------------------------------ // Тактовые сигналы //------------------------------------------------------------ initial begin forever #(CLK_REF_PERIOD/2) clk_ref = ~clk_ref; end initial begin forever #(CLK_ETH_PHY_PERIOD/2) clk_eth_phy = ~clk_eth_phy; end //------------------------------------------------------------ // Таски для тестирования //------------------------------------------------------------ // Таски работы с AXI-Stream task automatic dut_soft_reset(virtual axis_if#(8).tb vif); logic [7:0] tx_packet[]; tx_packet = '{8'h0f}; vif.master_send(tx_packet); endtask task automatic dut_start(virtual axis_if#(8).tb vif); logic [7:0] tx_packet[]; tx_packet = '{8'hf0}; vif.master_send(tx_packet); endtask task automatic dut_send_system_config( virtual axis_if#(8).tb vif, input logic [31:0] pulse_width, input logic [31:0] pulse_period, input logic [15:0] pulse_num, input logic [13:0] pulse_height, // achtung! p_height strictly must have 14 bits of width input logic [31:0] pulse_period_adc, input logic [31:0] window_size ); // Создаем временный фиксированный массив и упаковываем всё одной строкой logic [7:0] tx_packet[]; // Ахтунг, 14-битный ЦАП захардкожен if (DAC_DATA_WIDTH != 14) $warning("[WARNING] -dut_send_system_config- Default pulse height (DAC bitwidth) is equal to 14. Be aware, controller packet structure is coded for 14 bits"); tx_packet = '{ 8'h88, // Команда pulse_width[7:0], pulse_width[15:8], pulse_width[23:16], pulse_width[31:24], pulse_period[7:0], pulse_period[15:8], pulse_period[23:16], pulse_period[31:24], pulse_num[7:0], pulse_num[15:8], pulse_height[7:0], 8'({2'b00, pulse_height[13:8]}), pulse_period_adc[7:0], pulse_period_adc[15:8], pulse_period_adc[23:16], pulse_period_adc[31:24] }; vif.master_send(tx_packet); // TODO remove for new controller window_size_port = window_size; endtask // Таски сбора статистики task automatic dut_read_output( virtual axis_if#(8).tb vif, input int sample_num, input int window_size, input bit randomize_recv_delays, output int output_data[] ); logic [7:0] rx_packet[]; logic [ACCUM_WIDTH-1:0] data_packet[]; int numbers_per_packet = PACKET_SIZE/(ACCUM_WIDTH/8); int packet_num = $ceil(real'(sample_num / window_size) / real'(numbers_per_packet)); int timeout_flag = 0; int packet_counter = 0; if (sample_num % window_size) begin $error("-dut_read_output- Sample_num must be multiple of window_size: %0d %% %0d = %0d", sample_num, window_size, sample_num % window_size); $finish; end data_packet = new[numbers_per_packet]; output_data = new[numbers_per_packet * packet_num]; // count send_request pulses (equal to number of packets) fork begin : packet_counter_proc forever begin @(posedge clk_eth_phy); if(send_request === 1) packet_counter++; end end join_none // Wait until reflectometer done sampling and averaging wait(processing_done == 1); // recv loop // если число пакетов превышает заложенное предрассчитанное значение -- ошибка fork : recv_loop_proc begin // packet recv loop forever begin if (packet_counter > packet_num) begin $error("-dut_read_output- Packet overflow detected. Number of data packets exceeds expected amount of packets"); $finish; end if (randomize_recv_delays) repeat($urandom_range(0, 500)) @(posedge clk_eth_phy); timeout_flag = 0; fork : receive_packet_timeout begin request_ready = 1; vif.slave_recv(rx_packet); request_ready = 0; end begin repeat(REQUEST_TIMEOUT) @(posedge clk_eth_phy); timeout_flag = 1; end join_any disable receive_packet_timeout; if (timeout_flag) begin $error("-dut_read_output- Timeout detected when receiving packet"); $finish; end if (rx_packet.size() != PACKET_SIZE) begin $error("-dut_read_output- Wrong packet size received: %0d bytes received, %0d bytes expected", rx_packet.size(), PACKET_SIZE); $finish; end // unpack values data_packet = {<< byte {rx_packet}}; data_packet = {<< ACCUM_WIDTH {data_packet}}; // copy and convert values for (int j = 0; j < data_packet.size(); j++) begin output_data[(packet_counter-1) * data_packet.size() + j] = int'(data_packet[j]); end end end begin // IP workflow completion event wait(workflow_done == 1); end join_any disable recv_loop_proc; disable packet_counter_proc; if (packet_counter != packet_num) begin $error("-dut_read_output- Wrong number of packets received: %0d received, %0d expected", packet_counter, packet_num); $finish; end wait(processing_done == 0); endtask //------------------------------------------------------------ // Функции и таски для верификации сигналов //------------------------------------------------------------ // Таска генерации идеального тестового сигнала task automatic reference_signal( input int pulse_width, input int pulse_height, input int pulse_period_adc, input int window_size, output real result[] ); /* Globals: ADC and DAC clock periods, Virtual ADC and DAC voltage steps Task developed with assumption that first discrete values of DAC and ADC are syncrhonized at t==0 and started simultaneously. Gains and biases of virtual ADC & DAC are default and ranges are [-5V;5V]. Bitwidths may be altered. Returned result[] array is an array of sums of voltage potentials in discrete time points. Discrete samples summed over a time window. result[time] = (voltage) */ int sample_num = pulse_period_adc / window_size; // total averaged output samples from accumulator real current_signal_sample, partial_sum; real ref_signal_active_voltage = virtual_dac.code_to_voltage(pulse_height); real ref_signal_zero_voltage = virtual_dac.code_to_voltage(ZERO_LEVEL); if (pulse_period_adc % window_size) begin $error("-reference_signal- pulse_period_adc must be multiple of window_size: %0d %% %0d = %0d", pulse_period_adc, window_size, pulse_period_adc % window_size); $finish; end result = new[sample_num]; partial_sum = 0; for (int i = 0; i < pulse_period_adc; i++) begin // var i in ADC timespace // i == 0 is a t0 of pulse generation and sampling current_signal_sample = (i*CLK_ADC_PERIOD <= pulse_width*CLK_DAC_PERIOD) ? ref_signal_active_voltage : ref_signal_zero_voltage; partial_sum += current_signal_sample; if (i % window_size == (window_size-1)) begin result[i / window_size] = partial_sum; partial_sum = 0; end end endtask // Функция проверки размеров выборок function automatic void check_size( input real a[], input real b[] ); if (a.size() != b.size()) $fatal(1, "Array size mismatch: %0d != %0d", a.size(), b.size()); if (a.size() == 0) $error(1, "Empty array"); endfunction // Среднее по выборке function automatic real array_mean( input real a[] ); real sum = 0.0; foreach (a[i]) sum += a[i]; return sum / a.size(); endfunction // MSE двух выборок function automatic real calc_mse( input real a[], input real b[] ); real sum = 0.0; check_size(a, b); foreach (a[i]) begin real err; err = a[i] - b[i]; sum += err * err; end return sum / a.size(); endfunction // NRMSE двух выборок (нормирование RMSE) function automatic real calc_nrmse( input real a[], input real b[] ); const real EPS = 1e-12; real mse, ms = 0; mse = calc_mse(a, b); foreach (a[i]) begin ms += a[i] * a[i]; end ms /= a.size(); return $sqrt(mse / (ms + EPS)); endfunction // Функция модуля function automatic real abs_f(input real x); return (x < 0.0) ? -x : x; endfunction // Максимальная абсолютная ошибка function automatic real calc_max_error( input real a[], input real b[] ); real max_err = 0.0; check_size(a, b); foreach (a[i]) begin real err; err = abs_f(a[i] - b[i]); if (err > max_err) max_err = err; end return max_err; endfunction // Коэффициент корреляции Пирсона function automatic real calc_pearson( input real a[], input real b[] ); real mean_a; real mean_b; real numerator = 0.0; real denom_a = 0.0; real denom_b = 0.0; check_size(a, b); mean_a = array_mean(a); mean_b = array_mean(b); foreach (a[i]) begin real da; real db; da = a[i] - mean_a; db = b[i] - mean_b; numerator += da * db; denom_a += da * da; denom_b += db * db; end if ((denom_a == 0.0) || (denom_b == 0.0)) return 0.0; return numerator / $sqrt(denom_a * denom_b); endfunction // Вспомогательная функция для вывода массива function automatic void display_array_f(input real a[]); $write("\t"); foreach(a[i]) $write("%f ", a[i]); $write("\n"); endfunction // Вспомогательная функция для вывода массива function automatic void display_array(input int a[]); $write("\t"); foreach(a[i]) $write("%0d ", a[i]); $write("\n"); endfunction // Основная таска типового теста task automatic run_test_case( virtual axis_if#(8).tb ctrl_vif, virtual axis_if#(8).tb accum_vif, input int pulse_width, input int pulse_period, input int pulse_num, input int pulse_height, input int pulse_period_adc, input int window_size, input bit rand_recv_delays, input bit use_reset, output int result ); int output_data[]; // raw accum values real output_signal_v[]; // accum values after voltage conversion real reference_signal_v[]; // reference signal voltage values real nrmse, pearson, max_err; // error and correlation metrics if (use_reset) begin dut_soft_reset(ctrl_vif); #100; end dut_send_system_config( .vif(ctrl_vif), .pulse_width(pulse_width), .pulse_period(pulse_period), .pulse_num(pulse_num), .pulse_height(pulse_height), .pulse_period_adc(pulse_period_adc), .window_size(window_size) ); #100; dut_start(ctrl_vif); dut_read_output( .vif(accum_vif), .sample_num(pulse_period_adc), .window_size(window_size), .randomize_recv_delays(rand_recv_delays), .output_data(output_data) ); // actual size of payload is pulse_period_adc / window_size output_signal_v = new[pulse_period_adc / window_size]; `ifdef DEBUG $display("[TB] Output data stream"); display_array(output_data); `endif // voltage conversion begin // zero level for partial sum real zero_level_bias = window_size * virtual_adc.ZERO_CODE; // common voltage multiplier for step & amplifier real voltage_multiplier = virtual_adc.VOLTAGE_STEP / virtual_adc.VOLTAGE_GAIN; // array conversion foreach (output_signal_v[i]) begin real average_code_per_pulse = real'(output_data[i]) / pulse_num; output_signal_v[i] = (average_code_per_pulse - zero_level_bias) * voltage_multiplier; end end reference_signal( .pulse_width(pulse_width), .pulse_height(pulse_height), .pulse_period_adc(pulse_period_adc), .window_size(window_size), .result(reference_signal_v) ); `ifdef DEBUG $display("[TB] Output signal"); display_array_f(output_signal_v); $display("[TB] Reference signal"); display_array_f(reference_signal_v); `endif nrmse = calc_nrmse(output_signal_v, reference_signal_v); pearson = calc_pearson(output_signal_v, reference_signal_v); max_err = calc_max_error(output_signal_v, reference_signal_v); `ifdef DEBUG $display("[TB] Metrics:\n\tNRMSE = %0.4f\t|\tPearson = %0.4f\t|\tMax error = %0.4f", nrmse, pearson, max_err); `endif // check metrics result = 0; // if (pearson < PEARSON_THRESHOLD) // result += 1; if (nrmse > NRMSE_THRESHOLD) result += 2; /* Max error not used in evaluation because of fast pulse edge falling resulting in plain difference between active signal level and zero level For ex.: zero_level = 0x00 = -5V. pulse_height = 2^14-1 = 0x3fff = 5V In some cases like jitter this may cause max error = 5 - (-5) = 10(V) This cases are hardly traceble, thus max error not used in eval. Pearson not used in evaluation because it only shows correlation of changing signals. Tests broke on static signals. Pearson and max err remain in test for info. */ endtask //------------------------------------------------------------ // ОСНОВНОЙ ПРОЦЕСС ТЕСТИРОВАНИЯ //------------------------------------------------------------ initial begin int result_flag; int total_failed_tests = 0, total_tests = 0; automatic virtual axis_if.tb control_vif = axis_control_if.tb; automatic virtual axis_if.tb accumulator_vif = axis_accumulator_if.tb; $info("[TB] DUT initializaton"); // Инициализация request_ready = 0; rst_n = 0; #100; rst_n = 1; wait(mmcm_locked === 1'b1); #150; $info("[TB] MMCM locked"); // Meause periods because actual values hardcoded in IP fork `MEASURE_CLK(DUT.clk_sampler, CLK_ADC_PERIOD); `MEASURE_CLK(DUT.clk_generator, CLK_DAC_PERIOD); join $info("[TB] ADC & DAC clock periods measured: ADC_period = %0.3f, DAC_period = %0.3f", CLK_ADC_PERIOD, CLK_DAC_PERIOD); // Тесты $info("[TB] Tests start"); $info("[TB] Simple test run"); run_test_case( .ctrl_vif(control_vif), .accum_vif(accumulator_vif), .pulse_width(4000), .pulse_period(10000), .pulse_num(5), .pulse_height(12000), .pulse_period_adc(6000), .window_size(10), .rand_recv_delays(1), .use_reset(1), .result(result_flag) ); `ERR_CHECK $info("[TB] Random test run"); for (int i = 0; i < TEST_NUM; i++) begin int pulse_width, pulse_period, pulse_num, pulse_height, pulse_period_adc, window_size; bit rand_recv_delays, use_reset; // Генерируемые параметры pulse_period = $urandom_range(500, 5000); pulse_width = $urandom_range(50, pulse_period); pulse_num = $urandom_range(1, 10); pulse_height = $urandom_range(0, 2**DAC_DATA_WIDTH-1); window_size = $urandom_range(1, 11); pulse_period_adc = $urandom_range(50, N_MAX-1) * window_size; rand_recv_delays = 1; use_reset = 1; // ($urandom_range(0, 10) >= 9); `ifdef DEBUG $display("Test #%0d", total_tests); $display("Parameters:\n\tpulse_width=%0d\n\tpulse_period=%0d\n\tpulse_num=%0d\n\tpulse_height=%0d\n\tpulse_period_adc=%0d\n\twindow_size=%0d\n\trand_recv_delays=%0d\n\tuse_reset=%0d", pulse_width, pulse_period, pulse_num, pulse_height, pulse_period_adc, window_size, rand_recv_delays, use_reset); `endif run_test_case( .ctrl_vif(control_vif), .accum_vif(accumulator_vif), .pulse_width(pulse_width), .pulse_period(pulse_period), .pulse_num(pulse_num), .pulse_height(pulse_height), .pulse_period_adc(pulse_period_adc), .window_size(window_size), .rand_recv_delays(rand_recv_delays), .use_reset(use_reset), .result(result_flag) ); `ERR_CHECK if (result_flag) begin $display("Parameters:\n\tpulse_width=%0d\n\tpulse_period=%0d\n\tpulse_num=%0d\n\tpulse_height=%0d\n\tpulse_period_adc=%0d\n\twindow_size=%0d\n\trand_recv_delays=%0d\n\tuse_reset=%0d", pulse_width, pulse_period, pulse_num, pulse_height, pulse_period_adc, window_size, rand_recv_delays, use_reset); end end $info("[TB] Corner case test run"); run_test_case( .ctrl_vif(control_vif), .accum_vif(accumulator_vif), .pulse_width(0), .pulse_period(1000), .pulse_num(5), .pulse_height(12000), .pulse_period_adc(600), .window_size(10), .rand_recv_delays(0), .use_reset(1), .result(result_flag) ); `ERR_CHECK run_test_case( .ctrl_vif(control_vif), .accum_vif(accumulator_vif), .pulse_width(1000), .pulse_period(1000), .pulse_num(5), .pulse_height(12000), .pulse_period_adc(600), .window_size(10), .rand_recv_delays(0), .use_reset(1), .result(result_flag) ); `ERR_CHECK run_test_case( .ctrl_vif(control_vif), .accum_vif(accumulator_vif), .pulse_width(500), .pulse_period(1000), .pulse_num(5), .pulse_height(2**(DAC_DATA_WIDTH-1)), .pulse_period_adc(600), .window_size(10), .rand_recv_delays(0), .use_reset(1), .result(result_flag) ); `ERR_CHECK run_test_case( .ctrl_vif(control_vif), .accum_vif(accumulator_vif), .pulse_width(500), .pulse_period(1000), .pulse_num(5), .pulse_height(15000), .pulse_period_adc(10), .window_size(1), .rand_recv_delays(0), .use_reset(1), .result(result_flag) ); `ERR_CHECK $display("[TB] Tests done. [%0d/%0d] tests passed, %0d failed", total_tests - total_failed_tests, total_tests, total_failed_tests); if (!total_failed_tests) $display("[TB] ALL PASSED"); $finish; end endmodule