Глава 2. основы языка описания аппаратуры verilog
В этой главе мы кратко знакомим с языком описания аппаратуры Verilog. Мы не собираемся дать полное описание языка. Полное описание Verilog есть в руководстве по языку. В этой главе, как и в книге в целом, уделено внимание функциональным аспектам языка. В приложении А приводится синтаксис Verilog, а в приложении В приводится список ключевых слов Verilog.
В этой книге термин Verilog относится к языку описания аппаратуры Verilog, а не к программе моделирования Verilog-XL simulator. Все ключевые слова, начинающиеся с символа $, являются командами Verilog-XL simulator и не являются частью языка Verilog.
Verilog очень похож на язык программирования Си. Разработчики, знакомые с Си, легко овладеют языком Verilog. Как и Си, Verilog - язык со свободным форматом, учитывает регистр, все ключевые слова находятся на нижнем регистре. Для улучшения читаемости можно использовать пробелы, символы табуляции, начало с новой строки и комментарии.
ПОНЯТИЕ МОДУЛЯ
Модуль (module) является основной единицей в Verilog. Он представляет собой некий логический объект, который обычно реализуется в аппаратуре. Например, модуль может быть простым вентилем, 32-разрядным счетчиком, подсистемой памяти, вычислительной системой или вычислительной сетью.
Прежде, чем обратиться к подробностям языковых конструкций, рассмотрим пример описания модуля (рис. 2.1).
Модуль add2bit в примере имеет два входа и один выход. Входы одноразрядные и объявлены проводами (wire). Выход объявлен двухразрядным регистром. В модуле есть один исполняемый блок, расположенный между оператором "always ... begin" и "end". Он постоянно следит за своими входами и когда один из входов изменяется, модуль вычисляет значение выхода как сумму двух входов и печатает значение входов, выхода и текущее время моделирования.
,----------------------------------------------------------.
| module add2bit (in1, in2, sum); |
| input in1, in2; |
| output[1:0] sum; |
| wire inl, in2; |
| reg[1:0] sum; |
| |
| always @ (inl or in2) begin |
| sum = inl + in2; |
| $display ("The sum of %b and %b is %0d (time = %0d)",|
| inl, in2, sum, $time); |
| end |
| endmodule |
`----------------------------------------------------------'
Рис.2.1 Пример описания модуля
Хотя модуль add2bit очень простой, он демонстрирует основные компоненты, присущие всем модулям. На рис. 2.2 показан синтаксис определения модуля.
Каждый модуль имеет заголовок (header), содержащий имя модуля (module name) и список входов и выходов. Он описывает средства, с помощью которых модуль взаимодействует со своим окружением. Все (внешние) входы и выходы, так же как все (внутренние) переменные должны быть описаны. Обычно они объявляются либо проводами (wire), которые просто обеспечивают межсоединения между субблоками модуля, или регистрами, которые могут сохранять информацию и состояние которых может быть изменено функциональными операторами.
,-------------------------------------------------------------.
| module <имя_модуля> <необязательный список входов/выходов>; |
| <описание входов/выходов> |
| <описание локальных переменных> |
| |
| <элемент_модуля> |
| ... |
| <элемент_модуля> |
| |
| endmodule |
`-------------------------------------------------------------'
Рис.2.2 Синтаксис определения модуля
Ядро тела модуля на рис.2.3 представлено <элементами_модуля>. Эти элементы могут быть различного типа, наиболее распространенными из них являются непрерывное присваивание, структурные элементы и функциональные элементы.
Непрерывное присваивание - это лаконичный способ описания комбинационной части модуля. Например, строчки
wire[7:0] result;
assign result = op1 + op2;
или эквивалентная строчка
wire[7:0] result = op1 + op2;
определяют result как сумму op1 и op2. Всякий раз, когда op1 или op2 изменяются, result вычисляется заново.
Структурный элемент - это элемент модуля более низкого уровня в текущем модуле. Например, строки
fulladd f1 (cin0, a0, b0, sum0, cout0);
fulladd f2 (cout0, a1, b1, sum1, cout2);
создают экземпляры двух модулей fulladd в текущем модуле, и дают им имена f1 и f2. Структурный элемент эквивалентен встраиванию схемы более низкого уровня в текущую схему. Если модуль состоит только из структурных элементов, тогда модуль представляет собой список элементов.
Так как структурные элементы только определяют разделение и структуру модуля, должен быть способ описания функциональной сущности модулей самого нижнего уровня. Один способ описания функции - использовать примитивы языка Verilog, такие как вентили AND, OR и т.д. Но хотя проектирование на вентильном уровне может быть очень эффективным, более предпочтителен способ, который поддерживает Verilog, заключающийся в использовании функциональных конструкций более высокого уровня для описания функционирования модели, называемых функциональными элементами.
Функциональным элементом является блок программы на Verilog, которому предшествует ключевое слово "initial" или "always". Все элементы в модуле (как функциональные, так и структурные) выполняются одновременно. Функциональный элемент "initial" выполняется один раз в начале моделирования. Например, блок
initial
i = 0;
устанавливает i в 0 в начале моделирования. Функциональный элемент "always" выполняется постоянно в бесконечном цикле. Например, блок
always
#10 i = i + 1;
увеличивает i на 1 каждые 10 единиц времени все время моделирования.
Остальная часть этой главы дает обзор функционального аспекта Verilog.
ОСНОВНЫЕ ТИПЫ ДАННЫХ
Подобно большинству языков программирования, Verilog имеет константы, которые хранят фиксированную информацию, и переменные (variables), которые могут изменяться во время моделирования. Константы в Verilog могут быть десятичными, шестнадцатиричными, восьмиричными или двоичными и имеют формат
размер'основание величина
где размер - необязательное десятичное целое, описывающее разрядность константы, основание - необязательный параметр, могущий принимать одно из значений b,B,d,D,o,O,h и H.
В и b обозначают двоичную константу,
О и о обозначают восьмеричную константу,
D и d обозначают десятичную константу,
H и h обозначают шестнадцатеричную константу.
Основание B и b означает двоичную константу, основание О и o означает восьмеричную константу, основание D и d означает десятичную константу, а основание H и h означает шестнадцатеричную константу.
Если размер не указан, он вычисляется из величины константы, а если не указано основание, то подразумевается десятичное основание. Некоторые примеры:
15 (десятичная 15)
'h15 (десятичная 21, шестнадцатеричная 15)
5'b10011 (десятичная 19,двоичная 10011)
12'h01F (десятичная 31, шестнадцатеричная 01F)
Строковые константы пишутся в кавычках (например, "mystring") и преобразуются в эквивалентный двоичный формат ASCII. Например, строка "ab" эквивалентна 16'h5758.
Переменные в Verilog могут иметь тип reg (регистр), wire (провод), integer (целое), real (вещественное), event (событие) и time (время). В то время как типы integer, float, time и event представляют абстрактные понятия, типы reg и wire непосредственно отражаются в аппаратуре. Wire представляет межсоединения между блоками, reg представляет элемент памяти. Как reg, так и wire имеют размер, указанный в описании или равный по умолчанию 1. Каждый разряд в переменной типа reg или wire может принимать одно из четырех значений: 1, 0, x или z. х представляет или неинициализированную переменную, или конфликт, такой как в случае, когда соединены два выхода и каждый пытается установить сигнал в разное состояние. z представляет высокий импеданс или плавающую величину и используется для шин с тремя состояниями. reg и wire могут использоваться в арифметических выражениях и интерпретируется моделирующей программой как целое без знака.
Ниже приведены примеры описания переменных:
integer i, j; // два целых числа
real f, d; // два вещественных числа
wire [7:0] bus; // 8-и разрядная переменная
// bus (шина)
reg [0:15] word; // 16-и разрядная переменная
// word (слово)
event trigger,clock_high; // два события
time t_setup,t_hold; // t1, t2
C одноразрядными и многоразрядными подполями переменных типа reg и wire действуют как с другими переменными и к ним можно обращаться и изменять в выражениях:
reg [15:0] word;
reg [7:0] byte;
...
word[0] = ... // первый справа разряд
// переменной word
byte = ... +word[15:8]; // левая половина word
... word ... // эквивалентно word[15:0]
Следующие примеры иллюстрируют использование переменных в выражениях Verilog:
i = i + j;
assign bus = word[15:8] + word[7:0];
f = (g + 1.2) * 3.29E-5;
@e1 #5 ->e2; // ждать совершения события е1
// ждать 5 единиц времени
// и запустить событие е2
if ($time - t1 < t2) error ("timing violations");
Целые - это 32-разрядные числа со знаком, которые могут сохранять рабочие переменные. Переменные типа time могут содержать значение модельного времени и обычно реализуются в виде 64-разрядных целых без знака. Переменные типа event и time будут рассмотрены позже в разделе "Понятие времени и событий".
В Verilog есть только одна структура данных, а именно array (массив), которая может иметь тип любой переменной. Далее следуют примеры описания массива:
integer num[99:0]; // массив из 100 целых
reg [7:0]mem[0:1024]; // массив из 1024 байтов
reg [10*8:1]names[20]; // массив из 20 переменных
// names, каждая имеет длину
// 10 символов, каждый символ
// является 8-разрядным байтом
Обращение к элементу массива имеет такой же синтаксис, как обращение к разряду вектора, таким образом word[i] может быть i-ым элементом в массиве word или i-ым разрядом в векторе word, в зависимости от того, как описано word. Для обращения к разряду подполя или элемента массива элемент сначала надо записать в рабочую переменную:
reg [15:0] array [0:10];
reg [15:0]temp;
...
temp = array[3];
...temp[7:5]
ОСНОВНЫЕ ОПЕРАЦИИ И ВЫРАЖЕНИЯ
Язык Verilog заимствует синтаксис и семантику большинства операций из языка программирования Си. Исключением является отсутствие операций увеличение (++) и уменьшение(--). На рис.2.3 приводится сводка операций Verilog. На рис.2.6 приводится порядок выполнения операций.
,----------------------------------------------------.
| + - * / (арифметические операции) |
| > >= < <= (операции отношения) |
| ! && || (логические операции) |
| == != (логическое равенство) |
| ?: (условная) |
| { } (конкатенация) |
| % (операция "по модулю") |
| === !== (сравнение) |
| ~ & | (по-битовые) |
| << >> (сдвиг) |
`----------------------------------------------------'
Рис.2.3 Операции языка
Все операции над двумя операндами можно также использовать как одноместные редукционные операции. Например, выражение "+varname" даст сумму всех разрядов varname. Более типичный пример редукционного использования "^varname", что дает исключительное ИЛИ всех разрядов varname, то есть его четность. Другие примеры одноместных редукционных операций представляет выражение
^word === 'bx // истинно, если в word есть разряд,
// равный х
или выражение
&word == 0 // истинно, если в word есть разряд,
// равный 0
-----------. Отличие логических ||, && от |, &
Примечание | -------------------------------------
Шевцова С. | 1) при &,| неравные векторы выравниваются по
-----------' большему, дополняя нулями слева меньший, и
после этого производится поразрядное срав-
нение векторов по & или |. Результат - век-
тор поразрядных сравнений.
2) при &&, || вектора сначала оцениваются: ес-
ли в векторе есть хотя бы одна единица, он
считается равным 1, иначе - равным 0, - и
после оценки выполняется операция & или |
уже над однобитовыми значениями; результат -
также однобитовый.
Операции логического сравнения идентичны соответствующим языка Си. При сравнении двух переменных, которые не равны 0 или 1 ( то есть х или z) операция эквивалентности дает результат ложь. Для проверки равенства переменных, которые могут содержать х или z, Verilog имеет операции сравнения "===" и "!==". На рис.2.4 показана разница между "==" и "===".
,-------------------------------------------------------.
| module equ_equ; |
| |
| initial begin |
| $display ("'bx == 'bx is %b", 'bx == 'bx); |
| $display ("'bx === 'bx is %b", 'bx === 'bx); |
| $display ("'bz != 'bx is %b", 'bz != 'bx); |
| $display ("'bz !== 'bx is %b", 'bz!== 'bx); |
| end |
| |
| endmodule |
`-------------------------------------------------------'
Рис.2.4 Различие между " == " и " ==="
Выполнение модуля equ_equ даст следующие результаты:
'bx == 'bx is x
'bx === 'bx is 1
'bz != 'bx is x
'bz !== 'bx is 1
Две операции, отсутствующие в Си, это конкатенация и тиражирование. Две и более переменные (или константы) могут быть конкатенированы заключением их в фигурные скобки { }, разделенные запятой:
{2'b1x, 4'h7} === 6'b1x0111
Конкатенированные переменные могут содержаться в любом выражении или в левой части оператора присваивания. Константа, участвующая в конкатенации, должна иметь явно заданный размер ( то есть 1'bz , а не 'bz). Размер конкатенации равен сумме размеров ее составляющих.
Тиражирование обеспечивает краткую форму записи для дублирования константы или переменной. Выражение может быть тиражировано заключением его в двойные фигурные скобки с помещением коэффициента тиражирования между двумя открывающими скобками:
{3{2'b01}} === 6'b010101
На рис.2.5 показан пример использования конкатенации для перестановки двух байтов и использование тиражирования для знакового расширения переменной word.
,----------------------------------------------.
| module concat_replicate(swap,signextend); |
| |
| input swap,signextend; |
| |
| reg[15:0] word; |
| reg[31:0] double; |
| reg[7:0] byte1, byte2; |
| |
| initial begin |
| byte1 = 5; byte2 = 7; |
| if(swap) |
| word = {byte2, byte1}; |
| else |
| word = {byte1, byte2}; |
| if(signextend) |
| double = {{16{word[15]}},word}; |
| else |
| double = word; |
| end |
| |
| endmodule |
`----------------------------------------------'
Рис.2.5 Конкатенация и тиражирование
Выражения можно переставлять, используя обычные правила последовательности выполнения операций. Можно использовать скобки для улучшения читаемости и избежания двусмысленности.
ПРОЦЕДУРНЫЕ ОПЕРАТОРЫ
Хотя выражения можно использовать для вычисления величины, их нельзя вычислять изолировано, они должны быть частью оператора. Простой оператор может быть оператором присваивания или оператором порядка выполнения. Составной оператор или блок состоит из группы операторов, заключенных в "begin" и "end". Каждый функциональный элемент состоит из простого или составного оператора. Блок может быть именован:
begin : instruction_fetch
...
end
где идентификатор, следующий за ":" - это имя блока. Именованные блоки могут иметь внутри себя описание локальных переменных.
,---------------------------------------------------.
| binary op. самый высокий приоритет |
| !~ |
| * / % |
| + - |
| << >> |
| < <= > >= |
| == != === !== |
| & |
| ^ ^~ |
| | |
| && |
| || |
| ?: самый низкий приоритет |
`---------------------------------------------------'
Рис.2.6 Приоритет операций
Язык Verilog имеет много управляющих конструкций из других языков высокого уровня, особенно из языка Си.
Цикл for
Cледующий пример (рис.2.7) показывает использование цикла for.
Выполнение модуля for_loop дает следующие результаты:
i = 0 (0 в двоичном виде)
i = 1 (1 в двоичном виде)
i = 2 (10 в двоичном виде)
i = 3 (11 в двоичном виде)
,---------------------------------------------------.
| module for_loop |
| |
| integer i; |
| |
| initial |
| for (i = 0; i < 4; i = i + 1) begin |
| $display ("i = %0d (%b binary)", i, i); |
| end |
| |
| endmodule |
`---------------------------------------------------'
Рис.2.7 Оператор цикла for
Цикл c условием продолжения while
Результат выполнения цикла for можно получить использованием конструкции while, как показано на рис.2.8. Выполнение модуля while loop дает такие же результаты, как и выполнение модуля for loop на рис. 2.7.
Оператор выбора
Следующий пример (рис.2.9) показывает использование управляющей структуры выбора. Оператор выбора соответствует переключателю switch языка Си.
,--------------------------------------------------.
| module while_loop |
| integer i; |
| initial begin |
| i = 0; |
| while (i < 4) begin |
| $display ("i = %0d (%b binary)", i, i);|
| i = i + 1; |
| end |
| end |
| endmodule |
`--------------------------------------------------'
Рис.2.8 Оператор цикла while
,--------------------------------------.
| module case_statement; |
| integer i; |
| initial i = 0; |
| always begin |
| $display ("i = %0d", i); |
| case (i) |
| 0: i = i + 2; |
| 1: i = i + 7; |
| 2: i = i - 1; |
| default: $stop; |
| endcase |
| end |
| endmodule |
`--------------------------------------'
Рис.2.9 Оператор выбора
Выполнение модуля case statement дает следующие результаты:
i = 0
i = 2
i = 1
i = 8
Выбирающее выражение (в скобках) сравнивается со значениями (перед ":"), совпадение будет при поразрядном совпадении (подобно оператору ===). Если нет совпадения ни с одним вариантом, выполняется действие по несовпадению (default).
Если действие по несовпадению не описано, то продолжается выполнение после оператора выбора. Хорошим программистским тоном является всегда описывать действие при несовпадении. Если такое не может случиться, то модель должна печатать сообщение об ошибке или прекратить моделирование.
Операторы casez и casex очень похожи на оператор case, за одним исключением. Оператор casez не обращает внимание на разряды, находящиеся в состоянии z, а оператор casex не обращает внимание на разряды, находящиеся в состоянии z и x.
Цикл repeat
В языке Verilog есть еще две управляющие структуры, не являющиеся обычными для других языков программирования. На рис.2.10 показан цикл repeat, который ждет 5 тактов, а затем прекращает моделирование.
Цикл forever
На рис.2.11 показан цикл forever, который следит за некоторым условием и выдает сообщение, когда условие выполняется.
Хотя операторы repeat и forever можно реализовать с помощью других управляющих операторов, например, с помощью оператора for, они очень удобны, особенно при выдаче команд с клавиатуры. Их преимущество в том, что они не требуют описания переменных заранее.
,------------------------------------.
| module repeat_loop (clock); |
| input clock; |
| |
| initial begin |
| repeat (5) |
| @ (posedge clock); |
| $stop; |
| end |
| |
| endmodule |
`------------------------------------'
Рис.2.10 Цикл repeat
,----------------------------------------------------.
| module forever_statement (a,b,c); |
| input a,b,c; |
| |
| initial forever begin |
| @ (a or b or c) |
| if (a + b == c) begin |
| $display ("a(%d)+b(%d) = c(%d)", a,b,c);|
| $stop; |
| end |
| end |
| |
| endmodule |
`----------------------------------------------------'
Рис.2.11 Цикл forever
ПОНЯТИЕ ВРЕМЕНИ И СОБЫТИЙ
До сих пор описание поведения на языке Verilog очень похоже на программирование на любом структурированном языке высокого уровня. Единственное и наиболее важное различие – в понятии времени и его влиянии на порядок выполнения операторов в модуле. В большинстве языков программирования существует единственный программный счетчик, указывающий текущее место выполнения программы. Так как в аппаратуре все элементы работают параллельно, последовательная модель выполнения не соответствует языку описания аппаратуры и, таким образом, выполнение в Verilog управляется событиями. Глобальная переменная обозначает модельное время. В каждый момент времени может быть одно или более событий, которые следует выполнить. Планировщик событий программы моделирования Verilog simulator занимает место программного счетчика языков программирования.
Verilog simulator выполняет все события, запланированные на текущее модельное время и удаляет их из списка событий. Когда в текущее модельное время нет больше событий, тогда модельное время продвигается к первому элементу, запланированному на следующий раз. По мере выполнения событий генерируются новые события для будущего времени (или, возможно, для текущего). На рис.2.12 показана ось модельного времени с несколькими событиями, запланированными в различных точках. Заметим, что время перед текущим модельным временем не может иметь никаких событий, связанных с ним, так как все события были выполнены и удалены.
Порядок выполнения событий внутри одного и то же модельного времени, в общем случае, не известен и нельзя полагаться на него, так как Verilog simulator может попытаться оптимизировать выполнение упорядочиванием событий особым образом. Однако, Verilog гарантирует, что линейный код, который не имеет управления временем, будет выполняться как одно событие без прерывания. Также гарантируется, что порядок выполнения двух одинаковых прогонов моделирования будет одинаковым. Пример на рис.2.13 иллюстрирует это использованием нескольких функциональных элементов.
текущее t1 t2 t3
-----------,----------------,----------------,------->
время | | |
,-----'-----. ,-----'-----. ,-----'-----.
| событие 0 | | событие 3 | | событие 4 |
`-----,-----' `-----------' `-----,-----'
,-----'-----. ,-----'-----.
| событие 1 | | событие 5 |
`-----,-----' `-----------'
,-----'-----.
| событие 2 |
`-----------'
Рис.2.12 Ось времени
,--------------------------------------------------------.
| module event_control; |
| |
| register [4:0] r; |
| |
| initial begin |
| $display ("First initial block, line 1."); |
| $display ("First initial block, line 2."); |
| end |
| |
| initial |
| for (r = 0; r <= 3; r =r + 1) |
| $display ("r = %0b",r); |
| |
| endmodule |
`--------------------------------------------------------'
Рис.2.13 Несколько функциональных элементов
Выполнение модуля event_control дает следующие результаты:
Fist initial block, line 1.
Fist initial block, line 2.
r = 0
r = 1
r = 10
r = 11
Здесь видно, что все блоки initial по плану должны выполняться в одно и то же модельное время (момент 0).
Обращаясь к результатам моделирования на рис.2.13, можно видеть, что Verilog выбирает некоторый порядок выполнения блоков, который не очевиден из рассмотрения программы модели. Заметим, однако, что если блок был запланирован к выполнению, он продолжает выполняться до полного окончания.
УПРАВЛЕНИЕ ВРЕМЕНЕМ И СОБЫТИЯМИ
Процесс языка Verilog (то есть блок initial или always) может перепланировать свое собственное выполнение использованием одной из трех форм управления временем:
#выражение
@событие-выражение
wait (выражение)
Форма #выражение, используемая для синхронного управления, останавливает выполнение процесса на фиксированное время, указанное параметром "время", а форма @событие-выражение, используемая для асинхронного контроля , останавливает выполнение до тех пор, пока не случится указанное событие. В обоих случаях планировщик удаляет исполняемые в настоящий момент события из списка событий текущего модельного времени и заносит их в некоторый список будущих событий.
Форма wait (выражение) - это управление событиями, чувствительное к уровню. Если wait выражение ложно, выполнение будет остановлено до тех пор, пока оно не станет истинным (из-за выполнения некоторого оператора в другом процессе).
Пример на рис.2.14 показывает использование структуры управления временем.
Выполнение модуля time_control дает следующие результаты:
r = 2 в момент 5
r = 1 в момент 10
r = 2 в момент 25
r = 1 в момент 30
r = 2 в момент 55
r = 1 в момент 60
Заметим, что первый оператор initial
initial #70 $stop;
- это обычный способ для остановки моделирования в определенный момент времени.
В этом примере, хотя все блоки initial начинаются в один и тот же момент времени, некоторые из них были остановлены (и перепланированы) в разные моменты модельного времени. Обратите внимание на использование именованных блоков (b1 и b2). Именованный блок может внутри себя иметь описание локальных переменных, хотя в этом примере это свойство не используется, а имена используют только для удобства записи.
Форма управления @событие-выражение ждет, чтобы случилось событие, прежде, чем продолжать выполнение блока. Событие может быть одной из нескольких форм:
а) переменная <или переменная> ....
b) posedge одноразрядная переменная
c) negedge одноразрядная переменная
d) событие-переменная
В форме а) выполнение задерживается до тех пор, пока одна из переменных не изменится. В форме b) и с) выполнение задерживается, пока переменная не изменится от 0, х или z к
,-----------------------------------------------------.
| module time_control; |
| |
| reg[1:0] r; |
| |
| initial #70 $stop; |
| |
| initial begin : b1 // именованный блок b1 |
| #10 r = 1; // ждать 10 единиц времени |
| #20 r = 1; // ждать 20 единиц времени |
| #30 r = 1; // ждать 30 единиц времени |
| end |
| |
| initial begin : b2 // именованный блок b2 |
| #5 r = 2; // ждать 5 единиц времени |
| #20 r = 2; // ждать 20 единиц времени |
| #30 r = 2; // ждать 30 единиц времени |
| end |
| |
| always @r begin |
| $display ("r = %0d at time %0d", r, $time); |
| end |
| |
| endmodule |
`-----------------------------------------------------'
Рис.2.14 Пример управления временем
1 (в случае posedge) или от 1, х или z к 0 (в случае negedge). В форме d) выполнение блока останавливается до тех пор, пока не выполнится событие.
,-------------------------------------------------------.
| module event_control; |
| |
| event e1,e2; |
| |
| initial @e1 begin |
| $display ("I am in the middle."); |
| ->e2; |
| end |
| |
| initial @e2 |
| $display ("I am supposed to execute last."); |
| |
| initial begin |
| $display ("I am the first."); |
| ->e1; |
| end |
| |
| endmodule |
`-------------------------------------------------------'
Рис.2.15 Пример управления событиями
Событие может быть запущено выполнением выражения->cобытие-переменная. Следующий пример на рис.2.15 использует событие-переменные для управления порядком выполнения трех блоков initial, которые выполняются в один и тот же момент модельного времени.
Выполнение модуля event_control даст следующие результаты:
I am the first.
I am in the middle.
I am supposed to execute last.
Эта форма управления обеспечивает порядок выполнения. Без оператора управления событиями планировщик языка Verilog может спланировать выполнение блоков initial в произвольном порядке.
Особой формой конструкции управления временем и событиями является ее использование внутри оператора присваивания.
Присваивание current_state = #clock_period next_state;
эквивалентно следующим двум операторам
temp = next_state;
#clock_period current_state = next_state;
и, аналогично, присваивание
current_state = @ (posedge clock) next_state;
эквивалентно двум операторам
temp = next_state;
@ (posedge clock) current_state = temp;
ПОНЯТИЕ ПАРАЛЛЕЛИЗМА
Язык Verilog имеет еще несколько управляющих структур, которые необычны для других языков программирования. Одна из них - конструкция ветвление-соединение (fork-join), другая - оператор блокировки (disable statement).
Пара fork-join
На рис.2.16 показан пример использования конструкции ветвление-соединение.
В этом примере выполнение блока initial будет остановлено, пока не будут выполнены в некотором порядке события а и b. Во время ветвления (fork) активизируется два или более путей выполнения. Когда все будут закончены, продолжается выполнение в соединении (join). Если некоторые из путей заканчиваются раньше других, эти пути останавливаются и ждут остальные.
,------------------------------------------.
| module fork_join; |
| |
| event a, b; |
| |
| initial |
| fork |
| @a; |
| @b; |
| join |
| end |
| |
| endmodule |
`------------------------------------------'
Рис.2.16 Пример параллельных процессов
Оператор блокировки
Оператор блокировки работает, как оператор прекращения break языка Си. Но в то время как оператор break в программе на языке Си только изменяет программный счетчик, оператор disable должен удалить ждущие события из очередей событий. Оператор блокировки берет имя блока в качестве аргумента и удаляет остальные события, связанные с этим блоком, из очереди. Можно блокировать только именованные блоки и задачи. Следующий пример на рис.2.17 - это модификация предыдущего, но вместо ожидания выполнения обоих событий а и b, этот модуль ждет только одного из них - или а, или b.
,------------------------------------------.
| module disable_block; |
| event a, b; |
| // Требуется имя блока |
| initial begin : block1 |
| fork |
| @a disable block1; |
| @b disable block1; |
| join |
| end |
| endmodule |
`------------------------------------------'
Рис.2.17 Пример блокировки
ФУНКЦИИ И ЗАДАЧИ
Один из наиболее мощных методов моделирования в языке Verilog - инкапсуляция части программы в виде задачи (task) или функции (function). На рис.2.19 показан пример задачи.
,-------------------------------------------------.
| function [7:0] func; |
| input i1; |
| integer i1; |
| |
| reg [7:0] rg; |
| |
| begin |
| rg=1; |
| for (i=1; i<=i1; i++) |
| rg=rg+1; |
| func=rg; |
| end |
| endfunction |
`-------------------------------------------------'
Рис.2.18 Пример функции
,-----------------------------------------------------.
| task tsk; |
| |
| input i1,i2; |
| output o1,o2; |
| |
| $display ("Task tsk, i1=%0b, i2=%0b", i1,i2); |
| #1 o1 = i1 & i2; |
| #1 o2 = i1 | i2; |
| |
| endtask |
`-----------------------------------------------------'
Рис.2.19 Пример задачи
Есть несколько различий между задачей и функцией.
Задача может содержать конструкции управления временем, а функция не может. Это означает, что функция выполняется за нулевое модельное время и выдает результат немедленно (то есть она по существу комбинационная), а задача может иметь задержку и программа, запускающая задачу, должна ждать, пока не закончится выполнение задачи или задача не будет блокирована, прежде, чем продолжить выполнение программы. Управление выполнением возвращается к оператору, непосредственно следующему за оператором, который запустил задачу или функцию.
Задача может иметь как входы, так и выходы, а функция должна иметь по меньшей мере один вход и не имеет выходов. Функция выдает результат по ее имени.
Вызов задачи - это оператор, а функция вызывается, когда на нее ссылаются в выражении. Например,
tsk (out, in1, in2);
вызывает задачу с именем tsk, а
i = func (a, b, c); // или
assign x = func (y);
вызывает функцию с именем func.
На рис.2.18 показан пример функции. Функции играют важную роль в логическом синтезе. Так как функции являются комбинационными, их можно синтезировать и использовать в описании систем. Задачи - это очень важный инструмент организации программы и улучшения ее читаемости и простоты сопровождения. Часть программы, которая используется более одного раза, следует инкапсулировать в задачу. Это помогает локализовать любое изменение до этой части программы. Если ожидается, что программа будет выдаваться интерактивно с терминала, ее также следует преобразовать в задачу для того, чтобы сэкономить на вводе. Кроме того, полезно разбивать длинные процедурные блоки на меньшие задачи, чтобы улучшить читаемость программы.
ФУНКЦИОНАЛЬНОЕ ОПИСАНИЕ
Verilog - это язык для проектирования сверху вниз, который обеспечивает функциональное описание, структурное описание и смешанное описание. В следующих нескольких разделах мы проиллюстрируем эти аспекты языка на примере полного описания 4-разрядного сумматора.
Первый пример на рис.2.20 описывает поведение модуля 4-разрядного сумматора с использованием конструкций высокого уровня языка Verilog.
Заметим, что порты sum и zero были объявлены регистрами. Это сделано, чтобы функциональный оператор мог присваивать им значение. В этом описании есть два функциональных элемента, один элемент initial и один элемент always.
Конструкция @(in1 или in2) заставляет моделирование ждать, пока один из входов in1 или in2 не изменит свое последнее значение. Без этой конструкции цикл always всегда бы выполнялся с одними и теми же значениями входов и модельное время никогда не продвинется.
Заметим, блок always можно также запрограммировать как блок initial, используя управляющую структуру forever, как показано на рис.2.21.
,----------------------------------------------------.
| module adder4 (in1,in2,sum,zero); |
| |
| input [3:0] in1; |
| input [3:0] in2; |
| output [4:0] sum; |
| output zero; |
| reg [4:0] sum; |
| reg zero; |
| |
| initial begin |
| sum = 0; |
| zero = 1; |
| end |
| |
| always @ (in1 or in2) begin |
| sum = in1 + in2; |
| if (sum == 0) |
| zero = 1; |
| else |
| zero = 0; |
| end |
| endmodule |
`----------------------------------------------------'
Рис.2.20 Функциональное описание 4-х разрядного сумматора
Следующий пример на рис.2.22 является модификацией примера на рис.2.20, он моделирует adder4, используя непрерывное присваивание.
Здесь zero является проводом, а не регистром. Когда изменяется sum после регистра, zero перевычисляется, используя третичную операцию "?:" , которая имеет тот же смысл, что и в языке Си, и является выражением, эквивалентным оператору если-то-иначе. Объявление zero проводом и его непрерывное присваивание можно записать в один оператор следующим образом:
wire zero = (sum == 0) ? 1 : 0;
,----------------------------------------------.
| initial begin |
| forever begin |
| @ (in1 or in2) begin |
| sum = in1 + in2; |
| if (sum == 0) |
| zero = 1; |
| else |
| zero = 0; |
| end |
| end |
| end |
`----------------------------------------------'
Рис.2.21 Использование initial-forever вместо always
,-----------------------------------------------.
| module adder4 (in1,in2,sum,zero); |
| input [3:0] in1; |
| input [3:0] in2; |
| output [4:0] sum; |
| reg [4:0] sum; |
| output zero; |
| |
| assign zero = (sum == 0) ? 1 : 0; |
| |
| initial sum = 0; |
| |
| always @ (in1 or in2) |
| sum = in1 + in2; |
| endmodule |
`-----------------------------------------------'
Рис.2.22 Использование непрерывного присваивания
СТРУКТУРНОЕ ОПИСАНИЕ
В примере на рис.2.23 показана реализация модуля 4-х разрядного сумматора как структуры, состоящей из субмодулей 1-разрядного полного сумматора и вентилей.
,------------------------------------------------------.
| module adder4 (in1,in2,sum,zero); |
| |
| input [3:0] in1; |
| input [3:0] in2; |
| output [4:0] sum; |
| output zero; |
| |
| fulladd u1 (in1[0],in2[0], 0,sum[0],c0); |
| fulladd u2 (in1[1],in2[1],c0,sum[1],c1); |
| fulladd u3 (in1[2],in2[2],c1,sum[2],c2); |
| fulladd u4 (in1[3],in2[3],c2,sum[3],sum[4]); |
| |
| nor u5 (zero,sum[0],sum[1],sum[2],sum[3],sum[4]); |
| |
| endmodule |
`------------------------------------------------------'
Рис.2.23 Структурное описание 4-х разрядного сумматора
В этом примере 4-х разрядный сумматор состоит из четырех элементов модулей 1-разрядного сумматора (fulladd) и одного элемента модуля вентиля НЕ-ИЛИ. В этой реализации описывается аппаратная структура и есть однозначное соответствие со схемотехникой. Она использует два типа модулей более низкого уровня: fulladd и nor. Хотя модуль fulladd определен пользователем, а nor - это примитив языка Verilog, оба используются одинаковым способом.
Заметим, что структурное описание неявно объявляет три провода: с0, с1 и с2. Эти провода связывают разряд переноса одной ступени fulladd со входом другой ступени. Verilog разрешает неявное объявление одиночных разрядов проводами. Если межсоединение представляет собой многоразрядную шину, требуется явное описание, например:
wire [3:0] databus;
Заметим также, что когда используется модуль, важен порядок входных/выходных портов. Модуль более высокого уровня adder4 требует установления связи между его схемой и соответствующими портами модуля более низкого уровня fulladd. На рис.2.24 показана простая реализация модуля fulladd.
,------------------------------------------------------.
| module fulladd (in1,in2,сarryin,sum,carryout); |
| |
| input in1,in2,carryin; |
| output sum,carryout; |
| |
| assign {carryout,sum} = in1 + in2 + carryin; |
| endmodule |
`------------------------------------------------------'
Рис.2.24 Функциональное описание 1-разрядного полного сумматора
СМЕШАННОЕ ПРЕДСТАВЛЕНИЕ
Последний пример на рис.2.25 показывает adder4 как комбинацию структурных и функциональных элементов.
Эта модель вычисляет выход sum посредством структурных элементов модулей fulladd, а выход zero вычисляется с использованием функционального элемента.
,------------------------------------------------------.
| module adder4 (in1,in2,sum,zero); |
| |
| input [3:0] in1; |
| input [3:0] in2; |
| output [4:0] sum; |
| output zero; |
| reg zero; |
| |
| fulladd u1 (in1[0],in2[0], 0,sum[0],c0); |
| fulladd u2 (in1[1],in2[1],c0,sum[1],c1); |
| fulladd u3 (in1[2],in2[2],c1,sum[2],c2); |
| fulladd u4 (in1[3],in2[3],c2,sum[3],sum[4]); |
| |
| always @ sum |
| if (sum == 0) |
| zero = 1; |
| else |
| zero = 0; |
| |
| endmodule |
`------------------------------------------------------'
Рис.2.25 Смешанное представление