Files
APS/Basic Verilog structures/Multiplexors.md
Andrei Solodovnikov 1ac13c1828 Update Multiplexors.md
2025-02-20 18:13:04 +03:00

233 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Описание мультиплексоров в SystemVerilog
**Мультипле́ксор** — устройство, имеющее **несколько сигнальных входов**, **один или более управляющих входов** и **один выход**. Мультиплексор позволяет передавать сигнал **с одного из входов на выход**; при этом выбор желаемого входа осуществляется подачей соответствующей комбинации управляющих сигналов[[1]](https://ru.wikipedia.org/wiki/Мультиплексор_(электроника)).
Иными словами, мультиплексор — это переключатель (коммутатор), соединяющий выход с одним из множества входов.
![../.pic/Basic%20Verilog%20structures/multiplexor/fig_01.drawio.svg](../.pic/Basic%20Verilog%20structures/multiplexors/fig_01.drawio.svg)
Для начала создадим простой двухвходовой мультиплексор. Предположим, на `Y` нам необходимо передать один из сигналов — `D0` или `D1` в зависимости от значения управляющего сигнала `S`: когда `S==0`, на `Y` подается сигнал `D0`, в противном случае — `D1`.
![../.pic/Basic%20Verilog%20structures/multiplexors/fig_02.drawio.svg](../.pic/Basic%20Verilog%20structures/multiplexors/fig_02.drawio.svg)
На языке SystemVerilog это можно описать несколькими способами. Первый — с помощью **[тернарного условного оператора](https://ru.wikipedia.org/wiki/Тернарная_условная_операция)**:
## Тернарный условный оператор
<details>
<summary>О тернарном условном операторе</summary>
Операторы бывают различной **[арности](https://ru.wikipedia.org/wiki/Арность)**(количества аргументов оператора[операндов]):
- унарный (с одним операндом), пример: `-a`;
- бинарный (с двумя операндами), пример: `a+b`;
- тернарный (с тремя операндами), пример: `cond ? if_true : if_false`;
- и др.
Несмотря на то, что тернарным оператором может быть любой оператор, принимающий три операнда, обычно под ним подразумевается **тернарный условный оператор**, работающий следующим образом:
```text
<условие> ? <значение_если_условиестинно> : <значение_если_условиеожно>
```
Первым операндом идет некоторое условие (любое выражение, которое может быть сведено к 1 или 0). Далее ставится знак вопроса (часть тернарного оператора, отделяющая выражение первого операнда от выражения второго операнда). Далее пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось истинным. После чего ставится двоеточие (часть тернарного условного оператора, отделяющая выражение второго операнда от выражения третьего операнда). Затем пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось ложным.
Пример для языка C++:
```c++
a = b+c >= 5 ? b+c : b+d;
```
Сперва вычисляется первый операнд (выражение `b+c >= 5`). Если это выражение оказалось истинным (равно единице), то переменной `a` будет присвоено значение второго операнда (выражения `b+c`), в противном случае переменной `a` будет присвоено значение третьего операнда (выражения `b+d`).
</details>
```Verilog
logic Y;
assign Y = S==1 ? D1 : D0;
```
Данное выражение говорит нам, что если `S==1`, то `Y` присваивается значение `D1`, в противном случае — значение `D0`.
![../.pic/Basic%20Verilog%20structures/multiplexors/fig_03.drawio.svg](../.pic/Basic%20Verilog%20structures/multiplexors/fig_03.drawio.svg)
Также мультиплексор можно описать через конструкцию `if-else` в блоке `always`.
> Далее будет ключевой параграф сложного для понимания текста, очень важно запомнить, что там написано и разобрать приведенные листинги.
<br><br>
---
## Блок always
Блок `always` — это специальный блок, который позволяет описывать комбинационные и последовательностные схемы (см. документ "[Последовательностная логика](../Introduction/Sequential%20logic.md)"), используя более сложные конструкции, такие как `if-else`, `case`. На самом деле, в языке SystemVerilog помимо общего блока `always`, которым можно описать любой вид логики, существует множество специализированных блоков, предназначенных для описания отдельно комбинационной, синхронной и последовательностной асинхронной логики соответственно:
- always_comb
- always_ff
- always_latch
Мультиплексор можно описать в любом из этих блоков, разница будет лишь в том, к чему именно будет подключен выход мультиплексора: к проводу, регистру, или защелке.
В зависимости от вида `always`-блока используется один из двух видов присваиваний: **блокирующее присваивание** (`=`) и **неблокирующего присваивания** (`<=`). Подробно о различиях между присваиваниями рассказано в [этом документе](Assignments.md). До его прочтения запомните:
- внутри блока `always_ff` и `always_latch` необходимо использовать оператор неблокирующего присваивания (`<=`);
- внутри блока `always_comb` необходимо использовать оператор блокирующего присваивания (`=`).
---
> Остановитесь на выделенном выше фрагменте документа, пока полностью не разберете его. Без освоения всех описанных выше особенностей языка SystemVerilog вы столкнетесь в будущем с множеством ошибок.
<br><br>
## Блок if-else
Реализация мультиплексора через блок `if-else` похожа на реализацию мультиплексора через тернарный оператор. Если в тернарном операторе управляющий сигнал указывался в качестве первого операнда, отделяемого оператором `?`, то в данном блоке управляющий сигнал указывается в скобках после ключевого слова `if`.
Далее описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном единице (значение до оператора `:` в тернарном операторе).
После, в блоке `else` описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном нулю (значение после оператора `:` в тернарном операторе).
```Verilog
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.
Неправильно:
```Verilog
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**.
Будьте **очень внимательны** при использовании данного блока. Он обманчиво похож на условный блок в языках программирования, из-за чего возникает желание пользоваться им так же, как можно пользоваться условными блоками в языках программирования. Это не так. Обратите внимание на то, что данный блок выше упоминается исключительно как блок `if-else`. При реализации мультиплексора, у любого блока `if` должен быть соответствующий блок `else`, как у тернарного оператора должно быть два выходных операнда. Если не указать блок `else` при описании мультиплексора, у него будет только один вход, и в итоге на выходе мультиплексора будет сгенерирована **защелка**. Подробнее о защелках описано [здесь](Latches.md).
Существуют ситуации, когда блок `if` может быть использован без блока `else` (например, при описании дешифраторов или сигналов разрешения записи). Однако при описании мультиплексоров таких ситуаций не бывает.
## case-блок
Мультиплексор также можно описать с использованием **конструкции case**. Блок `case` лучше подходит для описания мультиплексора, когда у того более двух входов (ведь в случае конструкции `if-else` пришлось бы делать вложенное ветвление).
Конструкция `case` представляет собой инструмент множественного ветвления, который сравнивает значение заданного выражения с множеством вариантов, и, в случае первого совпадения, использует соответствующую ветвь. Как и блок `if-else`, блок `case` должен описывать все возможные комбинации управляющего сигнала (иначе будет сгенерирована защёлка). На случай, если ни один из вариантов не совпадет с заданным выражением, конструкция `case` поддерживает вариант `default`. Данная конструкция визуально похожа на оператор `switch-case` в Си, однако вы должны понимать, что используется она не для написания программы, а описания аппаратуры, в частности **мультиплексоров**/**демультиплексоров** и **дешифраторов**.
**Конструкция `case`, наряду с `if-else`, может быть описана только в блоке `always`**.
Реализация двухвходового мультиплексора с помощью `case` может выглядеть так:
```Verilog
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)
```
Рассмотрим вариант посложнее и опишем следующую схему:
![../.pic/Basic%20Verilog%20structures/multiplexors/fig_04.drawio.svg](../.pic/Basic%20Verilog%20structures/multiplexors/fig_04.drawio.svg)
Здесь уже используется мультиплексор 8в1. Управляющий сигнал `S` в данном случае 3-битный. В блоке `case` мы перечисляем все возможные варианты значений `S` и описываем выход мультиплексора.
```Verilog
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'b000: Y = A;
3'b001: Y = C | B; // в блоке case можно мультиплексировать
// не только провода, но и логические выражения
3'b010: Y = (C|B) & D;
/*
Обратите внимание, что разрядность сигнала S — 3 бита.
Это означает, что есть 8 комбинаций его разрядов.
Выше было описано только 3 комбинации из 8.
Если для всех остальных комбинаций на выходе мультиплексора должно
быть какое-то одно значение "по умолчанию", используется специальная
комбинация "default":
*/
default: Y = D;
endcase
end
endmodule
```
## Оператор адресации
Представим, что нам необходимо мультиплексировать реально много сигналов. Например, отдельные биты 1024-разрядной шины. Описывать `case` на 1024 варианта будет сущим безумием. В этом случае, можно будет воспользоваться оператором '[]', который наверняка известен вам как "оператор адресации по массиву" в Си-подобных языках. Работает он интуитивно понятно:
- перед оператором указывается имя массива или вектора (читай как "памяти или шины"), по которым будет идти индексация;
- за именем в квадратных скобках указывается индекс (не важно, в виде константы, или выражения, использующего другие сигналы).
В контексте примера по мультиплексированию 1024 бит использование оператора может быть выполнено следующим образом:
```Verilog
logic [1023:0] bus1024;
logic [ 9:0] select;
logic one_bit_result;
assign one_bit_result = bus1024[select];
```
Реализация мультиплексоров через оператор '[]' будет активно применяться вами при реализации различных памятей.
## Итоги главы
1. Мультиплексор — это **комбинационный** блок, подающий на выход один из нескольких входных сигналов.
2. Мультиплексор можно описать множеством способов, среди них:
1. использование [тернарного условного оператора](#тернарный-условный-оператор);
2. использование конструкции [`if-else`](#блок-if-else) внутри блока [`always`](#блок-always);
3. использование конструкции [`case`](#case-блок) внутри блока [`always`](#блок-always);
4. использование [оператора '[]'](#оператор-адресации).
3. Во избежание появления [защелок](Latches.md) при описании мультиплексора, необходимо убедиться что у блоков `if` есть соответствующие им блоки `else`, а в блоке `case` описаны все комбинации управляющего сигнала (при необходимости, множество оставшихся комбинаций можно покрыть с помощью комбинации `default`). Появление непреднамеренной защелки в дизайне ведет к ухудшению временных характеристик, избыточному использованию ресурсов, а также непредсказуемому поведению схемы из-за возможного удержания сигнала.
4. Важно отметить, что блоки `if-else` и `case` могут использоваться не только для описания мультиплексоров.
5. Конструкции `if-else` и `case` в рамках данных лабораторных работ можно описывать только внутри блока [`always`](#блок-always). При работе с этим блоком необходимо помнить следующие особенности:
1. Существует несколько типов блока `always`: `always_comb`, `always_ff`, `always_latch`, определяющих то, к чему будет подключена описанная в этом блоке логика: проводу, регистру или защелке соответственно. В данных лабораторных работах вам нужно будет пользоваться блоками `always_ff` и `always_comb`, причем:
1. внутри блока `always_ff` необходимо использовать оператор неблокирующего присваивания (`<=`);
2. внутри блока `always_comb` необходимо использовать оператор блокирующего присваивания (`=`).
2. Присваивание для любого сигнала возможно только внутри **одного** блока always. Два разных сигнала могут присваиваться как в одном блоке always, так и каждый в отдельном, но операция присваивания одному и тому же сигналу в двух разных блоках always — нет.
---
## Проверь себя
Как описать на языке SystemVerilog следующую схему?
![../.pic/Basic%20Verilog%20structures/multiplexors/fig_05.drawio.svg](../.pic/Basic%20Verilog%20structures/multiplexors/fig_05.drawio.svg)
## Список источников
1. [Мультиплексор (электроника)](https://ru.wikipedia.org/wiki/Мультиплексор_(электроника)).