24 KiB
Описание мультиплексора на языке SystemVerilog
Мультипле́ксор — устройство, имеющее несколько сигнальных входов, один или более управляющих входов и один выход. Мультиплексор позволяет передавать сигнал с одного из входов на выход; при этом выбор желаемого входа осуществляется подачей соответствующей комбинации управляющих сигналов.
Иными словами, мультиплексор — это переключатель (коммутатор), соединяющий выход с одним из множества входов.
Для начала создадим простой двухвходовой мультиплексор. Предположим, на Y
нам необходимо передать один из сигналов — D0
или D1
в зависимости от значения управляющего сигнала S
: когда S==0
, на Y
подается сигнал D0
, в противном случае — D1
.
На языке SystemVerilog это можно описать несколькими способами. Первый — с помощью тернарного условного оператора:
Тернарный условный оператор
О тернарном условном операторе
Операторы бывают различной арности(количества аргументов оператора[операндов]):
- унарный (с одним операндом), пример:
-a
; - бинарный (с двумя операндами), пример:
a+b
; - тернарный (с тремя операндами), пример:
cond ? if_true : false
; - и др.
Несмотря на то, что тернарным оператором может быть любой оператор, принимающий три операнда, обычно под ним подразумевается тернарный условный оператор, работающий следующим образом:
<условие> ? <значение_если_условие_истинно> : <значение_если_условие_ложно>
Первым операндом идет некоторое условие (любое выражение, которое может быть сведено к 1 или 0). Далее ставится знак вопроса (часть тернарного оператора, отделяющая выражение первого операнда от выражения второго операнда). Далее пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось истинным. После чего ставится двоеточие (часть тернарного условного оператора, отделяющая выражение второго операнда от выражения третьего операнда). Затем пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось ложным.
Пример для языка C++:
a = b+c >= 5 ? b+c : b+d;
Сперва вычисляется первый операнд (выражение b+c >= 5
). Если это выражение оказалось истинным (равно единице), то переменной a
будет присвоено значение второго операнда (выражения b+c
), в противном случае переменной a
будет присвоено значение третьего операнда (выражения b+d
).
logic Y;
assign Y = S==1 ? D1 : D0;
Данное выражение говорит нам, что если S==1
, то Y
присваивается значение D1
, в противном случае — значение D0
.
Также мультиплексор можно описать через конструкцию if-else
в блоке always
.
Блок if-else
Далее будет ключевой параграф сложного для понимания текста, очень важно запомнить что там написано и разобрать приведенные листинги.
Блок always
Блок always
— это специальный блок, который позволяет описывать комбинационные и последовательностные схемы, используя более сложные конструкции, такие как if-else
, case
. На самом деле, в языке SystemVeriog помимо общего блока always
, которым можно описать любой вид логики, существует множество специализированных блоков, предназначенных для описания отдельно комбинационной, синхронной и последовательностной асинхронной логики соответственно:
- always_comb
- always_ff
- always_latch
Мультиплексор можно описать в любом из этих блоков, разница будет лишь в том, к чему именно будет подключен выход мультиплексора: к проводу, регистру, или защелке.
При присваивании внутри блоков always
используйте специальный оператор неблокирующего присваивания <=
. Бывает еще оператор блокирующего присваивания =
, объяснение различий в этих операторах требует отдельного документа, поэтому на текущий момент, во избежание проблем в будущем просто запомните: внутри любого блока always необходимо использовать только оператор неблокирующего присваивания <=.
logic Y;
always_comb begin // 1) Используется always_comb, т.к. мы хотим подключить
// выход мультиплексора к проводу
if(S) begin // 2) if-else может находиться только внутри блока always.
Y <= D1; // 3) Используется оператор неблокирующего присваивания.
end else begin
Y <= D0;
end
end
Кроме того, важно запомнить, что присваивание сигналу допускается только в одном блоке always.
Неправильно:
logic Y;
always_comb begin
if(S==1) begin
Y <= D1;
end
end
always_comb begin
if(S==0) begin // Нельзя выполнять операцию присваивания
Y <= D0; // для одного сигнала (Y) в нескольких
end // блоках always!
end
Если нарушить это правило то в будущем (возможно не сразу, но в любом случае — обязательно), возникнет ошибка, которая так или иначе будет связана с multiple drivers.
Остановитесь на выделенном выше фрагменте документа, пока полностью не разберете его. Без освоения всех описанных выше особенностей языка SystemVerilog вы столкнетесь в будущем с множеством ошибок.
Будьте очень внимательны при использовании данного блока. Он обманчиво похож на условный блок в языках программирования, из-за чего возникает желание пользоваться им так же, как можно пользоваться условными блоками в языках программирования. Это не так. Обратите внимание на то, что данный блок выше упоминается исключительно как блок if-else
. При реализации мультиплексора, у любого блока if
должен быть соответствующий блок else
(иначе у мультиплексора будет только один вход, и в итоге на его выходе будет сгенерирована защелка, о которой будет рассказано позднее). Существуют ситуации, когда блок if
может быть использован без блока else
(например при описании дешифраторов или сигналов разрешения записи). Однако при описании мультиплексоров таких ситуаций не бывает.
case-блок
Мультиплексор также можно описать с использованием конструкции case. Блок case
лучше подходит для описания мультиплексора, когда у того более двух входов (ведь в случае конструкции if-else
пришлось бы делать вложенное ветвление).
Конструкция case
представляет собой инструмент множественного ветвления, который сравнивает значение заданного выражения с множеством вариантов, и, в случае первого совпадения, использует соответствующую ветвь. На случай, если ни один из вариантов не совпадет с заданным выражением, конструкция case
поддерживает вариант default
. Данная конструкция визуально похожа на оператор switch-case
в Си, однако вы должны понимать, что используется она не для написания программы, а описания аппаратуры, в частности мультиплексоров/демультиплексоров и дешифраторов.
Конструкция case
, наряду с if-else
, может быть описана только в блоке always
.
Реализация двухвходового мультиплексора с помощью case
может выглядеть так:
logic Y;
always_comb begin
case(S) // Описываем блок case, где значение сигнала S
// будет сравниваться с различными возможными его значениями
1'b0: Y <= D0; // Если S==0, то Y = D0
1'b1: Y <= D1;
endcase // Каждый case должен заканчиваться endcase
end // (так же как каждый begin должен оканчиваться end)
Рассмотрим вариант посложнее и опишем следующую схему:
Здесь уже используется мультиплексор 4в1. Управляющий сигнал S
в данном случае двухбитный. В блоке case
мы перечисляем всевозможные варианты значений S
и описываем выход мультиплексора.
module case_mux_ex(
input logic A,
input logic B,
input logic C,
input logic D,
input logic [2:0] S,
output logic Y
);
always_comb begin
case(S)
3'b00: Y <= A;
3'b01: Y <= C | B; // в блоке case можно мультиплексировать
// не только провода, но и логические выражения
3'b10: Y <= (C|B) & D;
/*
Обратите внимание, что разрядность сигнала S — 3 бита.
Это означает, что есть 8 комбинаций его разрядов.
Выше было описано только 3 комбинации из 8.
Если для всех остальных комбинаций на выходе мультиплексора должно
быть какое-то одно значение "по умолчанию", используется специальная
комбинация "default":
*/
default: Y <= D;
endcase
end
endmodule
Защелка
Очень важно при описании мультиплексора с помощью блока case
описывать оставшиеся комбинации управляющего сигнала с помощью default
(а при использовании блока if
— описывать блок else
) — в противном случае в вашей схеме может появиться защелка (даже несмотря на то, что для описания защелок в SytemVerilog есть отдельный блок always
: always_latch
).
Защелка представляет из себя элемент памяти, причем данные в нее записываются не по тактовому синхроимпульсу, а на протяжении относительно длинного промежутка времени, когда управляющий сигнал "открывает" защелку (в этом случае говорят, что защелка становится "прозрачной"). Из-за этого она не является ни комбинационной, ни синхронной схемой.
Защелка — это всего лишь элемент цифровой схемы и будет неправильно говорить о нем в терминах "плохой" или "хороший". Защелка имеет свои плюсы для ASIC-проектирования. Однако защелка совершенно не подходит при проектировании устройств под ПЛИС.
Обычно появление защелки в цифровой схеме говорит об ошибке разработки: в случае, если планировалась комбинационная логика, добавление защелки приведет к непредвиденному удержанию предыдущих значений (поскольку защелка сохраняет предыдущее значение до прихода очередной комбинации управляющего сигнала, описанной в блоке case
). В случае синхронной логики, будет непредсказуемое поведение данных, т.к. информация в защелку будет записываться не синхронно, как это ожидается, а на протяжении длительного промежутка времени, пока защелка "открыта".
Пример:
module unexpected_d_latch_ex(
input logic [1:0] S,
input logic D0,
input logic D1,
output logic R
);
always_comb begin
case(S)
2'b00: R <= D0;
2'b01: R <= D1;
// Поскольку сигнал S двухразрядный, осталось еще две комбинации:
// S == 2'b10
// S == 2'b11
endcase
end
endmodule
На данной схеме различные её части обозначены следующим образом:
- Мультиплексор, который мы хотели описать
- Защелка
- Мультиплексор, который был добавлен чтобы генерировать сигнал, "открывающий" защелку
- Константная единица (питание)
- Константный ноль (земля).
В случае, если S == 0
или S == 1
, на выход мультиплексора 3 будет подана единица, которая переведет защелку в "прозрачный" режим (данные с выхода мультиплексора 1 будут проходить сквозь защелку).
В случае, если S > 1
, на выход мультиплексора 3 будет подан ноль, который переведет защелку в "непрозрачный" режим (данные с выхода мультиплексора 1 не будут идти сквозь защелку, вместо этого на выходе защелки останутся последние данные, которые шли через нее, пока она была "открыта").
Кроме того, защелка усложняет временной анализ и ухудшает временные характеристики, из-за чего схема может работать на меньших частотах, чем могла бы.
Таким образом, во избежание появления защелки, необходимо описывать все возможные комбинации в блоке case
(при необходимости покрывая множество оставшихся комбинаций с помощью default
) и для каждого блока if
описывать блоки else
. В случае, если подобная комбинация не планируется к использованию, можно присвоить сигналу значение ноль. Конечно в этом случае будет создана избыточная логика для присваивания ненужного значения, которое никогда не должно произойти (и существуют способы описания аппаратуры, позволяющие этого избежать), но в данном случае это самый простой способ.
Итоги
- Мультиплексор — это комбинационный блок, подающий на выход один из нескольких входных сигналов.
- Мультиплексор можно описать множеством способов, среди них:
- использование тернарного условного оператора через непрерывное присваивание;
- использование конструкции
if-else
внутри блокаalways
; - использование конструкции
case
внутри блока always.
- Во избежание появления защелок при описании мультиплексора, необходимо убедиться что у блоков
if
есть соответствующие им блокиelse
, а у мультиплексоров описаны все комбинации управляющего сигнала (при необходимости, множество оставшихся комбинаций можно покрыть с помощью комбинацииdefault
). Появление непреднамеренной защелки в дизайне ведет к ухудшению временных характеристик, избыточному использованию ресурсов, а так же непредсказуемому поведению схемы из-за возможного удержания сигнала. - Важно отметить, что блоки
if-else
иcase
могут использоваться не только для описания мультиплексоров. - Конструкции
if-else
иcase
в рамках данных лабораторных работ можно описывать только внутри блокаalways
. При работе с этим блоком необходимо помнить следующие особенности:- Существует несколько типов блока
always
:always_comb
,always_ff
,always_latch
, определяющих то, к чему будет подключена описанная в этом блоке логика: проводу, регистру или защелке соответственно. - Внутри блока always следует использовать оператор неблокирующего присваивания
<=
. - Присваивание для любого сигнала возможно только внутри одного блока always. Два разных сигнала могут присваиваться как в одном блоке always, так каждый в отдельном, но операция присваивания одному и тому же сигналу в двух разных блоках always — нет.
- Существует несколько типов блока
Проверь себя
Как, по-вашему, описать на языке SystemVerilog схему, приведённую ниже?