DocsTech
/
ICARUS VERILOG
/

~ cd 3. симуляция с помощью icarus verilog

Симуляция — это процесс создания моделей, имитирующих поведение проектируемого устройства (имитационные модели), и моделей для отработки работы устройства (испытательного стенды). Имитационная модель не обязательно должна отражать какое-либо понимание технологии, лежащей в ее основе, а симулятор не должен знать, что проект предназначен для какой-либо конкретной технологии.

Симулятор Verilog, по сути, обычно представляет собой другую программу, чем синтезатор. Он даже может быть от другого производителя. Симулятор не обязательно должен знать или генерировать нетлисты для целевой технологии, поэтому можно написать один симулятор, который можно использовать для моделирования конструкций, предназначенных для широкого спектра технологий. Синтезатор, с другой стороны, должен знать многое о целевой технологии, чтобы генерировать эффективные нетлисты. Синтезаторы часто ориентированы на конкретную технологию и поставляются поставщиками, обладающими специальными знаниями, в то время как симуляторы имеют более общее назначение.

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

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

Компиляция и Элаборация

Моделирование конструкции равносильно компиляции и выполнению программы. Исходный код Verilog, представляющий имитационную модель и испытательный стенд, компилируется в исполняемую форму и выполняется механизмом моделирования. Внутри Icarus Verilog компиляция исходного текста программы в исполняемую форму разделена на несколько этапов, базовое понимание которых помогает понять природу сбоев и ошибок. Первым шагом является предварительная обработка макросов, затем компиляция, элаборация, опциональные оптимизации и, наконец, генерация кода. Граница между этими этапами часто бывает размыта, но эта последовательность остается полезной моделью процесса компиляции.

На этапе предварительной обработки макросов выполняются текстовые замены макросов, определенных с помощью операторов «define», текстовое включение с помощью операторов «include» и условная компиляция с помощью операторов «ifdef» и «ifndef». Макропроцессор для Icarus Verilog внутренне представляет собой отдельную программу, доступ к которой можно получить независимо, используя флаг «-E» в команде «iverilog», как показано ниже:
...
Копировать
% iverilog -E -o out.v example.v

Эта команда приводит к препроцессированию(обработке) входного Verilog-файла «example.v», а выходной Verilog-файл без препроцессорных операторов записывается в «out.v». Директивы «include» и «ifdef» во входном файле интерпретируются, а определенные макросы заменяются, так что на выходе получается тот же Verilog, но без директив препроцессора. Все явно указанные исходные файлы также объединяются препроцессором, так что результатом препроцессирования является единый поток Verilog.

Обычно, однако, флаг «-E» не используется, и препроцессированный Verilog отправляется непосредственно на следующий этап, в компилятор. Ядро компилятора принимает на вход предварительно обработанный Verilog и генерирует внутреннюю проанализированную форму. Проанализированную форма является внутренним представлением исходного текста Verilog в формате, удобном для дальнейшей обработки, и недоступна для пользователя.

На следующем этапе, в процессе элаборации, из проанализированной формы выбираются корневые модули и инстанцируются (создаются экземпляры) этих корней. Экземпляры корней могут содержать экземпляры других модулей, которые, в свою очередь, могут содержать экземпляры еще одних модулей. В процессе элаборации создается иерархия экземпляров модулей, которая заканчивается примитивными вентилями и операторами.

Обратите внимание, что существует разница между модулем и экземпляром модуля. Модуль — это тип. Он представляет собой описание содержимого экземпляров модуля, которые имеют его тип. Когда модуль объявляется экземпляром внутри другого модуля, имя модуля идентифицирует тип экземпляра, а имя экземпляра идентифицирует конкретный экземпляр модуля. Экземпляров любого модуля может быть много.

Корневые модули(модули верхнего уровня) — это особый случай, поскольку программист не присваивает им имена экземпляров. Вместо этого имена экземпляров корневых модулей совпадают с именем модуля. Это справедливо, поскольку, в силу особенностей синтаксиса Verilog, модуль может быть корневым только один раз, поэтому имя модуля само по себе является безопасным именем экземпляра.

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

