9. Процедурные назначения. Поведенческое моделирование в Verilog HDL.

Процедурные конструкции, которые описываются, хорошо подходят для решения задач, как описание микропроцессора или реализация сложных проверок синхронизации.
Содержание
Превью(обложка) видео для поста 9. Процедурные назначения. Поведенческое моделирование в Verilog HDL.

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

Этот пункт начинается с краткого обзора поведенческой модели, чтобы обеспечить контекст для многих типов поведенческих утверждений в Verilog HDL.

9.1 Обзор поведенческой модели

Поведенческие модели Verilog содержат процедурные конструкции, которые управляют моделированием и манипулируют переменными типов данных, описанных ранее. Эти утверждения содержатся в процедурах. Каждая процедура имеет связанный с ней поток действий.

Деятельность начинается с управляющих конструкций initial и always. Каждая конструкция initial и always запускает отдельный поток активности. Все потоки активности являются параллельными, чтобы смоделировать присущую аппаратуре параллельность. Формальное описание этих конструкций приведено в разделе 9.9.

В следующем примере показана полная поведенческая модель Verilog.
module behave; reg [1:0] a, b; initial begin a = 'b1; b = 'b0; end always begin #50 a = ~a; end always begin #100 b = ~b; end endmodule

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

В этой модели переменные reg a и b инициализируются в 1 и 0, соответственно, в нулевой момент времени моделирования. После этого начальная конструкция завершается и больше не выполняется во время этого цикла моделирования. Эта начальная конструкция содержит блок операторов begin-end (также называемый последовательным блоком). В этом блоке begin-end сначала инициализируется a, а затем b.

