9. Initial, always, задержки, блоки Verilog HDL

Описание конструкций always, initial, 3-х видов задержек(wait, события(event), задержки), последовательного begin-end и параллельного блоков fork-join.
Содержание
Превью(обложка) видео для поста 9. Initial, always, задержки, блоки Verilog HDL

9.7 Процедурное управление временем

Verilog HDL имеет два типа явного контроля времени выполнения процедурных объявлений. Первый тип — это управление задержкой, в котором выражение определяет продолжительность времени между первоначальной встречей объявления и моментом, когда объявление фактически выполняется. Выражение задержки может быть динамической функцией состояния схемы, но может быть и простым числом, которое разделяет выполнение операторов во времени. Управление задержкой является важной характеристикой при задании источника описаний формы сигнала. Он описан в разделах 9.7.1 и 9.7.7.

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

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

  • Контроль задержки, который вводится символом #.
  • Управление событием, которое представлено символом @.
  • Оператор wait, который работает как комбинация управления событиями и цикла while.

Синтаксис 9-8 описывает управление временем в процедурных операторах.

delay_control ::= (From A.6.5) # delay_value | # ( mintypmax_expression ) event_control ::= @ hierarchical_event_identifier | @ ( event_expression ) | @* | @ (*) procedural_timing_control ::= delay_control | event_control procedural_timing_control_statement ::= | procedural_timing_control statement_or_null
Синтаксис 9-8 — Синтаксис для процедурного управления временем

Задержки в вентилях и сетках также увеличивают время моделирования, как обсуждалось в пункте 6. Три процедурных метода управления временем обсуждаются в пунктах 9.7.19.7.7.

9.7.1 Управление задержкой

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

Например:

