Menu

Векторы и массивы в Verilog: многобитные сигналы

Как объявлять многобитные сигналы через [7:0], нарезать их, объединять и в чём разница между packed-вектором и memory-массивом.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Один 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.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