Процесс элаборации создает из проанализированной формы иерархию областей видимости, включая примитивные объекты в каждой области видимости. Затем разработанная конструкция оптимизируется, чтобы свести ее к более оптимальной, но эквивалентной конструкции. На этапе оптимизации полностью проработанная конструкция преобразуется в эквивалентную консрукцию, который меньше или эффективнее. Эти оптимизации представляют собой, например, формы распространения констант и устранения мертвого кода. Бесполезная логика удаляется, а константные выражения предварительно вычисляются. Результирующая конструкция ведет себя так же, как если бы оптимизация не проводилась, но при этом он меньше и эффективнее. Устранение (и самопроизвольное создание) вентилей и операторов затрагивает программиста только при написании модулей VPI, которые через API имеют ограниченный доступ к структурам конструкции.

Наконец, оптимизированная конструкция, который все еще находится во внутренней форме, недоступной для пользователей, передается генератору кода, который записывает конструкцию в исполняемую форму. Для моделирования генератор кода выбирается для создания формата vvp — текстового формата, который может быть выполнен механизмом моделирования. Пользователь Icarus Verilog может выбрать другие генераторы кода, даже сторонние, но для целей моделирования по умолчанию используется генератор кода vvp.

Создание и использование библиотек

Хотя простые программы можно записывать в один исходный файл, это становится неудобным по мере увеличения размеров конструкции. Кроме того, запись всей программы в один файл затрудняет совместное использование общего кода разными программами. Поэтому имеет смысл разделить большие программы на несколько исходных файлов и поместить общеполезные файлы исходного кода куда-нибудь, где они будут доступны для нескольких конструкций.

После того как программа разделена на множество файлов, компилятору нужно указать, как найти файлы программы. Самый простой способ сделать это — перечислить исходные файлы в командной строке или в командном файле. Это, например, лучший способ разделить и интегрировать код испытательного стенда с имитационной моделью.

Макропрепроцессор

Еще один прием — использование макропрепроцессора для включения библиотечных файлов в основной файл. Директива include принимает имя исходного файла для включения. Препроцессор вставляет все содержимое включаемого файла на место директивы include. Обычно препроцессор ищет включаемый файл в текущем рабочем каталоге (текущем рабочем каталоге запущенного компилятора, а не в каталоге, в котором находится исходный файл), но переключатель «-I» в «iverilog» может добавить каталоги в список мест поиска.
...
Копировать
% iverilog -I/directory/to/search example.v

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

Препроцессорный метод размещения исходного кода в библиотеках является общим (произвольный исходный код может быть помещен в включаемые файлы), но статичным, в том смысле, что программист должен явно включать нужные библиотечные файлы. Автоматическая библиотека модулей немного более ограничена, но тоже автоматическая.

Автоматические библиотеки модулей

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

Чтобы это работало правильно, файлы библиотек должны быть исходниками Verilog, они должны содержать одно определение модуля, и файлы должны быть названы в честь модуля, который они содержат. Например, если модуль «AND2» является модулем библиотеки, то он находится в файле с именем «AND2.v», и этот файл содержит только модуль «AND2». Таким образом, библиотека — это каталог, содержащий правильно названные и отформатированные исходные файлы.
...
Копировать
% iverilog -y /library/to/search example.v

Флаг «-y» в программе «iverilog» указывает компилятору искать библиотечные модули в указанном каталоге всякий раз, когда программа объявляет экземпляр(включает) модуля, не определенный иным образом. Программист может указать несколько флагов «-y» в командной строке (или в командном файле), и компилятор будет искать в каждом каталоге по порядку, пока не будет найден подходящий библиотечный файл для разрешения модуля.

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

Icarus Verilog обращается к автоматическим библиотекам во время элаборации, после того как он уже препроцессировал и разобрал исходные файлы без библиотек. Модули в библиотеках не являются кандидатами в корневые модули и даже не анализируются, если они не объявлен в других исходных файлах. Однако библиотечный модуль может ссылаться на другие библиотечные модули, и чтение библиотечного модуля приводит к его разбору и проработке, а также разрешению дальнейших библиотечных ссылок, как и для небиблиотечного модуля. Процесс поиска и разрешения библиотек повторяется до тех пор, пока не будут разрешены все ссылающиеся модули или не станет известно, что они отсутствуют в библиотеках.

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

Расширенные командные файлы

Командные файлы упоминались в главе «Начало работы», но лишь вскользь. На практике программы Verilog быстро перерастают границы полезности простых опций командной строки, и даже макропрепроцессору не хватает гибкости, чтобы комбинировать исходные и библиотечные модули в соответствии с развивающимся процессом разработки.

