Автор: https://github.com/VSHEV92

Исходные коды:
https://github.com/pcbproj/AXI-Stream-Adder/tree/axis-adder-v1
https://github.com/pcbproj/AXI-Stream-Adder/tree/axis-adder-v2

Оглавление

  • Введение

  • Тестовое окружение для проверки сумматора

  • Настройки тестового окружения

  • Вспомогательные функции

  • Сигналы тестового окружения

  • Драйверы и мониторы AXI-Stream интерфейсов

  • Описание Scoreboard

  • Остальные компоненты окружения

  • Примеры использования окружения

  • Заключение

Введение

В предыдущей части был рассмотрен основной подход, применяемым для тестирования сложных цифровых устройств - constraint random testing. Мы узнали, как автоматизировать проверку корректности работы устройства с помощью сравнения его выходов с эталонной моделью. Тестовые окружения, работающие по такому принципу, называются self-test testbench. Мы увидели из каких компонентов строятся тестовые окружения и разработали структуру окружения для проверки сумматора с AXI-Stream интерфейсами. В этой статье мы перейдем от теории к практике и покажем, как реализовать это окружение на языке Verilog.

Тестовое окружение для проверки сумматора

Напомним структуру тестового окружения, которое мы хотим описать.

Окружение состоит из трех драйверов, два из которых выступают в роли передатчиков, а один - приемника. Задача передатчиков заключается в формировании случайных слагаемых и отправки их сумматору в соответствии с правилами AXI-Stream интерфейса. Эти два драйвера будут управлять сигналами tdata и tvalid входных интерфейсов сумматора. Драйвер-приемник должен управлять сигналом tready для выходного AXI-Stream интерфейса сумматора. Задержки на интерфейсах будут случайными в пределах настраиваемого диапазона.

Также окружение содержит три монитора, по одному на каждый AXI-Stream интерфейс. Мониторы будут непрерывно анализировать сигналы tvalid и tready и при обнаружении handshake отправлять данные на шине tdata в scoreboard.

В свою очередь scoreboard будет получать транзакции от мониторов и принимать решение о корректности выполнения суммирования. Входные слагаемые будут временно сохраняться в массивы с помощью коллекторов. После получения транзакции от выходного монитора scoreboard будет считывать слагаемые из массивов, передавать их в эталонную модель и далее отправлять эталонный результат в checker. Checker получает результаты от эталонной модели и тестируемого сумматора и выполняет их сравнение. В случае несовпадения выводится сообщение об ошибке.

Также мы будем использовать сторожевой таймер (watchdog), чтобы иметь возможность завершить тест при зависании проверяемого устройства или одного из компонентов окружения.

Перейдем, наконец, к практической части и начнем поэтапно реализовывать наше тестовое окружение на языке Verilog.

Настройки тестового окружения

Для удобства описания тестового окружения выделим некоторые его части в виде отдельных файлов. Окружение будет запускать один случайный тест, который можно будет конфигурировать с помощью различных настроек. Ниже представлено содержимое файла tb_defines.vh, который содержит все параметры теста.

`ifndef TB_DEFINES_VH
`define TB_DEFINES_VH

// разрядность шины входных слагаемых
`ifndef WIDTH
  `define WIDTH 4