Конструкции always также начинаются в нулевой момент времени, но значения переменных не изменяются до тех пор, пока не истечет время, заданное регуляторами задержки (введенными через #). Таким образом, reg a инвертируется через 50 единиц времени, а reg b — через 100 единиц времени. Поскольку конструкции всегда повторяются, эта модель будет генерировать две меандра. Reg a переключается с периодом 100 единиц времени, а reg b переключается с периодом 200 единиц времени. Эти две всегда повторяющиеся конструкции выполняются одновременно на протяжении всего времени моделирования.

9.2 Процедурные назначения

Как описано в пункте 6, процедурные назначения используются для обновления типов данных reg, integer, time, real, realtime и memory. Существует существенная разница между процедурными и непрерывными назначениями:

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

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

  • Тип данных reg, integer, real, realtime или time. Присвоение ссылки на имя одного из этих типов данных.
  • Битовая выборка типа данных reg, integer или time. Назначение одного бита, при котором остальные биты остаются нетронутыми.
  • Частичная выборка типа данных reg, integer или time. Частичная выборка одного или нескольких смежных битов, при которой остальные биты остаются нетронутыми.
  • Слово памяти: одно слово памяти.
  • Конкатенация или вложенная конкатенация любого из вышеперечисленных. Конкатенация или вложенная конкатенация любой из предыдущих четырех форм.

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

Как описано в разделе 5.4, если правая часть оценивается в меньшее количество битов, чем левая часть, значение правой части подставляется в размер левой части. Если правая часть беззнаковая, она заполняется в соответствии с правилами, указанными в пункте 5.4.1. Если правая часть знаковая, она расширяется по знаку.

Verilog HDL содержит два типа процедурных операторов назначения:

  • Блокирование процедурных операторов назначения(присваивания)
  • Неблокирующие процедурные операторы назначения(присваивания)

Блокирующие и неблокирующие операторы процедурного назначения задают различные процедурные потоки в последовательных блоках.

9.2.1 Блокирование процедурных назначений(присваиваний)

Блокирующий процедурный оператор назначения должен быть выполнен до выполнения операторов, следующих за ним последовательном блоке(см. 9.8.1). Блокирующий процедурный оператор назначения не должен препятствовать выполнению операторов, следующих за ним параллельном блоке (см. 9.8.2).

Синтаксис блокирующего процедурного назначения приведен в Синтаксисе 9-1.

blocking_assignment ::= variable_lvalue = [ delay_or_event_control ] expression delay_control ::= # delay_value | # ( mintypmax_expression ) delay_or_event_control ::= delay_control | event_control | repeat ( expression ) event_control event_control ::= @ hierarchical_event_identifier | @ ( event_expression ) | @* | @ (*) event_expression ::= expression | posedge expression | negedge expression | event_expression or event_expression | event_expression , event_expression variable_lvalue ::= hierarchical_variable_identifier [ { [ expression ] } [ range_expression ] ] | { variable_lvalue { , variable_lvalue } }
Синтаксис 9-1-Синтаксис для блокирующих назначений

В этом синтаксисе variable_lvalue — это тип данных, допустимый для процедурного оператора назначения. Символ = — оператор назначения, а delay_or_event_control — это необязательное управление временем внутри назначения. Управление может быть либо управлением задержки (например, #6), либо управлением события (например, @(posedge clk)).

Выражение — это значение правой части, которое должно быть присвоено левой части. Если variable_lvalue требует оценки, она должна быть оценена в момент времени, указанный внутриприсвоительным контролем времени.

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

Например:

В следующих примерах показаны блокирующие процедурные назначения:
rega = 0; rega[3] = 1; // битовая выборка(bit-select) rega[3:5] = 7; // частичная выборка(part-select) mema[address] = 8'hff; // присвоение элементу памяти {carry, acc} = rega + regb; // конкатенация

9.2.2 Неблокирующее процедурное назначение(присвоение)

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

Синтаксис неблокирующего процедурного назначения приведен в Синтаксисе 9-2.

nonblocking_assignment ::= variable_lvalue <= [ delay_or_event_control ] expression delay_control ::= # delay_value | # ( mintypmax_expression ) delay_or_event_control ::= delay_control | event_control | repeat ( expression ) event_control event_control ::= @ hierarchical_event_identifier | @ ( event_expression ) | @* | @ (*) event_expression ::= expression | posedge expression | negedge expression | event_expression or event_expression | event_expression , event_expression variable_lvalue ::= hierarchical_variable_identifier [ { [ expression ] } [ range_expression ] ] | { variable_lvalue { , variable_lvalue } }
Синтаксис 9-2-Синтаксис для неблокирующих назначений(присвоений)

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

Неблокирующий оператор назначения — это тот же оператор, что и реляционный оператор less- than-or-equal-to. Интерпретация должна определяться контекстом, в котором появляется <=. Когда <= используется в выражении, он должен интерпретироваться как реляционный оператор; а когда он используется в неблокирующем процедурном назначении, он должен интерпретироваться как оператор присвоения.

Неблокирующие процедурные задания должны оцениваться в два этапа, как описано в п. 11. Эти два
module evaluates2 (out); output out; reg a, b, c; initial begin a = 0; b = 1; c = 0; end always c = #5 ~c; always @(posedge c) begin a <= b; // evaluates, schedules, b <= a; // and executes in two steps end endmodule

Шаг 1: В позе c симулятор оценивает правые части неблокирующих назначений и планирует присвоение новых значений в конце событий обновления неблокирующих назначений (см. 11.4). Неблокирующие назначение происходит каждом смене значения переменой c

a = 0
b = 1

Шаг 2: Когда симулятор активирует события обновления неблокирующего назначения, симулятор обновляет левую часть каждого неблокирующего назначения.

Значения назначений:

a = 1
b = 0

Перевод Официального Стандарта Verilog HDL

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

В отличие от события или управления задержкой блокирующих присвоений, неблокирующее присвоение не блокирует процедурный поток. Неблокирующее присвоение оценивает, планирует присвоение, но не блокирует выполнение последующих операторов в блоке begin-end.

Пример 2
//non_block1.v module non_block1; reg a, b, c, d, e, f; //blocking assignments initial begin a = #10 1; // a будет присвоено 1 через 10 b = #2 0; // b будет присвоено 0 через 12 c = #4 1; // c будет присвоено 1 через 16 end //non-blocking assignments initial begin d <= #10 1; // d будет присвоено 1 через 10 e <= #2 0; // e будет присвоено 0 через 2 f <= #4 1; // f будет присвоено 1 через 4 end endmodule

Запланированные изменения в момент времени 2: e = 0

Запланированные изменения в момент времени 4: f = 1

Запланированные изменения в момент времени 10: d = 1

Перевод Официального Стандарта Verilog HDL

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

Пример 3
//non_block1.v module non_block1; reg a, b; initial begin a = 0; b = 1; a <= b; // evaluates, schedules, and b <= a; // executes in two steps end initial begin $monitor ($time, ,"a = %b b = %b", a, b); #100 $finish; end endmodule

Шаг 1: Симулятор оценивает правую часть неблокирующих назначений и присваивает задания на конец текущего временного шага.

Шаг 2: В конце текущего временного шага симулятор обновляет левую часть каждого неблокирующего оператора назначения.

Значения назначений: a = 1 || b = 0

Перевод Официального Стандарта Verilog HDL

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

Пример 4
module multiple; reg a; initial a = 1; // Присвоенное значение reg является детерминированным initial begin a <= #4 0; // планирует a = 0 в момент времени 4 a <= #4 1; // планирует a = 1 в момент времени 4 end // Через 4, a = 1 endmodule

Если симулятор выполняет два процедурных блока одновременно и если эти процедурные блоки содержат неблокирующие операторы назначения одной и той же переменной, конечное значение этой переменной является неопределенным. Например, значение reg a является неопределенным в следующем примере:

Пример 5
module multiple2; reg a; initial a = 1; initial a <= #4 0; // планируется 0 черезе 4 initial a <= #4 1; // планируется 1 черезе 4 // черезе 4, a = ?? // Присвоенное значение регистра является неопределенным endmodule

Тот факт, что два неблокирующих присвоения одной и той же переменной находятся в разных блоках, сам по себе не является достаточным для того, чтобы сделать порядок присвоений переменной неопределенным. Например, значение reg a в конце временного цикла 16 является детерминированным в следующем примере:

Пример 6
module multiple3; reg a; initial #8 a <= #8 1; // выполняется в момент времени 8; // планирует обновление на 1 в момент времени 16 initial #12 a <= #4 0; // выполняется в момент времени 12; // планирует обновление 0 в момент времени 16 // Поскольку определено, что обновление a до значения 1 // запланировано до обновления a до значения 0, // тогда определено, что a будет иметь значение 0 // в конце временного интервала 16. endmodule

Следующий пример показывает, как значение i[0] назначается на r1 и как назначение планируется после каждой временной задержки:

Пример 7

module multiple4; reg r1; reg [2:0] i; initial begin // выполняет присвоение r1 без отмены предыдущих присвоений for (i = 0; i <= 5; i = i+1) r1 <= # (i*10) i[0]; end endmodule
Временное описание симуляции глава 9 Поведенческое моделирование в Verilog HDL. Часть 1.
Синтаксис 9-2-Синтаксис для неблокирующих назначений(присвоений)

9.3 Процедурные непрерывные назначения

Процедурные непрерывные назначения (с использованием ключевых слов assign и force) — это процедурные назначения, которые позволяют непрерывно передавать выражения переменным или сетям. Синтаксис этих операторов приведен в Синтаксисе 9-3.

Левая часть назначения в операторе assign должна быть ссылкой на переменную или конкатенацией переменных. Это не должно быть слово памяти (ссылка на массив) или битовая или частичная выборка переменной.

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

net_assignment ::= net_lvalue = expression procedural_continuous_assignments ::= assign variable_assignment | deassign variable_lvalue | force variable_assignment | force net_assignment | release variable_lvalue | release net_lvalue variable_assignment ::= variable_lvalue = expression net_lvalue ::= hierarchical_net_identifier [ { [ constant_expression ] } [ constant_range_expression ] ] | { net_lvalue { , net_lvalue } } variable_lvalue ::= hierarchical_variable_identifier [ { [ expression ] } [ range_expression ] ] | { variable_lvalue { , variable_lvalue } }
Синтаксис 9-3-Синтаксис для процедурных непрерывных назначений

9.3.1 Процедурные назначения assign и deassign

Процедурный оператор непрерывного назначения assign должен переопределять все процедурные назначения переменной. Процедурный оператор deassign завершает процедурное непрерывное присвоение переменной. Значение переменной остается неизменным до тех пор, пока переменной не будет присвоено новое значение с помощью процедурного назначения или процедурного непрерывного назначения. Процедурные операторы assign и deassign позволяют, например, моделировать асинхронный сброс/очистку на D-типе flip-flop с фронтальным триггером, где тактовый генератор блокируется, когда активен сброс или предустановка.

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

Например:

В следующем примере показано использование процедурных операторов assign и deassign в описании поведения flip-flop D-типа с входами preset и clear:
module dff (q, d, clear, preset, clock); output q; input d, clear, preset, clock; reg q; always @(clear or preset) if (!clear) assign q = 0; else if (!preset) assign q = 1; else deassign q; always @(posedge clock) q = d; endmodule

Если и clear, и preset низкие, то выход q будет постоянно удерживаться на соответствующем постоянном значении, и положительный фронт тактового генератора не повлияет на q. Если и clear, и preset высокие, то q дезассигнуется.

9.3.2 Процедурные назначения force и release

Другая форма процедурного непрерывного назначения обеспечивается процедурными операторами force и release. Эти операторы имеют схожий эффект с парой assign-deassign, но force может быть применена как к сеткам, так и к переменным. Левая сторона назначения может быть переменной, сеткой, константным битовой или частичной выборкой векторной сетки или конкатенацией. Это не может быть слово памяти (ссылка на массив), битовая выборка или частичная выборка векторной переменной.

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

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

Например:
module test; reg a, b, c, d; wire e; and and1 (e, a, b, c); initial begin $monitor("%d d=%b,e=%b", $stime, d, e); assign d = a & b & c; a = 1; b = 0; c = 1; #10; force d = (a | b | c); force e = (a | b | c); #10; release d; release e; #10 $finish; end endmodule

Результаты:
0 d=0,e=0
10 d=1,e=1
20 d=0,e=0

Перевод Официального Стандарта Verilog HDL

В этом примере экземпляр вентиля and «and1» «исправлен», чтобы действовать как вентиль or. Это сделано с помощью процедурного оператора force, который присваивает его выход на значение входов ORed.

Правая часть процедурного непрерывного назначения или оператора force может быть выражением. Оно должно рассматриваться так же, как и непрерывное назначение. То есть, если какая-либо переменная в правой части назначения изменяется, то назначение должно быть переоценено, пока действует assign или force. Например:
force a = b + f(c) ;

Если b изменится или c изменится, a будет принудительно приведен к новому значению выражения b+f(c).