Compare commits

...
Author SHA1 Message Date
babintsev.lv 735bc6518d upd: TB quick_fix 2026-07-31 19:30:39 +03:00
Zer0Nu11 e354cb2742 upd: TB done 2026-07-31 19:18:59 +03:00
babintsev.lv 7c7cd451b8 upd: readout task
add: reference signal generation, correlation metrics functions, baseline test task
fix: small fixes of ports and code refactoring
2026-07-29 19:03:15 +03:00
2 changed files with 476 additions and 53 deletions
+10 -8
View File
@@ -86,15 +86,15 @@
Система работает в нескольких тактовых доменах:
- Ethernet RX (`gmii_rx_clk`)
- Ethernet TX (`gmii_tx_clk`)
- DAC (`dac_clk`)
- ADC (`adc_clk`)
- Ethernet RX (`clk_axis_control`)
- Ethernet TX (`clk_axis_accumulator`)
- DAC (`clk_generator`)
- ADC (`clk_sampler`)
Для корректной синхронизации между DAC и ADC используются специальные CDC-регистры для сигналов:
- `sample_req`
- `sample_done`
- `done`
- `request`
Это обеспечивает безопасную передачу handshake-сигналов между тактовыми доменами.
@@ -110,8 +110,8 @@
Типовые значения:
- `8192` — середина диапазона ЦАП
- `0` — нулевой уровень
- `8192` — середина диапазона ЦАП (0V)
- `0` — нулевой уровень (-5V)
### ADC_DATA_WIDTH
Ширина входных данных, получаемых с АЦП.
@@ -143,3 +143,5 @@
```make all``` - собрать все до битстрима
```make vivado``` - открыть проект в Vivado
```make sim``` - симуляция и верификация проекта
+461 -40
View File
@@ -2,6 +2,25 @@
`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;
//------------------------------------------------------------
// Параметры
@@ -17,6 +36,10 @@ module reflectometer_tb;
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"
@@ -26,7 +49,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 +62,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 +163,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 +179,6 @@ module reflectometer_tb;
.adc_data(adc_data),
.adc_otr(adc_otr)
);
assign window_size = WINDOW_SIZE;
//------------------------------------------------------------
// Тактовые сигналы
@@ -197,7 +220,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 +233,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 +280,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 +302,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 +332,323 @@ 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
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
// Основная таска типового теста
// 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 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 output_data[];
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;
$display("[TB] DUT initializaton");
$info("[TB] DUT initializaton");
// Инициализация
request_ready = 0;
rst_n = 0;
@@ -334,39 +656,138 @@ module reflectometer_tb;
rst_n = 1;
wait(mmcm_locked === 1'b1);
#150;
$display("[TB] MMCM locked");
$info("[TB] MMCM locked");
dut_soft_reset(control_vif);
#100;
// 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);
// Тесты
$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)
$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)
);
#100;
dut_start(control_vif);
`ERR_CHECK
dut_read_output(
.vif(accumulator_vif),
.sample_num(2600),
.randomize_recv_delays(0),
.output_data(output_data)
$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);
$display("Test #%0d", total_tests);
`ifdef DEBUG
$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)
);
#1000;
$display("Received %0d numbers", output_data.size());
for (int i = 0; i < output_data.size(); i++) begin
$write("%0d ", output_data[i]);
`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
$display("");
$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