diff --git a/designs/reflectometer_base/reflectometer_tb.sv b/designs/reflectometer_base/reflectometer_tb.sv index 1d1cbd9..8aaac1e 100644 --- a/designs/reflectometer_base/reflectometer_tb.sv +++ b/designs/reflectometer_base/reflectometer_tb.sv @@ -1,6 +1,19 @@ `timescale 1ns / 1ps `include "interfaces.svh" + // start push + +`define DEBUG + +`define MEASURE_CLK(clk, period) \ +begin \ + realtime t1, t2; \ + @(posedge clk); \ + t1 = $realtime; \ + @(posedge clk); \ + t2 = $realtime; \ + period = t2 - t1; \ +end module reflectometer_tb; //------------------------------------------------------------ @@ -26,7 +39,8 @@ module reflectometer_tb; //------------------------------------------------------------ // Глобальные перменные //------------------------------------------------------------ - int unsigned WINDOW_SIZE = 65; // fixed subwindow size to average by time + realtime CLK_ADC_PERIOD; + realtime CLK_DAC_PERIOD; //------------------------------------------------------------ // Тактовые сигналы и сброс @@ -38,7 +52,7 @@ module reflectometer_tb; //------------------------------------------------------------ // Управление и конфиг DUT //------------------------------------------------------------ - logic [31:0] window_size; + logic [31:0] window_size_port; // AXI-S интерфейс для управления axis_if axis_control_if ( .clk(clk_eth_phy), @@ -139,7 +153,7 @@ module reflectometer_tb; // Control AXI-S bus .clk_axis_control(clk_eth_phy), // GMII PHY TX clock .axis_control(axis_control_if.slave), - .window_size(window_size), // direct signal crutch (old controller) + .window_size(window_size_port), // direct signal crutch (old controller) // RTL-MAC handshake .request_ready(request_ready), @@ -155,7 +169,6 @@ module reflectometer_tb; .adc_data(adc_data), .adc_otr(adc_otr) ); - assign window_size = WINDOW_SIZE; //------------------------------------------------------------ // Тактовые сигналы @@ -197,7 +210,7 @@ module reflectometer_tb; // Ахтунг, 14-битный ЦАП захардкожен if (DAC_DATA_WIDTH != 14) - $display("[WARNING] -dut_send_system_config- Default pulse height (DAC bitwidth) is equal to 14. Be aware, controller packet structure is coded for 14 bits"); + $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, // Команда @@ -210,25 +223,26 @@ module reflectometer_tb; vif.master_send(tx_packet); // TODO remove for new controller - WINDOW_SIZE = window_size; + 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 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 - $display("[ERROR] -dut_read_output- Sample_num must be multiple of WINDOW_SIZE: %0d %% %0d = %0d", sample_num, WINDOW_SIZE, sample_num % WINDOW_SIZE); + 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 @@ -256,7 +270,7 @@ module reflectometer_tb; // packet recv loop forever begin if (packet_counter > packet_num) begin - $display("[ERROR] -dut_read_output- Packet overflow detected. Number of data packets exceeds expected amount of packets"); + $error("-dut_read_output- Packet overflow detected. Number of data packets exceeds expected amount of packets"); $finish; end @@ -278,12 +292,12 @@ module reflectometer_tb; disable receive_packet_timeout; if (timeout_flag) begin - $display("[ERROR] -dut_read_output- Timeout detected when receiving packet"); + $error("-dut_read_output- Timeout detected when receiving packet"); $finish; end if (rx_packet.size() != PACKET_SIZE) begin - $display("[ERROR] -dut_read_output- Wrong packet size received: %0d bytes received, %0d bytes expected", rx_packet.size(), PACKET_SIZE); + $error("-dut_read_output- Wrong packet size received: %0d bytes received, %0d bytes expected", rx_packet.size(), PACKET_SIZE); $finish; end @@ -308,25 +322,314 @@ module reflectometer_tb; disable packet_counter_proc; if (packet_counter != packet_num) begin - $display("[ERROR] -dut_read_output- Wrong number of packets received: %0d received, %0d expected", packet_counter, packet_num); + $error("-dut_read_output- Wrong number of packets received: %0d received, %0d expected", packet_counter, packet_num); $finish; end 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 двух выборок (нормирование MSE) + function automatic real calc_nrmse( + input real a[], + input real b[] + ); + real mse; + real min_val; + real max_val; + + mse = calc_mse(a, b); + + min_val = a[0]; + max_val = a[0]; + + foreach (a[i]) begin + if (a[i] < min_val) + min_val = a[i]; + + if (a[i] > max_val) + max_val = a[i]; + end + + if (max_val == min_val) + return 0.0; + + return $sqrt(mse) / (max_val - min_val); + 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 + // Основная таска типового теста - // todo + 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 bit 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]; + + // 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 = 1; + if (pearson < 0.99) + result = 0; + if (nrmse > 0.1) + result = 0; + /* + 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. + */ + endtask //------------------------------------------------------------ // ОСНОВНОЙ ПРОЦЕСС ТЕСТИРОВАНИЯ //------------------------------------------------------------ initial begin - int output_data[]; + bit 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; - $display("[TB] DUT initializaton"); + $info("[TB] DUT initializaton"); // Инициализация request_ready = 0; rst_n = 0; @@ -334,40 +637,44 @@ module reflectometer_tb; rst_n = 1; wait(mmcm_locked === 1'b1); #150; - $display("[TB] MMCM locked"); - + $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); + dut_soft_reset(control_vif); #100; - + // Тесты - $display("[TB] Tests start"); - dut_send_system_config( - .vif(control_vif), - .pulse_width(32'd123), - .pulse_period(32'd5000), - .pulse_num(16'd1), - .pulse_height(14'd15000), // 0V - .pulse_period_adc(32'd2600), - .window_size(1) - ); - #100; - dut_start(control_vif); - - dut_read_output( - .vif(accumulator_vif), - .sample_num(2600), - .randomize_recv_delays(0), - .output_data(output_data) - ); - #1000; - - $display("Received %0d numbers", output_data.size()); - for (int i = 0; i < output_data.size(); i++) begin - $write("%0d ", output_data[i]); - end - $display(""); + $info("[TB] Tests start"); - $display("[TB] ALL PASSED"); + $info("[TB] Simple test run"); + + run_test_case( + .ctrl_vif(control_vif), + .accum_vif(accumulator_vif), + .pulse_width(400), + .pulse_period(1000), + .pulse_num(5), + .pulse_height(12000), + .pulse_period_adc(600), + .window_size(10), + .rand_recv_delays(0), + .use_reset(0), + .result(result_flag) + ); + + $info("[TB] Random test run"); + + $info("[TB] Corner case test run"); + + + // if (!failed) + $info("[TB] ALL PASSED"); $finish; end endmodule