Основное содержимое командного файла — это список исходных файлов Verilog. Вы можете назвать в командном файле все исходные файлы, составляющие ваш проект. Это удобный способ собрать вместе все файлы, составляющие ваш проект. Компиляция проекта может быть сведена к простой командной строке, как показано ниже:
...
Копировать
% iverilog -c example.cf

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

Конфигурация также может определять использование библиотек. Например, разные конфигурации могут быть реализацией разных технологий, поэтому могут использовать библиотеки разных частей. Для этого в командных файлах могут присутствовать операторы «-y». Они работают в командных файлах точно так же, как и в командной строке «iverilog». За каждым флагом «-y» следует имя каталога, и в каталогах производится поиск библиотечных модулей в том порядке, в котором они перечислены в командном файле.

Путь поиска include также может быть указан в конфигурационных файлах с помощью маркеров «+incdir+». Эти лексемы начинаются со строки «+incdir+», затем идут пути каталогов, отделенные друг от друга символами «+» (не пробелами) на всю длину строки.

В командный файл может быть включена и другая информация. Подробные сведения о том, что может содержаться в командном файле, см. в разделе Формат командного файла.

Входные данные во время выполнения программы

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

Существует множество способов, с помощью которых программа на языке Verilog может получать данные из внешнего окружения во время выполнения. Аргументы можно вводить в командной строке, а большие объемы данных можно считывать из файлов. Самый простой метод — принимать аргументы из командной строки.

Рассмотрим пример работы калькулятора квадратного корня
...
Копировать
module sqrt32(clk, rdy, reset, x, .y(acc));
  input  clk;
  output rdy;
  input  reset;

  input [31:0] x;
  output [15:0] acc;

  // acc holds the accumulated result, and acc2 is
  //  the accumulated square of the accumulated result.
  reg [15:0] acc;
  reg [31:0] acc2;

  // Keep track of which bit I'm working on.
  reg [4:0]  bitl;
  wire [15:0] bit = 1 << bitl;
  wire [31:0] bit2 = 1 << (bitl << 1);

  // The output is ready when the bitl counter underflows.
  wire rdy = bitl[4];

  // guess holds the potential next values for acc,
  // and guess2 holds the square of that guess.
  wire [15:0] guess  = acc | bit;
  wire [31:0] guess2 = acc2 + bit2 + ((acc << bitl) << 1);

  task clear;
     begin
        acc = 0;
        acc2 = 0;
        bitl = 15;
     end
  endtask

  initial clear;

  always @(reset or posedge clk)
     if (reset)
      clear;
     else begin
        if (guess2 <= x) begin
           acc  <= guess;
           acc2 <= guess2;
        end
        bitl <= bitl - 1;
     end

endmodule

Можно написать испытуемый стенд в виде программы, которая передает устройству репрезентативный набор входных значений и проверяет результат на выходе. Однако мы также можем написать программу, которая принимает в командной строке целочисленное значение, используемое в качестве входного сигнала для устройства. Мы можем написать и скомпилировать эту программу, а затем передавать различные входные значения в командной строке во время выполнения без перекомпиляции моделирования.

Этот пример демонстрирует использование «$value$plusargs» для доступа к аргументам командной строки симуляции
...
Копировать
module main;

  reg clk, reset;
  reg [31:0] x;
  wire [15:0] y;
  wire        rdy;

  sqrt32 dut (clk, rdy, reset, x, y);

  always #10 clk = ~clk;

  initial begin
     clk = 0;
     reset = 1;

     if (! $value$plusargs("x=%d", x)) begin
        $display("ERROR: please specify +x=<value> to start.");
        $finish;
     end

     #35 reset = 0;

     wait (rdy) $display("y=%d", y);
     $finish;
  end // initial begin

endmodule // main
Системная функция «$value$plusargs» принимает строковый шаблон, описывающий формат аргумента командной строки, и ссылку на переменную, которая принимает это значение. Программа «sqrt_plusargs» может быть скомпилирована и выполнена следующим образом:
...
Копировать
% iverilog –o sqrt_plusargs.vvp sqrt_plusargs.v sqrt.v
% vvp sqrt_plusargs.vvp +x=81
y=9

