From 8e2514e89578f67641a9a9edd348cfb407ba7d5f Mon Sep 17 00:00:00 2001 From: Nikolay Puzanov Date: Sat, 9 Jul 2022 12:34:40 +0300 Subject: [PATCH] Add examples. Add more info to README --- README.md | 117 +++++++++++++----- examples/simple-counter/simple_counter.sv | 37 ++++++ examples/simple-counter/simple_counter_tb.sv | 67 ++++++++++ .../simple-counter/simple_counter_tb.utest | 44 +++++++ .../simple_counter_tb_single.utest | 12 ++ .../simple_counter_tb_workpermanent.utest | 25 ++++ examples/simple-counter/utest.vh | 15 +++ 7 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 examples/simple-counter/simple_counter.sv create mode 100644 examples/simple-counter/simple_counter_tb.sv create mode 100644 examples/simple-counter/simple_counter_tb.utest create mode 100644 examples/simple-counter/simple_counter_tb_single.utest create mode 100644 examples/simple-counter/simple_counter_tb_workpermanent.utest create mode 100644 examples/simple-counter/utest.vh diff --git a/README.md b/README.md index ba4e5f8..e1ba37e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,10 @@ UTest UTest - приложение для одиночного или группового запуска и контроля выполнения тестбенчей на языке Verilog/SystemVerilog. На данный момент реализована поддержка только [Icarus Verilog](http://iverilog.icarus.com/), но в дальнейшем -я планирую добавить поддержку [Verilator](https://www.veripool.org/verilator/) и [SymbiYosys](https://github.com/YosysHQ/sby). +планирую добавить поддержку [Verilator](https://www.veripool.org/verilator/) и [SymbiYosys](https://github.com/YosysHQ/sby). + +Параметры командной строки +-------------------------- Если вызвать приложение с параметром `--help`, она выведет текст справки с описанием параметров: ``` @@ -14,7 +17,7 @@ if PATH is not specified. If argument is a file, testbench is launched from FILE Options: -k, --keep Do not delete work directory if test is pass. -d, --dump Force dump waveforms. - -r, --no-restart Do not restart testbench with waveform dump enabled if + -r, --norestart Do not restart testbench with waveform dump enabled if test failed (true by default) -n, --nocolor Do not use color for print log -j, --jobs NUM Use NUM threads for running testbenches. If <=0 @@ -26,22 +29,74 @@ Options: -h, --help Print this message and exit ``` -Программа рекурсивно ищет все файлы с расширением `*.utest` и использует их в качестве сценариев запуска тестбенчей. -Если параметров указать файл, то будет использован он. +При успешном завершении теста программа по удаляет рабочую папку. Запретить это делать можно опцией `--keep`. + +По умолчанию, для ускорения прохождения тестов создание дампов сигналов отключено. При этом, в случае неудачного +завершения тест автоматически перезапускается с созданием дампа. Однако, если тест длительный, потеря времени на +перезапуск может перекрыть выигрыш от несоздания дампа. Для управления этим поведением в программе есть две +опции - `--dump` и `--norestart`. Опция `--dump` заставляет программу делать дампы для всех тестов, как удачных, так +и неудачных, что позволяет избежать перезапуска для создания дампа. Опция `--no-restart` запрещает перезапуск неудачных +тестов, что позволяет максимально сэкономить время, но оставляет без дампов. + +Чтобы старые рабочие папки не засоряли файловую систему, их можно удалить запуском программы с опцией `--clean`. +Программа удалит все папки, для которых есть сценарий тестбенча. Если файла сценария больше нет, то можно воспользоваться +опцией `--force-clean`. + +По умолчанию программа запускает тесты в нескольких потоках, количество которых зависит от числа процессоров в системе. +Опцией `--jobs` можно управлять количеством потоков - от одного и более. + +Параметр `--verbose` управляет количеством информации, которая будет выведена на экран в процессе тестирования. Без неё +для успешных тестов будет выведена только важные сообщения. С этой опцией для всех тестов будет показан полный лог +вызова компилятора и симулятора, а так же все строки, которые будут выведены тестбенчем. При этом, независимо от этой опции +весь вывод тестбенчей будет сохранён в логи в рабочих папках (если указано опция `--keep` или тест завершился неудачей). + +Опция `--nocolor` отключает раскраску вывода. Это может быть полезно в случае перенаправления вывода в файл. + +Опция `--defines` выводит на экран исходник include-файла с макросами вывода информационных сообщений в тестбенче на Verilog: + +```verilog +`ifndef UTEST_VERILOG_DEFINES + `define UTEST_VERILOG_DEFINES + +// Log level string prefixes for use with $display function. +// Example usage: $display("%sError message", `LOG_ERR); + `define LOG_INFO "INFO#" + `define LOG_WARN "WARN#" + `define LOG_ERR "FAIL#" + +// Dirty hacked redefine of $display function. Must be used with two parentheses. +// Example usage: `log_info(("Information message")); + `define log_info(msg) begin $display({`LOG_INFO, $sformatf msg}); end + `define log_warn(msg) begin $display({`LOG_WARN, $sformatf msg}); end + `define log_error(msg) begin $display({`LOG_ERR, $sformatf msg}); end +`endif +``` + +Эти макросы можно применять в тестбенче на Verilog для вывода информационных сообщений, в частности для оповещения +UTest об ошибке в симуляции. К сожалению, в Icarus Verilog это пока единственная возможность сообщить об ошибке в тесте, +т.к. при вызове функций `$error` и `$fatal` симулятор возвращает нулевой exit code. В следующей версии авторы обещают +исправить это досадное недоразумение. + +Сценарии запуска тестбенчей +--------------------------- + +Программа рекурсивно ищет все файлы с расширением `*.utest` в папке `PATH` и использует их в качестве сценариев запуска +тестбенчей. Если параметром указать файл, то будет использован только этот файл. Если запустить программу без параметров, +она будет искать сценарии в текущей папке. Сценарии запуска тестбенчей описываются на языке Scheme (а именно [Guile](https://www.gnu.org/software/guile/), на котором и написана программа), и представляют из себя скрипт, который возвращает функцию (или список функций). Эта функция (функции) будет вызвана в процессе запуска тестов, и в зависимости от результатов её выполнения тест будет помечен как -успешный или неуспешный. Функция вызывается в контексте приложения, по этому ёй доступны все переменные и функции, +успешный или неуспешный. Функция вызывается в контексте приложения, по этому ей доступны все переменные и функции, объявленные в коде приложения. Функция возвращает булево значение, и имеет один опциональный аргумент типа symbol. Если функция вызвана без аргументов, она должна выполнить тестбенч и вернуть `#true` или `#false` в зависимости от результата его выполнения. Если функции передается агрумент, то она должна вернуть некоторые метаданные в зависимости -от значения аргумента, или `#false` если таких метаданных нет. Сейчас использутся два типа метаданных - название теста +от значения аргумента, или `#false` если таких метаданных нет. Сейчас используется два типа метаданных - название теста (агрумент `'name`) и описание теста (аргумент `'description`). -Для того, чтобы не писать вручную диспетчеризацию агрументов в приложении есть макрос `utest/tb`, который упрощает +Для того, чтобы не писать вручную диспетчеризацию аргументов в приложении есть макрос `utest/tb`, который упрощает описание сценариев: ```scheme @@ -55,7 +110,9 @@ Options: ``` После имени макроса в скобках указывается имя тестбенча и произвольное количество строк описания. Если аргументов нет, -тестбенч будет помечен как `noname`. +тестбенч будет помечен как `noname`. Имя тестбенча используется для именования рабочей папки, при этом пробелы заменяются +на подчеркивания, а заглавные буквы на прописные. Желательно не употреблять в имени тестбенча специальные символы и не +использовать слишком длинные имена. После скобочек с именем и описанием следует тело тестбенча. Тело - это любой валидный код на Guile, который будет выполнен в процессе запуска тестбенчей. Результат работы этого кода должно быть булево значение, показывающее успешность выполнения @@ -68,6 +125,12 @@ Options: тип не указать, сообщение будет выведено только в режиме `--verbose`. Независимо от типа сообщения и флага `--verbose` все сообщения будут сохранены в логе в рабочей папке теста. +Тесты можно собрать в список для запуска нескольких вариантов с разными параметрами, дефайнами, исходниками или другими +опциями. + +В теле тестбенча в общем случае можно выполнять любые действия, не только запуск симуляции RTL. Но т.к. программа была +написана предназначена для симуляции RTL, в ней есть некоторые полезные для этого функции. + Запуск симуляции в Icarus Verilog выполняется с помощью функции `utest/run-simulation-iverilog`. Функция принимает два обязательных параметра и несколько опциональных: @@ -96,7 +159,8 @@ Options: ``` -*Все агрументы, кроме `parameters` и `defines`, которые принимают список, так же могут принимать и одиночные значения.* +*Все аргументы, кроме `parameters` и `defines`, которые принимают список, так же могут принимать и одиночные значения. +В список `includes` по-умолчанию включена папка с файлом сценария* Функция возвращает `#true` в случае успешного завершения теста, и `#false` в случае ошибки. Далее приведен пример использования: @@ -130,27 +194,22 @@ Options: В случае неудачного завершения теста тест перезапускается с дампом всех сигналов в файл (это поведение можно отключить опцией `--no-restart`). Кроме того, в случае ошибки будет выведен полный лог симулятора и его параметры. -Опция `--defines` выводит на экран исходник include-файла с макросами вывода информационных сообщений в тестбенче на Verilog: +Все относительные пути в аргументах функции `utest/run-simulation-iverilog` считаются относительными папке со сценарием. -```verilog -`ifndef UTEST_VERILOG_DEFINES - `define UTEST_VERILOG_DEFINES +Тестбенч Verilog +---------------- -// Log level string prefixes for use with $display function. -// Example usage: $display("%sError message", `LOG_ERR); - `define LOG_INFO "INFO#" - `define LOG_WARN "WARN#" - `define LOG_ERR "FAIL#" +Тестбенч на Verilog ни чем не отличается от самого обычного тестбенча, кроме того, что он должен сигнализировать об ошибке +с помощью вывода информационного сообщения `log_error((...))`. -// Dirty hacked redefine of $display function. Must be used with two parentheses. -// Example usage: `log_info(("Information message")); - `define log_info(msg) begin $display({`LOG_INFO, $sformatf msg}); end - `define log_warn(msg) begin $display({`LOG_WARN, $sformatf msg}); end - `define log_error(msg) begin $display({`LOG_ERR, $sformatf msg}); end -`endif -``` +Для удобства работы программа предопределяет два макроопределения: -С помощью макроса `log_error` можно сообщить в UTest, что во время симуляции произошла ошибка. К сожалению, -в Icarus Verilog это пока единственная возможность сообщить об ошибке в тесте, т.к. при вызове функций `$error` -и `$fatal` симулятор возвращает нулевой exit code. В следующей версии авторы обещают исправить это досадное -недоразумение. +- `UTEST_BASE_DIR` - макрос с путём к папке, в которой находится сценарий запуска. Может быть нужен, например для указания + на файл с входными данными для теста. +- `UTEST_WORK_DIR` - путь ко временной рабочей папке теста. Сюда можно сохранить результаты тестбенча для последующей + проверки в коде сценария. + +Примеры +------- + +В папке [examples](examples) находятся примеры сценариев и тестбенча. diff --git a/examples/simple-counter/simple_counter.sv b/examples/simple-counter/simple_counter.sv new file mode 100644 index 0000000..10f8079 --- /dev/null +++ b/examples/simple-counter/simple_counter.sv @@ -0,0 +1,37 @@ +`timescale 1ps/1ps +`default_nettype none + +module simple_counter #(parameter COUNT = 16, + localparam WIDTH = $clog2(COUNT-1)) // <-- ERROR + (input wire clock, + input wire reset, + + input wire i_inc, + input wire i_dec, + + output reg [WIDTH-1:0] o_count); + + logic [WIDTH-1:0] count_next; + + always_comb + case ({i_inc, i_dec}) + 2'b01: + if (o_count == '0) + count_next = WIDTH'(COUNT-1); + else + count_next = o_count - 1'b1; + + 2'b10: + if (o_count == WIDTH'(COUNT-1)) + count_next = '0; + else + count_next = o_count + 1'b1; + + default: count_next = o_count; + endcase + + always_ff @(posedge clock) + if (reset) o_count <= '0; + else o_count <= count_next; + +endmodule // simple_counter diff --git a/examples/simple-counter/simple_counter_tb.sv b/examples/simple-counter/simple_counter_tb.sv new file mode 100644 index 0000000..6a7333e --- /dev/null +++ b/examples/simple-counter/simple_counter_tb.sv @@ -0,0 +1,67 @@ +`timescale 1ps/1ps + +`include "utest.vh" + +module simple_counter_tb; + logic clock = 1'b0; + logic reset = 1'b1; + + always #(10ns/2) clock = ~clock; + + parameter COUNT = 16; + parameter ITERATIONS = 100; + parameter DIRECTION = 1; // 1 - increment, -1 - decrement, 0 - random + + localparam WIDTH = $clog2(COUNT-1); // <-- ERROR + + logic i_inc; + logic i_dec; + logic [WIDTH-1:0] o_count; + + simple_counter #(.COUNT(COUNT)) + DUT (.*); + + int gold_count; + + //// Shows that a `UTEST_BASE_DIR define exists + // initial begin + // `log_info(("From verilog code. Base dir: %s", `UTEST_BASE_DIR)); + // end + + initial begin + i_inc = 1'b0; + i_dec = 1'b0; + reset = 1'b1; + repeat(2) @(posedge clock) #1; + + reset = 1'b0; + @(posedge clock) #1; + + gold_count = '0; + + for (int i = 0; i < ITERATIONS; i += 1) begin + case (DIRECTION) + -1: {i_inc, i_dec} = 2'b01; + 1: {i_inc, i_dec} = 2'b10; + default: {i_inc, i_dec} = 2'($urandom); + endcase + + @(posedge clock) #1; + + if (i_inc && !i_dec) + gold_count ++; + else if (!i_inc && i_dec) + gold_count --; + + if (gold_count >= COUNT) gold_count = 0; + if (gold_count < 0) gold_count = COUNT-1; + + if (gold_count != int'(o_count)) + `log_error(("#%0t: Gold count = %0d, DUT count = %0d", $time, gold_count, o_count)); + end + + repeat(2) @(posedge clock) #1; + $finish; + end + +endmodule // simple_counter_tb diff --git a/examples/simple-counter/simple_counter_tb.utest b/examples/simple-counter/simple_counter_tb.utest new file mode 100644 index 0000000..7ce5af6 --- /dev/null +++ b/examples/simple-counter/simple_counter_tb.utest @@ -0,0 +1,44 @@ +;; -*- scheme -*- + +;;; Make lists combinations +;;; Example: (combinations '(1 2 3) '(a b)) -> '((1 a) (1 b) (2 a) (2 b) (3 a) (3 b)) +(define (combinations . lists) + (cond + ((null? lists) '()) + ((null? (cdr lists)) (car lists)) + (else + (fold (lambda (comb out) + (append out + (map (lambda (x) + (if (list? comb) + (cons x comb) + (list x comb))) + (car lists)))) + '() (apply combinations (cdr lists)))))) + +;;; Testbenches +(map + (lambda (l) + (let ((count (car l)) + (direction (cadr l))) + (utest/tb + ((format "c~a_d~a" count direction) + "More complex testbench for Simple Counter" + (format "COUNT=~a\tDIRECTION=~a" count direction)) + + ;; Instead of a description, you can display the message in log + ;; (utest/log 'info "COUNT = ~a" count) + + ;; testbench body + (utest/run-simulation-iverilog + (utest/find-files ".*\\.sv$") + "simple_counter_tb" + #:parameters `((COUNT ,count) + (ITERATIONS ,(* count 3)) + (DIRECTION ,direction)))))) + + (combinations + (append '(10 100 1000 16 64 256) + (let ((state (seed->random-state 0))) + (map (lambda (x) (+ 2 (random 200 state))) (iota 100)))) + '(1 -1 0))) diff --git a/examples/simple-counter/simple_counter_tb_single.utest b/examples/simple-counter/simple_counter_tb_single.utest new file mode 100644 index 0000000..dbb5338 --- /dev/null +++ b/examples/simple-counter/simple_counter_tb_single.utest @@ -0,0 +1,12 @@ +;; -*- scheme -*- +;; ^^^ this comment tells Emacs to use Scheme mode + +(utest/tb + ("simple_counter_tb" + "Simplest testbench Simple Counter") + + (utest/run-simulation-iverilog + ;; sources + '("simple_counter.sv" "simple_counter_tb.sv") + ;; top module name + "simple_counter_tb")) diff --git a/examples/simple-counter/simple_counter_tb_workpermanent.utest b/examples/simple-counter/simple_counter_tb_workpermanent.utest new file mode 100644 index 0000000..e2e8c0a --- /dev/null +++ b/examples/simple-counter/simple_counter_tb_workpermanent.utest @@ -0,0 +1,25 @@ +;; -*- scheme -*- +;; ^^^ this comment tells Emacs to use Scheme mode + +(utest/tb + ("simple_counter_permanent" + "Testbench for Simple Counter with permanent work dir") + + ;; get base dir and make work dir + (let* ((base (utest/base-path)) + (work (format "~a/work" base))) + + (when (not (file-exists? work)) + (mkdir work)) + + ;; parameterize work dir + (parameterize + ((utest/work-path work) + (utest/force-dump #t)) + + ;; testbench body + (utest/run-simulation-iverilog + ;; sources + '("simple_counter.sv" "simple_counter_tb.sv") + ;; top module name + "simple_counter_tb")))) diff --git a/examples/simple-counter/utest.vh b/examples/simple-counter/utest.vh new file mode 100644 index 0000000..f6c3bf5 --- /dev/null +++ b/examples/simple-counter/utest.vh @@ -0,0 +1,15 @@ +`ifndef UTEST_VERILOG_DEFINES + `define UTEST_VERILOG_DEFINES + +// Log level string prefixes for use with $display function. +// Example usage: $display("%sError message", `LOG_ERR); + `define LOG_INFO "INFO#" + `define LOG_WARN "WARN#" + `define LOG_ERR "FAIL#" + +// Dirty hacked redefine of $display function. Must be used with two parentheses. +// Example usage: `log_info(("Information message")); + `define log_info(msg) begin $display({`LOG_INFO, $sformatf msg}); end + `define log_warn(msg) begin $display({`LOG_WARN, $sformatf msg}); end + `define log_error(msg) begin $display({`LOG_ERR, $sformatf msg}); end +`endif