# О различиях между блокирующими и неблокирующими присваиваниями

Вскоре после начала курса студенты сталкиваются с понятиями "блокирующего" и "неблокирующего" присваивания. Часто объяснения преподавателей по этой теме сопровождаются словами "последовательный" и "параллельный", а также предлагается _просто запомнить_ [[1, стр. 2]](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf):

- при описании последовательностной логики (регистров) используйте **неблокирующее** присваивание;
- при описании комбинационной логики используйте **блокирующее** присваивание.

Давайте разберемся что это за присваивания и почему необходимо руководствоваться этими правилами.

Начать придется издалека. Несмотря на то, что SystemVerilog является **языком описания аппаратуры**, он так же является и языком для верификации описанной аппаратуры (слово `Verilog` является объединением двух слов: `verification` и `logic`). Для целей верификации в языке выделено целое подмножество конструкций, которые не могут быть использованы для описания аппаратуры — так называемое "_несинтезируемое подмножество языка SystemVerilog_". Разумеется, часть языка, которая может быть использована для описания аппаратуры ("_синтезируемое подмножество языка SystemVerilog_") тоже может использоваться в верификации.

Давайте для начала разберемся в том, как будут использоваться операторы присваивания при программном моделировании (так называемой симуляции) — одним из инструментов верификации. Разобравшись в поведении операторов во время симуляции будет куда проще объяснить результат использования операторов при синтезе цифровой схемы.

Введем пару сокращений для удобства дальнейшего повествования:

- под `LHS` (left hand side) мы будем подразумевать "выражение, **которому** присваивают";
- под `RHS` (right hand side) мы будем подразумевать "выражение **которое** присваивают".

В выражении `a = b+c`, `a` является `LHS`, `b+c` является `RHS`.

 два вида присваиваний: **непрерывное** и **процедурное**.

```SystemVerilog
module assignment_example(
  input  logic a, b
  output logic c, d
);

// непрерывное присваивание
assign c = a + b;

// процедурное присваивание
always_comb begin
  d = a + b;
end

endmodule
```

_Листинг 1. Пример непрерывного и процедурного присваивания._

С непрерывным присваиванием вы знакомитесь в самом начале — это оператор `assign`. Непрерывное присваивание постоянно следит за `RHS` этого оператора, и каждый раз, когда любая часть этого выражения меняет своё значение, производит пересчёт значения `RHS`, а затем сразу же передает это значение `LHS`. Если мы произведем `assign a = b+c`, то каждый раз, когда будет меняться значение `b` или `c`, будет пересчитываться результат их суммы, который сразу же будет присвоен выражению `a`.

**Непрерывное присваивание может быть использовано только вне программных блоков.**

