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

20 KiB
Raw Blame History

Описание мультиплексора на языке SystemVerilog

Мультипле́ксор — устройство, имеющее несколько сигнальных входов, один или более управляющих входов и один выход. Мультиплексор позволяет передавать сигнал с одного из входов на выход; при этом выбор желаемого входа осуществляется подачей соответствующей комбинации управляющих сигналов.

Иными словами, мультиплексор — это переключатель (коммутатор), соединяющий выход с одним из множества входов.

../.pic/Basic%20Verilog%20structures/multiplexor/fig_01.drawio.png

Для начала создадим простой двухвходовой мультиплексор. Предположим, на Y нам необходимо передать один из сигналов — D0 или D1 в зависимости от значения управляющего сигнала S: когда S==0, на Y подается сигнал D0, в противном случае — D1.

../.pic/Basic%20Verilog%20structures/multiplexors/fig_02.drawio.png

На языке 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.

../.pic/Basic%20Verilog%20structures/multiplexors/fig_03.drawio.png

Также мультиплексор можно описать через конструкцию 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 вы столкнетесь в будущем с множеством ошибок.



case-блок

Мультиплексор также можно описать с использованием конструкции case. Блок case лучше подходит для описания мультиплексора, когда у того более двух входов (ведь в случае конструкции if-else пришлось бы делать вложенное ветвление).

Конструкция case представляет собой инструмент множественного ветвления, который сравнивает значение заданного выражения с множеством вариантов, и, в случае первого совпадения, использует соответствующую ветвь. На случай, если ни один из вариантов не совпадет с заданным выражением, конструкция case поддерживает вариант default. Данная конструкция визуально похожа на оператор switch-case в Си, однако вы должны понимать, что используется она не для написания программы, а описания аппаратуры, в частности мультиплексоров/демультиплексоров и дешифраторов.

Конструкция case, наряду с if-else, может быть описана только в блоке always.

Реализация двухвходового мультиплексора с помощью case может выглядеть так:

logic Y;
always @(*) 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.png

Здесь уже используется мультиплексор 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 — в противном случае в вашей схеме может появиться защелка (даже несмотря на то, что для описания защелок в SytemVerilog есть отдельный блок always: always_latch).

Защелка позволяет хранить данные, причем данные в нее записываются не по тактовому синхроимпульсу, а на протяжении относительно длинного промежутка времени, когда управляющий сигнал "открывает" защелку. Из-за этого она не является ни комбинационной, ни синхронной схемой. Обычно появление защелки в цифровой схеме говорит об ошибке разработки: в случае, если планировалась комбинационная логика, добавление защелки приведет к непредвиденному удержанию предыдущих значений (поскольку защелка сохраняет предыдущее значение до прихода очередной комбинации управляющего сигнала, описанной в блоке 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

../.pic/Basic%20Verilog%20structures/multiplexors/fig_06.png

На данной схеме различные её части обозначены следующим образом:

  1. Мультиплексор, который мы хотели описать
  2. Защелка
  3. Мультиплексор, который был добавлен чтобы генерировать сигнал, "открывающий" защелку
  4. Константная единица (питание)
  5. Константный ноль (земля).

В случае, если S == 0 или S == 1, на выход мультиплексора 3 будет подана единица, которая переведет защелку в "прозрачный" режим (данные с выхода мультиплексора 1 будут проходить сквозь защелку).

В случае, если S > 1, на выход мультиплексора 3 будет подан ноль, который переведет защелку в "непрозрачный" режим (данные с выхода мультиплексора 1 не будут идти сквозь защелку, вместо этого на выходе защелки останутся последние данные, которые шли через нее, пока она была "открыта").

../.pic/Basic%20Verilog%20structures/multiplexors/fig_07.png

Таким образом, во избежание появление защелки, необходимо описывать все возможные комбинации в блоке case (при необходимости покрывая множество оставшихся комбинаций с помощью default).

Итоги

  1. Мультиплексор — это комбинационный блок, подающий на выход один из нескольких входных сигналов.
  2. Мультиплексор можно описать множеством способов, среди них:
    1. Использование тернарного условного оператора через непрерывное присваивание;
    2. Использование конструкции if-else внутри блока always;
    3. Использование конструкции case внутри блока always (при этом стоит помнить, что через case можно описывать не только мультиплексоры).
      1. При использовании case, во избежание появления защелок, необходимо убедиться в том, что описаны все возможные комбинации управляющего сигнала (при необходимости, множество оставшихся комбинаций можно покрыть с помощью комбинации default)
  3. Конструкции if-else и case в рамках данных лабораторных работ можно описывать только внутри блока always. При работе с этим блоком необходимо помнить следующие особенности:
    1. Существует несколько типов блока always: always_comb, always_ff, always_latch, определяющих то, к чему будет подключена описанная в этом блоке логика: проводу, регистру или защелке соответственно.
    2. Внутри блока always следует использовать оператор неблокирующего присваивания <=.
    3. Присваивание для любого сигнала возможно только внутри одного блока always. Два разных сигнала могут присваиваться как в одном блоке always, так каждый в отдельном, но операция присваивания одному и тому же сигналу в двух разных блоках always — нет.

Проверь себя

Как, по-вашему, описать на языке SystemVerilog схему, приведённую ниже?

../.pic/Basic%20Verilog%20structures/multiplexors/fig_04.drawio.png