`endif

// ширина входных и выходных шин AXI-Stream интерфейса
`define AXIS_IN_WIDTH $ceil($itor(`WIDTH)/8)*8
`define AXIS_OUT_WIDTH $ceil($itor(`WIDTH+1)/8)*8

// число транзакций в тесте
`ifndef TRANS_NUMBER
  `define TRANS_NUMBER 5
`endif

// начальное состояние генератора случайных чисел
`ifndef SEED
  `define SEED 0
`endif

// минимальная задержка в тактах на AXI-Stream интерфейсе
`ifndef MIN_AXIS_DELAY
  `define MIN_AXIS_DELAY 0
`endif

// максимальная задержка в тактах на AXI-Stream интерфейсе
`ifndef MAX_AXIS_DELAY
  `define MAX_AXIS_DELAY 10
`endif

// максимальное значение, генерируемое драйвером AXI-Stream интерфейса
`ifndef MAX_AXIS_VALUE
  `define MAX_AXIS_VALUE 2**`WIDTH - 1
`endif

// максимальная длительность теста в тактах
`ifndef MAX_CLK_IN_TEST
  `define MAX_CLK_IN_TEST 300
`endif

`endif

Разберем назначение каждого параметра:

  • WIDTH - разрядность входных слагаемых;

  • AXIS_IN_WIDTH - разрядность шины tdata для входных слагаемых;

  • AXIS_OUT_WIDTH - разрядность шины tdata для результата суммирования;

  • TRANS_NUMBER - число суммирований, которое будет выполнено в тесте. Используется для завершения теста;

  • SEED - параметр, задающий начальное состояние генераторов случайных чисел. Изменяя это значение, можно формировать разные последовательности случайных воздействий;

  • MIN_AXIS_DELAY - минимальная задержка в тактах перед установкой сигналов tvalid или tready в AXI-Stream интерфейсе;

  • MAX_AXIS_DELAY - максимальная задержка в тактах перед установкой сигналов tvalid или tready в AXI-Stream интерфейсе;

  • MAX_AXIS_VALUE - максимальное значение, которое может появится на шине tdata. Используется для ограничения значений для входных слагаемых. По умолчанию определяется, исходя из значения WIDTH;

  • MAX_CLK_IN_TEST - максимальная длительность теста в тактах. Используется для настройки watchdog.

Отметим, что почти все настройки объявлены внутри конструкции ifndef ... endif. Это сделано для того, чтобы при запуске теста их значения можно было переопределять. Например, если в качестве симулятора используется Icarus Verilog, то добавив в команду запуска конструкцию -D TRANS_NUMBER=15, мы получим тест, который завершится после выполнения 15 суммирований. Если при запуске теста значение для какого-либо параметра не задано, то будет использовано значение по умолчанию, определенное в файле tb_defines.vh. Например, для числа суммирований TRANS_NUMBER значение по умолчанию равно 5. Исключением являются параметры AXIS_IN_WIDTH и AXIS_OUT_WIDTH, которые напрямую зависят от значения WIDTH и не задаются вручную.

В начале файла также присутствуют следующие две строки:

`ifndef TB_DEFINES_VH
`define TB_DEFINES_VH

Это известная в программировании конструкция, которая называется include guard. Она служит для защиты от ошибок множественного определения при многократном выполнении директивы include для одного и того же файла.

Вспомогательные функции

Создадим файл с именем tb_tasks.vh, в котором объявим некоторые вспомогательные процедуры. Файл будет начинаться с include guard и включать в себя настройки теста из tb_defines.vh.

`ifndef TB_TASKS_VH
`define TB_TASKS_VH

`include "tb_defines.vh"

С помощью отдельной процедуры gold_adder опишем эталонную модель сумматора. Модель будет принимать входные слагаемые data1_i и data2_i и вычислять результат суммирования data_o. Ширина шина tdata из-за требований AXI-Stream интерфейса (кратность восьми битам) может превышать значение WIDTH, поэтому перед вычислением суммы выделяем из слагаемых младшие WIDTH бит:

// эталонный сумматор
task gold_adder(input integer data1_i, input integer data2_i, output integer data_o);
  integer in_1, in_2;
  begin
    in_1 = data1_i[`WIDTH-1:0];
    in_2 = data2_i[`WIDTH-1:0];
    data_o = in_1 + in_2;
  end
endtask

Также в виде отдельной процедуры с именем compare реализуем checker. Процедура принимает входные слагаемые data1_i и data2_i от коллекторов, пропускает их через модель gold_adder и получает эталонный результат gold_out. Далее этот эталон сравнивается с выходом сумматора data_o. Если значения не совпадают, то выводится сообщение об ошибке. Также на вход процедуры поступает однобитный сигнал error_flag, который служит индикатором наличия ошибок в процессе работы теста. Если checker обнаруживает несовпадение данных, то сигнал error_flag устанавливается в единицу и остается в этом состоянии до конца теста.

// сравнение с эталонной моделью
task compare(input integer data1_i, input integer data2_i, input integer data_o, inout reg error_flag);
  integer gold_out, dut_out;
  begin
    gold_adder(data1_i, data2_i, gold_out);
    dut_out = data_o[`WIDTH:0];
    // вывод на экран и установка флага ошибки
    if (gold_out != dut_out) begin
      $display("ERROR! Data mismatch! input 1: %0d, input 2: %0d, output: %0d, gold: %0d, time: %0t", data1_i, data2_i, dut_out, gold_out, $time);
      error_flag = 1'b1;
    end
  end
endtask

Последняя процедура check_finish отвечает за завершение теста и вывод отчета о его результатах. На вход поступает число выполненных суммирований trans_cnt, запланированное число суммирований trans_number и сигнал наличия ошибок error_flag. Если число выполненных суммирований совпадает с числом запланированных (trans_cnt == trans_number), то тест завершается с помощью функции $finish. Перед этим проверяется значение сигнала error_flag. Если оно равно единице, то это означает, что во время выполнения теста были обнаружены ошибки, поэтому выводится сообщение TEST FAILED!. Иначе выводится сообщение TEST PASSED!.

// проверка числа обработанных сложений и завершение теста
task check_finish(input integer trans_cnt, input integer trans_number, input reg error_flag);
  begin
    if (trans_cnt == trans_number) begin
      if (error_flag) begin
        $display("----------------------");
        $display("---- TEST FAILED! ----");
        $display("----------------------");
      end else begin
        $display("----------------------");
        $display("---- TEST PASSED! ----");
        $display("----------------------");
      end
      $finish;
    end
  end
endtask

Сигналы тестового окружения

Разобравшись со вспомогательными файлами, начнем описывать основные части тестового окружения. Для начала включим в окружение вспомогательные файлы:

module adder_axis_tb ();

  `include "tb_defines.vh"
  `include "tb_tasks.vh"

Далее объявим входные и выходные сигналы сумматора:

  // тактовый сигнал и сигнал сброса
  reg aclk = 1'b0;
  reg aresetn = 1'b0;

  // сигналы для AXI-Stream интерфейсов
  reg  data1_i_tvalid, data2_i_tvalid, data_o_tready;
  wire data1_i_tready, data2_i_tready, data_o_tvalid;

  // слагаемые и результат суммы
  reg  [ `AXIS_IN_WIDTH-1:0]  data1_i_tdata;
  reg  [ `AXIS_IN_WIDTH-1:0]  data2_i_tdata;
  wire [ `AXIS_OUT_WIDTH-1:0] data_o_tdata;

Объявим массивы, в которые коллекторы будут складывать входные слагаемые. Длина массивов определяется через запланированное число суммирований (TRANS_NUMBER):

  // массивы для сохранения входных слагаемых
  reg [`WIDTH-1:0] axis_data1 [0:`TRANS_NUMBER];
  reg [`WIDTH-1:0] axis_data2 [0:`TRANS_NUMBER];

Создадим несколько счетчиков. Сигналы axis_data1_cnt и axis_data2_cnt подсчитывают число входных слагаемых и используются для индексирования в массивах коллекторов. Счетчик числа суммирований trans_cnt передается в scoreboard для проверки условия завершения теста.

  // счетчики числа слагаемых и результатов суммы
  integer unsigned axis_data1_cnt = 0;
  integer unsigned axis_data2_cnt = 0;
  integer unsigned trans_cnt = 0;

Отдельно объявим переменную seed для задания начального состояния генераторов случайных чисел. Функция $urandom() требует в качестве аргумента целочисленную переменную. Мы не можем в $urandom() напрямую передавать константу ``SEEDиз файла **tb_defines.vh**. Для этих целей будет использоваться переменнаяseed`.

  // начальное состояние генератора случайных чисел
  integer seed = `SEED;

В Verilog все блоки initial и always, а также непрерывные присваивания assign, выполняются одновременно и параллельно друг относительно друга. Для обеспечения cинхронизации этих процессов в Verilog используются события (events). В нашем окружении с помощью events будет координироваться совместная работа мониторов и scoreboard:

  // события handshake на AXI-Stream интерфейсах
  event data1_i_e, data2_i_e, data_o_e;

Объявим однобитный сигнал, указывающий на появление ошибок в процессе выполнения теста, и инициализируем его нулевым значением:

  // флаг наличия ошибок в тесте
  reg error_flag = 1'b0;

Наконец, добавим в окружение наш сумматор и подключим к его портам все необходимые сигналы:

// проверяемый модуль
  adder_axis_pipe #(
      .ADDER_WIDTH(`WIDTH)
  ) dut (
      .aclk          (aclk),
      .aresetn       (aresetn),
      .data1_i_tdata (data1_i_tdata),
      .data1_i_tvalid(data1_i_tvalid),
      .data1_i_tready(data1_i_tready),
      .data2_i_tdata (data2_i_tdata),
      .data2_i_tvalid(data2_i_tvalid),
      .data2_i_tready(data2_i_tready),
      .data_o_tdata  (data_o_tdata),
      .data_o_tvalid (data_o_tvalid),
      .data_o_tready (data_o_tready)
  );

Драйверы и мониторы AXI-Stream интерфейсов

Для начала рассмотрим реализацию драйверов для входных интерфейсов сумматора. В нашем окружении должно быть два драйвера, которые выступают в роли передатчиков. Их задача заключается в создании входных слагаемых и отправки их сумматору. Каждый драйвер должен формировать данные для шины tdata и управлять сигналом валидности tvalid. Чтобы обнаружить момент возникновения handshake, драйвер должен следить за сигналом tready.

Ниже представлено описание драйвера для первого входного интерфейса сумматора:

  // драйвер для data1_i AXI-Stream интерфейса
  initial begin
    // ожидаем выхода из состояния сброса
    @(posedge aresetn);
  
    while (1) begin
      data1_i_tvalid <= 1'b0;
      // выполняем задержку на случайное число тактов
      repeat ($urandom(seed) % (`MAX_AXIS_DELAY + 1) + `MIN_AXIS_DELAY) @(posedge aclk);
      // выставляем сигнал tvalid и данные
      data1_i_tdata  <= $urandom(seed) % (`MAX_AXIS_VALUE + 1);
      data1_i_tvalid <= 1'b1;
      @(posedge aclk);
      // ожидаем сигнал tready для handshake
      while (!data1_i_tready) @(posedge aclk);
    end
  end

Драйвер реализован внутри блока initial. В начале теста сумматор будет находиться в состоянии сброса. Перед тем, как подавать на него транзакции, мы ожидаем установки сигнала сброса в неактивный единичный уровень (@(posedge aresetn)). Далее драйвер в бесконечном цикле (while (1)) начинает формировать случайные слагаемые и отправлять их сумматору.

В начале драйвер не имеет транзакций для сумматора, поэтому сигнал валидности data1_i_tvalid устанавливается в нулевое значение. С помощью функции $urandom() мы получаем целое число в диапазоне от MIN_AXIS_DELAY до MAX_AXIS_DELAY и используем его для формирования случайной задержки. Для этих целей мы используем цикл repeat, внутри которого ожидаем заданное число фронтов тактового сигнала @(posedge aclk).

После этого мы генерируем случайные данные data1_i_tdata, выставляем сигнал валидности data1_i_tvalid в единичное значение и ждем появления фронта сигнала aclk. Если сигнал готовности data1_i_tready равен единице, то это значит, что на шине произошел handshake. Сумматор получил транзакцию и мы можем повторить весь цикл заново. Иначе с помощью цикла while на каждом такте @(posedge aclk) мы проверяем, произошел ли handshake. Цикл while выполняется до тех пор, пока сигнал data1_i_tready не примет единичное значение.

Драйвер для второго входного интерфейса имеет тот же вид, за исключением того, что теперь мы используем сигналы data2_i_tdata, data2_i_tvalid и data2_i_tready. Его описание представлено ниже:

  // драйвер для data2_i AXI-Stream интерфейса
  initial begin
    // ожидаем выхода из состояния сброса
    @(posedge aresetn);

    while (1) begin
      data2_i_tvalid <= 1'b0;
      // выполняем задержку на случайное число тактов
      repeat ($urandom(seed) % (`MAX_AXIS_DELAY + 1) + `MIN_AXIS_DELAY) @(posedge aclk);
      // выставляем сигнал tvalid и данные
      data2_i_tdata  <= $urandom(seed) % (`MAX_AXIS_VALUE + 1);
      data2_i_tvalid <= 1'b1;
      @(posedge aclk);
      // ожидаем сигнал tready для handshake
      while (!data2_i_tready) @(posedge aclk);
    end
  end

Теперь рассмотрим, как устроен драйвер для выходного интерфейса сумматора. В этом случае драйвер выступает в качестве приемника данных, и его задача заключается в управлении сигналом tready. В соответствии с правилами AXI-Stream интерфейса сигнал tready не должен зависеть от сигнала tvalid и может изменять свое значение на каждом такте сигнала aclk.

Описание драйвера для выходного интерфейса сумматора представлено ниже:

  // драйвер для data_o AXI-Stream интерфейса
  initial begin
    // ожидаем выхода из состояния сброса
    @(posedge aresetn);
    
    while (1) begin
      // сбрасываем сигнал tready
      data_o_tready <= 1'b0;
      // выполняем задержку на случайное число тактов
      repeat($urandom(seed) % (`MAX_AXIS_DELAY + 1) + `MIN_AXIS_DELAY)
        @(posedge aclk);
      // выставляем сигнал tready
      data_o_tready <= 1'b1;
      @(posedge aclk);
      // опять выполняем задержку на случайное число тактов
      repeat($urandom(seed) % (`MAX_AXIS_DELAY + 1) + `MIN_AXIS_DELAY)
        @(posedge aclk);
    end
  end

Мы ждем выхода из состояния сброса (@(posedge aresetn)), после чего в бесконечном цикле (while (1)) на каждом такте формируем значение сигнала data_o_tready. Сначала задаем нулевое значение и с помощью функции $urandom() и цикла repeat удерживаем сигнал data_o_tready в этом состоянии в течение случайного числа тактов. Далее присваиваем единичное значение и опять выполняем задержку на случайное число тактов. После этого цикл повторяется.

Разобравшись с драйверами, перейдем к описанию мониторов. Монитор должен наблюдать за сигналами интерфейса и определять моменты времени, когда происходит handshake. Для этого на каждом такте отслеживаются значения сигналов tvalid и tready. Handshake наступает, когда оба сигнала равны единице. Описание всех мониторов представлено ниже:

  // монитор для data1_i AXI-Stream интерфейса
  always begin
    @(posedge aclk);
    if (data1_i_tready && data1_i_tvalid) -> data1_i_e;
  end

  // монитор для data2_i AXI-Stream интерфейса
  always begin
    @(posedge aclk);
    if (data2_i_tready && data2_i_tvalid) -> data2_i_e;
  end

    // монитор для data_o AXI-Stream интерфейса
  always begin
    @(posedge aclk);
    if (data_o_tready && data_o_tvalid) -> data_o_e;
  end

Для примера, рассмотрим, монитор для выходного интерфейса. Внутри блока always на каждом такте (@(posedge aclk)) проверяется условие, что сигналы data_o_tready и data_o_tvalid принимают единичное значение. При его выполнении с помощью оператора -> запускается событие data_o_e. Так монитор сообщает scoreboard о появлении транзакции на выходном AXI-Stream интерфейсе сумматора.

Описание Scoreboard

Теперь рассмотрим реализацию самого крупного компонента окружения - scoreboard. Он состоит из нескольких частей: коллекторы, эталонная модель и checker. Описание коллектора для слагаемых на первом входном интерфейсе сумматора показано ниже:

  always begin
      @(data1_i_e);
      axis_data1[axis_data1_cnt] = data1_i_tdata;
      axis_data1_cnt = axis_data1_cnt + 1;
  end

В блоке always мы ожидаем срабатывания event (@(data1_i_e)). Его появление означает, что в текущий момент времени на интерфейсе происходит handshake. Поэтому мы берем значение на шине data1_i_tdata и сохраняем его в массив коллектора axis_data1. После этого мы увеличиваем счетчик полученных слагаемых (axis_data2_cnt) на единицу и ждем следующего срабатывания события data1_i_e.

Коллектор для второго входного интерфейса работает таким же образом:

  always begin
      @(data2_i_e);
      axis_data2[axis_data2_cnt] = data2_i_tdata;
      axis_data2_cnt = axis_data2_cnt + 1;
  end

Оставшаяся часть scoreboard, состоящая из эталонной модели и checker, реализуется с помощью еще одного блока always:

  always begin
      @(data_o_e);
      compare(axis_data1[trans_cnt], axis_data2[trans_cnt], data_o_tdata, error_flag);
      trans_cnt = trans_cnt + 1;
      check_finish(trans_cnt, `TRANS_NUMBER, error_flag);
  end

Мы ожидаем событие data_o_e, срабатывание которого указывает на появление транзакции на выходном AXI-Stream интерфейсе. Это в свою очередь означает, что ранее уже были получены входные слагаемые, которые сейчас находятся в массивах коллекторов.

Далее мы вызываем процедуру compare, в которую передаем слагаемые из коллекторов axis_data1[trans_cnt] и axis_data2[trans_cnt], результат сложения на выходе сумматора (data_o_tdata) и сигнал наличия ошибок error_flag. Эта процедура вызывает эталонную модель и выполняет сравнение. В случае несовпадения результатов выводится сообщение об ошибке и значение сигнала error_flag устанавливается в единицу. После этого мы увеличиваем счетчик выполненных суммирований (trans_cnt) и проверяем, можно ли завершить тест. Когда число выполненных суммирований trans_cnt станет равным TRANS_NUMBER, тест завершается и выводится отчет о его выполнении. Если сигнал error_flag равен нулю, то тест пройден успешно, если единице - то нет.

Остальные компоненты окружения

Для завершения описания окружения нам необходимо добавить еще несколько компонентов. С помощью блоков always и initial сформируем тактовый сигнал и сигнал сброса:

  // создание тактового сигнала
  always #5 aclk = ~aclk;

  // создание сигнала сброса
  initial begin
    repeat (10) @(posedge aclk);
    aresetn <= 1'b1;
  end

Включим в окружение сторожевой таймер. Для этого в блоке initial с помощью цикла repeat будем ожидать появления заданного числа фронтов тактового сигнала (@(posedge aclk)). Если моделирование выполняется уже на протяжении MAX_CLK_IN_TEST тактов, то мы считаем, что тест завис. Сторожевой таймер завершает тест, и выводится соответствующее сообщение об ошибке.

  // сторожевой таймер для отслеживания зависания теста
  initial begin
    repeat(`MAX_CLK_IN_TEST) @(posedge aclk);
    $display("ERROR! Watchdog error!");
    $display("----------------------");
    $display("---- TEST FAILED! ----");
    $display("----------------------");
    $finish;
  end

Наконец, добавим последний блок inital, отвечающий за дамп временных диаграмм в формате VCD.

  // дамп waveforms в VCD файл
  initial begin
    $dumpfile("wave_dump.vcd");
    $dumpvars(0);
  end

Примеры использования окружения

Наше тестовое окружение готово к использованию. Давайте проверим его работу. Для начала рассмотрим, как оно детектирует различные ошибки. Для этого изменим описание сумматора таким образом, чтобы входной регистр для второго слагаемого работал некорректно и всегда содержал нулевое значение. Запускаем тест, который должен выполнить 5 суммирований, и видим следующие временные диаграммы:

На входы сумматора поступают случайные слагаемые, однако результат суммы всегда совпадает со значением первого слагаемого. Ниже представлены сообщения от симулятора:

Можно увидеть, что окружение обнаружило 5 несовпадений. В сообщениях указаны входные слагаемые, эталонный и фактический результаты сложения, а также момент времени, когда обнаружена ошибка. Также выводится сообщение о том, что тест не пройден.

Теперь внесем в сумматор другую ошибку. Пусть мы забыли подключить к выходному порту data_o_tvalid сумматора сигнал валидности данных от блока управления. После запуска теста получим следующую временную диаграмму:

Драйверы генерируют множество входных транзакций, но сигнал data_o_tvalid всегда находится в z-состоянии. Из-за этого монитор не видит ни одной транзакции на выходе сумматора и ничего не передает в scoreboard. Тот в свою очередь никогда не получит запланированное число транзакций (TRANS_NUMBER) и не завершит тест. Однако, тест не будет выполняться бесконечно долго, так как сторожевой таймер обнаружит зависание и прервет моделирование. В результате мы получим следующее сообщение от симулятора:

Наконец, уберем все ошибки из сумматора и запустим моделирование. Глядя на временные диаграммы, мы можем увидеть, что сложение выполняется правильно:

Однако, чтобы понять, что сумматор работает корректно, не обязательно просматривать все временные диаграммы. Достаточно просто прочитать сообщение симулятора о результатах теста:

Заключение

Мы реализовали на языке Verilog тестовое окружение, структура которого была разработана в предыдущей статье. Окружение построено по принципу self-test testbench и выполняет проверку сумматора с помощью consraint-random testing. Однако, у нас есть еще широкий простор для его улучшения.

Во-первых, при описании драйверов и мониторов мы получили большой объем дублированного кода. Это всегда приводит к сложностям при дальнейшей поддержке и модификации окружения. Во-вторых, для задания параметров теста мы использовали директивы define. Это не самый лучший подход, так как при изменении настроек теста, нам придется заново компилировать все окружение. В случае больших проектов перекомпиляция исходников может занимать достаточно много времени. Есть более удобный способ настройки окружения. Мы исправим эти недостатки и получим окончательный вариант тестового окружения в следующей статье.

Комментарии (0)