upd: readout task

add: reference signal generation, correlation metrics functions, baseline test task
fix: small fixes of ports and code refactoring
This commit is contained in:
2026-07-29 19:03:15 +03:00
parent 7840e0ea7b
commit 7c7cd451b8
+353 -46
View File
@@ -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