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

This commit is contained in:
Andrei Solodovnikov
2024-02-02 15:34:44 +03:00
parent 43fd6027c3
commit 34274ccb40
14 changed files with 446 additions and 0 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 166 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 181 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 218 KiB

View File

@@ -0,0 +1,426 @@
# Различие блокирующего и неблокирующего присваивания
Вскоре после начала курса студенты сталкиваются с понятиями "блокирующего" и "неблокирующего" присваивания. Часто объяснения преподавателей по этой теме сопровождаются словами "последовательный" и "параллельный", а также предлагается _просто запомнить_[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`.
Существует два вида присваиваний: **непрерывное** и **процедурное**.
С непрерывным присваиванием вы знакомитесь в самом начале — это оператор `assign`. Непрерывное присваивание постоянно следит за `RHS` этого оператора, и каждый раз, когда любая часть этого выражения меняет своё значение, производит пересчёт значения `RHS`, а затем сразу же передает это значение `LHS`. Если мы произведем `assign a = b+c`, то каждый раз, когда будет меняться значение `b` или `c`, будет пересчитываться результат их суммы, который сразу же будет присвоен выражению `a`.
**Непрерывное присваивание может быть использовано только вне программных блоков**. Под "программными блоками" подразумеваются блоки `always` (всех типов) и `initial`. Есть и другие программные блоки, но в рамках данного курса лабораторных работ вы с ними не столкнетесь. Вообще говоря, синтаксис языка SystemVerilog допускает использование оператора `assign` внутри программного блока, однако в рамках данного курса не существует ни одной ситуации, когда это может потребоваться и со 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]. Выполнение отложенных присваиваний происходит в том же порядке, в котором они шли в программном блоке. Подробнее о том как работает событийная симуляция (event based simulation) в SystemVerilog вы можете прочесть в стандарте [IEEE 1800-2017](https://ieeexplore.ieee.org/document/8299595) (раздел 4). Стандарт доступен бесплатно всем желающим по программе "IEEE GET Program".
Таким образом, если `LHS` блокирующего присваивания используется в качестве операнда `RHS` любого другого последующего присваивания, результат предыдущего выражение будет передан дальше **в тот же момент времени**, что очень похоже на "_последовательное вычисление_".
С другой стороны `LHS` неблокирующего присваивания не может использоваться в качестве операнда `RHS` последующих присваиваний, что создает иллюзию "_параллельного вычисления_".
Теперь, понимая как работают присваивания с точки зрения моделирования, посмотрим на то, во что могут синтезироваться подобные операторы.
Начнем с непрерывного присваивания. Оно превращается в провод, передающий данные от `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
```
_Листинг 1. Пример описания модуля, использующего цепочку блокирующих присваиваний._
Если вы уже знакомы с содержимым документа о том, [как описывать регистры](Registers.md), подумайте, какой будет результат синтеза у этой схемы?
---
Давайте "прочитаем" эту схему. Мы видим модуль, с входом `in`, выходом `out` и тактирующим синхроимпульсом `clk`. Так же мы видим три сигнала `a`,`b`,`c`, которые описываются в блоке `always_ff`, предназначенном для описания регистров. Значение `in` по цепочке этих регистров передается до регистра `c`, выход которого подключен к выходу `out`.
Похоже, что здесь был описан [**сдвиговый регистр**](https://ru.wikipedia.org/wiki/Регистр_(цифровая_техника)#Сдвигающие_(последовательные)_регистры), представленный на _рис. 3_.
![../.pic/Basic%20Verilog%20structures/assignments/fig_03.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_03.drawio.svg)
_Рисунок 3. Трехразрядный сдвиговый регистр._
Давайте откроем цифровую схему, сгенерированную Vivado и убедимся в наших выводах.
![../.pic/Basic%20Verilog%20structures/assignments/fig_04.png](../.pic/Basic%20Verilog%20structures/assignments/fig_04.png)
_Рисунок 4. Схема, сгенерированная Vivado по описанию из Листинга 1._
Произошло что-то странное. Вместо трех регистров Vivado создал только один и судя по названию — это последний регистр `c`. Почему это произошло?
Изучим внимательней поведение цепочки блокирующих присваиваний, представленное на _рис. 1_.
Каждое последующее присваивание ожидало, пока не выполнится предыдущее, таким образом, `RHS` первого присваивания (`5`) сразу же (без каких-либо задержек) распространился по всем регистрам. Моделируя _Листинг 1_ мы получим **поведение**, когда на вход каждого регистра будет подаваться сигнал `in`.
Таким образом на самом деле, мы должны были изображать нашу схему как на _рис. 5_.
![../.pic/Basic%20Verilog%20structures/assignments/fig_05.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_05.drawio.svg)
_Рисунок 5. Схема, описанная Листингом 1._
Но почему тогда на схеме Vivado не осталось регистров `a` и `b`? Посмотрим на них внимательней. Их выходы ни на что не влияют, они висят неподключенные. А значит эти регистры не имеют никакого смысла и если их убрать, ничего не изменится.
При генерации схемы, Vivado вывел в `Tcl Console` следующие предупреждения:
```text
WARNING: [Synth 8-6014] Unused sequential element a_reg was removed. [block_assignment.sv:10]
WARNING: [Synth 8-6014] Unused sequential element b_reg was removed. [block_assignment.sv:11]
```
Если вы используете Vivado 2023.1 и новее, вы можете обнаруживать подобные ошибки более удобным способом — посредством линтера, который можно вызвать из вкладки `RTL ANALYSIS` окна `Flow Navigator`.
![../.pic/Basic%20Verilog%20structures/assignments/fig_06.png](../.pic/Basic%20Verilog%20structures/assignments/fig_06.png)
_Рисунок 6. Пример вызова линтера._
Давайте заменим в _Листинге 1_ блокирующие присваивания на неблокирующие. Напоминаем, что оператор неблокирующего присваивания записывается как `<=`.
```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
```
_Листинг 2. Пример описания модуля, использующего цепочку неблокирующих присваиваний._
Посмотрим, какую схему сгенерирует Vivado в этот раз.
![../.pic/Basic%20Verilog%20structures/assignments/fig_07.png](../.pic/Basic%20Verilog%20structures/assignments/fig_07.png)
_Рисунок 7. Схема, сгенерированная Vivado по описанию из Листинга 2._
Вряд ли полученный результат стал для вас неожиданным сюжетным поворотом, но давайте разберемся, почему в этот раз сгенерировалась схема, аналогичная представленной на _рис. 3_.
Для этого обратимся к примеру, представленному на _рис. 2_. В данном примере **неблокирующем присваивании** сперва вычислялись значения всех `RHS` (запоминались значения выходов всех регистров) и только потом происходило присваивание новых значений. Подобное **поведение** аналогично поведению сдвиговых регистров.
Слово "_поведение_" было выделено дважды неспроста. Описание схем, которое мы сделали называется "**поведенческим описанием схемы**".
Можно ли реализовать сдвиговый регистр, используя блокирующие присваивания? Конечно. Например, можно поменять порядок присваиваний как в _Листинге 3_.
```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
```
_Листинг 3. Цепочка блокирующих присваиваний в порядке обратном приведенному в Листинге 1._
В этом случае, линтер не сообщит ни о каких ошибках, а Vivado сгенерирует схему, аналогичную _рис. 7_
Так произошло, поскольку мы разорвали зависимость значений `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
```
_Листинг 4. Сдвиговый регистр, описанный через блокирующие присваивания в отдельных блоках always._
Сгенерированная в Vivado схема будет аналогична _рис. 7_. Но давайте попробуем промоделировать работу этой схемы, подавая случайные воздействия на вход `in`.
![../.pic/Basic%20Verilog%20structures/assignments/fig_08.png](../.pic/Basic%20Verilog%20structures/assignments/fig_08.png)
_Рисунок 8. Симуляция модуля, описанного Листингом 4._
Выглядит как-то не по "сдвигово-регистрерски". В чем же дело?
Как уже упоминалось ранее, программные блоки (коими являются блоки `always`) исполняются во время моделирования независимо друг от друга в недетерминированном стандартом порядке. На практике это означает то, что сперва может исполниться второй блок, потом третий, а потом первый — либо в любом другом порядке. Так сделано для того, чтобы разработчик не мог рассчитывать на порядок блоков `always` при описании.
Конкретно в данной ситуации, симулятор воспроизвел блоки ровно в том порядке, в котором они были описаны. Сперва `a` получил значение `in`, потом `b` получил обновленное значение `a`, затем `c` получил обновленное значение `b`.
Поскольку поведение недетерминировано, нельзя однозначно сказать, какую схему должен был воспроизвести синтезатор. В данной ситуации, он воспроизвел схему которую мы хотели получить, что не помогло, тем не менее с моделированием.
Если заменить порядок `always` подобно тому, как мы изменили порядок в _Листинге 3_, результат на временной диаграмме совпадет с поведением сдвигового регистра.
![../.pic/Basic%20Verilog%20structures/assignments/fig_09.png](../.pic/Basic%20Verilog%20structures/assignments/fig_09.png)
_Рисунок 9. Моделирование поведения сдвигового регистра._
Однако, как уже объяснялось ранее, вы не можете рассчитывать на такой результат. Сегодня симулятор смоделировал поведение одним образом — завтра он смоделирует этот же код, в котором не изменилась ни одна строка, и будет по прежнему работать в соответствии со стандартом.
Для того, чтобы получить детерминированный результат, вам необходимо снова воспользоваться неблокирующим присваиванием, поскольку и в этом случае порядок исполнения блоков `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
```
_Листинг 5. Пример цепочки блокирующих присваиваний с комбинационной логикой._
Остановитесь на минуту и подумайте, схему с каким поведением описывает _Листинг 5_?
---
Как вы могли догадаться, данный листинг описывает схему с одним регистром `d`, на вход которого подается результат комбинационной логики `c & (a | b)`, поскольку сперва в `temp` попадает результат `a | b` и только после этого вычисляется значение `c & temp`.
![../.pic/Basic%20Verilog%20structures/assignments/fig_10.png](../.pic/Basic%20Verilog%20structures/assignments/fig_10.png)
_Рисунок 10. Схема, сгенерированная Vivado по описанию из Листинга 5._
Попробуйте догадаться о том, что произойдет, если снова заменить блокирующие присваивания на неблокирующие?
---
Результат изменится следующим образом.
![../.pic/Basic%20Verilog%20structures/assignments/fig_11.png](../.pic/Basic%20Verilog%20structures/assignments/fig_11.png)
_Рисунок 11. Схема, сгенерированная Vivado по описанию из Листинга 5 после замены блокирующих присваиваний на неблокирующие._
Из прочтенного может сложиться впечатление, будто бы автор хочет показать, что блокирующее присваивание — это плохо, а неблокирующее — хорошо, однако это не так. Это просто два похожих инструмента, работающих разными способами, о которых должен знать профессионал, использующий эти инструменты.
Одно и тоже описание, использующее разные типы присваиваний приводит к синтезу разных схем.
Рассмотрим предыдущий пример еще раз. Нельзя сказать, что одна схема лучше другой — это просто две разные схемы и то, какая из них вам нужна зависит только от вашей задачи.
Однако нельзя не заметить, что при использовании блокирующего присваивания, мы "теряли" регистры.
Пока что мы рассматривали только синхронные схемы (схемы, работающие по тактовому синхроимпульсу).
Рассмотрим зависимость от типа присваивания в комбинационных схемах. Для этого возьмем предыдущий пример, у уберем тактирующий синхроимпульс.
```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
```
_Листинг 6. Пример цепочки блокирующих присваиваний в комбинационной схеме._
> Обратите внимание на то, что изменился и блок always.
Как вы думаете, какая схема будет сгенерирована по описанию, представленному _Листинга 6_, и что произойдет с этой схемой, если заменить в нем все блокирующие присваивания на неблокирующие?
---
Вас может это удивить, но в обоих случаях будет сгенерирована схема, представленная на _рис. 12_.
![../.pic/Basic%20Verilog%20structures/assignments/fig_12.png](../.pic/Basic%20Verilog%20structures/assignments/fig_12.png)
_Рисунок 12. Схема, сгенерированная Vivado по описанию из Листинга 6._
> Девочка остолбенела. У неё возникло отчётливое чувство какой-то ужасной несправедливости по отношению к ней. Гарри Поттер был грязным, отвратительным обманщиком и лжецом. Но во время игры все его ответы были верными. [Элиезер Юдковский / Гарри Поттер и методы рационального мышления]
На протяжении всего документа вам рассказывали, что использование блокирующих присваиваний приведет к изменению поведения и синтезу другой схемы, а теперь сами же приводят пример, где схема остается точно такой же!
Давайте разберемся по порядку, что же произошло.
Все дело в изменении блока `always`. Когда мы использовали `always_ff @(posedge clk)`, этот программный блок исполнялся только один раз за такт.
Теперь, когда мы стали использовать блок `always_comb`, правила игры изменились. Нет, принцип работы блокирующих и неблокирующих присваиваний остался тем же самым. Изменилось только то, сколько раз будет вызван данный блок.
Начнем со схемы, построенной по блокирующему присваиванию. В общем-то, тут у вас не должно было возникнуть вопросов, логика ровно та же, что была и при построении схемы по _Листингу 5_ (_рис. 10)_, только без выходного регистра. Что логично, ведь мы убрали тактирующий сигнал.
Вопрос в том, почему это вдруг схема, построенная после замены блокирующих присваиваний на неблокирующие ведет себя точно так же?
Рассмотрим _рис. 13_.
![../.pic/Basic%20Verilog%20structures/assignments/fig_13.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_13.drawio.svg)
_Рисунок 13. Моделирование цепочки присваиваний в комбинационном блоке `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`, при неблокирующем потребовалось несколько проходов. Однако с точки зрения пользователя, читающего временную диаграмму, в обоих ситуациях сигналы изменили свое значение мгновенно.
Поэтому не смотря на различия в типах присваиваний схемы получились одинаковыми.
> Получается что для комбинационной логики нет разницы между блокирующим и неблокирующим присваиванием, после переходных процессов результат будет одинаковым?
И да и нет. С точки зрения синтеза схемы так и есть. Однако есть нюанс в случае моделирования схемы.
Особенность планировщика событий симуляции такова, что все `RHS` неблокирующих присваиваний будут вычислены после выполнения всех блокирующих присваиваний.
Чем это может быть удобно?
Давайте сделаем небольшое лирическое отступление и разберем часто используемую в мире разработки цифровых схем аббревиатуру `RTL`. Она расшифровывается как `Register Transfer Level` — уровень межрегистровых передач. Цифровую схему можно рассматривать на различных уровнях абстракции. Можно рассматривать с физического уровня: как набор проводников, транзисторов, а так же электронов и "дырок", бегающих по ним. Можно рассматривать схему на уровне логических вентилей (gate-level). Одним из таких уровней абстракции является уровень межрегистровых передач, когда мы делим схему на две части: регистры, и комбинационную логика их связывающая.
По каждому такту синхроимпульса, в регистры будет записываться новое значение, что приведет к изменению на выходе регистра и начнет цепочку изменений в комбинационной логике, соединяющей регистры. К следующему такту на концах комбинационной логике, являющихся входами регистров должны оказаться установившееся значения, которые будут записаны и все повторится снова.
Было бы удобно разделить эти две части: обновление значений в регистрах и обновление значений в комбинационной логике таким образом, чтобы сперва обновлялась комбинационная логика, а потом уже обновлялось значение в регистрах.
Именно этого можно добиться, если комбинационная логика будет описана посредством блокирующих присваиваний, которые будут выполнены до неблокирующих присваиваний, используемых при описании регистров.
Подведем итоги прочитанному:
- Блокирующее присваивание блокирует выполнение остальных операций до завершения текущего присваивания. Оно подобно обычному присваиванию в парадигме программирования.
- Неблокирующее присваивание сперва вычисляет `RHS`, давая исполниться остальным операциям до самого присваивания. Причем `RHS` в случае, если после неблокирующего присваивания выполняется блокирующее от `LHS` которого зависело `RHS` неблокирующего присваивания — значение `RHS` будет обновлено.
В связи с особенностями поведения блокирующего и неблокирующего присваивания, выведены следующие две максимы:
- При описании последовательностной логики (регистров) используйте **неблокирующее** присваивание.
- При описании комбинационной логики используйте **блокирующее** присваивание.
Кроме того, существуют следующие рекомендации и требования[1, стр. 5]:
-ри описании как последовательностной логики, так и комбинационной в одном блоке `always` используйте неблокирующее присваивание._
- _Не смешивайте в одном блоке блокирующие и неблокирующие присваивания_ — стандарт допускает подобное описание, но оно затрудняет его чтение.
- _Не смешивайте блокирующие и неблокирующие присваивания для одного и того же сигнала_ — стандарт это запрещает.
Использованная литература:
1. [Clifford E. Cummings / Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kil](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)