Поиск

Полнотекстовый поиск:
Где искать:
везде
только в названии
только в тексте
Выводить:
описание
слова в тексте
только заголовок

Рекомендуем ознакомиться

'Руководство'
С 1 июля в МОУ СОШ №3 приступила к работе вторая смена по работе на пришкольном участке. Учащиеся среднего звена под руководством Глушковой Т.А., Хору...полностью>>
'Документ'
Кемерово ул. Волгоградская, 8в тел....полностью>>
'Конкурс'
Рыбы – это живые организмы, максимально приспособленные к жизни в воде. Их относят к первичноводным обитателям водной среды обитания. Интересен тот фа...полностью>>
'Документ'
Разработка необходимой документации для организации деятельности ПМПк ( планы, программы наблюдений, диагностический инструментарий, журнал регистраци...полностью>>

Главная > Документ

Сохрани ссылку в одной из сетей:
Информация о документе
Дата добавления:
Размер:
Доступные форматы для скачивания:

1

Смотреть полностью

23

Verilog – инструмент разработки цифровых электронных схем.

Verilog – инструмент разработки цифровых электронных схем.

Введение.

Язык Verilog был разработан фирмой Gateway Design Automaton как внутренний язык симуляции. Cadence приобрела Gateway в 1989 году и открыла Verilog для общественного пользования. В 1995 году был определен стандарт языка - Verilog LRM (Language Reference Manual), IEEE1364-1995. Таким образом, датой появления языка Verilog следует считать 1995 год. К этому времени уже успел получить распространение другой язык высокого уровня для описания принципиальных схем – VHDL (Very high-speed IC Hardware Description Language), появившийся в 1987 году. Verilog HDL и VHDL, несмотря на похожие названия, различные языки. В статье я буду сравнивать конструкции этих языков, предполагая, что читателю известен VHDL, но надеюсь также, что данный материал буден полезен для начинающих изучение HDL с языка Verilog. По моему мнению Verilog достаточно простой язык, сходный с языком программирования С как по синтаксису, так и по «идеологии». Малое количество служебных слов и простота основных конструкций упрощает изучение и позволяет использовать Verilog для обучения. Но в то же время это эффективный и специализированный язык. VHDL обладает большей универсальностью и может быть использован не только для описания моделей цифровых электронных схем, но и для других моделей (например, модели экосистемы). Но из-за своих расширенных возможностей VHDL проигрывает в эффективности, то есть на описание одной и той же конструкции в Verilog потребуется в 3-4 раза меньше символов (ASCII), чем в VHDL. В Verilog существуют специфические объекты (UDP, specify блоки), не имеющие аналогов в VHDL. Также следует упомянуть стандарт PLI (Program Language Interface), который позволяет включать функции написанные пользователем (например, на С) в код симулятора. В настоящее время важным недостатком Verilog является отсутствие документации, особенно на русском. Данная статья является скромной попыткой улучшить ситуацию.

Основы синтаксиса.

Для иллюстрации примеров будет использоваться Verilog-XL симулятор от Cadence (www.cadence.com ). Это наиболее стандартный, если так можно высказаться, симулятор. Существует несколько проектов свободных Verilog симуляторов. Мне известны veribest (/~jching) и icarus verilog (/eda/verilog/index.html). В настоящее время данные продукты не являются завершенными и их работоспособность на больших проектах, полнота и правильность в поддержке стандарта являются неудовлетворительными, но для примеров рассмотренных в статье эти программы подходят. Замечу также, что собрать эти симуляторы для работы под Windows мне не удалось (пакеты поставляются в исходном коде и бинарниках для Соляриса или Линукса). Также большинство производителей предлагает бесплатные демо-версии Verilog симуляторов. Неплохой список линков /Z/1/index.shtml . Для Windows платформы популярен ModelSim (www.model.com ). Под Линуксом хорошо зарекомендовал себя finsim (), для которого существует и Windows версия. Также в большинстве современных продуктов предназначенных для синтеза и верификации проектов на FPGA или ASIC существует поддержка Verilog HDL и VHDL, как для синтеза, так и для симуляции.

Симуляторы можно условно разбить на две группы – интерпретирующие и компилирующие в «родной код». Так Verilog-XL – интерпретирующий, NC-Verilog – компилирующий, а finsim может работать как в том, так и в другом режиме. Для работы компилирующих симуляторов обычно требуется, чтобы в системе был установлен С компилятор.

Запуск симулятора может производиться из командной строки или из графической оболочки, которая формирует строку для запуска. Часть параметров передаваемая в командной строке уникальна для конкретного симулятора, но есть основной стандартный набор. Так вызов симулятора со списком файлов вызовет их исполнение (verilog test.v modul.v), список файлов можно поместить в отдельный файл и вызывать симулятор с опцией –f (verilog –f file_list).

Все приводимые в тексте примеры можно загрузить с www.verilog.boom.ru .

Данное описание не является полным изложением синтаксиса языка. Будут рассмотрены только основные конструкции необходимые для понимания структуры и принципов языка.

Типы данных:

Verilog поддерживает следующие «стандартные» типы данных: целое – integer (32-х битовое со знаком) и real – число с плавающей точкой.

Для моделирования также используются time – время, специфический тип, применяется встроенными функциями для моделирования времени, обычно 64-х битовое целое; event – событие, в языке существует ряд операторов и конструкций для работы с событиями.

Создавать свои типы данных как в VHDL нельзя. При разработке синтезируемых моделей из перечисленных типов используется только integer.

Сигналы, в отличие от VHDL, бывают двух основных типов: «цепи» и «регистры». Самые распространенные из них описываются ключевыми словами wire и reg соответственно. Однако следует помнить, что средство синтеза не всегда реализует reg в виде триггера. Отличие wire от reg состоит в том, что reg способен сохранять присвоенное значение (работает как переменная в языках программирования), а к wire требуется прилагать непрерывное воздействие (driver). То есть wire моделирует провод, который переходит в неопределенное состояние при отключении драйвера. Существуют также wand, wor, tri0, tri1, triand, trior, trireg (это цепь, а не регистр!) для моделирования различных типов цепей (wand – wired and, tri0 – резистор к 0, trireg – емкость, и т.п.), но такие цепи встречаются редко и в статье не рассматриваются.

Идентификаторы в Verilog являются чувствительными к регистру и подчиняются обычным правилам: не могут начинаться с цифры или знака $ и могут содержать буквы, цифры, $, _ . Существуют так называемые escaped идентификаторы (в основном в структурных моделях полученных после синтеза), которые начинаются с \ содержат любые символы и заканчиваются пробелом или переводом строки.

Пример:

// - это комментарий

/*

и это комментарий

*/

integer i, j, k; // объявление переменных i, j и k типа integer

time start, duration;

real freq_div;

event start_process;

/* далее следуют объявления

однобитовых сигналов */

wire a, b, c;

wire d;

reg store, ff, A; // reg A не совпадает с wire a (case sensitive vs VHDL)

reg \dut/cntr/reg_s ; // escaped идентификатор (пробел перед ;)

Для описания шин или регистров неединичной ширины используются диапазоны (range) вида [n:m], где m и n целые числа или параметры. В языке допускается как m>n, так и наоборот. Но это имеет значение в операциях, для которых важен порядок битов (например, сложение или присвоение целого). Поэтому принято располагать индексы в убывающем порядке.

wire [7:0] data_bus;

reg [3:0] high_nibble, low_nibble; // два 4-х битовых регистра

reg [0:5] a_reg; // регистр с обратным порядком битов – не рекомендуется

Массивы в Verilog не поддерживаются, но существуют «памяти», собственно одномерный массив или модель памяти.

reg [8:0] Fifo [31:0]; // 32 слова 9-ти битовой памяти

В отличие от VHDL представление сигналов реализовано в самом языке (а не в библиотеке std_logic_1164). Всего существует четыре типа значений, которые может принимать сигнал – как «цепь», так и «регистр»: 0, 1, z, x. Первые три соответствуют логическим уровням и состоянию с высоким импедансом. Четвертый (х) означает неопределенное состояние и используется при моделировании неинициализированных сигналов, конфликтов (два выхода с противоположными состояниями соединены вместе), метастабильных состояний триггеров (при нарушении временных соотношений между входами данных и тактовым входом) и т.п. Во всех случаях, когда симулятор не может определить значение данного сигнала. В реальном приборе такого сигнала не бывает :-).