Под "программными блоками" подразумеваются блоки `always` (всех типов) и `initial`. Есть и другие программные блоки, но в рамках данного курса лабораторных работ вы с ними не столкнетесь. Вообще говоря, синтаксис языка SystemVerilog допускает использование оператора `assign` внутри программного блока (так называемое "**процедурное непрерывное присваивание**")[[2, стр. 232]](https://ieeexplore.ieee.org/document/8299595), однако в рамках данного курса не существует ни одной ситуации, когда это может потребоваться и со 100% вероятностью будет ошибочно.

В отличие от непрерывного присваивания, **процедурное присваивание может быть использовано только в программных блоках**.

С точки зрения моделирования (не описания аппаратуры), программный блок — это программа (в привычном вам понимании парадигмы программирования), исполняющаяся в отдельном процессе. Программные блоки исполняются независимо друг от друга по определенным событиям.

Блоки `initial` (их может быть много) исполняются в момент начала моделирования. Блоки `always` исполняются по событиям указанным в **списке чувствительности**:

- `always @(posedge clk)` будет исполняться каждый раз когда произойдет положительный фронт `clk`;
- `always @(a,b,c)` будет исполняться каждый раз, когда изменится значение любого из сигналов `a`,`b`,`c`;
- `always @(*)` будет исполняться каждый раз, когда изменится состояние любой составляющей любого `RHS` в этом блоке (когда изменится хоть что-то, от чего зависит любое выражение слева от оператора присваивания в этом блоке).

Похожие правила применимы и к остальным блокам `always`: `always_comb`, `always_ff`, `always_latch` (с некоторыми оговорками, не имеющими значения в рамках данного повествования).

Под независимостью исполнения подразумевается то, что порядок исполнения блоков не зависит от очередности, в которой они были описаны. Более того, исполнение одного блока может быть приостановлено, чтобы исполнить другой блок.

А вот выражения внутри отдельного программного блока (с точке зрения моделирования) исполняются последовательно. И вот тут на сцену выходят два типа процедурного присваивания: блокирующее и неблокирующее.

Блокирующее присваивание (оператор `=`) **блокирует** исполнение дальнейших выражений до завершения вычисления `RHS` и присвоения вычисленного результата `LHS` (иными словами, это привычное вам присваивание из мира программирования, там все работает точно так же).

Неблокирующее присваивание (оператор `<=`) производит вычисление `RHS`, запоминает его, и **откладывает** присваивание вычисленного значения, позволяя выполняться остальным выражениям до завершения присваивания `LHS`.

Рассмотрим пример, представленный на _рис. 1_.

![../.pic/Basic%20Verilog%20structures/assignments/fig_01.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_01.drawio.svg)

_Рисунок 1. Пример цепочки блокирующих присваиваний._

1. Сперва вычисляется `RHS` первого присваивания программного блока — константа `5`.
2. Затем, вычисленное значение записывается в LHS первого присваивания — сигнал `a` становится равным `5`.
3. Далее вычисляется `RHS` следующего присваивания — `a`, которое к этому моменту уже равно `5`.
4. Поскольку вычисленное `RHS` равняется `5` то `LHS` второго присваивания (`b`) тоже становится равным `5`.
5. Аналогичным образом `c` тоже становится равным `5`.

Обратите внимание, что все это произошло в нулевой момент времени. На временной диаграмме Vivado просто отобразится, что все сигналы одновременно стали равны `5`, однако с точки зрения симулятора это было не так. Другие симуляторы (например `QuestaSim`) позволяют настроить временную диаграмму таким образом, чтобы отображались все переходы между присваиваниями.

Посмотрим, как работает аналогичная цепочка неблокирующих присваиваний. Чтобы иллюстрация была более наглядной, предположим, что перед присваиваниями был исполнен какой-то код, который привел `a` в состояние `3`, `b` в `2`, `c` в `7`.

![../.pic/Basic%20Verilog%20structures/assignments/fig_02.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_02.drawio.svg)

_Рисунок 2. Пример цепочки неблокирующих присваиваний._

1. Сперва вычисляется значение `RHS` первого присваивания (`5`). Присваивание этого значения **откладывается** на потом.
2. Затем вычисляется значение `RHS` второго присваивания. Поскольку `a` еще не присвоили значение `5`, результатом `RHS` становится текущее значение `a` — 3. Присваивание этого значения сигналу `b` **откладывается** на потом.
3. Аналогичным образом вычисляется `RHS` третьего присваивания (`2`). Присваивание этого значения также **откладывается** на потом.

Так называемое "**потом**" наступает когда завершается вычисление `RHS` всех неблокирующих присваиваний и завершение присвоений всех блокирующих присваиваний (однако "потом" все равно происходит в тот же момент времени, обратите внимание на значение времени на _рис. 2_). В стандарте SystemVerilog этот момент называется `NBA-region` (сокр. от "Non-Blocking Assignment region") [[2, стр. 61]](https://ieeexplore.ieee.org/document/8299595). Выполнение отложенных присваиваний происходит в том же порядке, в котором они шли в программном блоке. Подробнее о том как работает событийная симуляция (event based simulation) в SystemVerilog вы можете прочесть в стандарте [IEEE 1800-2017](https://ieeexplore.ieee.org/document/8299595) (раздел 4). Стандарт доступен бесплатно всем желающим по программе "IEEE GET Program".

Таким образом, если `LHS` **блокирующего** присваивания используется в качестве операнда `RHS` любого другого последующего присваивания, это выражение будет иметь уже обновленное значение, что очень похоже на "_последовательное вычисление_".

С другой стороны значение, присвоенное `LHS` значение с помощью **неблокирующего** присваивания не может использоваться в качестве операнда `RHS` последующих присваиваний, что создает иллюзию "_параллельного вычисления_" (см. _рис. 3_).

![../.pic/Basic%20Verilog%20structures/assignments/fig_03.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_03.drawio.svg)

_Рисунок 3. Иллюстрация блокирующих и неблокирующих присваиваний._

Теперь, понимая как работают присваивания с точки зрения моделирования, посмотрим на то, во что могут синтезироваться подобные операторы.

Начнем с непрерывного присваивания. Оно превращается в провод, передающий данные от `RHS` к `LHS`. При этом вы должны контролировать что к чему вы присваиваете (не путайте местами `RHS` и `LHS`).

То во что синтезируются блокирующие и неблокирующие присваивания зависит от описываемой логики, поэтому давайте разберем несколько примеров.

Начнем с исходного примера c цепочкой блокирующих присваиваний, только теперь перепишем его в синтезируемом виде, сохранив изначальную идею.

```SystemVerilog
module example_1(
  input  logic        clk,
  input  logic [31:0] in,
  output logic [31:0] out
);

logic [31:0] a,b,c;

always_ff @(posedge clk) begin
  a = in;
  b = a;
  c = b;
end

assign out = c;

endmodule
```

_Листинг 2. Пример описания модуля, использующего цепочку блокирующих присваиваний._

Если вы уже знакомы с содержимым документа о том, [как описывать регистры](Registers.md), подумайте: какой будет результат синтеза у этой схемы?

---

Давайте "прочитаем" эту схему. Мы видим модуль, с входом `in`, выходом `out` и тактирующим синхроимпульсом `clk`. Также мы видим три сигнала `a`,`b`,`c`, которые описываются в блоке `always_ff`, предназначенном для описания регистров. Значение `in` по цепочке этих регистров передается до регистра `c`, выход которого подключен к выходу `out`.

Похоже, что здесь был описан [**сдвиговый регистр**](https://ru.wikipedia.org/wiki/Регистр_(цифровая_техника)#Сдвигающие_(последовательные)_регистры), представленный на _рис. 4_.

![../.pic/Basic%20Verilog%20structures/assignments/fig_04.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_04.drawio.svg)

_Рисунок 4. Трехразрядный сдвиговый регистр._

Давайте откроем цифровую схему, сгенерированную Vivado и убедимся в наших выводах.

![../.pic/Basic%20Verilog%20structures/assignments/fig_05.png](../.pic/Basic%20Verilog%20structures/assignments/fig_05.png)

_Рисунок 5. Схема, сгенерированная Vivado по описанию из Листинга 2._

Произошло что-то странное. Вместо трех регистров Vivado создал только один и судя по названию — это последний регистр `c`. Почему это произошло?

Изучим внимательней поведение цепочки блокирующих присваиваний, представленное на _рис. 1_.

Каждое последующее присваивание ожидало, пока не выполнится предыдущее, таким образом, `RHS` первого присваивания (`5`) сразу же распространился по всем регистрам. Моделируя _Листинг 2_ мы получим **поведение**, когда на вход каждого регистра будет подаваться сигнал `in`.

Таким образом на самом деле, мы должны были изобразить нашу схему как на _рис. 6_.

![../.pic/Basic%20Verilog%20structures/assignments/fig_06.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_06.drawio.svg)

_Рисунок 6. Схема, описанная Листингом 2._

Но почему тогда на схеме Vivado не осталось регистров `a` и `b`? Посмотрим на них внимательней. Их выходы ни на что не влияют, они висят неподключенные. А значит эти регистры не имеют никакого смысла и если их убрать, ничего не изменится.

При генерации схемы, Vivado вывел в `Tcl Console` следующие предупреждения:

```text
WARNING: [Synth 8-6014] Unused sequential element a_reg was removed.  [example_1.sv:10]
WARNING: [Synth 8-6014] Unused sequential element b_reg was removed.  [example_1.sv:11]
```

Vivado обнаружил, что регистры `a` и `b` ни на что не влияют и удалил их со схемы.

Если вы используете Vivado 2023.1 и новее, вы можете обнаруживать подобные предупреждения более удобным способом — посредством линтера, который можно вызвать из вкладки `RTL ANALYSIS` окна `Flow Navigator`.

![../.pic/Basic%20Verilog%20structures/assignments/fig_07.png](../.pic/Basic%20Verilog%20structures/assignments/fig_07.png)

_Рисунок 7. Пример вызова линтера._

Давайте заменим в _Листинге 2_ блокирующие присваивания на неблокирующие. Напоминаем, что оператор неблокирующего присваивания записывается как `<=`.

```SystemVerilog
module example_2(
  input  logic        clk,
  input  logic [31:0] in,
  output logic [31:0] out
);

logic [31:0] a,b,c;

always_ff @(posedge clk) begin
  a <= in;
  b <= a;
  c <= b;
end

assign out = c;

endmodule
```

_Листинг 3. Пример описания модуля, использующего цепочку неблокирующих присваиваний._

Посмотрим, какую схему сгенерирует Vivado в этот раз.

![../.pic/Basic%20Verilog%20structures/assignments/fig_08.png](../.pic/Basic%20Verilog%20structures/assignments/fig_08.png)

_Рисунок 8. Схема, сгенерированная Vivado по описанию из Листинга 3._

Вряд ли полученный результат стал для вас неожиданным сюжетным поворотом, но давайте разберемся, почему в этот раз сгенерировалась схема, аналогичная представленной на _рис. 4_.

Для этого обратимся к примеру, представленному на _рис. 2_. В данном примере **неблокирующего присваивания** сперва вычислялись значения всех `RHS` (запоминались значения выходов всех регистров) и только потом происходило присваивание новых значений. Подобное **поведение** аналогично поведению сдвиговых регистров.

Слово "_поведение_" было выделено дважды неспроста. Описание схем, которое мы сделали называется "**поведенческим описанием схемы**".

Можно ли реализовать сдвиговый регистр, используя блокирующие присваивания? Конечно. Например, можно поменять порядок присваиваний как в _Листинге 4_.

```SystemVerilog
module example_3(
  input  logic        clk,
  input  logic [31:0] in,
  output logic [31:0] out
);

logic [31:0] a,b,c;

always_ff @(posedge clk) begin
  c = b;
  b = a;
  a = in;
end

assign out = c;

endmodule
```

_Листинг 4. Цепочка блокирующих присваиваний в порядке обратном приведенному в Листинге 2._

В этом случае, линтер не сообщит ни о каких ошибках, а Vivado сгенерирует схему, аналогичную _рис. 8_

Так произошло, поскольку мы разорвали зависимость значений `RHS` последующих присваиваний от значений `LHS` предыдущих (поведение, которое демонстрировала цепочка неблокирующих присваиваний с самого начала). Однако данное решение является скорее хаком, чем примером хорошего проектирования. По сути, мы просто подстроили код, описанный с помощью блокирующих присваиваний таким образом, чтоб он вел себя как код, использующий неблокирующие присваивания.

Важно отметить, что при использовании неблокирующих присваиваний, их порядок вообще не имеет значения с точки зрения синтеза.

Давайте разнесем логику работы каждого регистра по отдельным блокам `always`.

```SystemVerilog
module example_4(
  input  logic        clk,
  input  logic [31:0] in,
  output logic [31:0] out
);

logic [31:0] a,b,c;

always_ff @(posedge clk) begin
  a = in;
end

always_ff @(posedge clk) begin
  b = a;
end

always_ff @(posedge clk) begin
  c = b;
end

assign out = c;

endmodule
```

_Листинг 5. Сдвиговый регистр, описанный через блокирующие присваивания в отдельных блоках always._

Сгенерированная в Vivado схема будет аналогична _рис. 8_. Но давайте попробуем промоделировать работу этой схемы, подавая случайные воздействия на вход `in`.

![../.pic/Basic%20Verilog%20structures/assignments/fig_09.png](../.pic/Basic%20Verilog%20structures/assignments/fig_09.png)

_Рисунок 9. Симуляция модуля, описанного Листингом 5._

Выглядит как-то не по "сдвигово-регистерски". В чем же дело?

Как уже упоминалось ранее, программные блоки (коими являются блоки `always`) исполняются во время моделирования независимо друг от друга в недетерминированном стандартом порядке. На практике это означает то, что сперва может исполниться второй блок, потом третий, а потом первый — либо в любом другом порядке. Разработчик не может (и не должен) рассчитывать на порядок блоков `always` при описании схемы.

Конкретно в данной ситуации, симулятор воспроизвел блоки ровно в том порядке, в котором они были описаны. Сперва `a` получил значение `in`, потом `b` получил обновленное значение `a`, затем `c` получил обновленное значение `b`.

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

Если заменить порядок `always` подобно тому, как мы изменили порядок в _Листинге 4_, результат на временной диаграмме совпадет с поведением сдвигового регистра.

![../.pic/Basic%20Verilog%20structures/assignments/fig_10.png](../.pic/Basic%20Verilog%20structures/assignments/fig_10.png)

_Рисунок 10. Моделирование поведения сдвигового регистра._

Однако, как уже объяснялось ранее, вы не можете рассчитывать на такой результат. Сегодня симулятор смоделировал поведение одним образом — завтра он смоделирует этот же код (в котором не изменилась ни одна строка) по-другому, и будет по прежнему работать в соответствии со стандартом.

Для того, чтобы получить детерминированный результат, вам необходимо снова воспользоваться неблокирующим присваиванием, поскольку и в этом случае порядок исполнения блоков `always` не влияет на результат присваиваний — сначала вычисляются значения `RHS` всех неблокирующих присваиваний всех программных блоков, и только потом происходит присваивание этих значений `LHS`.

Рассмотрим еще один пример того, как различие в присваиваниях приведет к описанию двух различных схем:

```SystemVerilog
module example_5(
  input  logic clk,
  input  logic a, b, c,
  output logic d
);

logic temp;

always_ff @(posedge clk) begin
  temp  = a | b;
  d     = c & temp;
end

endmodule
```

_Листинг 6. Пример цепочки блокирующих присваиваний с комбинационной логикой._

Остановитесь на минуту и подумайте, схему с каким поведением описывает _Листинг 6_?

---

Как вы могли догадаться, данный листинг описывает схему с одним регистром `d`, на вход которого подается результат комбинационной логики `c & (a | b)`, поскольку сперва в `temp` попадает результат `a | b` и только после этого вычисляется значение `c & temp`.

![../.pic/Basic%20Verilog%20structures/assignments/fig_11.png](../.pic/Basic%20Verilog%20structures/assignments/fig_11.png)

_Рисунок 11. Схема, сгенерированная Vivado по описанию из Листинга 6._

Попробуйте догадаться о том, что произойдет, если снова заменить блокирующие присваивания на неблокирующие?

---

Результат изменится следующим образом.

![../.pic/Basic%20Verilog%20structures/assignments/fig_12.png](../.pic/Basic%20Verilog%20structures/assignments/fig_12.png)

_Рисунок 12. Схема, сгенерированная Vivado по описанию из Листинга 6 после замены блокирующих присваиваний на неблокирующие._

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

Одно и тоже описание, использующее разные типы присваиваний может привести к синтезу разных схем.

Рассмотрим предыдущий пример еще раз. Нельзя сказать, что одна схема лучше другой — это просто две разные схемы и то, какая из них вам нужна зависит только от вашей задачи.

Однако нельзя не заметить, что при использовании блокирующего присваивания, мы "теряли" регистры. Более того, моделирование **неблокирующих** присваиваний ближе всего по поведению приближено к моделированию регистровой логики [[1, стр. 14]](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf).

Пока что мы рассматривали только синхронные схемы (схемы, работающие по тактовому синхроимпульсу).

Рассмотрим зависимость от типа присваивания в комбинационных схемах. Для этого возьмем предыдущий пример, и уберем тактирующий синхроимпульс.

```SystemVerilog
module example_6(
  input  logic a, b, c,
  output logic d
);

logic temp;

always_comb begin
  temp  = a | b;
  d     = c & temp;
end

endmodule
```

_Листинг 7. Пример цепочки блокирующих присваиваний в комбинационной схеме._

> Обратите внимание на то, что `always_ff` поменялся на `always_comb`.

Как вы думаете, какая схема будет сгенерирована по описанию, представленному _Листинга 7_, и что произойдет с этой схемой, если заменить в нем все блокирующие присваивания на неблокирующие?

---

Вас может это удивить, но в обоих случаях будет сгенерирована схема, представленная на _рис. 13_.

![../.pic/Basic%20Verilog%20structures/assignments/fig_13.png](../.pic/Basic%20Verilog%20structures/assignments/fig_13.png)

_Рисунок 13. Схема, сгенерированная Vivado по описанию из Листинга 7._

> Девочка остолбенела. У неё возникло отчётливое чувство какой-то ужасной несправедливости по отношению к ней. Гарри Поттер был грязным, отвратительным обманщиком и лжецом. Но во время игры все его ответы были верными. [Элиезер Юдковский / Гарри Поттер и методы рационального мышления]

На протяжении всего документа вам рассказывали, что использование блокирующих присваиваний приведет к изменению поведения и синтезу другой схемы, а теперь сами же приводят пример, где схема остается точно такой же!

Давайте разберемся по порядку, что же произошло.

Все дело в изменении блока `always`. Когда мы использовали `always_ff @(posedge clk)`, этот программный блок исполнялся только один раз за такт.

Теперь, когда мы стали использовать блок `always_comb`, правила игры изменились. Нет, принцип работы блокирующих и неблокирующих присваиваний остался тем же самым. Изменилось только то, сколько раз будет вызван данный блок.

Начнем со схемы, построенной по описанию, использующему блокирующее присваивание. В общем-то, тут у вас не должно было возникнуть вопросов, логика ровно та же, что была и при построении схемы по _Листингу 6_ (_рис. 11)_, только без выходного регистра. Что логично, ведь мы убрали из описания тактирующий сигнал.

Вопрос в том, почему это вдруг схема, построенная после замены блокирующих присваиваний на неблокирующие ведет себя точно так же?

Рассмотрим _рис. 14_.

![../.pic/Basic%20Verilog%20structures/assignments/fig_14.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_14.drawio.svg)

_Рисунок 14. Моделирование цепочки присваиваний в комбинационном блоке `always`._

Комбинационный блок `always` начинает исполняться **каждый раз**, когда операнд любого `RHS` этого блока меняет своё значение.

Изначально, блок `always` начал исполняться, когда операнды `a`, `b` и `c` приняли значение `1`, `0` и `1` соответственно. В этот момент (поскольку присваивание неблокирующее), вычисляются значения `RHS` для присваивания сигналам `temp` и `d`. Новое значение `temp` будет `1`, но пока что этого не произошло, и `temp` все ещё в состоянии `x`, поэтому новым значением `d` все ещё будет `x`.

После происходит присваивание вычисленных значений сигналам `temp` и `d`.

Однако `temp` является операндом `RHS` в выражении `d = c & temp`, поэтому блок `always` запускается еще один раз в этот же момент времени (в примере это `5ns`). Поскольку значения `a` и `b` не менялись, значение `RHS` первого выражения останется прежним. А вот значение `temp` уже иное, поэтому `RHS` второго выражения станет `1`.

После повторного присваивания сигналы `temp` и `d` примут установившееся значение. Поскольку ни один из операндов `RHS`—выражений больше не изменил своего значения, блок `always` больше не вызывается.

Подобное поведение можно сравнить с переходным процессом в комбинационной схеме.

Обратите внимание, поведение схем описанных при разных типах присваивания слегка различаются. При блокирующем присваивании все сигналы приняли установившиеся значения за один проход блока `always`, при неблокирующем потребовалось несколько проходов. Однако с точки зрения пользователя, читающего временную диаграмму, в обоих ситуациях сигналы изменили свое значение мгновенно.

Поэтому не смотря на различия в типах присваиваний схемы получились одинаковыми.

> Получается что для комбинационной логики нет разницы между блокирующим и неблокирующим присваиванием, после переходных процессов результат будет одинаковым?

И да и нет. С точки зрения синтеза схемы так и есть. Однако есть нюанс в случае моделирования схемы. Поведение комбинационной логики лучше моделирует блокирующее присваивание[[1, стр. 14]](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf).

Подведем итоги прочитанному:

- Блокирующее присваивание блокирует выполнение остальных операций до завершения текущего присваивания. Оно подобно обычному присваиванию в парадигме программирования.
- Неблокирующее присваивание сперва вычисляет `RHS`, давая исполниться остальным операциям до самого присваивания.

В связи с особенностями поведения блокирующего и неблокирующего присваивания, выведены следующие две максимы:

- при описании последовательностной логики (регистров) используйте **неблокирующее** присваивание;
- при описании комбинационной логики используйте **блокирующее** присваивание.

Кроме того, существуют следующие рекомендации и требования[[1, стр. 5]](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf):

- _При описании как последовательностной логики, так и комбинационной в одном блоке `always` используйте неблокирующее присваивание._
- _Не смешивайте в одном блоке блокирующие и неблокирующие присваивания_ — стандарт допускает подобное описание, но оно затрудняет его чтение. Представьте, что читая описание схемы, вам бы постоянно приходилось держать в голове какие присваивания уже произошли, а какие только произойдут, чтобы понять как эта схема работает.
- _Не смешивайте блокирующие и неблокирующие присваивания для одного и того же сигнала_ — стандарт это запрещает (для блоков `always_ff`, `always_comb`, `always_latch`).

Использованная литература:

1. [Clifford E. Cummings / Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf)
2. [1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language](https://ieeexplore.ieee.org/document/8299595)
