- 9.7 Процедурное управление временем
- 9.7.1 Управление задержкой
- 9.7.2 Контроль событий
- 9.7.3 Именованные события
- 9.7.4 Событийный оператор or
- 9.7.5 Неявный список событийных выражений
- 9.7.6 Чувствительное к уровню в управлении событиями
- 9.7.7 Контроль времени внутри назначения
- 9.8 Блочные объявления
- 9.8.1 Последовательные блоки
- 9.8.2 Параллельные блоки
- 9.8.3 Именованные блоков
- 9.8.4 Время начала и окончания
- 9.9 Структурированные процедуры
- 9.9.1 Конструкция initial
- 9.9.2 Конструкции always

9.7 Процедурное управление временем
Verilog HDL имеет два типа явного контроля времени выполнения процедурных объявлений. Первый тип — это управление задержкой, в котором выражение определяет продолжительность времени между первоначальной встречей объявления и моментом, когда объявление фактически выполняется. Выражение задержки может быть динамической функцией состояния схемы, но может быть и простым числом, которое разделяет выполнение операторов во времени. Управление задержкой является важной характеристикой при задании источника описаний формы сигнала. Он описан в разделах 9.7.1 и 9.7.7.
Второй тип управления временем — выражение события, которое позволяет отложить выполнение оператора до наступления некоторого события имитации, происходящего в процедуре, выполняющейся параллельно с этой процедурой. Событием имитации может быть изменение значения сети или переменной (неявное событие) или наступление явно именного события, которое вызывается из других процедур (явное событие). Чаще всего управление событием — это положительный или отрицательный фронт на тактовом сигнале. Управление событиями рассматривается в разделах 9.7.2—9.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
Задержки в вентилях и сетках также увеличивают время моделирования, как обсуждалось в пункте 6. Три процедурных метода управления временем обсуждаются в пунктах 9.7.1—9.7.7.
9.7.1 Управление задержкой
Процедурный оператор, следующий за элементом управления задержкой, задерживается в своем выполнении относительно процедурного оператора, предшествующего элементу управления задержкой, на указанную задержку. Если выражение задержки оценивается в неизвестное или высокоимпедансное значение, оно интерпретируется как нулевая задержка. Если выражение задержки оценивается отрицательным значением, оно интерпретируется как двузначное целое без знака того же размера, что и переменная времени. В выражении задержки разрешено указывать параметры. Они могут быть переопределены аннотацией SDF, в этом случае выражение переоценивается.
Например:
#10 rega = regb;
#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.
Переходы | к 0 | к 1 | к x | к z |
---|---|---|---|---|
с 0 | — | posedge | posedge | posedge |
с 1 | negedge | — | negedge | negedge |
с x | negedge | posedge | — | — |
с z | negedge | posedge | — | — |
Неявное событие обнаруживается при любом изменении значения выражения. Событие на фронтах должно быть обнаружено только в наименее значимом бите выражения. Изменение значения любого операнда выражения без изменения результата выражения не должно быть зафиксировано как событие.
Например:
@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.7.2.
Объявленное событие происходит при активации оператора запуска события, синтаксис которого приведен в Синтаксисе 9-10. Событие не возникает при изменении индекса массива событий в выражении управления событиями.
event_trigger ::=
-> hierarchical_event_identifier { [ expression ] } ;
Управляемый событием оператор (например, @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, должны быть включены в эти правила.
Например:
always @(*) // эквивалентно @(a or b or c or d or f)
y = (a & b) | (c & d) | myfunction(f);
always @* begin // эквивалентно @(a or b or c or d or tmp1 or tmp2)
tmp1 = a & b;
tmp2 = c & d;
y = tmp1 | tmp2;
end
always @* begin // эквивалентно @(b)
@(i) kid= b; // i не добавляется к @*
end
always @* begin // эквивалентно @(a or b or c or d)
x = a ^ b;
@* // эквивалентно @(c or d)
x = c ^ d;
end
always @* begin // эквивалентно @(a or en)
y = 8'hff;
y[a] = !en;
end
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
Например:
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
Задержка внутри назначения и контроль событий могут применяться как к блокирующим назначениям, так и к неблокирующим назначениям. Управление repeat события должно задавать задержку внутри назначения на определенное количество повторений события. Если литерал счетчика повторений или знаковый регистр, содержащий счетчик повторений, меньше или равен 0 в момент оценки, назначение происходит так, как будто нет конструкции повторения.
repeat (-3) @ (event_expression)
// не будет выполняться event_expression.
repeat (a) @ (event_expression)
// если a присвоено значение -3, то будет выполнено выражение event_expression
// если a объявлено как беззнаковый reg, но не если a является знаковым
Эта конструкция удобна, когда события должны быть синхронизированы с отсчетами тактовых сигналов. Например:
Таблица 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
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 показаны действия, возникающие при повторном управлении событиями.

В этом примере значение 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 ;
Например
begin
areg = breg;
creg = areg; // creg хранит значение breg
end
Выполняется первое присвоение, и areg обновляется перед передачей управления на второе присвоение.
begin
areg = breg;
@(posedge clock) creg = areg; // присвоение отложено до положительного фронта clock
end
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 ;
Элементы управления временем в блоке fork—join не обязательно должны быть упорядочены последовательно во времени. Например:
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 Время начала и окончания
Как последовательные, так и параллельные блоки имеют понятие времени начала и окончания. Для последовательных блоков время начала — это время выполнения первого объявления, а время окончания — время выполнения последнего объявления. Для параллельных блоков время начала одинаково для всех объявлений, а время окончания — когда выполнено последнее упорядоченное по времени объявление.
Последовательные и параллельные блоки могут быть встроены друг в друга, что позволяет легко и с высокой степенью структурированности выражать сложные управляющие структуры. Когда блоки вложены друг в друга, важно время начала и завершения блока. Выполнение не должно продолжаться в операторе, следующем за блоком, пока не будет достигнуто время завершения блока, то есть пока блок полностью не завершит выполнение.
Например:
fork
#250 -> end_wave;
#200 r = 'hF7;
#150 r = 'h00;
#100 r = 'hE2;
#50 r = 'h35;
join
begin
fork
@Aevent;
@Bevent;
join
areg = breg;
end
Эти два события могут произойти в любом порядке (или даже в одно и то же время моделирования), блок fork-join завершится, и назначение будет выполнено. Напротив, если бы блок fork—join был блоком begin—end и событие B произошло до события A, то блок ожидал бы следующего события B.
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
Например:
initial begin
areg = 0; // иницилизация регистра
for (index = 0; index < size; index = index + 1)
memory[index] = 0; //иницилизация слов памяти
end
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
Конструкция always из-за своей циклической природы полезна только при использовании в сочетании с некоторой формой контроля времени. Если конструкция always не имеет контроля за продвижением времени моделирования, она создаст состояние тупика моделирования.
always areg = ~areg;
always #half_period areg = ~areg;