Для записи многоразрядных сигналов (констант) используется следующие конструкции: 1’bz – одноразрядный высокоимпедансный сигнал, 10’d1_000 – десятиразрядное число 1000 записанное в десятичной системе (символ _ игнорируется), 4’bx01z – четырехразрядный сигнал с неопределенным старшим битом, высокоимпедансным младшим, вторым и третьим в 1 и 0 соответственно. То есть разрядность, одинарная кавычка ‘ (не путать с апострофом ` , использующимся в директивах), основание системы счисления (b,o,d,h) и цифры использующиеся в данной системе счисления. В двоичной системе допустимо использование символов z и x. Символ подчеркивания служит для облегчения записи и игнорируется. Использование констант без указания разрядности не желательно, так как по умолчанию константа воспринимается с длиной 32 бита. Данные типа integer также могут присваиваться регистрам.

Структурное описание:

Основной структурной единицей Verilog описания является module. Модуль соответствует entity в VHDL. Модуль описывается ключевыми словами module – endmodule. В файле может быть описано несколько модулей. Другие модули могут подключаться к цепям модуля, образуя иерархическую структуру. При запуске Verilog симулятор строит иерархическое дерево из всех модулей, которые обнаружены в файлах поданных на вход симулятора и находит модуль верхнего уровня. Если таких модулей несколько, то происходит ошибка. Как правило, модуль содержит список портов – интерфейсных сигналов, которые служат для подключения его в других модулях. Порты бывают трех типов input – входы, output –выходы, inout – двунаправленные. Входы и двунаправленные порты должны иметь тип wire, а выходы могут быть как wire, так и reg.

Синтаксис модуля рассмотрим на примере накопительного сумматора:

module NCO (FC, CO, C); // имя модуля и список портов

input FC, C; // входы

output CO; // выход

// описание используемых сигналов

wire [3:0] FC;

wire C; // не обязательно – так как по умолчанию вход – однобитный провод

reg [3:0] acc;

reg CO;

// описание поведения системы

initial

begin

acc=0;

CO=0;

end

always @(posedge C) // событие – фронт С

{CO,acc}={CO, acc}+FC;

endmodule

Модуль NCO не включает в себя другие модули и является модулем нижнего уровня иерархии. В модуле присутствуют две «поведенческие» конструкции: initial и always. Initial служит для описания действий которые выполняются один раз (при запуске модели), а always обозначает действия, которые выполняются постоянно. Ключевые слова begin/end имеют такое же значение как в процедурном языке Паскаль (соответствуют {} в С). Для того чтобы always имело смысл используется событийный контроль – конструкция @(posedge C) означает по положительному фронту сигнала С. То есть операция {CO,acc}={CO,acc}+FO; выполняется по каждому фронту С. Фигурные скобки обозначают объединение сигналов с различными именами в шину. «Объединение» может находится как слева, так и справа от знака = в операции присвоения.

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

module hello_word; // интерфейсные порты отсутствуют

initial

$display(“HELLO, WORLD !!!”); // вызов системной функции

endmodule

После запуска симулятора должно получиться приблизительно следующее:

Highest level modules:
hello_word
 
HELLO, WORLD !!!
0 simulation events

В данном примере была использована системная функция $display, которая используется для печати либо форматированной строки (как С функция printf из библиотеки stdio), либо своих аргументов (как writeln в Паскале). Все системные функции и функции, написанные пользователем и подключенные через PLI интерфейс, начинаются со знака $.

Немного забегая вперед, следует сказать, что язык Verilog (так же как и VHDL) изначально предназначался для моделирования, и средства синтеза появились позже. Поэтому часть конструкций языка не поддерживается синтезом. Одной из таких конструкцией является initial. Для того чтобы осуществить начальную инициализацию в синтезируемой модели следует предусмотреть специальный сигнал сброса.

module NCO_syn (FC, CO, C, Rst); // имя модуля и список портов

input FC, C, Rst; // входы

output CO; // выход

// описание используемых сигналов

wire [3:0] FC;

wire C, Rst; // не обязательно – так как по умолчанию вход – однобитный провод

reg [3:0] acc;

reg CO;

// описание поведения системы

always @(posedge C or posedge Rst) // событие – фронт С или Rst

if (Rst)

{CO,acc}=5’b0;

else

{CO,acc}={CO, acc}+FC;

endmodule

В данном примере добавлена процедурная конструкция if. Как можно видеть, она подобна конструкции if в языке C, нулевое значение в скобках соответствует false, ненулевое - true. Подробно о процедурных конструкциях будет рассказано позже. Но следует заметить, что если в скобках стоит выражение, имеющее после вычисления биты со значением z или х, то выполняется else ветвь.

Для проверки работоспособности модуля используются испытательные стенды (testbench). Это модуль верхнего уровня, в котором могут использоваться несинтезируемые конструкции (initial) и типы данных (event, time, real). Также испытательные стенды содержат системные функции для вывода информации ($display, $write, $monitor), записи файлов изменения сигналов (vcd – value change dump) для последующего анализа, исследования статистических свойств сигналов и т.п.

Предположим, что модуль NCO будет использоваться для генерации частот 33МГц и 40МГц при тактовой частоте 100МГц. Для этого в накопительный сумматор на вход FC (frequency code) следует подать 11 и 13 соответственно. При этом полученные частоты будут 34.375МГц (ошибка 1.375МГц) и 40.9125МГц (ошибка 0.9125МГц), что предположим и требуется. В модуле testbench будут использоваться временные задержки – конструкции вида #NN, где NN время в наносекундах. Более подробно различные виды задержек (delay) будут рассмотрены ниже. Оба модуля (NCO и testbench) могут быть записаны либо в один, либо в разные файлы.

`timescale 1ns/10ps //директива симулятора – установка шага времени (необязательно, так как 1ns/10ps – значения по умолчанию)

module testbench;

// объявление сигналов

reg clk, rst;

reg [3:0] fc1,fc2

wire f1, f2;

// объявление переменных

integer clk_cnt, f1_cnt, f2_cnt;

real ratio;

//построение иерархии

NCO_syn nco1(.Rst(rst), .C(clk), .FC(fc1), .CO(f1)); //подключение по имени

NCO_syn nco2(fc2, f2, clk, rst); //подключение по расположению

initial

begin

clk=0;

rst=0;

fc1=4’d11;

fc2=4’d13;

clk_cnt=0;

f1_cnt=0;

f2_cnt=0;

end

always #5 clk=~clk; // генератор тактовой частоты 100МГц

// управление

initial

#1 rst=1'b1; // формирование сброса

#2 rst=1'b0;

#1200; //время симуляции

$display("toggle: clk ", clk_cnt, " , f1 ", f1_cnt, " , f2", f2_cnt); /*вывод результатов – без использования формат строки */

ratio=100.0*$itor(f1_cnt)/$itor(clk_cnt); // $itor – преобразование integer в real

$write("freqv @ clk=100MHz f1=%f", ratio);

ratio=100.0*$itor(f2_cnt)/$itor(clk_cnt);

$display(" f2=%f", ratio);

$finish; // завершение симуляции

end

// сбор статистики

always @(posedge clk)

clk_cnt=clk_cnt+1;

always @(posedge f1)

f1_cnt=f1_cnt+1;

always @(posedge f2)

f2_cnt=f2_cnt+1;

//два метода индикации (выбрать один, чтобы не засорять выход)

// 1

// печатает при изменении одного из сигналов

//initial $monitor("Time %t clk %b rst %b f1 %b f2 %b",$time,clk,rst,f1,f2);

// 2

//печатает по срезу clk

always @(negedge clk) $write("Time %t clk %b rst %b f1 %b f2 %b \n", $time, clk, rst, f1, f2);

endmodule.

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

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

После запуска со вторым вариантом печати будет выдано следующее:

Compiling source file "testbench.v"

Compiling source file "nco.v"

Highest level modules:

testbench

Time 1000 clk 0 rst 0 f1 0 f2 0

Time 2000 clk 0 rst 0 f1 1 f2 1

Time 3000 clk 0 rst 0 f1 0 f2 0

Time 4000 clk 0 rst 0 f1 0 f2 1

Time 5000 clk 0 rst 0 f1 1 f2 0

Time 6000 clk 0 rst 0 f1 0 f2 0

Time 7000 clk 0 rst 0 f1 0 f2 1

Time 8000 clk 0 rst 0 f1 1 f2 0

Time 9000 clk 0 rst 0 f1 0 f2 1

[----------- skipped --------------]

Time 118000 clk 0 rst 0 f1 1 f2 1

Time 119000 clk 0 rst 0 f1 1 f2 0

Time 120000 clk 0 rst 0 f1 0 f2 1

toggle: clk 120 , f1 41 , f2 49

freqv @ clk=100MHz f1=34.166667 f2=40.833333

L41 "testbench.v": $finish at simulation time 120300

Время измеряется в единицах, заданных вторым параметром директивы timescale (10ps), и соответственно time 7000 означает 70ns после старта симуляции. Общее время симуляции составило 1+2+1200=1203ns. Размерность временные параметров задаваемых в исходном коде модуля определяется первым параметром директивы timescale (1ns).

Вычисленные значения f1 и f2 отличаются от полученных моделированием. При увеличении длины выборки (времени моделирования) разница будет уменьшаться.

Операторы :

Синтаксис операторов в языке Verilog подобен синтаксису языка программирования С. Отсутствуют (к сожалению) операторы ++, -- и все операторы модификации вида (операция)=, например, *=, ^= и т.п. Но в то же время существуют логические операторы полезные для моделирования цифровых схем: при одинаковом синтаксисе (\, ~|, ^, ~^, & ~&) данные операторы могут быть битовыми (bitwize) и работать с двумя операндами или операторами свертки (reduction) и работать с одним операндом. Тип выполняемой операции определяется по положению оператора в выражении. Кроме логических операций языком поддерживаются и арифметические операции, но по поводу синтезируемости выражений с арифметическими операторами следует ознакомиться с документацией на средства синтеза. Для эффективной реализации (синтеза) арифметических выражений в «железе», возможно, потребуется приобретать специальные средства или библиотеки для datapath элементов.

Рассмотрим применение операторов на примере.

module op_test;

reg [3:0] D0,D1,D2,D3;

reg [3:0] A,B,C,D; //Verilog case-sensitive

reg a,b,c,d; // А и а – различные переменные

initial

begin

D0=4'b0;

D1=4'b1111;

D2=4'b1010;

D3=4'b01xz;

A=D1~^D2; // bitwise операция – два операнда

a=~^D2; // reduction операция – один операнд

B=D0^D3;

b=^D3;

C=D2&D3;

c=&D3;

D=D2|D3;

d=|D3;

$display("A=%b a=%b B=%b b=%b C=%b c=%b D=%b d=%d",A,a,B,b,C,c,D,d);

end

endmodule

Полученный результат будет выглядеть так:

Highest level modules:

op_test

A=1010 a=1 B=01xx b=x C=00x0 c=0 D=111x d=1

При выполнении логических и битовых операций состояние с высоким импедансом (z) воспринимается как неопределенное (х). При моделировании комбинаторная логика обычно способствует распространению х, но если, например, один из входов элемента И (AND) подан 0, то независимо от значения на других входах на выходе будет 0. Это иллюстрируют полученные в примере значения С и с.

В языке присутствует условный оператор ?: , который работает также как и в языке С. Таким образом, простейшей записью мультиплексора из 2 в 1 является:

assign Y=(SEL)?A:B;

Список операторов языка Verilog.

{} concatenation

+-*/ arithmetic

% modulus

> >= < <= relational

! logical negation

&& logical and

|| logical or

== logical equality

!= logical inequality

=== case equality *

!== case inequality *

~ bit-wise negation

& bit-wise and

| bit-wise inclusive or

^ bit-wise exclusive or

^~ or ~^ bit-wise equivalence

& reduction and

~& reduction nand

| reduction or

~| reduction nor

^ reduction xor

~^ or ^~ reduction xnor

<< shift left

>> shift right

<<< arithmetic shift left

>>> arithmetic shift right

? : conditional

Операторы помеченные * будут рассмотрены при рассмотрении поведенческих конструкциях.

Часть операторов повторяется, например, << и <<< выполняют одинаковое действие, а, например, последовательное выполнение ~ и ^ имеет такое же значение как и ^~ или ~^ , но эта табличка стандартна, и я привожу ее без изменений.

Применяться операторы могут как к цепям (wire) , так сигналам (reg) и переменным. Различия состоят в различных типах присваивания (не путать с := и <= в VHDL). Для цепей, которые являются моделью физического соединения (провода), требуется подключение непрерывного воздействия, которое моделируется непрерывным (continuous) присвоением. Значения же регистров и переменных могут изменяться в результате процедурных действий и сохраняться между воздействиями (так же как и переменные процедурного языка программирования), для моделирования этого используется процедурное (procedural) присвоение. Непрерывное присвоение употребляется вне процедурных блоков (initial или begin) и используется либо в описании цепи, либо с ключевым словом assign. (Существуют также процедурные непрерывные присвоения в блоках initial или begin с ключевыми словами assign и deassign). Слева от оператора непрерывного присвоения (=) должен находится объект типа цепь. При изменении значения какого-либо из объектов входящих в выражение справа от =, данное выражение будет вычислено и новое значение будет присвоено.

Пример иллюстрирующий работу непрерывного присвоения.

module assign_test;

reg [3:0] data;

wire parity, forth;

wire gnd=1'b0; //объявление присвоение

wire y=(data[0])?data[1]:gnd; //мультиплексор

assign parity=^data; //непрерывное присвоение

assign forth=~|data[1:0]; //еще одно

initial

$monitor(data,,parity,,forth,,y); //запускаем системную функцию для индикации изменений

initial

for (data=0; data!=15; data=data+1'd1) //переберем варианты

#1; //задержка нужна чтобы $monitor работал правильно

endmodule

результат работы этого примера

Highest level modules:

assign_test

0 0 1 0

1 1 0 0

2 1 0 0

3 0 0 1

4 1 1 0

5 0 0 0

6 0 0 0

7 1 0 1

8 1 1 0

9 0 0 0

10 0 0 0

11 1 0 1

12 0 1 0

13 1 0 0

14 1 0 0

15 0 0 1

Процедурные присвоения бывают двух типов blocking (=) и nonblocking (<=). Большинство разработчиков переводит на русский язык как «блочные» и «неблочные» присвоения, но по смыслу более подходящим кажутся термины «блокирующие» и «неблокирующие». Для того чтобы понять разницу между данными типами присвоения нужно рассмотреть работу Verilog симулятора. В реальном объекте (цифровой схеме), который моделируется с помощью языка Verilog, события могут происходить одновременно – при изменении входного сигнала во всех элементах начинаются процессы, которые протекают одновременно и приводят к каким-либо изменениям выходных сигналов. Программа симулятора не может выполнять события одновременно и создает списки событий, которые должны выполнятся последовательно. Когда все события из списка выполнены, симулятор переходит к следующему временному шагу – увеличивает время на временной интервал (второй параметр директивы `timescale) и выполняет список событий, которые должны произойти на этом шаге. Об исполнении событий в других временных шагах (механизме задержек) будет рассказано далее, а сейчас рассмотрим события происходящие «одновременно» - в одном временном шаге.

Допустим, имеется следующее описание:

always @ (posedge CLK) a=b;

always @ (posedge CLK) b=a;

Пусть а и b регистры единичной длины и к моменту положительного фронта тактового сигнала CLK а==0 b==1. Какое значение будут иметь эти переменные после прохождения фронта? Это не определенно в языке и зависит от того, в какой последовательности операции присваивания попадут в список. То есть получается конструкция, поведение которой непредсказуемо. Это значит, что либо обе эти переменные будут равны 0, либо обе равны 1. «Блокирующее» или «блочное» присвоение (=) блокирует исполнение других операций в блоке последовательных операций (до тех пор, пока не будет выполнена данная операция). Использование «блокирующего» присвоения в подобной конструкции в конкурентно исполняемых блоках нежелательно и его следует избегать. Но в то же время, если в блоке требуется провести последовательное исполнение операторов, следует применять данный тип присвоения.

Такая конструкция

always @ (posedge CLK)

begin

a=0;

b=a;

end

гарантирует, что после фронта CLK обе переменные a и b будут обнулены.

Если же предыдущие примеры переписать с использованием «неблокирующего» или «неблочнго» присвоения (<=), то поведение модели изменится:

always @ (posedge CLK) a<=b;

always @ (posedge CLK) b<=a;

в этом случае в список событий, исполняемом во временном шаге после изменения CLK, эти операции будут помещены параллельно, то есть переменные обменяются своими значениями. После прохождения фронта CLK будет а==1 b==0. И данная конструкция будет эквивалентна следующей:

always @ (posedge CLK)

begin

a<=b;

b<=a;

end

Таким образом описываются два D-триггера, выход каждого подан на вход другого, а на тактовые входы подан сигнал CLK. При этом последовательность записи a<=b; b<=a; или b<=a;a<=b; не играет роли так как моделируются одновременно происходящие события.

Вторая конструкция в этом случае:

always @ (posedge CLK)

begin

a<=0;

b<=a;

end

также описывает два D-триггера. На вход одного (а) подан 0, а выход подключен к входу другого (b). На тактовые входы подан сигнал CLK.

Для пользователей VHDL, можно провести параллель между variable assignment (:= VHDL) и blocking assignment (= Verilog) и signal assignment (<= VHDL) и nonblocking assignment (<= Verilog) соответственно. Но следует учесть, что в процедурных конструкциях Verilog, различий между регистром и переменной не делается.

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

Проверим работоспособность данной схемы следующим испытательным стендом (это упрощенный testbench – правильный пример создания испытательных стендов приведен в первой части):

`timescale 1ns/10ps

module test;

reg CLK,SET,a,b;

always

#5 CLK=~CLK; //100 МГц тактовый сигнал

always @ (posedge CLK or posedge SET) //испытываемый блок

begin

if (SET) a<=1'b1;

else

begin

a<=0;

b<=a;

end

end

initial //инициализируем переменные

begin

CLK=0;

SET=0;

a=0;b=0;

$monitor($time,,SET,,a,,b);

end

initial #100 $finish; // через 100 нс завершить моделирование

initial //подача «асинхронного» сигнала SET

begin

#57 SET=1'b1;

#1 SET=1'b0;

end

endmodule

Результат показывает

Highest level modules:

test

0 0 0 0

57 1 1 0

58 0 1 0

65 0 0 1

75 0 0 0

Что асинхронный сигнал SET длительностью 1 нс (фронт на 57 нс после пуска) был синхронизирован по фронту CLK в регистре b – длительность 10 нс (фронт 65 нс, срез 75 нс).

Временной и событийный контроль.

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

Следует заметить, что средства синтеза (как для Verilog, так и для VHDL) игнорируют временные конструкции в исходном коде. Для правильной генерации цифровой схемы (нетлиста) из библиотечных элементов в средства синтеза наряду с описанием на языке высокого уровня вводятся файлы содержащие “constrain” описания. В этих файлах описываются временные ограничения распространения сигналов. Использующийся для этого синтаксис не стандартизован и определяется используемым средством синтеза. Профессиональная работа с HDL языками высокого уровня подразумевает не только умение создавать грамотные поведенческие описания, но и умение правильно управлять средством синтеза с помощью “constrain”-ов.

Но для моделирования временной контроль необходим, и в примерах этой статьи неоднократно использовались выражения вида #<число>. Вместо числа может использоваться выражение содержащее целые и/или вещественные параметры. В синтаксисе языка определено несколько форм временных задержек для описания различных технологических условий – так называемая mintypmax форма - #(min, typ, max), например - #(2,3,4). Но данные конструкции используются, как правило, для моделирования на уровне вентилей (нетлиста), а более эффективным методом таких описаний является SDF аннотация с использованием специальных SDF файлов. Поэтому в статье будет использоваться упрощенная форма задания задержки с одним параметром #typ. Для анализа исходных файлов «третьей стороны» можно считать, что всегда используется типовое (среднее) значение.

Рассмотрим применение задержки в непрерывном присвоении.

assign #10 c=a^b;

Данная конструкция описывает элемент ИСКЛЮЧАЮЩЕЕ ИЛИ с задержкой распространения 10 нс (вернее 10 единиц первого параметра директивы `timescale, который по умолчанию равен 1 нс). При этом все задержки в непрерывных присвоениях являются инерциальными. То есть если сигнал А изменит свое состояние на время меньшее 10 нс, а затем изменит еще раз, то изменения сигнала С не произойдет. Для того чтобы произошло изменение сигнала С требуется, чтобы сигнал А был зафиксирован в новом состоянии более 10 нс. Такая модель поведение соответствует распространению задержки при прохождении через элементы электронной схемы. Другой тип задержки – транспортная задержка, которая обеспечивает точное соответствие формы задержанного сигнала и сдвиг его по шкале времени. В отличие от VHDL (в котором существуют спецификаторы типа задержки inertial и transport) Verilog не позволяет использовать оба типа задержек в одном моделировании. Для переключения типа задержки служит ключ командной строки запуска Verilog симулятора.

Рассмотрим задержки в процедурных блоках. Первая форма – простая задержка (она собственно и использовалась в примерах статьи). Имеет вид:

#1 x=y;

Вызывает останов последовательного блока на 1 нс (не влияет на исполнение конкурентно исполняющихся блоков). Не обязательно используется с оператором присваивания, может использоваться с пустым оператором #1; То есть #1 x=y; может быть записано в такой форме #1; x=y; или #0.5; #0.5 x=y; Поведение этих конструкций одинаковое.

Также в процедурном блоке задержка может встречаться с другой стороны знака =. Это так называемая intra-assignment delay.

x=#1 y;

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

tmp=y;

#1;

x=tmp;

Кроме временного контроля существует событийный контроль (который можно считать другой формой временного контроля). Признаком событийного контроля является знак @. В рассмотренных ранее примерах событийный контроль использовался в блоках always. Отличие Verilog от VHDL в данном случае состоит в том, что для описания фронтов и срезов сигналов используются не специализированные атрибуты сигнала, а специальная конструкция языка. Это создает впечатление, что разработчики языка Verilog несколько лучше представляли себе процесс разработки цифровых схем . Событийный контроль используется в процедурных блоках также как и временной контроль. При этом задержка исполнения происходит не на фиксированный временной интервал, а до тех пор, пока не произойдет нужное событие. События бывают следующих типов @(name) – изменение name, при этом name может являться цепью, регистром, переменной, или переменной типа event; @(posedge A) или @(negedge А) – фронт или срез сигнала А, при этом А однобитовый регистр или цепь; или комбинацией перечисленных событий с ключевым словом “or”, например, @ (posedge CLK or posedge SET). В третьем случае следует различать “or” с одноименной логической операцией. В событийном контроле “or” означает, что ожидается любое из перечисленных событий, а не определенный результат логической операции. Для генерации синхронного сброса может использоваться такая конструкция:

nReset=0;

repeat (16) @(posedge CLK);

nReset=1’b1;

Завершая рассмотрение временного и событийного контроля, следует упомянуть о применении intra-assignment delay в «неблокирующем» присвоении. То есть в конструкциях вида

x<=#1 y;

a<= @(posedge c) b;

Поведение этих конструкций таково, что значение выражения вычисляется и блокирования последовательного исполнения операций не происходит, но новое значение будет присвоено только после истечении времени указанного во временной конструкции или после совершения события указанного в событийной конструкции. В терминах работы программы Verilog симулятора операция присвоения переносится на другой временной шаг. В работе таких конструкций проявляется интересное отличие Verilog симуляторов от VHDL симуляторов (спасибо Stewart Little за демонстрацию этого факта). В VHDL каждая следующая по тексту программы операция присвоения одному и тому же сигналу отменяет предыдущую, пусть даже исполнение которой должно произойти в более ранний момент времени. В Verilog все подобные операции будут помещены в список для соответствующего временного шага, и сигнал, изменение которого вызывают эти операции, будет изменяться в соответствии со всеми операциями. Какой механизм поведения более правильный – вопрос спорный. Так как при синтезе временной контроль игнорируется, да и непонятно каким образом должна синтезироваться конструкция с присвоением из нескольких источников без специальной разрешающей функции, то это отличие может проявляться только на уровне моделирования с несинтезируемыми элементами. В то же время для работы с несинтезируемыми элементами Verilog предлагает операции способные отменить (вернее «пересилить») все остальные операции присвоения к одному определенному сигналу. Эти операции присвоения записываются с ключевыми словами force и release.

Проиллюстрировать поведение можно следующим примером:

/* это пример на VHDL (спасибо Stewart Little)

LIBRARY ieee;
USE ieee.STD_logic_1164.all;
USE ieee.std_logic_arith.all;

ENTITY AT91R IS
PORT(
NRD: OUT std_logic :='0'
);
END AT91R;

ARCHITECTURE EBI OF AT91R IS

BEGIN

modeler : PROCESS
BEGIN
NRD <= '1' AFTER 100 ns;
WAIT FOR 30 ns;
NRD <= 'Z' AFTER 30 ns;
WAIT;
END PROCESS modeler;

END EBI;

*/

В результате через 60 нс от начала симуляции выход переходит в Z-состояние, и далее не изменяется. Таким образом можно видеть, что последующая операция отменяет предыдущую.

Если переписать этот модуль на Verilog без учета изложенного выше получим (обратите внимание на лаконичность языка Verilog):

module AT91R (nrd);

output nrd;

reg ndr;

initial

begin : modeler //пример именованного блока

ndr<= #100 1’b1;

#30;

ndr<= #30 1'bz;

end

endmodule

При этом поведение будет другое. Написав соответствующий testbench, можно увидеть:

Highest level modules:

tst

0 x

60 z

100 1

Это значит, что на 60 нс сработает ndr<= #30 1'bz, а затем на 100 нс расположенный выше по тексту ndr<= #100 1’b1. То есть в Verilog предыдущая операция не отменяется. Для того чтобы поведения модуля было таким же, как и поведение VHDL кода, нужно записать его следующим образом:

module AT91R (nrd);

output nrd;

reg ndr;

initial

begin

ndr<= #100 1’b1;

#30;

#30 force ndr = 1'bz;

end

endmodule

Использовать force c «неблокирующим» присвоением и/или intra-assignment delay нельзя. Для того чтобы разрешить дальнейшее использование присвоений в других параллельных блоках сигнал должен быть отпущен с помощью release (например, release ndr;).

Завершая обзор временного контроля, следует упомянуть об еще одной форме задержки – нулевой задержке. То есть в Verilog коде встречаются такие конструкции: #0 a=b; Нулевая задержка означает, что операция будет выполнена в самом конце текущего временного шага. Если в одном временном шаге встречается несколько нулевых задержек, то между собой их порядок не определен.

Поведенческие конструкции.

В поведенческих блоках initial или always могут применяться конструкции управления сходные с операторами процедурных языков. Данные поведенческие конструкции подразделяются на несколько групп:

  1. группа принятия решений

if – else – if , case, casez, casex

  1. группа повторений

repeat, while, for, forever

  1. параллельного исполнения

fork-join

  1. оператор wait

Конструкция if записывается следующим образом

if ()

else

Для выбора из нескольких вариантов могут применяться вложенные if.

if ()

else if ()

else if ()

else

Здесь expression – любое выражение языка, а statement – оператор или группа операторов между begin и end. Ветвь else может отсутствовать, но если имеются вложенные if (как в примере), то else относится к ближайшему if. Для изменения порядка следует пользоваться begin и end. Если получаемое в выражении expression значение не равно 0 и не является неопределенной (x или z), то выполняется ветвь statement1, иначе statement2. Следует помнить, что так же как и в языке С операция сравнения записывается == (два знака =), в отличие от операции присваивания = (один знак). Операции сравнения при неопределенных операндах возвращают неопределенное значение (x). Поэтому в поведенческом моделировании (не принимается средствами синтеза) могут использоваться операции === (три знака =) и !==. Эти операции позволяют произвести литеральное сравнение определенных битов в выражении. Еще раз обращаю внимание, что выражение expression не является выражением какого-либо специального типа (boolean), а является любым выражением, которое может может быть приведено к типу integer. Здесь прослеживается аналогия с языком С, единственное отличие состоит в том что Verilog integer в отличие от C int может принимать неопределенные значения (x или z). В этом случае выполняется ветвь else.

Исполнение такого кода:

module if_test;

initial

begin

if (2*5) $display("2*5 != 0 ==> true");

if (2*0) $display("never print this");

else $display("2*0 != 0 ==> false");

if (1'bz) $display("never print this");

else $display("undefined ==> false");

if (1'bx) $display("never print this");

else $display("undefined ==> false");

end

endmodule

Даст следующее:

Highest level modules:

if_test

2*5 != 0 ==> true

2*0 != 0 ==> false

undefined ==> false

undefined ==> false

Следующий пример иллюстрирует применение операторов сравнения (сколько = в каком случае ставить ).

module if_test;

reg a,b,c,d;

initial

begin

a=(2'b10>3'b001);b=(2'b10==3'b001);c=(2'b10>2'b0x);d=(2'b10==2'bz0);

$display("a=%b b=%b c=%b d=%b",a,b,c,d);

a=(2'b10!==3'b01);b=(2'b10===2'b10);c=(2'b10!==2'b0x);d=(2'bx1===2'bz1);

$display("a=%b b=%b c=%b d=%b",a,b,c,d);

a=(2'b0x==2'b0x);b=(2'bx1!=2'bx1);c=(2'b0x===2'b0x);d=(2'bx1!==2'bx1);

$display("a=%b b=%b c=%b d=%b",a,b,c,d);

end

endmodule

Результат:

a=1 b=0 c=x d=x

a=1 b=1 c=1 d=0

a=x b=x c=1 d=0

Для выбора из нескольких вариантов также применяется оператор case:

Например, данная конструкция реализует дешифратор подобный К155ИД3.

case (rega)

4'd0: result = 10'b0111111111;

4'd1: result = 10'b1011111111;

4'd2: result = 10'b1101111111;

4'd3: result = 10'b1110111111;

4'd4: result = 10'b1111011111;

4'd5: result = 10'b1111101111;

4'd6: result = 10'b1111110111;

4'd7: result = 10'b1111111011;

4'd8: result = 10'b1111111101;

4'd9: result = 10'b1111111110;

default result = 'bx;

endcase

Оператор case является «непроваливающимся» в отличие от оператора switch языка С и гарантирует исполнение одной ветви. В случае если ни одно из условий не совпадает, то выполняется ветвь default. Допустимо другое применение – наоборот, в case константа, а в ветвях вычисляемые выражения или переменные и там и там (такого использования оператора выбора в процедурных языках, как правило, нет). Оператор case часто используется в синтезируемом коде для синтеза FSM и мультиплексоров. При этом в несинтезируемых моделях (а в некоторых средствах синтеза и в синтезируемых) в выражениях case могут использоваться литералы с неопределенными значениями. Для поведенческого моделирования используются операторы casez и casex, которые особым образом обрабатывают неопределенные состояния. Синтаксис casez и casex подобен синтаксису case. При этом добавляется символ ? используемый в двоичной записи литерала для того, чтобы замаскировать биты, которые не должны влиять на принятие решения.

Для демонстрации обработки неопределенных состояний операторами case, casez и casex рассмотрим следующий пример.

module case_test;

integer a,b,c,d;

reg clk;

always #5 clk=~clk;

always

begin : demo

integer i;

for(i=0; i<16; i=i+1)

begin

$write("i = %d ",i);

casex (i) // x, z, ? - the same function - ignore bit

4'b0xxx : $display ("less than 8");

4'b10zz : $display ("not less than 8 and less than 12");

4'b11?1 : $display ("not less than 12 and odd");

4'bx?z? : $display ("other case");

default $display ("never print this");

endcase

wait (clk==1'b1); // the same as @(posedge clk)

end

end // demo

initial clk=0;

endmodule

Данный пример не содержит $finish, а события будут происходить непрерывно из-за always блоков. Поэтому он будет исполняться «вечно». Чтобы остановить – нужно воспользоваться средствами среды.

Фрагмент результата работы:

i = 0 less than 8

i = 1 less than 8

i = 2 less than 8

i = 3 less than 8

i = 4 less than 8

i = 5 less than 8

i = 6 less than 8

i = 7 less than 8

i = 8 not less than 8 and less than 12

i = 9 not less than 8 and less than 12

i = 10 not less than 8 and less than 12

i = 11 not less than 8 and less than 12

i = 12 other case

i = 13 not less than 12 and odd

i = 14 other case

i = 15 not less than 12 and odd

i = 0 less than 8

Таким образом, case – проверяет литеральные совпадения, его можно сравнить с использованием if и === (= три раза). В операторах casex и casez биты со значениями x и z или только z игнорируются. В литералах сравнения x и z (casex) или z (casez) могут быть заменены «?».

Операторы повторения могут встречаться в синтезируемом коде. При этом упрощается и становится более понятной запись. В испытательных стендах и несинтезируемых моделях использование операторов повторения имеет такой же смысл, как и в процедурных языках программирования. Операторы for и repeat были продемонстрированы ранее. Так как Verilog не позволяет воспользоваться вечным циклом языка С (for(;;)), то введен оператор forever. Для выхода из циклов (блоки должны быть именованы) служит оператор disable. Продолжая сравнение с языком С: disable работает как С операторы break и continue.

initial

begin :break

for(i = 0;i

begin :continue

@clk

if(a == 0) // "continue" loop

disable continue;

... ...

@clk

if(a == b) // "break" from loop

disable break;

......

end

end

Еще один цикл while имеет следующую форму:

while (condition)

begin

statement

step_assignment;

end

Операторы циклов взаимозаменяемы, и выбор определяется личными предпочтениями программиста.

Цикл repeat может использоваться в intra-assignment delay для описания задержки в несколько циклов. Например так: a = repeat(3)@(posedge clk) b;

Операторы fork – join служат для параллельного исполнения ветвей кода в одном процедурном блоке. Это является несинтесируемой конструкцией и, по моему мнению, используется редко.

Оператор wait (см. пример casex) используется для приостановки конкурентно исполняемого блока до тех пор, пока не будет выполнено его условие (как правило, элементы выражения условия wait должны изменяться в другом блоке).

Процедуры и функции.

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

function [7:0] swap;

input [7:0] byte;

begin

swap = {byte[3:0],byte[7:4]};

end

endfunction

Вызов функции осуществляется следующим образом:

a=swap(b);

Прежде чем рассматривать процедуры, рассмотрим иерархическую структуру Verilog модели и области видимости объектов. Локальные переменные (сигналы или параметры) могут объявляться внутри модуля, именованного блока, процедуры или функции. Если в локальной области видимости симулятору не удается обнаружить переменную, то поиск продолжается в более «высокой» области видимости до тех пор, пока не дойдет дело до переменных, сигналов, параметров объявленных внутри модуля. Если внутри модуля переменная не обнаружена, то выдается сообщение об ошибке. Таким образом, модуль является высшим элементом иерархии областей видимости. Для доступа к объектам в других модулях, которые собранные в иерархическую структуру, служит операция разрешения контекста. Эта операция задается с помощью имен модулей или именованных блоков внутри модуля (процедур или функций) разделенных точкой. Так в примере с делителем частоты (первая часть статьи) для доступа к внутреннему регистру acc модуля NCO_syn из модуля верхнего уровня testbench нужно воспользоваться такой конструкцией <имя включения (instance)>.acc .

// 2

always @(negedge clk) $write("Time %t clk %b rst %b f1 %b f2 %b phase1 %b phase2 %b\n", $time, clk, rst, f1, f2, nco1.acc, nco2.acc );

При этом на печать будут выдаваться значения фазы (из области видимости модулей NCO_syn)

Time 112000 clk 0 rst 0 f1 1 f2 1 phase1 0000 phase2 0000

Time 113000 clk 0 rst 0 f1 1 f2 1 phase1 1011 phase2 1101

Time 114000 clk 0 rst 0 f1 0 f2 0 phase1 0110 phase2 1010

Time 115000 clk 0 rst 0 f1 1 f2 1 phase1 0001 phase2 0111

Time 116000 clk 0 rst 0 f1 1 f2 0 phase1 1100 phase2 0100

Time 117000 clk 0 rst 0 f1 0 f2 1 phase1 0111 phase2 0001

Time 118000 clk 0 rst 0 f1 1 f2 1 phase1 0010 phase2 1110

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

Такой механизм доступа создает ограничение, налагаемое на локальные переменные. Также следует вспомнить о параллельном исполнении и о том, что процедуры в языках HDL не «вызываются», а «разрешаются». Такое название свидетельствует о том, что одновременно может исполняться несколько копий процедуры, так как в процедуре разрешен временной и/или событийный контроль. Но при этом память для локальных переменных НЕ ВЫДЕЛЯЕТСЯ. То есть копии одной и той же процедуры, работающие параллельно, будут «портить» друг другу локальные переменные. Иллюстрацией является практическая невозможность рекурсивных процедур или функций. На эти грабли может наступить программист, работавший с процедурными языками.

Синтаксис процедур следующий:

task my_task;

input a, b;

inout c;

output d, e;

reg foo1, foo2, foo3;

begin

// the set of statements that performs the work of the task

c = foo1; // the assignments that initialize

d = foo2; // the results variables

e = foo3;

end

endtask

При этом любое число параметров может передаваться в/из процедуры.

Вызов (или «разрешение») процедуры производится следующим образом:

my_task (v, w, x, y, z);

Функция не имеет права вызывать процедуру, а процедура может разрешать другие процедуры и вызывать себя рекурсивно (см. локальные переменные) или вызывать функции.

Системные функции.

В примерах данной статьи я неоднократно использовал системные функции $monitor, $display, $write, $finish, $time. Это малая часть средств, которые предоставляются Verilog системой программисту для анализа результатов моделирования. Благодаря наличию механизма PLI, обеспечивающего подключение исполняемого кода (написанного либо пользователем, либо третьей стороной), число системных функций и задач, которые выполняются с их помощью, очень велико. Основное назначение – это сбор/анализ информации и взаимодействие с системой. Признак системной функции - $. Остановимся на наиболее популярных системных функциях:

$finish – завершение моделирования

$stop – переход в интерактивный режим

$display, $write – вывод данных в stdout (дублируется в лог-файл), поведение либо как C функция printf с формат строкой (поддерживаются дополнительные форматы, например, %b -бинарный), либо как Паскаль процедура write с разделенными «,» аргументами. $display завершает вывод «переводом строки»

$monitor – отслеживает изменения аргументов, в конце каждого временного шага печатает при обнаружении изменения значения. Формат как у $display

$readmemb, $readmemh – обеспечивают считывание данных (в бинарном или шестнадцатеричном представлении) из файла в память (см. первую часть статьи). Формат файла очень простой – в каждой строке либо слово требуемой разрядности, либо конструкция @<адрес загрузки>. Очень удобно применять для моделирования ПЗУ.

$system – выполняет команду ОС (вызов С функции system())

файловые операции - $fopen, $fclose, $fwrite, $fmonitor … – позволяют записывать в файлы

запись дампфайлов - $dumpfile, $dumpvars … позволяют записать изменения сигналов модуля, всего проекта или отдельных в специальном формате для последующего изучения. Очень полезный и сильный механизм

$time – возвращает время симуляции

численные функции $itor, $random … - выполняют преобразования или возвращают результат математической функции.

Это малая часть стандартных функций. Полный список следует искать в документации к симулятору. Так же есть функции, которые не являются стандартными (в настоящий момент времени), но поставляются в виде отдельных объектных модулей или С кода. Примером таких функций являются $utConnectivity, записывающая список соединений модели для последующего просмотра с помощью Undertow (/), или $toggle_count, служащая для сбора статистики переключения сигналов.

Параметры.

При описании цифровых схем возникает необходимость в задании констант, определяющих какие-либо фиксированные параметры. Эти константы могут описывать характерные задержки, ширину шин или любой другой параметр, не изменяющийся во время симулирования модели и известный на момент компиляции. Но в то же время при использовании модели одного и того же модуля в различных технологических условиях или включения его различным образом в модули высшего уровня данные константы должны иметь возможность изменятся. В языке VHDL данную функцию решают с помощью generic. В Verilog для этого используются параметры. То есть в описании модуля наряду с описанием переменных и сигналов могут описываться и параметры:

module parity (bus, out);

parameter width=8, typ_delay=1, modul_delay=typ_delay*2;

wire [width-1:0] bus;

wire out;

assign #modul_delay out=^bus;

endmodule

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

Включать модуль в иерархию можно следующим образом:

parity U1(bus1,out1); // будут использованы значения по умолчанию (width=8, typ_delay=1, modul_delay=typ_delay*2)

parity #(4, 0.5) U2(bus2,out2); // ширина шины – 4 бита, типовая задержка 0.5 (задержка в модуле 0.5*2=1)

parity #(16, 1, 5) U3(bus3,out3); // ширина шины – 16 бит, типовая задержка 1 (игнорируется), задержка в модуле 5

То есть конструкция #(фактический параметр, фактический параметр, … ) соответствует map generic языка VHDL. Может возникнуть путаница между конструкциями временного контроля и параметрами, но, во-первых, временной контроль и привязка параметров происходит в разных конструкциях языка и по контексту видно значение знака #, а во-вторых, параметры, как правило, определяют временные задержки внутри модуля.

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

module top; //модуль верхнего уровня

…..

parity U1(bus1,out1);

parity U2(bus2,out2);

parity U3(bus3,out3);

….

defparam

top.U2.width=4;

top.U2.typ_delay=0.5;

top.U2.width=16;

top.U2.modul_delay=5;

endmodule

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

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

Препроцессор языка Verilog (директивы компилятора).

Наряду с механизмом параметров описанным выше в языке Verilog существует механизм сходный с текстовым препроцессором языка С. Этот механизм (по аналогии с С назову его препроцессором) позволяет проводить предварительную обработку текста до того как данные будут обработаны симулятором или средством синтеза. Данный механизм обеспечивает условную компиляцию, выполнение макроподстановок, интерфейс с внешней средой. Аналогом условной компиляции Verilog является конструкция if-generate языка VHDL.

Для работы препроцессора, а также других директив компилятора используется значек “ ` ” апострофа (не путать с кавычкой в литералах).

Наиболее употребимые директивы препроцессора `define, `include, `ifdef, `else, `endif.

Таким же образом задаются директивы компилятора, не являющиеся директивами препроцессора – например, `timescale.

Для включения текста из одного файла в другой используется `include имя_файла (например, `include "dumppins.v"). Следует помнить, что Verilog модули не могут объявляться внутри модуля (не могут быть вложенными) поэтому `include можно использовать либо вне модуля, либо включать в модуль код, не содержащий модулей (module … endmodule).

Использование `define для описания текстового макроса можно проиллюстрировать таким образом:

`define nc @(negedge clk)

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

аlways `nc ….

или

a<= `nc b;

Пример директив условной компиляции может быть следующим:

`ifdef test0

`define CPUTEST0

`include "fault/test0"

`else

`define CPUTEST1

`include "fault/test1"

`endif

Так как определить макрос можно из командной строки компилятора (опция +define+_NAME_), то данный механизм позволяет использовать конфигурационные скрипты.

В настоящее время существуют средства синтеза, как не поддерживающие препроцессор (старые или недоделанные версии), так и расширяющие стандартный набор директив. Например, средство синтеза может поддерживать директиву `for , что позволяет реализовать конструкцию for-generate языка VHDL.

Конструкции, применяемые для симулирования библиотечных ячеек.

Данная статья направлена на то, чтобы осветить поведенческое моделирование и написание такого поведенческого кода, который может быть синтезирован средством синтеза. Кроме этого в языке существуют предопределенные модули. Которые могут быть использованы как структурные элементы нижнего уровня. Некоторые элементы мне не приходилось использовать, и для задач разработки СБИС или ПЛИС эти конструкции неприменимы. Например, описания транзисторных ключей: nmos pmos rnmos rpmos cmos rcmos.

Также существуют предопределенные логические ячейки: and nand nor or xor xnor buf not bufif0 bufif1 notif1 notif0.Совпадающие по названию с булевыми функциями являются двухвходовыми элементами, выполняющими соответствующую функцию, not – инвертор, buf – буффер, последние четыре элемента имеют вход разрешения и состояние высокого импеданса (z). Но так как любой из этих элементов может быть описан с использованием операторов языка, то смысл применения данных конструкций определяется предпочтением разработчика.

Например:

and U1(out,in1,in2);

то же самое что и

out=in1&in2;

или

bufif1 U2 (out,in,control);

то же самое что и

out=control?in:1’bz;

Следует заметить, что для облегчения записи элементы могут подключаться на шину или иметь неименованные включения (instance), но все равно использование операторов кажется предпочтительнее, а результат синтеза или поведение модели будут в обоих случаях одинаковые.

Для моделирования библиотечных элементов может применяться табличный механизм – UDP.

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

primitive and_or(out, a1,a2,a3, b1,b2);

output out;

input a1,a2,a3, b1,b2;

table

//state table information goes here

...

endtable

endprimitive

Как можно видеть основным элементом UDP является таблица истинности. Ее элементы могут принимать следующее значение:

0 Logic 0

1 Logic 1

x Unknown

? Iteration of 0, 1, and x Cannot be used in output field

b Iteration of 0 and 1 Like ?, except x is excluded Cannot be used in output field

- No change Can only be used in output field of a sequential UDP

(vw) Value change from v to w v and w can be any one of: 0, 1, x, ?, or b

* Same as ?? Any value change on input

r Same as 01 Rising edge on input

f Same as 10 Falling edge on input

p Iteration of (01), (0x), and (x1) Positive edge on input

n Iteration of (10), (1x), and (x0) Negative edge on input

Количество пробельных символов в описании таблицы не играет роли, важен порядок.

Пример комбинаторного UDP (мультиплексора):

primitive multiplexer(mux,control,dataA,dataB ) ;

output mux ;

input control, dataA, dataB ;

table

// control dataA dataB mux

0 1 ? : 1 ; // ? = 0,1,x

0 0 ?:0;

1 ?1:1;

1 ?0:0;

x 0 0:0;

x 1 1:1;

endtable

endprimitive

Пример последовательного UDP:

primitive srff (q,s,r);

output q;

input s,r;

reg q;

initial q = 1'b1; // initial statement specifies that output

// terminal q has a value of 1 at the start

// of the simulation

table

// s r q q+

1 0 : ? : 1 ;

f 0 : 1 : - ;

0 r : ? : 0 ;

0 f : 0 : - ;

1 1 : ? : 0 ;

endtable

endprimitive

Синтезируемое подмножество языка.

Прежде чем вести разговор о синтезируемом подмножестве языка следует остановиться на общих принципах разработки СБИС или ПЛИС с использованием языков высокого уровня. Производитель микросхемы ПЛИС или фабрика, производящая СБИС, предоставляет модель библиотечных элементов, которые могут быть использованы в схеме. Эти библиотеки направлены на использование в различных областях проектирования (моделирование, синтез, топология кристалла и т.д.) и представляются в различных форматах. Один из форматов представляет из себя библиотеку элементов, описанных на языке высокого уровня и предназначенных для моделирования. В данной библиотеке содержатся элементы, описанные посредством несинтезируемых конструкций. В описании библиотечных элементов встречаются нерассмотренные в статье конструкции для описания сквозных задержек распространения (path delay) и механизмы контроля временных параметров (setup, hold time). Сквозные задержки позволяют описать задержку модуля, не вдаваясь в его внутреннюю структуру, и поддерживаются специальными словами языка (specify,specparam, endspecify) и специальным синтаксисом (например, (a,b,c*>x,y,z) = Tin; (d+=>x) = Tout;). А системные функции контроля используются для проверки того, что изменения сигналов происходят в нужные моменты времени, например, для того чтобы последовательная логика не попадала в метастабильное состояние. Я не останавливался подробно на этих элементах, потому что пользователю средств синтеза не нужно описывать библиотечные элементы, а в текстовом описании библиотеки обычно содержится информация о поведении элемента и его временной диаграмме. Можно сказать, что при разработке проекта СБИС или ПЛИС, возможно даже не придется вручную подключать/отключать эти элементы в структурном описании и вообще знать об их существовании .

Следующие два элемента разработки предоставляются пользователем – это синтезируемое описание на языке HDL и набор директив для средства синтеза. Также, конечно, нужен набор средств разработки, включающий в себя, симулятор и средство синтеза. Следует остановиться на отличиях синтеза от компиляторов поведенческих языков. Компилятор языка (например С) детерминированным образом переводит операторы языка в команды машинного языка. Синтез переводит последовательные операторы языка HDL в структурную схему, состоящую из библиотечных элементов. Данный процесс больше похож на перебор вариантов и выбор наилучшего, удовлетворяющего временным ограничениям и занимаемой площади. Следствием этого является то, что синтез это итеративный процесс, когда результаты предыдущего шага используются для коррекции директив для следующего шага. Методология написания директив синтеза и подход к синтезу схемы имеющей минимальное количество вентилей и максимальную тактовую частоту, является отдельным вопросом и в статье не рассматривается. Но в любом случае предоставляемое пользователем описание на языке HDL должно быть правильным: оно должно быть работоспособным и синтезируемым.

Рассмотрим шаги обычно требующиеся для разработки прибора на СБИС или ПЛИС:

  1. подготовка синтезируемого поведенческого описания схемы (этому шагу может предшествовать моделирование на С, Matlab, или специализированных средствах)

  2. написание тестовой оболочки – испытательного стенда (testbench), в которой проводится полное тестирование всех режимов модели

  3. итеративная процедура синтеза поведенческой модели, результатом которой является структурная схема (нетлист) с использованием библиотечных элементов и файл задержек распространения (SDF standard delay file)

  4. проверка работоспособности нетлиста, при этом обычно используется тот же testbench, что и для 2). Нарушением работоспособности может быть связано с нарушениями допустимых времен библиотечных элементов, гонками фронтов и пр. В зависимости от опыта разработчика происходит возврат к 1), 3) или переход к 5). Также полезно на этом шаге проверять результаты работы средств временного анализа (static timing report) для выяснения «узких мест» - критических путей проекта.

  5. процедура размещения и трассировки. Выполняется специальными средствами. Результатом является коррекция SDF файла (помимо прошивки ПЛИС или разводки СБИС ).

  6. проверка нетлиста с новыми задержками. Подобна 4), но возврат возможен к 1), 3), 5)

  7. in-place оптимизация (для большинства современных ПЛИС отсутствует), производится выравнивание времен распространения сигналов – усиление/ослабление выходов ячеек, установка буферов/элементов задержки

  8. повтор 6) до тех пор пока не будут достигнуты требуемые характеристики

  9. подготовка производственных тестов предназначенных для поиска неисправностей в СБИС (для ПЛИС этот шаг необязателен)

Шаг 3) в этой схеме раньше выполнялся вручную, когда поведенческое описание заменялось структурным самим разработчиком. В настоящее время существуют средства синтеза, которые могут синтезировать более эффективную схему. Пользование этими средствами накладывает ограничение на использование конструкций языка в исходном коде. То есть требуется синтезируемая модель. Кроме того, чтобы многократно не переписывать поведенческую модель, следует четко представлять, какое Verilog описание приведет к синтезу того или иного элемента схемы.

Ранее в статье было продемонстрировано как работает Verilog симулятор. Далее будут рассмотрены требования средств синтеза.

Любой элемент языка в результате синтеза может быть: 1) синтезирован, 2) проигнорирован, 3) вызвать ошибку. Конструкции языка могут поддерживаться полностью, частично или не поддерживаться.

Как уже упоминалось: иерархические имена, initial, fork-join или primitive не поддерживаются.

Временной контроль #nn игнорируется в синтезируемой модели.

Событийный контроль поддерживается частично – только в блоках always.

В документации к средству синтеза указывается какие ограничения вводятся на элементы языка. И перед написанием модели следует ознакомится с документацией.

Кроме этого существуют правила описания комбинаторной логики, последовательной логики, мультиплексоров, FSM и т.п.

Комбинаторная логика синтезируется из следующих конструкций:

1) непрерывное присвоение

assign a=b+c&d;

wire b={e,f} | g[1:0];

2) сигналы, описанные в функциях

3) сигналы, описанные следующей или подобной конструкцией

reg data_out;

always @(a or b or c)

if (b)

data_out = a ;

else

data_out = c ;

то есть блок always в списке чувствительности, которого перечислены все входные сигналы.

Как можно видеть в 3 (иногда во 2) случае описание сигнала с ключевым словом reg не приводит к созданию регистра.

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

Последовательная логика имеет ограничения в синтезируемых конструкциях.

Для описания регистра-защелки (latch) применяется следующая конструкция:

reg data_out ;

always @(data_in or enable)

if (enable)

data_out = data_in ;

Для регистров работающих по фронту/срезу сигнала применяется описание с posedge|negedge конструкцией.

reg data_out ;

always @(posedge clock)

data_out = data_in;

Чтобы добавить синхронный сброс, описание нужно дополнить:

reg data_out ;

always @(posedge clock)

if (set_sig)

data_out = 1'b1 ;

else if (reset_sig)

data_out = 1'b0 ;

else

data_out = data_in;

Сигналом установки/сброса, но не вносить эти сигналы в список чувствительности.

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

reg data_out ;

always @(posedge clock or posedge set_sig or posedge reset_sig)

if (set_sig)

data_out = 1'b1 ;

else if (reset_sig)

data_out = 1'b0 ;

else

data_out = data_in;

Пользуясь этими принципами можно описать триггер любого типа.

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

Блок case, у которого не все входные воздействия расшифровываются, синтезируется в элемент последовательной логики. Также может использоваться для описания FSM (finite state machine). Поэтому кажется более правильным воспользоваться комбинаторным case и явно описать регистр для хранения результата (к примеру, для реализации FSM).

Ограничения, накладываемые средством синтеза на HDL язык, позволяют описывать логику работы схемы в виде комбинаторной логики и регистров для хранения результатов. Такое описание называется RTL (register transfer level) и иногда отделяется (по смыслу) от поведенческого. Основная идея, которую я хотел бы высказать в статье – это то, что следует пользоваться RTL описанием для моделирования и синтеза. Данный подход является эффективным методом ведения разработки. Так как «правильное» (написанное с применением правил изложенных выше), поведенческое описание может быть автоматически синтезировано на уровень вентилей, это позволяет значительно ускорить и упростить процесс разработки. При этом если временные ограничения были выбраны правильно (с учетом задержек в элементах библиотеки), то поведение RTL (поведенческой модели), не будет отличаться от поведения нетлиста (gate-level), и не должно отличаться от поведения готового изделия.

Завершая рассмотрение синтезируемого (RTL) кода следует упомянуть о директивах синтеза, не являющихся элементами языка Verilog. Директивы синтеза задаются в поле комментария и игнорируются при моделировании, но управляют средством синтеза: например

// synopsis synthesis off

$write(“this string is ignored by synthesis”);

// synopsis synthesis on

Или для уже упоминавшегося case существует директива синтеза (в последовательную или параллельную логику его синтезировать), которая может быть указана в поле комментария.

// ambit synthesis case = full | parallel | mux

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

Заключительное слово.

Данная статья не является документацией по языку Verilog. Целью написания было проиллюстрировать работу Verilog симулятора и показать возможность использования языка Verilog для разработки цифровых схем. При этом рассматривались аспекты связанные как с тестированием модели (написание испытательных стендов), так и проблемы написания синтезируемого кода. Приведенные примеры и объяснения к ним должны были (по замыслу автора) показать различия между процедурными языками и языками описания HDL. Развитие CsoC и ПЛИС позволяет предположить, что все большие части проекта могут быть реализованы в виде описания HDL. В статье сделана попытка показать, что Verilog является сильным и удобным средством для этого.

1

Смотреть полностью


Похожие документы:

  1. Контрольные вопросы 23

    Контрольные вопросы
    ... деятельности, как объект разработки и как технологии разработки информационных систем. Это ... Предполагают, что между первыми инструментами обработки материальных объектов и средствами ... услугой. Переход к технологиям цифровой связи приводит к сближению служб ...
  2. ПЕРЕЧЕНЬ НАУЧНЫХ СТАТЕЙ И ДОКЛАДОВ опубликованных в 2014 году

    Документ
    ... для линейного логического синтеза цифровых структур / Н.Н. ... уравнения базовых схем включения мультидифференциальных ... 21 Разработка оптико-электронного устройства для ... Каращенко, В.В. Учёт финансовых инструментов и инвестиций в соответствии ... .auris-verlag.de/ ...
  3. Оправилах записи программы 9 Очем можно не беспокоиться 9 Описание архитектуры объекта 9

    Реферат
    ... описывать поведение, т.е. алгоритмы функционирования цифровых систем, а также проводить ... определение можно заменить структурной схемой. Можно моделировать: параллелизм, ... следующие стили проектирования и описания схем: структурный, потоковый, поведенческий, ...
  4. Болотова алла Константиновна

    Документ
    ... Л.С.Выготского через разработку общепсихологической теории ... (см. схему) Схема хронотопической детерминации ... деятельности является необходимым инструментом приспособления к меняющимся ... наименьшей ошибкой (показания электронного секундомера, вмонтированного ...
  5. Базы знаний интелектуальных систем гаврилова хорошевский

    Документ
    ... , традиционная схема разработки СПЗ I ... электронные учебники. Размещение в узлах сети не только текстовой и цифровой ... 1994. «Сценарий» — инструмент визуальной разработки компьютерных программ // Компьютерные техноло­ ... and Languages, Springer-Verlag. 287. Fridman ...

Другие похожие документы..