Пример 1-В следующем примере выполнение задания задерживается на 10 единиц времени:
#10 rega = regb;
Пример 2-В следующих трех примерах приводится выражение, следующее за знаком числа (#). Выполнение задания задерживается на количество времени моделирования, заданное значением выражения.
#d rega = regb; //d определяется как параметр #((d+e)/2) rega = regb; // задержка - это среднее значение d и e #regr regr = regr + 1; // задержка - это значение в regr

9.7.2 Контроль событий

Выполнение процедурного оператора может быть синхронизировано с изменением значения сети или переменной или наступлением объявленного события. Изменения значений сетей и переменных можно использовать в качестве событий для запуска выполнения оператора. Это известно как обнаружение неявного события. Событие также может быть основано на направление изменения, то есть в сторону значения 1 (posedge(восходящий фронт)) или в сторону значения 0 (negedge(спадающий фронт)). Поведение событий posedge и negedge показано в таблице 9-1 и может быть описано следующим образом:

  • При переходе от 1 к x, z или 0 и от x или z к 0 обнаруживается спадающий фронт сигнала
  • Восходящий фронт сигнала обнаруживается при переходе от 0 к x, z или 1 и от x или z к 1.
Таблица 9-1-Определение posedge и negedge
Переходык 0к 1к xк z
с 0posedgeposedgeposedge
с 1negedgenegedgenegedge
с xnegedgeposedge
с znegedgeposedge

Неявное событие обнаруживается при любом изменении значения выражения. Событие на фронтах должно быть обнаружено только в наименее значимом бите выражения. Изменение значения любого операнда выражения без изменения результата выражения не должно быть зафиксировано как событие.

Например:

В следующем примере показаны иллюстрации объявления с контролем фронта:
@r rega = regb; // управляется любым изменением значения в reg r @(posedge clock) rega = regb; // управляется положением на такте always @(negedge clock) rega = regb; // управляется отрицательным фронтом

9.7.3 Именованные события

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

Имя события должно быть объявлено в явном виде перед его использованием. В синтаксисе 9-9 приведен синтаксис для объявления событий.

event_declaration ::= event list_of_event_identifiers ; list_of_event_identifiers ::= event_identifier { dimension } { , event_identifier { dimension } } dimension ::= [ dimension_constant_expression : dimension_constant_expression ]
Синтаксис 9-9 — Синтаксис объявления события

Событие не должно содержать никаких данных. Ниже перечислены характеристики именованного события:

  • Его можно заставить произойти в любое определенное время.
  • Она не имеет временной продолжительности.
  • Его появление можно распознать с помощью синтаксиса управления событиями, описанного в разделе 9.7.2.

Объявленное событие происходит при активации оператора запуска события, синтаксис которого приведен в Синтаксисе 9-10. Событие не возникает при изменении индекса массива событий в выражении управления событиями.

event_trigger ::= -> hierarchical_event_identifier { [ expression ] } ;
Синтаксис 9-10 — Синтаксис для триггера события

Управляемый событием оператор (например, @trig rega = regb;) заставляет имитацию содержащей его процедуры ждать, пока какая-то другая процедура не выполнит соответствующий триггерный оператор (например, -> trig).

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

9.7.4 Событийный оператор or

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

Например:

Следующие два примера показывают логическое или двух и трех событий, соответственно:
@(trig or enable) rega = regb; // управляется trig или enable @(posedge clk_a or posedge clk_b or trig) rega = regb;
В следующих примерах показано использование запятой (,) в качестве логического оператора или оператора события:
always @(a, b, c, d, e) always @(posedge clk, negedge rstn) always @(a or b, c, d or e)

9.7.5 Неявный список событийных выражений

Список выражений event_expression управления событиями является распространенным источником ошибок при моделировании на уровне передачи регистров (RTL). Пользователи обычно забывают добавить некоторые сетки или переменные, считанные в операторе управления синхронизацией. Это часто обнаруживается при сравнении RTL и gate-level версий проекта.

Неявное выражение event_expression, @*, является удобным сокращением, которое устраняет эти проблемы путем добавления всех сеток и переменных, которые считываются оператором (который может быть группой операторов) процедурного выражения управления временем (procedural_timing_ control_statement), к выражению event_expression.

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

  • Идентификаторы, которые появляются только в выражениях ожидания или события.
  • Идентификаторы, которые появляются только как hierarchical_variable_identifier в variable_lvalue в левой части присваиваний.

Сетки и переменные, которые появляются в правой части присваиваний, в вызовах функций и задач, в выражениях case и условных выражениях(if), как индексная переменная в левой части присваиваний или как переменные в выражениях с элементами case, должны быть включены в эти правила.

Например:

Пример 1
always @(*) // эквивалентно @(a or b or c or d or f) y = (a & b) | (c & d) | myfunction(f);
Пример 2
always @* begin // эквивалентно @(a or b or c or d or tmp1 or tmp2) tmp1 = a & b; tmp2 = c & d; y = tmp1 | tmp2; end
Пример 3
always @* begin // эквивалентно @(b) @(i) kid= b; // i не добавляется к @* end
Пример 4
always @* begin // эквивалентно @(a or b or c or d) x = a ^ b; @* // эквивалентно @(c or d) x = c ^ d; end
Пример 5
always @* begin // эквивалентно @(a or en) y = 8'hff; y[a] = !en; end
Пример 6
always @* begin // эквивалентно @(state or go or ws) next = 4'b0; case (1'b1) state[IDLE]: if (go) next[READ] = 1'b1; else next[IDLE] = 1'b1; state[READ]: next[DLY ] = 1'b1; state[DLY ]: if (!ws) next[DONE] = 1'b1; else next[READ] = 1'b1; state[DONE]: next[IDLE] = 1'b1; endcase end

9.7.6 Чувствительное к уровню в управлении событиями

Выполнение процедурного оператора также может быть отложено до тех пор, пока условие не станет истинным. Для этого используется оператор wait(ожидания), который является специальной формой управления событиями. Оператор wait чувствителен к уровню, в отличие от базового управления событиями (задается символом @), которое чувствительно к фронтам.

Оператор wait должен оценить условие, и если оно ложно, то процедурные операторы, следующие за оператором wait должны оставаться заблокированными до тех пор, пока это условие не станет истинным, прежде чем продолжить выполнение. Оператор ожидания имеет форму, приведенную в синтаксисе 9-11.

wait_statement ::= wait ( expression ) statement_or_null
Синтаксис 9-11 — Синтаксис для оператора ожидания

Например:

В следующем примере показано использование оператора wait для осуществления чувствительного к уровню управления событиями:
begin wait (!enable) #10 a = b; #10 c = d; end

Если при входе в блок значение enable равно 1, оператор wait задержит оценку следующего оператора (#10 a = b;) до тех пор, пока значение enable не изменится на 0. Если при входе в блок begin-end значение enable уже равно 0, то назначение «a = b;» будет оценено после задержки в 10 и никакой дополнительной задержки не произойдет.

9.7.7 Контроль времени внутри назначения

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

Внутриприсвоение задержки или управление событиями задерживает присвоение нового значения левой части, но выражение правой части оценивается до задержки, а не после нее. Синтаксис для задержки внутри назначений и управления событиями приведен в Синтаксисе 9-12.

blocking_assignment ::= variable_lvalue = [ delay_or_event_control ] expression 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
Синтаксис 9-12 — Синтаксис для управления задержкой и событиями внутри назначения

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

Например:
repeat (-3) @ (event_expression) // не будет выполняться event_expression. repeat (a) @ (event_expression) // если a присвоено значение -3, то будет выполнено выражение event_expression // если a объявлено как беззнаковый reg, но не если a является знаковым

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

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

Таблица 9-2-Эквивалентность управления синхронизацией при назначении времени
С конструкцией внутри назначенияБез конструкции внутри назначения
a = #5 b;begin
temp = b;
#5 a = temp;
end
a = @(posedge clk) b;begin
temp = b;
@(posedge clk) a = temp;
end
a = repeat(3) @(posedge clk) b;begin
temp = b;
@(posedge clk);
@(posedge clk);
@(posedge clk) a = temp;
end

В следующих трех примерах используется поведенческая конструкция fork-join. Все объявления между ключевыми словами fork и join выполняются параллельно. Более подробно эта конструкция описана в разделе 9.8.2.

В следующем примере показано состояние гонки, которое можно предотвратить с помощью управления синхронизацией внутри назначения:
fork #5 a = b; #5 b = a; join
Код в этом примере выбирает и устанавливает значения a и b в одно и то же время моделирования, создавая тем самым состояние гонки. Внутри назначения форма управления синхронизацией, используемая в следующем примере, предотвращает это состояние гонки.
fork a = #5 b; b = #5 a; join

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

Также эффективно внутриприсваивающее ожидание событий. В следующем примере правые выражения оцениваются, когда встречаются операторы назначения, но присвоения задерживаются до нарастающего фронта тактового сигнала:
fork // сдвиг данных a = @(posedge clk) b; b = @(posedge clk) c; join
Ниже приведен пример управления повторным событием в качестве задержки внутри назначения неблокирующего назначения:
a <= repeat(5) @(posedge clk) data;

На рисунке 9-1 показаны действия, возникающие при повторном управлении событиями.

Глава 9 часть 3 Поведенчинское моделирование Verilog HDL. Рисунок 9-1 Симуляция времени задержки с циклическим оператором repeat.
Рисунок 9-1 Управление повторным событием с использованием фронта clk

В этом примере значение data оценивается, когда встречается назначение. После пяти повторений posedge clk значению a присваивается значение data.

Ниже приведен пример управления повторным событием в качестве задержки внутри назначения процедурного назначения:
a = repeat(num) @(clk) data;

В этом примере значение data оценивается, когда встречается назначение. После того, как количество переходов clk сравняется со значением num, a присваивается значение data.

Ниже приведен пример элемента управления повторяющимся событием с выражениями, содержащими операции для указания количества повторений события и события, которое подсчитывается:
a <= repeat(a+b) @(posedge phi1 or negedge phi2) data;

В этом примере значение data оценивается, когда встречается назначение. После того как сумма положительных фронтов phi1 и отрицательных фронтов phi2 равна сумме a и b, a присваивается значение данных. Даже если posedge phi1 и negedge phi2 возникли в одно и то же время моделирования, каждая из них будет определена отдельно.

9.8 Блочные объявления

Операторы блока — это средство группировки операторов вместе, так что синтаксически они действуют как один оператор. В Verilog HDL существует два типа блоков:

  • Последовательный блок, также называемый блоком begin-end
  • Параллельный блок, также называемый блоком с fork-join

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

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

9.8.1 Последовательные блоки

Последовательный блок должен иметь следующие характеристики:

  • Объявления должны выполняться последовательно, одно за другим.
  • Значения задержки для каждого оператора должны рассматриваться относительно времени моделирования выполнения предыдущего оператора.
  • Управление должно выйти из блока после выполнения последнего оператора.

Синтаксис 9-13 представляет формальный синтаксис последовательного блока.

seq_block ::= begin [ : block_identifier { block_item_declaration } ] { statement } end block_item_declaration ::= { attribute_instance } reg [ signed ] [ range ] list_of_block_variable_identifiers ; | { attribute_instance } integer list_of_block_variable_identifiers ; | { attribute_instance } time list_of_block_variable_identifiers ; | { attribute_instance } real list_of_block_real_identifiers ; | { attribute_instance } realtime list_of_block_real_identifiers ; | { attribute_instance } event_declaration | { attribute_instance } local_parameter_declaration ; | { attribute_instance } parameter_declaration ;
Синтаксис 9-13 — Синтаксис для последовательного блока

Например

Пример 1 — Последовательный блок позволяет следующим двум назначениям иметь детерминированный результат:
begin areg = breg; creg = areg; // creg хранит значение breg end

Выполняется первое присвоение, и areg обновляется перед передачей управления на второе присвоение.

Пример 2 — Управление задержкой может быть использовано в последовательном блоке для разделения двух назначений во времени.
begin areg = breg; @(posedge clock) creg = areg; // присвоение отложено до положительного фронта clock end
Пример 3 — Следующий пример показывает, как комбинация последовательного блока и управления задержкой может быть использована для задания формы волны с временной последовательностью:
parameter d = 50; // d объявлен как параметр reg [7:0] r; // r объявлен как 8-битный reg begin // волновая форма, управляемая последовательной задержкой #d r = 'h35; #d r = 'hE2; #d r = 'h00; #d r = 'hF7; #d -> end_wave; //запуск события под названием end_wave end

9.8.2 Параллельные блоки

Параллельный блок должен иметь следующие характеристики:

  • Объявления должны выполняться параллельно.
  • Значения задержки для каждого объявления должны рассматриваться относительно времени имитации входа в блок.
  • Управление задержкой можно использовать для упорядочивания назначений по времени.
  • Управление передается из блока, когда выполняется последний упорядоченный по времени оператор.

В синтаксисе 9-14 приведен формальный синтаксис параллельного блока.

par_block ::= fork [ : block_identifier { block_item_declaration } ] { statement } join block_item_declaration ::= { attribute_instance } reg [ signed ] [ range ] list_of_block_variable_identifiers ; | { attribute_instance } integer list_of_block_variable_identifiers ; | { attribute_instance } time list_of_block_variable_identifiers ; | { attribute_instance } real list_of_block_real_identifiers ; | { attribute_instance } realtime list_of_block_real_identifiers ; | { attribute_instance } event_declaration | { attribute_instance } local_parameter_declaration ; | { attribute_instance } parameter_declaration ;
Синтаксис 9-14 — Синтаксис для параллельного блока

Элементы управления временем в блоке forkjoin не обязательно должны быть упорядочены последовательно во времени. Например:

Следующий пример кодирует описание формы сигнала, показанное в примере 3 раздела 9.8.1, используя параллельный блок вместо последовательного. Форма сигнала, получаемая на регистре, совершенно одинакова для обоих вариантов реализации.
fork #50 r = 'h35; #100 r = 'hE2; #150 r = 'h00; #200 r = 'hF7; #250 -> end_wave; join

9.8.3 Именованные блоков

Как последовательные, так и параллельные блоки могут быть названы, добавляя : имя_блока после ключевых слов begin или fork. Именование блоков служит нескольким целям:

  • Он позволяет объявлять для блока локальные переменные, параметры и именованные события.
  • Это позволяет ссылаться на блок в таких операторах, как оператор disable (см. 8.3).

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

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

9.8.4 Время начала и окончания

Как последовательные, так и параллельные блоки имеют понятие времени начала и окончания. Для последовательных блоков время начала — это время выполнения первого объявления, а время окончания — время выполнения последнего объявления. Для параллельных блоков время начала одинаково для всех объявлений, а время окончания — когда выполнено последнее упорядоченное по времени объявление.

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

Например:

Пример 1 — Следующий пример показывает объявления из примера в 9.8.2, записанные в обратном порядке и дающие ту же форму волны.
fork #250 -> end_wave; #200 r = 'hF7; #150 r = 'h00; #100 r = 'hE2; #50 r = 'h35; join
Пример 2 — Когда назначение должно быть произведено после того, как произошли два отдельных события, известных как для соединения событий может быть полезен блок fork-join.
begin fork @Aevent; @Bevent; join areg = breg; end

Эти два события могут произойти в любом порядке (или даже в одно и то же время моделирования), блок fork-join завершится, и назначение будет выполнено. Напротив, если бы блок forkjoin был блоком beginend и событие B произошло до события A, то блок ожидал бы следующего события B.

Пример 3 — В этом примере показаны два последовательных блока, каждый из которых будет выполняться при наступлении управляющего события. Поскольку управляющие события находятся в блоке forkjoin, они выполняются параллельно, и, следовательно, последовательные блоки также могут выполняться параллельно.
fork @enable_a begin #ta wa = 0; #ta wa = 1; #ta wa = 0; end @enable_b begin #tb wb = 1; #tb wb = 0; #tb wb = 1; end join

9.9 Структурированные процедуры

Все процедуры в Verilog HDL задаются в одном из следующих четырех объявлений:

  • Конструкция initial
  • Конструкция always
  • task
  • function

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

task и function — это процедуры, которые включаются из одного или нескольких мест других процедур. Задачи и функции описаны в пункте 10.

9.9.1 Конструкция initial

Синтаксис для конструкции initial приведен в Синтаксисе 9-15.

initial_construct ::= initial statement
Синтаксис 9-15 — Синтаксис для конструкции initial

Например:

Следующий пример иллюстрирует использование конструкции initial для инициализации переменных в начале моделирования.
initial begin areg = 0; // иницилизация регистра for (index = 0; index < size; index = index + 1) memory[index] = 0; //иницилизация слов памяти end
Другим типичным использованием конструкции initial является спецификация описаний форм сигналов, которые выполняются один раз, чтобы обеспечить источник для основной части моделируемой схемы.
initial begin inputs = 'b000000; // initialize at time zero #10 inputs = 'b011001; // first pattern #10 inputs = 'b011011; // second pattern #10 inputs = 'b011000; // third pattern #10 inputs = 'b001000; // last pattern end

9.9.2 Конструкции always

Конструкция always повторяется непрерывно в течение всего времени моделирования. Синтаксис 9-16 показывает синтаксис для конструкции always.

always_construct ::= always statement
Синтаксис 9-16 — Синтаксис для конструкции always

Конструкция always из-за своей циклической природы полезна только при использовании в сочетании с некоторой формой контроля времени. Если конструкция always не имеет контроля за продвижением времени моделирования, она создаст состояние тупика моделирования.

Следующий код, например, создает бесконечный цикл с нулевой задержкой:
always areg = ~areg;
Предоставление контроля времени в вышеприведенном коде создает потенциально полезное описание, как показано ниже:
always #half_period areg = ~areg;