Files
APS/Basic Verilog structures/Verilog syntax.md
Andrei Solodovnikov f4c0960704 Initial commit
2023-09-07 17:06:55 +03:00

26 KiB
Raw Blame History

Конструкции языка Verilog

Verilog чувствителен к регистру. Это значит что "ЗаписЬ" и "запись" Verilog будет воспринимать как два разных слова. Все ключевые слова Verilog записываются строчными буквами.
Каждое утверждение оканчивается точкой с запятой (;). Таким образом, символ переноса строки не прерывает выражение и может быть использован для повышения читаемости кода.
В Verilog Си-подобные комментарии. Вы можете закомментировать одну строку или окончание строки с помощью двух косых черт (//). Кроме того, вы можете использовать многострочные комментарии — всё что находится между парами символов /* и */ считается комментарием.
Любое имя, определяемое пользователем (имя переменной/модуля и т.п.) должно начинаться с латинской буквы, а не с числа. Определяемые пользователем имена не должны совпадать с ключевыми словами Verilog.

В тексте будет много определений синтаксиса языка Verilog. При появлении новых конструкций будет использована следующая нотация:

жирным будут выделены ключевые слова Verilog, такие слова необходимо использовать в точности как они написаны
курсивом  будут выделены имена, определяемые пользователем
<>              в скобках будут указаны требуемые характеристики, такие как тип, направление input/output размерность и т.п.

По прочтению этой главы вы узнаете:

  • Типы данных, представленных в языке Verilog;
  • Как описывать базовую конструкцию модуля Verilog.

Типы данных

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

Verilog поддерживает четыре базовых состояния, которые может принять сигнал: 0, 1, X и Z. Ниже представлено описание каждого из этих состояний:

Значение Описание
0 Логический ноль / ЛОЖЬ
1 Логическая единица / ИСТИНА
X Неопределенное (неизвестное) или непроинициализированное состояние
Z Высокоимпедансное состояние

Класс данных "Цепь"

Как говорилось выше, каждому сигналу в Verilog должен быть назначен тип данных. Класс данных под названием "цепь" моделирует соединение (электрическую цепь) между компонентами и может принимать значения 0, 1, X, Z. Сигнал цепи постоянно управляется источником сигнала и меняет своё значение каждый раз, когда меняется значение на источнике (цепь не может хранить значение).

../.pic/Basic%20Verilog%20structures/verilog%20syntax/nets_variables.png

Рассмотрим рисунок выше. Красным цветом выделены сигналы класса "цепь". Сигнал net_11 используется для соединения выхода логического элемента И со входом данных D-триггера data_0. Выход логического элемента И является источником сигнала для цепи net_11. Выход data_0 является источником сигнала net_2.

В Verilog множество типов класса цепь, но самым распространенным является тип wire(провод). Вы можете представить подобный сигнал, как провод, подключенный к некому источнику. Значение сигнала в любой части провода одинаково и совпадает со значением подключенного к нему источника сигнала. Если провод не подключен ни к одному источнику, он находится в высокоимпедансном состоянии (принимает значение Z).

Есть еще один тип, полностью идентичный типу wire, который называется tri. Эти типы взаимозаменяемы и разделены только для повышения читаемости кода. Тип tri используется, когда сигналом управляют несколько источников.

../.pic/Basic%20Verilog%20structures/verilog%20syntax/

Пример сигнала, управляемого несколькими источниками.

Класс данных "Переменная"

Verilog также поддерживает типы данных, которые моделируют ячейки памяти. Сигналы класса "переменная" могут принимать значения 0, 1, X, Z. Типы класса "переменная" могут хранить присвоенное им значение до очередного присваивания. Ниже представлено описание переменных типов:

Тип Описание
reg Переменная, моделирующая базовую ячейку памяти. Может принимать значения 0, 1, X, Z
integer 32-битная переменная, представленная в дополнительном коде, представляющая диапазон чисел [2147483648:2147483647]
real 64-битная переменная, представляющая числа с плавающей точкой в диапазоне [2.2×10308:2.2×10308]
time Беззнаковая 64-битная переменная, принимающая значения в диапазоне [0:9.2×1018]
realtime Синоним типа real. Используется для повышения читаемости

Векторы

В Verilog вектором называют одномерный массив элементов. Все типы класса "цепь", а также тип reg могут использоваться для формирования вектора. Синтаксис объявления вектора представлен ниже:

<тип> [<старший индекс>:<младший индекс>] имя_вектора

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

wire [7:0] sum; // Объявляется 8-битный вектор с именем sum типа wire.
                // Старший индекс равен 7, младший — 0.
                // Векторы типа wire обычно называют "шиной"(bus).

reg [15:0] Q;   // В данной строке объявлен 16-битный вектор типа reg.

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

фрагмент кода описание
sum[0]; Обращение к младшему биту вектора sum, объявленного выше
Q[15:8]; Обращение к старшим восьми битам 16-битного вектора Q, объявленного выше

Обратите внимание, что нельзя объявить вектор типа integer, time, real, realtime. Однако, типы integer и time являются векторами сами по себе. Иными словами, вы не можете объявить следующий вектор:

integer [7:0] dunno_what_size_of_this_object;

но можете обращаться к отдельным битам объявленного объекта как если бы он был вектором:

фрагмент кода описание
integer int_object;
int_object[31];

Обращение к старшему биту вектора

Таким образом, вы можете считать integer синонимом reg signed [31:0] (о значении signed будет чуть позже), а time синонимом reg [63:0]

Поскольку real и realtime представляют числа с плавающей точкой, они не считаются векторами, и вы не можете обратиться к их отдельным битам.

Множества (Arrays)

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

<тип элемента> [<старший индекс элемента> : <младший индекс элемента>] 
имя_множества [начальный индекс множества : конечный индекс множества]

Пример:

reg [7:0] mem [0:4095]; // Объявляется множество 8-битных векторов типа
                        // reg, размером 4096.
                        // Множество, элементами которого являются вектора
                        // типа reg (например то, что объявлено здесь)
                        // называют памятью.
                        // Элементы памяти называют "словами".

integer A [1:100];      // Объявляется множество со 100 элементами типа
                        // integer.

К элементу множества можно обратиться, используя его индекс. Кроме того, если элементом множества является вектор, можно так же обратиться к отдельному биту этого вектора, добавив и его индекс.
Примеры:

фрагмент кода описание
mem[2]; Обращение к третьему элементу множества mem (третьему, поскольку нумерация начинается с нуля).
Подобное обращение вернет 8-битный вектор типа reg.
mem[2][7]; Обращение к старшему биту третьего элемента множества mem.
A[2]; Обращение ко второму элементу множества A (в этом случае, нумерация начинается с единицы, поскольку так было указано при объявлении множества A)

Целочисленные константы

В Verilog есть два способа выразить целое число. Первый способ — это обычная запись десятичного числа, формируемая последовательностью цифр от 0 до 9, с опциональным указанием знака перед ней. Второй способ — это запись целого числа с указанием основания системы исчисления. Данная форма записи состоит из трех частей:

  1. опциональной константы размера
  2. апострофа ('), за которым указывается формат основания системы исчисления
  3. набора цифр, представляющих значение числа
<размер>'[s]<основание><значение>

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

Первая часть (константа размера) указывает точное число бит целочисленной константы. Для указания размера используется ненулевое положительное десятичное число. Например, для указания двух шестнадцатиричных цифр потребуется 8 бит, поскольку каждая шестнадцатиричная цифра занимает 4 бита.
Вторая часть, формат основания системы исчисления состоит из апострофа и нечувствительной к регистру букве, обозначающей основание системы исчисления, перед которой допустимо указать символ s(или S) для указания того, что число знаковое.

Синтаксис Описание
'b Беззнаковое двоичное
'o Беззнаковое восьмеричное
'd Беззнаковое десятичное
'h Беззнаковое шестнадцатиричное
'sb Знаковое двоичное
'so Знаковое восьмеричное
'sd Знаковое десятичное
'sh Знаковое шестнадцатиричное

Третья часть содержит беззнаковое число, записанное цифрами, разрешенными для данной системы исчисления. Шестнадцатиричные цифры a-f не чувствительны к регистру. Кроме того, в качестве цифры могут использоваться символы x, z и ?(аналогичен z) для обозначения неопределенного/высокоимпендансного состояния соответствующих им бит.
Если размер беззнакового числа в третьей части меньше размера, указанного в первой части, это число дополняется слева нулями за исключением случаев, когда в старшем бите числа записано x или z, в этом случае, число дополняется слева x или z соответственно.
Если размер беззнакового числа в третьей части больше размера, указанного в первой части, это число обрезается слева.

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

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

Пример 1 — Беззнаковые константы:

фрагмент кода описание
659 знаковое десятичное число
'h 837FF беззнаковое шестнадцатиричное число
'o7460 беззнаковое восьмеричное число
4af запрещенная запись (шестнадцатиричная запись возможна лишь с 'h)

Пример 2 — Знаковые константы:
фрагмент кода описание
4'b1001 4-битное двоичное беззнаковое число
5 'D 3 5-битное десятичное беззнаковое число
3'b01x 3-битное беззнаковое двоичное число, у которого неизвестен младший бит
12'hx 12-битное неизвестное число
16'hz 16-битное высокоимпеднансное число

Пример 3 — Использование знака с константами:
фрагмент кода описание
8 'd -6 Запрещено, константа в третьей части записывается в виде беззнакового числа
-8 'd 6 Здесь записано -6 в дополнительном коде (1111_1010), хранящееся в 8-ми битах.
4 'shf Здесь записано 4-битное число с двоичной записью: '1111', которое интерпретируется как '-1' в десятичной записи.
Это эквивалент записи -4'h 1
-4 'sd15 Это эквивалент записи -(-4'd 1), или двоичного '0001'
16'sd? То же, что и 16'hz

Пример 4 — Выравнивание размера:
фрагмент кода описание
8 'h F0AA Константа превышает указаный размер и будет обрезана до AA
8'sb1001 Константа меньше указанного размера и дополняется слева нулями до "00001001"
13'h1z1 Константа меньше указанного размера и дополняется слева Z до "zzzzzzzzz0001"

Пример 5 — Использование нижнего подчеркивания:
фрагмент кода
27_195_000
16'b0011_0101_0001_1111
32 'h 12ab_f001

Присваивания между различными типами

Verilog является так называемым языком со слабой типизацией, что означает, что он допускает присваивания между различными типами. Причиной тому является то, что Verilog относится к любому типу как к группе битов. Присваивая значения одного типа другому, Verilog автоматически обрезает или дополняет необходимым для присваивания количеством старших бит. Ниже приведен пример того, как Verilog справляется с присваиваниями между различными типами (предположим, что мы создали переменную ABC_TB, которая объявлена как reg [2:0])

ABC_TB = 2'b00; // ABC_TB будет присвоено 3'b000. Автоматически добавлен
                // старший бит
ABC_TB = 5;     // ABC_TB будет присвоено 3'b101. Целочисленное число
                // размером минимум 32 бита обрезано до 3-х.
ABC_TB = 8;     // ABC_TB будет присвоено 3'b000. Целочисленное число
                // со значащими битами "1000" будет брезано до 3-х.

Проверь себя

Чаще всего используемыми типами в Verilog являются wire и reg. В чем их фундаментальное различие?

  1. У них нет различий, поскольку они оба могут принимать значения 0, 1, X, Z.
  2. wire является типом класса "цепь" — это значит, что значением этого сигнала постоянно управляет источник. reg является типом класса "переменная" — это значит, что он будет хранить своё значение после присваивания.
  3. wire может принимать лишь значения 0 и 1, в то время как reg может принимать значения 0, 1, X, Z.
  4. Они не могут подаваться на входы друг друга.

Создание модуля в Verilog

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

Синтаксис объявления модуля:

module module_name (<port_list> and <port_definitions>);
// module_items
endmodule

../.pic/Basic%20Verilog%20structures/verilog%20syntax/file_structure.png

Объявления портов

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