Обратите внимание, что строка «x=%d» функции «$value$plusargs» описывает формат аргумента. «%d» соответствует десятичному значению, которое в данном примере равно „81“. Это значение присваивается «x» функцией «$value$plusargs», которая возвращает TRUE, и моделирование продолжается с этого момента.

Если в испытуемый стенд необходимо передать два аргумента, то модуль main будет изменен следующим образом
...
Копировать
module main;

  reg clk, reset;
  reg  [31:0] x;
  reg  [31:0] z;
  wire [15:0] y1,y2;
  wire        rdy1,rdy2;

  sqrt32 dut1 (clk, rdy1, reset, x, y1);
  sqrt32 dut2 (clk, rdy2, reset, z, y2);

  always #10 clk = ~clk;

  initial begin
     clk = 0;
     reset = 1;

     if (! $value$plusargs("x=%d", x)) begin
        $display("ERROR: please specify +x=<value> to start.");
        $finish;
     end

     if (! $value$plusargs("z=%d", z)) begin
        $display("ERROR: please specify +z=<value> to start.");
        $finish;
     end


     #35 reset = 0;

     wait (rdy1) $display("y1=%d", y1);
     wait (rdy2) $display("y2=%d", y2);
     $finish;
  end // initial begin

endmodule // main
а программа «sqrt_plusargs» будет скомпилирована и выполнена следующим образом:
...
Копировать
% iverilog –o sqrt_plusargs.vvp sqrt_plusargs.v sqrt.v
% vvp sqrt_plusargs.vvp +x=81 +z=64
y1=9
y2=8

В общем случае команда «vvp», выполняющая скомпилированную симуляцию, принимает несколько предопределенных флагов аргументов, а затем имя файла симуляции. Все аргументы после имени файла симуляции являются расширенными аргументами команды «vvp» и передаются в выполняемый проект. Расширенные аргументы, начинающиеся с символа «+», доступны через системные функции «$test$plusargs» и «$value$plusargs». Расширенные аргументы, которые не начинаются с символа «+», доступны только системным задачам и функциям, написанным на языке C с использованием VPI.

В предыдущем примере программа получает аргумент из командной строки, присваивает его переменной «x» и запускает тестируемое устройство sqrt с этим значением. Эта программа может извлечь целый квадратный корень из любого значения. Конечно, если вы хотите провести тестирование с большим количеством входных значений, многократное выполнение программы может стать утомительным

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

Этот пример
...
Копировать
module main;

  reg clk, reset;
  reg [31:0] data[4:0];
  reg [31:0] x;
  wire [15:0] y;
  wire        rdy;

  sqrt32 dut (clk, rdy, reset, x, y);

  always #10 clk = ~clk;

  integer i;
  initial begin
     /* Load the data set from the hex file. */
     $readmemh("sqrt.hex", data);
     for (i = 0 ;  i <= 4 ;  i = i + 1) begin
       clk = 0;
       reset = 1;

       x = data[i];

       #35 reset = 0;

       wait (rdy) $display("y=%d", y);
     end
     $finish;
  end // initial begin

endmodule // main
демонстрирует использование функции «$readmemh» для чтения образцов данных из файла в массив Verilog. Начнем с того, что поместим в файл «sqrt.hex» числа:
...
Копировать
51
19
1a
18
1
Затем запустите моделирование с помощью последовательности команд:
...
Копировать
% iverilog -osqrt_readmem.vvp sqrt_readmem.vl sqrt.vl
% vvp sqrt_readmem.vvp
y=9
y=5
y=5
y=4
y=1

Эту программу достаточно легко изменить для работы с большими наборами данных или изменить файл «data.hex», чтобы он содержал другие данные. Эта техника также широко используется для моделирования алгоритмов, которые используют большие наборы данных. Можно немного расширить эту идею, используя оператор «$value$plusargs» для выбора файла для чтения.

Главная
Курсы
Вебинары
1. Руководство по установке Icarus Verilog
2. Начало работы с Icarus Verilog
3. Симуляция с помощью Icarus Verilog
4. Флаги командной строки iverilog
5. Формат командного файла
6. Атрибуты
7. IVLPP — препроцессор IVL
8. Флаги командной строки VVP
9. Интерактивный режим VVP
10. VVP как библиотека
11. Флаги командной строки vhdlpp
12. Icarus Verilog с GTKWave
13. Использование VPI
14. Расширения Icarus Verilog
Закрыть