diff --git a/designs/adc_dac_synchoronizer/debug.xdc b/designs/adc_dac_synchoronizer/debug.xdc index 473ad9c..a3cf907 100644 --- a/designs/adc_dac_synchoronizer/debug.xdc +++ b/designs/adc_dac_synchoronizer/debug.xdc @@ -1,3 +1,12 @@ # Primary clocks -create_clock -name eth_clk -period 8.000 [get_ports clk_dac] -create_clock -name acc_clk -period 15.385 [get_ports clk_adc] \ No newline at end of file +create_clock -name geneartor_clk -period 8.000 [get_ports clk_dac] +create_clock -name sampler_clk -period 15.385 [get_ports clk_adc] +set_clock_groups -asynchronous -group [get_clocks geneartor_clk] -group [get_clocks sampler_clk] + +# set_false_path -through [get_nets -hierarchical {*dac_signal* *internal_wire_singnal* *adc_singnal*}] +# set_false_path -through [get_nets {dac_done dac_done_stretched dac_request adc_done adc_request}] + + set_property DONT_TOUCH true [get_cells -hierarchical -filter {NAME =~ *generator_inst*pulse_height_reg*}] + set_property DONT_TOUCH true [get_cells -hierarchical -filter {NAME =~ *generator_inst*dac_out_reg*}] +# Применяем к самому проводу сигнала CE, чтобы Vivado не дробила его +# set_property DONT_TOUCH true [get_nets -of_objects [get_pins -hierarchical -filter {PIN_NAME =~ *CE} -of_objects [get_cells *pulse_height_reg*]]] \ No newline at end of file diff --git a/designs/adc_dac_synchoronizer/sync_top.sv b/designs/adc_dac_synchoronizer/sync_top.sv index ea5f28a..13588ab 100644 --- a/designs/adc_dac_synchoronizer/sync_top.sv +++ b/designs/adc_dac_synchoronizer/sync_top.sv @@ -6,7 +6,8 @@ module sync_top parameter int unsigned ADC_DATA_WIDTH = 12, // ADC bit-width parameter int unsigned PACK_FACTOR = 1, // number of ADC readings per transaction parameter int unsigned PROCESS_MODE = 0, // representation format of ADC readings (0 - direct code, 1 - 2's completment) - parameter int unsigned ZERO_LEVEL = 0 + parameter int unsigned ZERO_LEVEL = 0, + parameter int unsigned USE_DELAY_LINE = 0 ) ( input clk_adc, @@ -16,9 +17,9 @@ module sync_top input start, input out_of_range, input [31:0] pulse_width, - input [31:0] pulse_period, + input [31:0] pulse_period, // DAC counter limit input [DAC_DATA_WIDTH-1:0] pulse_height, - input [15:0] pulse_num, // DAC counter limit + input [15:0] pulse_num, input [31:0] smp_num, // ADC counter limit output [ADC_DATA_WIDTH*PACK_FACTOR-1:0] m_axis_tdata, output m_axis_tvalid @@ -29,13 +30,45 @@ module sync_top //------------------------------------------------------------ wire dac_done, dac_request, adc_done, adc_request; wire [DAC_DATA_WIDTH-1:0] dac_signal; - wire [ADC_DATA_WIDTH-1:0] adc_singnal; + wire [ADC_DATA_WIDTH-1:0] internal_wire_signal; + wire [ADC_DATA_WIDTH-1:0] adc_signal; + + // 1. Адаптация разрядности и «заземление» лишних бит generate if (ADC_DATA_WIDTH > DAC_DATA_WIDTH) begin : g_pad_zeros - assign adc_singnal = { {(ADC_DATA_WIDTH - DAC_DATA_WIDTH){1'b0}}, dac_signal }; + // АЦП шире ЦАП: добиваем нулями старшие биты + assign internal_wire_signal = { {(ADC_DATA_WIDTH - DAC_DATA_WIDTH){1'b0}}, dac_signal }; + end + else if (ADC_DATA_WIDTH < DAC_DATA_WIDTH) begin : g_truncate + // ЦАП шире АЦП (например, 14 -> 12): забираем младшие биты + assign internal_wire_signal = dac_signal[DAC_DATA_WIDTH-1:DAC_DATA_WIDTH-ADC_DATA_WIDTH]; +// (* mark_debug = "true" *) wire dummy; +// assign dummy = ^dac_signal; + end + else begin : g_match + // Разрядности равны + assign internal_wire_signal = dac_signal; + end + endgenerate + + generate + if (USE_DELAY_LINE > 0) begin : g_delay_line + localparam int DELAY_LENGTH = USE_DELAY_LINE; + // Двумерный массив для линии задержки + logic [DELAY_LENGTH-1:0][ADC_DATA_WIDTH-1:0] signal_delay_line; + + always_ff @(posedge clk_dac) begin + signal_delay_line[0] <= internal_wire_signal; + for (int i = 0; i < DELAY_LENGTH-1; i++) begin + signal_delay_line[i+1] <= signal_delay_line[i]; + end + end + + // ИСПРАВЛЕНО: читаем из последнего элемента массива + assign adc_signal = signal_delay_line[DELAY_LENGTH-1]; end - else begin : g_truncate - assign adc_singnal = dac_signal[ADC_DATA_WIDTH-1:0]; + else begin : g_no_delay + assign adc_signal = internal_wire_signal; end endgenerate @@ -112,7 +145,7 @@ module sync_top ) sampler_inst ( .clk_in(clk_adc), .rst(rst_adc), - .data_in(adc_singnal), + .data_in(adc_signal), .out_of_range(out_of_range), .smp_num(smp_num), .m_axis_tdata(m_axis_tdata), diff --git a/designs/adc_dac_synchoronizer/tb_sync_top.sv b/designs/adc_dac_synchoronizer/tb_sync_top.sv index fe1fefb..a722e7e 100644 --- a/designs/adc_dac_synchoronizer/tb_sync_top.sv +++ b/designs/adc_dac_synchoronizer/tb_sync_top.sv @@ -5,16 +5,21 @@ module tb_top; //------------------------------------------------------------ // Параметры //------------------------------------------------------------ + parameter string ZERO_LEVEL_PARAM = "logic"; // "logic" VS "true" + parameter VERBOSE = 1; + localparam DAC_DATA_WIDTH = 14; localparam ADC_DATA_WIDTH = 12; localparam PACK_FACTOR = 1; localparam PROCESS_MODE = 0; - 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 CLK_DAC_PERIOD = 8; localparam CLK_ADC_PERIOD = 15.385; + localparam USE_DELAY_LINE = 0; + 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 ZERO_LEVEL = (ZERO_LEVEL_PARAM == "logic") ? LOGIC_ZERO_LEVEL : VOLTAGE_ZERO_LEVEL; - localparam ZERO_LEVEL = LOGIC_ZERO_LEVEL; // "logic" VS "true" + localparam CLOCK_DEVIATION = 3; // Maximum clock deviation of pulse stats //------------------------------------------------------------ // Тактовые сигналы и сброс @@ -49,7 +54,8 @@ module tb_top; .ADC_DATA_WIDTH(ADC_DATA_WIDTH), .PACK_FACTOR(PACK_FACTOR), .PROCESS_MODE(PROCESS_MODE), - .ZERO_LEVEL(ZERO_LEVEL) + .ZERO_LEVEL(ZERO_LEVEL), + .USE_DELAY_LINE(USE_DELAY_LINE) ) dut ( .clk_adc(clk_adc), .clk_dac(clk_dac), @@ -77,12 +83,20 @@ module tb_top; end // === Таски для тестирования === + // Функция модуля + function automatic real fabs(real val); + return (val < 0.0) ? -val : val; + endfunction + + `define MIN(x, y) (((x) < (y)) ? (x) : (y)) + // Таска сброса DAC DUT task automatic reset_dut_dac( input int rst_duration // сколько тактов держать сброс ); + @(negedge clk_dac); rst_dac <= 1; - repeat(rst_duration) @(posedge clk_dac); + repeat(rst_duration) @(negedge clk_dac); rst_dac <= 0; endtask @@ -90,8 +104,9 @@ module tb_top; task automatic reset_dut_adc( input int rst_duration // сколько тактов держать сброс ); + @(negedge clk_adc); rst_adc <= 1; - repeat(rst_duration) @(posedge clk_adc); + repeat(rst_duration) @(negedge clk_adc); rst_adc <= 0; endtask @@ -99,8 +114,9 @@ module tb_top; task automatic start_dut( input int start_duration // сколько тактов держать импульс ); + @(negedge clk_dac); // to make signal stable dac_start <= 1; - repeat(start_duration) @(posedge clk_dac); + repeat(start_duration) @(negedge clk_dac); dac_start <= 0; endtask @@ -113,196 +129,435 @@ module tb_top; input logic [31:0] sn // число сэмплов ); // Задаем конфигурационные регистры - @(posedge clk_dac); pulse_width <= w; pulse_period <= p; pulse_num <= n; pulse_height <= h; smp_num <= sn; endtask + + // Основная таска проверки DUT + task automatic run_test_case( + input int pulse_width, + input int pulse_period, + input int pulse_height, + input int pulse_num, + input int sample_num, + input bit skip_reset, + input bit randomize_start_timing, + input bit out_of_range_val, + input bit randomize_out_of_range + ); + int error_flag = 0; + int start_hold_time = 1; + realtime sync_start_time, pulse_start_time, pulse_update_val_time; + realtime sync_time_stats[$], pulse_width_time_stats[$], pulse_period_time_stats[$], pulse_delay_time_stats[$]; + realtime avearge_pulse_delay, average_pulse_width, average_pulse_period, average_sync_time; - // // Таска проверки устойчивости к долгим управляющим импульсам - // task automatic check_impulses; - // // Локальные переменные для хранения случайных параметров - // int rand_start_duration; - // int rand_delay; - // int rand_ack; - // bit rand_first; - // int total_impulse_cycles = 0; + out_of_range = out_of_range_val; - // int pulse_w = 11; - // int pulse_p = 31; - // int pulse_n = 5; - // int pulse_h = 1024; + if (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Starting test case"); + end - // $display("[TB] -check_impulses- Check system stability under random latencies"); + if (randomize_out_of_range) + fork + begin : randomize_out_of_range_proc + forever begin + @(posedge clk_adc); + out_of_range = $urandom_range(0, 1); + end + end + join_none - // // Установка конфигурации - // set_config( - // .w(pulse_w), - // .p(pulse_p), - // .n(pulse_n), - // .h(pulse_h) - // ); + if (!skip_reset) + fork + reset_dut_adc(1); + reset_dut_dac(1); + join - // reset_dut(5); - // repeat(2) @(posedge clk); + set_config( + .w(pulse_width), + .p(pulse_period), + .n(pulse_num), + .h(pulse_height), + .sn(sample_num) + ); + @(posedge clk_dac); + @(posedge clk_dac); - // // Старт норме 1 такт. Сделаем случайным от 5 до 25 тактов. - // rand_start_duration = $urandom_range(5, 25); - // $display("[TB] Long start: %0d clocks", rand_start_duration); + if (randomize_start_timing) + start_hold_time = $urandom_range(1, 15); + fork // warning: check not forever + start_dut(start_hold_time); + begin + @(posedge clk_dac); + // старт первой синхронизации + sync_start_time = $realtime; + end + join_none - // // Фоновый процесс подсчета тактов импульса - // fork - // begin : counter_proc - // forever begin - // @(negedge clk); // 180 deg. phase shift for "DAC strobing signal" - // if (dac_out == pulse_h) begin - // total_impulse_cycles++; - // end - // end - // end - // join_none + if (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Starting pulse generation"); + end + + for (int i = 0; i < pulse_num; i++) begin + if (VERBOSE >= 3) begin + $display("[TB] -run_test_case- Start sync for pulse #%d", i); + end + @(posedge m_axis_tvalid); + if (VERBOSE >= 3) begin + $display("[TB] -run_test_case- Found valid pulse response data positive front"); + end + // Старт цикла. Завершение синхронизации + sync_time_stats.push_back($realtime - sync_start_time); + pulse_start_time = $realtime; + + fork + // Поток будет запущен для ненулевых импульсов и гарантированно завершится как только зафиксирует статистику импульса + // Начало импульса + if (pulse_height != ZERO_LEVEL && pulse_width != 0) begin + if (VERBOSE >= 4) begin + $display("[TB] -run_test_case- Wait until pulse become high"); + end + wait(m_axis_tdata != ZERO_LEVEL); + // Фактическое начало импульса. Поступление высокого уровня + pulse_update_val_time = $realtime; + pulse_delay_time_stats.push_back(pulse_update_val_time - pulse_start_time); + + // Проверим что высота импульса совпала с заданной. Т.к. OTR != 0 влияет на выходные данные сэмплера, то не будем проверять такие случаи. + @(posedge clk_adc); + #1.5; // Ожидание завершения переходных процессов + // Будем считать что из-за OTR данные изменились (по условию OTR + MSB), проверка пропускается, т.к. сложно понять точное значение OTR в момент обработки данных от tdata + if (m_axis_tdata != (pulse_height >> 2) && (randomize_out_of_range || out_of_range_val)) begin + $display("[ERROR] -run_test_case- Wrong pulse height: %d. Must be: %d", m_axis_tdata, pulse_height >> 2); + $finish; + end + + if (VERBOSE >= 4) begin + $display("[TB] -run_test_case- Wait until pulse become low"); + end + wait(m_axis_tdata == ZERO_LEVEL); + pulse_width_time_stats.push_back($realtime - pulse_update_val_time); + end + // Конец импульса + join_none + + @(negedge m_axis_tvalid); + if (VERBOSE >= 3) begin + $display("[TB] -run_test_case- Found valid pulse response data negative front"); + end + // Завершение цикла. Старт синхронизации + pulse_period_time_stats.push_back($realtime - pulse_start_time); + sync_start_time = $realtime; + end + + if (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Stop pulse generation"); + end + + fork // Проверка с таймаутом на лишние циклы + @(posedge m_axis_tvalid); + repeat(30) @(posedge clk_adc); + join_any + if (m_axis_tvalid == 1) begin + $display("[ERROR] -run_test_case- Extra pulse cycle num. More than must be."); + $finish; + end + + if (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Exit waiting via timeout"); + end + + if (randomize_out_of_range) begin + disable randomize_out_of_range_proc; + if (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Stop randomize_out_of_range_proc"); + end + end + out_of_range = 0; + + // Проверка по статистике. Подсчет средних значений + if (pulse_delay_time_stats.size() != pulse_num && pulse_height != ZERO_LEVEL && pulse_width != 0) begin // Detected with pulse level. Skip if pulse level undetectable + $display("[ERROR] -run_test_case- Size of pulse_delay_time_stats samples not equal to pulse_num: %d VS %d", pulse_delay_time_stats.size(), pulse_num); + $finish; + end + if (pulse_width_time_stats.size() != pulse_num && pulse_height != ZERO_LEVEL && pulse_width != 0) begin // Detected with pulse level. Skip if pulse level undetectable + $display("[ERROR] -run_test_case- Size of pulse_width_time_stats samples not equal to pulse_num: %d VS %d", pulse_width_time_stats.size(), pulse_num); + $finish; + end + if (pulse_period_time_stats.size() != pulse_num) begin + $display("[ERROR] -run_test_case- Size of pulse_period_time_stats samples not equal to pulse_num: %d VS %d", pulse_period_time_stats.size(), pulse_num); + $finish; + end + if (sync_time_stats.size() != pulse_num) begin + $display("[ERROR] -run_test_case- Size of sync_time_stats samples not equal to pulse_num: %d VS %d", sync_time_stats.size(), pulse_num); + $finish; + end + + avearge_pulse_delay = 0; + foreach (pulse_delay_time_stats[i]) + avearge_pulse_delay += pulse_delay_time_stats[i]; + avearge_pulse_delay /= pulse_num; + + average_pulse_width = 0; + foreach (pulse_width_time_stats[i]) + average_pulse_width += pulse_width_time_stats[i]; + average_pulse_width /= pulse_num; + + average_pulse_period = 0; + foreach (pulse_period_time_stats[i]) + average_pulse_period += pulse_period_time_stats[i]; + average_pulse_period /= pulse_num; + + average_sync_time = 0; + foreach (sync_time_stats[i]) + average_sync_time += sync_time_stats[i]; + average_sync_time /= pulse_num; - // // Параллельный запуск длинного старта и обработки синхронизации - // 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 тактов) + if (VERBOSE >= 1) begin + $display("[TB] -run_test_case- Pulse test stats:\n\tavearge_pulse_delay: %0.3f\n\taverage_pulse_width: %0.3f\n\taverage_pulse_period: %0.3f\n\taverage_sync_time: %0.3f", avearge_pulse_delay, average_pulse_width, average_pulse_period, average_sync_time); + end - // 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 + if (avearge_pulse_delay > CLOCK_DEVIATION * CLK_ADC_PERIOD) begin + $display("[ERROR] -run_test_case- avearge_pulse_delay too big: %0.3f", avearge_pulse_delay); + error_flag = 1; + end + if (fabs(average_pulse_width - pulse_width * CLK_DAC_PERIOD * (pulse_height != ZERO_LEVEL)) > CLOCK_DEVIATION * CLK_ADC_PERIOD && sample_num * CLK_ADC_PERIOD >= pulse_width * CLK_DAC_PERIOD) begin + $display("[ERROR] -run_test_case- average_pulse_width deviates from choosen pulse_width. Deviation: %0.3f > %0.3f ns", fabs(average_pulse_width - pulse_width * CLK_DAC_PERIOD), CLOCK_DEVIATION * CLK_ADC_PERIOD); + error_flag = 1; + end + if (fabs(average_pulse_period - sample_num * CLK_ADC_PERIOD) > CLOCK_DEVIATION * CLK_ADC_PERIOD) begin + $display("[ERROR] -run_test_case- average_pulse_period deviates from choosen pulse_width. Deviation: %0.3f > %0.3f ns", fabs(average_pulse_period - sample_num * CLK_ADC_PERIOD), CLOCK_DEVIATION * CLK_ADC_PERIOD); + error_flag = 1; + end - // 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 (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Pass error processing"); + end - // if (!skip_reset) begin - // reset_dut(1); - // @(posedge clk); - // end + if (error_flag) + $finish; - // 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 - // @(negedge clk); // 180 deg. phase shift for "DAC strobing signal" - // 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 + if (VERBOSE >= 2) begin + $display("[TB] -run_test_case- Passed checks"); + end + endtask + // Таска // --- ОСНОВНОЙ ПРОЦЕСС ТЕСТИРОВАНИЯ --- initial begin $display("[TB] Tests start"); // Инициализация - dac_start = 0; + dac_start = 0; pulse_width = 0; pulse_period = 0; pulse_height = 0; pulse_num = 0; smp_num = 0; out_of_range = 0; - fork - reset_dut_adc(3); - reset_dut_dac(6); - join - @(posedge clk_dac); - @(posedge clk_adc); - set_config( - .w(50), - .p(125), - .n(5), - .h(1024), - .sn(65) + rst_adc = 0; + rst_dac = 0; + + #100; // init + + $display("[TB] Test 1. Simple test. (1/4)"); + run_test_case( + .pulse_width(50), + .pulse_period(125), + .pulse_height(2**DAC_DATA_WIDTH-1), + .pulse_num(5), + .sample_num(65), + .skip_reset(0), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 1. Simple test. (2/4)"); + run_test_case( + .pulse_width(25), + .pulse_period(125), + .pulse_height(2**(ADC_DATA_WIDTH-1)), + .pulse_num(10), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 1. Simple test. (3/4)"); + run_test_case( + .pulse_width(10), + .pulse_period(50), + .pulse_height(ZERO_LEVEL), + .pulse_num(4), + .sample_num(25), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) ); - start_dut(1); - repeat(1000) @(posedge clk_adc); + $display("[TB] Test 1. Simple test. (4/4)"); + run_test_case( + .pulse_width(25), + .pulse_period(125), + .pulse_height(2**(DAC_DATA_WIDTH-1)), + .pulse_num(10), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 1 complete"); + + $display("[TB] Test 2. Edge cases. Pulse width 0%%. (1/7)"); + run_test_case( + .pulse_width(0), + .pulse_period(125), + .pulse_height(2**(ADC_DATA_WIDTH-1)), + .pulse_num(5), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 2. Edge cases. Pulse width 100%%. (2/7)"); + run_test_case( + .pulse_width(10), + .pulse_period(10), + .pulse_height(2**(ADC_DATA_WIDTH-1)), + .pulse_num(5), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 2. Edge cases. Pulse height == ZERO_LEVEL. (3/7)"); + run_test_case( + .pulse_width(10), + .pulse_period(125), + .pulse_height(ZERO_LEVEL), + .pulse_num(5), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 2. Edge cases. Pulse num == 0. (4/7)"); + run_test_case( + .pulse_width(10), + .pulse_period(125), + .pulse_height(2**(ADC_DATA_WIDTH-3)), + .pulse_num(0), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + $display("[TB] Test 2. Edge cases. Sample num time << Pulse width time. (5/7)"); + run_test_case( + .pulse_width(10), + .pulse_period(125), + .pulse_height(2**(ADC_DATA_WIDTH-1)), + .pulse_num(5), + .sample_num(2), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(0), + .randomize_out_of_range(0) + ); + + // Ожидание окончания работы генератора. Т.к. конец работы определяется по tvalid сэмплера, а он завершается сильно раньше. Чтобы не пропустить start следующей таски, ждем + wait(dut.generator_inst.enable == 0); + #50; + + $display("[TB] Test 2. Edge cases. Sample num == 0. (6/7)"); + // Запустим в работу вручную, т.к. run_test_case обязательно ждет pulse num циклов. Детекция цикла производится по активности сэплера. Ее не должно быть при sample num = 0 + set_config( + .w(10), + .p(125), + .h(2**(ADC_DATA_WIDTH-1)), + .n(5), + .sn(0) + ); + start_dut(3); + fork + begin : wait_sampler_active_proc + @(posedge m_axis_tvalid); + $display("[ERROR] Sampler active with sample num == 0"); + $finish; + end + begin + @(negedge dut.generator_inst.enable); + end + join_any + disable wait_sampler_active_proc; + repeat(30) @(posedge clk_adc); + + // Данный тест должен приводить к тому, что сэмплер будет давать крайние значения вместо заданного pulse height из-за OTR=1 + // TODO известно что при таких настройках OTR = 1 постоянно. Легко предсказать уровень + // Дописать авто тест + $display("[TB] Test 2. Edge cases. OTR == 1. (7/7)"); + run_test_case( + .pulse_width(10), + .pulse_period(125), + .pulse_height(10), + .pulse_num(5), + .sample_num(65), + .skip_reset(1), + .randomize_start_timing(1), + .out_of_range_val(1), + .randomize_out_of_range(0) + ); + $display("[TB] Test 2 complete"); + + $display("[TB] Test 3. Random tests"); + for (int i = 0; i < 100; i++) begin + int r_w, r_p, r_n, r_h, r_sn; + bit r_skip, r_otr, r_otr_rand; + + // Генерируем параметры + r_p = $urandom_range(50, 150); // Период от 5 до 50 + r_w = $urandom_range(10, r_p); // Ширина не больше периода + r_n = $urandom_range(1, 10); // Количество импульсов + r_h = $urandom_range(0, 2**(`MIN(ADC_DATA_WIDTH, DAC_DATA_WIDTH))-1); // Высота импульса + r_sn = $urandom_range(2, 40); // Число сэмплов + r_skip = $urandom_range(0, 1); // Случайный сброс (0 - сброс, 1 - пропуск) + r_otr = $urandom_range(0, 1); // Out Of Range стартовое значение + r_otr_rand = $urandom_range(0, 1); // Сделать OTR случайным + + if (VERBOSE >= 1) + $display("[TB] --- Test #%0d (Config: W=%0d, P=%0d, N=%0d, H=%0d, SN=%0d, SkipReset=%0b) ---", + i+1, r_w, r_p, r_n, r_h, r_sn, r_skip); + + run_test_case( + .pulse_width(r_w), + .pulse_period(r_p), + .pulse_height(r_h), + .pulse_num(r_n), + .sample_num(r_sn), + .skip_reset(r_skip), + .randomize_start_timing(0), + .out_of_range_val(r_otr), + .randomize_out_of_range(r_otr_rand) + ); + + wait(dut.generator_inst.enable == 0); // Проверка на завершение работы + #50; + end + $display("[TB] Test 3 complete"); + $display("[TB] ALL PASSED"); + $display("[TB] Maximum clock deviation of stats %0.2f", CLOCK_DEVIATION); $finish; end -endmodule +endmodule \ No newline at end of file diff --git a/designs/reflectometer_base/Makefile b/designs/reflectometer_base/Makefile index e7b027c..7ecf4a0 100644 --- a/designs/reflectometer_base/Makefile +++ b/designs/reflectometer_base/Makefile @@ -20,7 +20,7 @@ include ../../scripts/vivado.mk INC_FILES += interfaces.svh -TB_FILES += reflectometer_tb.sv +TB_FILES += reflectometer_tb.sv SYN_FILES += reflectometer.sv SYN_FILES += dac_model.sv SYN_FILES += adc_model.sv diff --git a/designs/reflectometer_base/reflectometer_tb.sv b/designs/reflectometer_base/reflectometer_tb.sv index bfadf2b..e6b3820 100644 --- a/designs/reflectometer_base/reflectometer_tb.sv +++ b/designs/reflectometer_base/reflectometer_tb.sv @@ -152,8 +152,8 @@ module reflectometer_tb; // AXI-S таски task automatic axis_send_packet( - virtual axis_if.tb vif, - const ref logic [7:0] payload[] + virtual axis_if.tb vif, + logic [7:0] payload[] ); int packet_size = payload.size(); @@ -163,17 +163,18 @@ module reflectometer_tb; end foreach (payload[i]) begin - vif.drv_cb.tdata <= payload[i]; - vif.drv_cb.tvalid <= 1'b1; - vif.drv_cb.tlast <= (i == packet_size - 1); // tlast активен только на последнем байте + $display("%1", payload[i]); + // vif.drv_cb.tdata <= payload[i]; + // vif.drv_cb.tvalid <= 1'b1; + // vif.drv_cb.tlast <= (i == packet_size - 1); // tlast активен только на последнем байте - // Шагаем на следующий фронт клока, чтобы симулятор применил выходы - @(vif.drv_cb); + // // Шагаем на следующий фронт клока, чтобы симулятор применил выходы + // @(vif.drv_cb); - // Ждем на последующих фронтах, пока tready не станет равен 1. - while (vif.drv_cb.tready !== 1'b1) begin - @(vif.drv_cb); - end + // // Ждем на последующих фронтах, пока tready не станет равен 1. + // while (vif.drv_cb.tready !== 1'b1) begin + // @(vif.drv_cb); + // end end // Снимаем валидность на следующем дельта-цикле @@ -206,7 +207,7 @@ module reflectometer_tb; input logic [31:0] pulse_period_adc ); // Выделяем 17 байт: 1 байт команды + 16 байт регистров - logic [7:0] full_packet[] = new[17]; + logic [7:0] full_packet[17]; // 1. Сначала пишем команду в нулевой байт пакета full_packet[0] = 8'b10001000; // Твоя команда set_data @@ -253,11 +254,13 @@ module reflectometer_tb; $display("[TB] Tests start"); // Инициализация + rst_n = 0; + #100; rst_n = 1; wait(mmcm_locked === 1'b1); #10; - // dut_soft_reset(); + dut_soft_reset(); // dut_send_system_config( // .pulse_width(32'd350), // .pulse_period(32'd1250), diff --git a/designs/reflectometer_base/reflectometer_tb_behav.wcfg b/designs/reflectometer_base/reflectometer_tb_behav.wcfg new file mode 100644 index 0000000..b1841ec --- /dev/null +++ b/designs/reflectometer_base/reflectometer_tb_behav.wcfg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + rst_n + rst_n + + + mmcm_locked + mmcm_locked + + + Signal + label + + dac_data[13:0] + dac_data[13:0] + + + signal_voltage + signal_voltage + + + adc_data[11:0] + adc_data[11:0] + + + adc_otr + adc_otr + + + + send_request + send_request + + + request_ready + request_ready + + + Controller + label + + + clk + clk + + + rst_n + rst_n + + + tdata[7:0] + tdata[7:0] + + + tvalid + tvalid + + + tlast + tlast + + + tready + tready + + + + Accumulator + label + + + clk + clk + + + rst_n + rst_n + + + tdata[7:0] + tdata[7:0] + + + tvalid + tvalid + + + tlast + tlast + + + tready + tready + + + diff --git a/rtl/sampler/src/sampler.sv b/rtl/sampler/src/sampler.sv index 35fc7bb..ccc36b7 100644 --- a/rtl/sampler/src/sampler.sv +++ b/rtl/sampler/src/sampler.sv @@ -13,91 +13,136 @@ module sampler input [31:0] smp_num, input request, - output logic [DATA_WIDTH*PACK_FACTOR-1:0] m_axis_tdata, - output logic m_axis_tvalid, + output logic [DATA_WIDTH*PACK_FACTOR-1:0] m_axis_tdata, + output logic m_axis_tvalid, output logic done ); logic [DATA_WIDTH-1:0] data_converted; + logic out_of_range_reg; logic [31:0] smp_num_reg, cnt_smp_num; - logic synced; - logic [31:0] pack_cnt; + logic enable; - generate + generate if (PROCESS_MODE) begin always @(posedge clk_in) begin if (rst) begin data_converted <= '0; + out_of_range_reg <= 0; end else begin - if (out_of_range) begin - if (data_in[DATA_WIDTH-1]==1'b1) data_converted <= {1'b1, {(DATA_WIDTH-2){1'b0}}}; - else if (data_in[DATA_WIDTH-1]==1'b0) data_converted <= {1'b0, {(DATA_WIDTH-2){1'b1}}}; - end else begin - if (data_in == {1'b1, {(DATA_WIDTH-1){1'b0}}}) - data_converted <= data_in; - else - data_converted <= data_in[DATA_WIDTH-1] ?{1'b1, (~data_in[DATA_WIDTH-2:0] + 1'b1)}:data_in; - end + out_of_range_reg <= out_of_range; + if (data_in == {1'b1, {(DATA_WIDTH-1){1'b0}}}) + data_converted <= data_in; + else + data_converted <= data_in[DATA_WIDTH-1] ?{1'b1, (~data_in[DATA_WIDTH-2:0] + 1'b1)}:data_in; end end end else begin always @(posedge clk_in) begin if (rst) begin data_converted <= '0; - end else begin - if (out_of_range) begin - if (data_in[DATA_WIDTH-1]==1'b1) data_converted <= '1; - else if (data_in[DATA_WIDTH-1]==1'b0) data_converted <= '0; - end else begin - data_converted <= data_in; - end + out_of_range_reg <= 0; + end + else begin + out_of_range_reg <= out_of_range; + data_converted <= data_in; end end end endgenerate + + logic [DATA_WIDTH*PACK_FACTOR-1:0] buffer; + logic buffer_ready; + logic [$clog2(PACK_FACTOR):0] cnt; - always @(posedge clk_in) begin - if (rst) begin - m_axis_tdata <= '0; - m_axis_tvalid <= 0; - cnt_smp_num <= '0; - smp_num_reg <= '0; - pack_cnt <= '0; - synced <= 0; - done <= 0; - end else begin - m_axis_tvalid <= 0; - if (!synced) begin - if (done && request) begin - synced <= 1; - done <= 0; - cnt_smp_num <= 0; - smp_num_reg <= smp_num; - end else begin - done <= 1; - end - end else begin - if (cnt_smp_num != smp_num_reg) begin - cnt_smp_num <= cnt_smp_num +1; - m_axis_tdata[pack_cnt*DATA_WIDTH +: DATA_WIDTH] <= data_converted; - if (pack_cnt == PACK_FACTOR-1) begin - pack_cnt <= 0; - m_axis_tvalid <= 1; - end else begin - pack_cnt <= pack_cnt + 1; - end - end else begin + generate + if (PACK_FACTOR == 1) begin + always @(posedge clk_in) begin + if (rst) begin + buffer <= '0; + buffer_ready <= 0; cnt_smp_num <= '0; - pack_cnt <= '0; - synced <= 0; - done <= 1; + smp_num_reg <= '0; + enable <= 0; + done <= 0; + end + else begin + buffer_ready <= 0; + if (!enable) begin + if (done && request) begin + enable <= 1; + done <= 0; + cnt_smp_num <= 0; + smp_num_reg <= smp_num; + end else begin + done <= 1; + end + end else begin + if (cnt_smp_num != smp_num_reg) begin + cnt_smp_num <= cnt_smp_num +1; + buffer_ready <= 1; + // if (!out_of_range_reg) begin + buffer <= data_converted; + // end + end else begin + cnt_smp_num <= '0; + buffer_ready <= 0; + enable <= 0; + buffer <= '0; + end + end + end + end + end else begin + always @(posedge clk_in) begin + if (rst) begin + buffer <= '0; + cnt <= '0; + buffer_ready <= 0; + cnt_smp_num <= '0; + smp_num_reg <= '0; + enable <= 0; + done <= 0; + end else begin + buffer_ready <= 0; + if (!enable) begin + if (done && request) begin + enable <= 1; + done <= 0; + cnt_smp_num <= 0; + smp_num_reg <= smp_num; + end else begin + done <= 1; + end + end else begin + if (cnt_smp_num != smp_num_reg) begin + cnt_smp_num <= cnt_smp_num +1; + buffer_ready <= 1; + // if (!out_of_range_reg) begin + buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted}; + if (cnt == PACK_FACTOR-1) begin + cnt <= 0; + buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted}; + end + else begin + cnt <= cnt + 1; + end + // end + end else begin + cnt_smp_num <= '0; + buffer_ready <= 0; + enable <= 0; + cnt <= 0; + end + end end end end - end + endgenerate - + assign m_axis_tdata = buffer; + assign m_axis_tvalid = buffer_ready; endmodule