Один wire vs много
До сих пор каждый сигнал, который мы видели, был шириной в один бит. Реальные проекты почти никогда так не выглядят: адреса - 16 или 32 бита, шины данных - 8 или 64, RGB-пиксели - 24. Verilog даёт один механизм, чтобы сделать любой сигнал многобитным: добавить диапазон в квадратных скобках.
wire [7:0] data; // 8-битный wire, бит 7 - MSB, бит 0 - LSB
reg [15:0] address; // 16-битный reg
output reg [31:0] result; // 32-битный output модуля
Числа в скобках - позиции бит старшего и младшего. [7:0] значит "у этого сигнала биты, пронумерованные от 7 до 0", что даёт 8 бит. Первое число - высокий индекс. Второе - низкий.
Иногда можно встретить [0:7] - то же количество бит, противоположный endianness для slice. Форма [high:low] - подавляющее соглашение в индустрии; держись её, если нет веской причины делать иначе.
Пример: 8-битный сумматор
Каждый + складывает два 8-битных вектора и даёт 9-битный результат. Литералы вроде 8'd10 означают "8-битное десятичное значение 10" - их разбираем в Number Literals.
Нарезка: выбираем биты
Когда есть вектор, можно вытаскивать отдельные биты или непрерывные диапазоны:
Не зацикливайся на строке force в testbench - нам просто нужен способ внедрить значение, чтобы показать slice. Интересны сами нарезки.
Несколько правил:
- Направление slice должно совпадать с объявлением. Если объявил
[7:0], режь как[high:low]. Обратное направление - синтаксическая ошибка. - Нарезка вне диапазона даёт
x(unknown) в симуляции. Синтезатор может предупредить или выдать ошибку. - Bit-select - zero-based по индексу, который ты написал:
data[0]- это бит с именем0, который (для объявления[7:0]) и есть LSB.
Slice с переменной базой: +: и -:
Частая потребность: "дай 8 бит, начиная с бита N". Нельзя написать data[N+7:N] напрямую, потому что Verilog требует, чтобы оба конца диапазона были константами. Синтаксис, который это решает:
data[base +: width] // width бит, начиная с `base`, идём ВВЕРХ
data[base -: width] // width бит, начиная с `base`, идём ВНИЗ
Ширина константная (берём по 8 бит за раз), а база может быть runtime-выражением. Это именно то, что нужно для byte-адресуемых memories, отводов shift-регистров и т.д.
Массивы: шаг за пределы векторов
Вектор - один многобитный сигнал. Массив - коллекция векторов, индексируемых независимо:
reg [31:0] mem [0:1023];
В этом объявлении два диапазона, и они значат разное:
[31:0]- packed-размерность - ширина каждого отдельного слова.[0:1023]- unpacked-размерность - сколько слов.
То есть mem - это 1024 отдельных 32-битных регистра. Доступ к одному - по одному индексу:
mem[5] = 32'hCAFE_BABE; // запись в слово 5
data = mem[address]; // чтение слова по `address`
Это крошечная memory, держащая квадраты. Реальные проекты используют тот же паттерн для register file, lookup-таблиц, FIFO и любого on-chip хранилища больше одного вектора.
Packed vs unpacked: почему это важно
Разделение packed/unpacked всплывает повсюду. Знание, что есть что, экономит много отладки:
- Packed-вектор - это один сигнал. К нему можно относиться как к числу:
data + 1работает,data == 32'h0работает,data[7:0]работает. - Unpacked-массив - это много сигналов. К нему нельзя относиться как к числу:
mem + 1- синтаксическая ошибка. Сначала выбери конкретное слово.
Несколько packed-размерностей тоже легальны:
reg [3:0][7:0] regs; // 4 байта, спакованных в 32-битный сигнал
regs[0] - байт (младший байт). regs целиком - 32 бита. SystemVerilog активно этим пользуется.
Несколько unpacked-размерностей создают 2D-memory:
reg [31:0] frame [0:479][0:639]; // 480x640 32-битных пикселей
Доступ к одному пикселю - frame[y][x]. Так бы выглядел image buffer в HDL.
Что дальше
Теперь умеешь объявлять и манипулировать сигналом любой ширины. Следующий документ - Parameters - показывает, как сделать эти ширины настраиваемыми, чтобы один module работал на 8 битах в одном instance и на 32 - в другом. Потом перейдём к правилам записи числовых литералов (8'b1010_1100, 32'hDEAD_BEEF) и значениям x/z, которые появляются, когда сигнал ничем не приводится.
Часто задаваемые вопросы
Что такое вектор в Verilog?
Вектор - это многобитный сигнал. Объявляется добавлением диапазона к wire или reg: wire [7:0] data - 8-битный wire. Числа в скобках - позиции бит - здесь бит 7 самый старший, бит 0 самый младший. Можно отрезать один бит (data[3]) или непрерывный диапазон (data[7:4]).
Что значит [7:0] в Verilog?
[7:0] объявляет диапазон от бита 7 до бита 0 включительно - 8-битный сигнал, где бит 7 - most significant. Первое число - высокий индекс, второе - низкий. Можно писать [0:7] для little-endian-индексации, но [high:low] - наиболее распространённое соглашение в промышленном коде.
Как нарезать биты в Verilog?
Через индексирование в квадратных скобках. data[3] выбирает один бит. data[7:4] выбирает старшие четыре бита как 4-битный вектор. Slice должен идти в том же направлении, что и объявление - если объявил [7:0], режь [high:low]. SystemVerilog также добавляет data[3 +: 4] для slice с переменной базой и постоянной шириной.
В чём разница между packed и unpacked массивом в Verilog?
Packed-массив - это одна непрерывная шина: reg [31:0] word - это один 32-битный сигнал. Unpacked-массив (или 'memory') - это коллекция независимых слов: reg [31:0] mem [0:1023] - это 1024 отдельных 32-битных регистра. Можно читать или писать целое слово unpacked-массива, но нельзя оперировать всем массивом как одним сигналом.
Как объявить memory в Verilog?
reg [31:0] mem [0:1023]; объявляет memory из 1024 элементов, каждый шириной 32 бита. Первая пара скобок - ширина слова (packed); вторая - количество слов (unpacked). Доступ к элементу - mem[address], а к нарезке этого элемента - mem[address][7:0], когда включена индексация SystemVerilog-2005.