МАРСОХОД

Open Source Hardware Project

Введение в Verilog, Четвертый урок. Поведенческие блоки.

Урок Verilog

Раньше мы уже познакомились с постоянным назначением сигналов, оно выглядит, например, вот так:


wire a,b,c;
assign c = a & b;

Постоянные назначения весьма полезны, но и они имеют недостатки. Такой код, когда его много, не очень легко читать. Чтобы сделать язык Verilog более выразительным, он имеет так называемые "always" блоки. Они используются при описании системы с помощью поведенческих блоков (behavioral blocks). Использование поведенческих блоков очень похоже на программирование на языке С. Оно позволяет выразить алгоритм так, чтобы он выглядел как последовательность действий (даже если в конечном счете в аппаратуре это будет не так).

Для описания поведенческого блока используется вот такой синтаксис:


always @(<sensitivity_list>) <statements>
<sensitivity_list> – это список всех входных сигналов, к которым чувствителен блок. Это список входных сигналов, изменение которых влияет выходные сигналы этого блока. "Always" переводится как "всегда". Такую запись можно прочитать вот так: "Всегда выполнять выражения <statements> при изменении сигналов, описаных в списке чувствительности <sensitivity list>".

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

always @(a or b or d) <statements>
Иногда гораздо проще и надежней включать в список чувствительности все сигналы. Это делается вот так:

always @* <statements>
Тогда исправляя выражения в  <statements> вам не нужно задумываться об изменении списка чувствительности.

При описании выражений внутри поведенческих блоков комбинаторной логики, с правой стороны от знака равенства, как и раньше, можно использовать типы сигналов wire или reg, а вот с левой стороны теперь используется только тип reg:

reg [3:0] c;
always @(a or b or d)
begin
c = <выражение использующее входные сигналы a,b,d>;
end

Обратите внимание, что регистры, которым идет присвоение в таких поведенческих блоках не будут выполнены в виде D-триггеров после синтеза. Это часто вызывает недоумение у начинающих.

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

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

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

wire [3:0] a, b, c, d, e;
reg [3:0] f, g, h, j;
always @(a or b or c or d or e)
begin
f = a + b;
g = f & c;
h = g | d;
j = h - e;
end

То же самое можно сделать по другому, вот так:

always @(a or b or c or d or e)
begin
j = (((a + b) & c) | d) - e;
end

На самом деле, после того, как проект будет откомпилирован, список всех сигналов проекта (netlist) может сильно сократиться. Многие сигналы, описанные программистом, могут исчезнуть – синтезатор выбросит их, создав цепи из оптимизированной комбинаторной логики. В нашем примере сигналы f, g и h могут исчезнуть из списка сигналов проекта после синтезатора, все зависит от того используются ли эти сигналы где-то еще в проекте или нет. Синтезатор даже может выдать предупреждение (warning) о том, что сигналу "f" присвоено значение, но оно нигде не используется – и такое тоже бывает.

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

Например, мы уже знаем как описать простой мультиплексор с помощью оператора "?", вот так:

reg [3:0] c;
always @(a or b or d)

begin
c = d ? (a & b) : (a + b);
end


А теперь можем написать это же, но по другому:

reg [3:0] c;
always @(a or b or d) begin
   if (d) begin
     c = a & b;
   end else begin
     c = a + b;
   end
end

Вместо параметра "d" может быть любое выражение. Если значение этого выражения истина (не равно нулю), то будет выполняться первое присвоение "c = a & b". Если значение выражения "d" ложь (равно нулю), то будет выполняться второе присвоение "c = a + b".

Если нужно сделать выбор из многих выриантов (это на языке схемотехники мультиплексор со многими входами), то можно использовать конструкцию case. Конструкции case очень похожи на switch из языка C.

Базовый синтаксис вот такой:

case (selector)
  option1: <statement>;
  option2: <statement>;
  default:  <if nothing else statement>;  //по желанию, но желательно
endcase

А вот и простой пример:

wire [1:0] option;
wire [7:0] a, b, c, d;
reg [7:0] e;
always @(a or b or c or d or option) begin
case (option)
 0: e = a;
  1: e = b;
  2: e = c;
  3: e = d;
endcase
end

Поскольку входы у нас – это 8-ми битные шины, то в результате синтеза получится восемь мультиплексоров четыре-к-одному.

Давайте теперь рассмотрим циклы. Тут нужно сделать замечание, что циклы в Verilog имеют несколько иной смысл, не такой, как в языках C или Pascal. На языке C цикл обозначает, что некоторое действие должно быть выполнено последовательно раз за разом. Чем больше итераций в цикле, тем дольше он будет исполняться процессором. На языке Verilog цикл скорее описывает сколько экземпляров логических функций должно быть реализовано аппаратно. Чтобы синтез прошел успешно, циклы должны иметь заданное фиксированное число итераций – иначе синтезатор просто не сможет ничего сделать.

Рассмотрим простой пример – нужно определить номер самого старшего ненулевого бита вектора (шины):

module find_high_bit(input wire [7:0]in_data, output reg [2:0]high_bit, output reg valid);

integer i;

always @(in_data)
begin
   //определим, есть ли в шине единицы
   valid = |in_data;

   //присвоим хоть что нибудь

   high_bit = 0;

   for(i=0; i<8; i=i+1)
   begin
     if(in_data[i])
     begin
       // запомним номер бита с единицей в шине
       high_bit = i;
     end
   end
end
endmodule

В приведенном примере цикл просматривает все биты "последовательно" от младшего к старшему. Когда будет найден ненулевой бит, то его порядковый номер запоминается в регистре high_bit. Когда цикл закончится, то регистр high_bit будет содержать индекс самого старшего ненулевого бита в шине (если конечно valid тоже не ноль). На самом деле, конечно, нужно представлять себе, что подобная запись цикла будет реализована в аппаратуре довольно длинной цепочкой из мультиплексоров. Вы должны представлять себе, что в этом примере цикл for - это примерно вот такая логическая схема:
Циклы в языке Verilog

Рассмотрим другой пример – нужно найти самый младший не нулевой бит в шине:

module find_low_bit(input wire [7:0]in_data, output reg [2:0]low_bit, output reg valid);

integer i;

always @(in_data)
begin
   //определим, есть ли в шине единицы
   valid = |in_data;

   //присвоим хоть что нибудь

   low_bit = 0;

   for(i=7; i>=0; i=i-1)
   begin
     if(in_data[i])
     begin
       // запомним номер бита с единицей в шине
       low_bit = i;
     end
   end
end
endmodule

Теперь просмотр битов идет от старшего к младшим. Обратите внимание, что счетчик циклов определен как integer - это целое число со знаком. И так нужно, потому что сравнение ">=0" как раз и означает "не отрицательный". Переменная типа reg всегда положительна, значит здесь использоваться не может.

Ну вот пожалуй и все для этого предпоследнего урока! Потерпите пожалуйста, остался один урок! Smile

Комментарии  

+1 #8 lesha birukov 14.03.2012 18:00
Цитирую Zolinger:
...Тогда еще: стоило бы добавить проверку if (valid) перед циклом, тогда при 0 на шине цикл вообще бы не выполнялся. В классическом программировании это сэкономило бы немного времени выполнения, но в ПЛИСах, возможно, лишь усложнит схему и использует лишние элементы. Или нет?

Да, усложнит схему. Может быть, чуть-чуть уменьшит энергопотреблен ие.
0 #7 Zolinger 14.03.2012 17:03
Цитирую lesha birukov:
Цитирую Zolinger:
... Какие значения вернутся в high_bit и low_bit соответственно, если ВСЕ биты в in_data равны 0? в high_bit будет 0, в low_bit - 7. Это неверно, т.к. в этих битах 0... .

Обратите внимание на выходной параметр valid. Он то и сигнализирует о том, что установленных битов нет вообще.

Так да, конечно. valid упустил. Просто сам бы делал по-другому :-) Использовал бы барьерные значения. Но так симпатично. Тогда еще: стоило бы добавить проверку if (valid) перед циклом, тогда при 0 на шине цикл вообще бы не выполнялся. В классическом программировани и это сэкономило бы немного времени выполнения, но в ПЛИСах, возможно, лишь усложнит схему и использует лишние элементы. Или нет?
0 #6 lesha birukov 13.03.2012 10:56
Цитирую Zolinger:
... Какие значения вернутся в high_bit и low_bit соответственно, если ВСЕ биты в in_data равны 0? в high_bit будет 0, в low_bit - 7. Это неверно, т.к. в этих битах 0... .

Обратите внимание на выходной параметр valid. Он то и сигнализирует о том, что установленных битов нет вообще.
0 #5 Zolinger 13.03.2012 05:21
Почему мне кажется, что примеры с ненулевыми битами неверны? Какие значения вернутся в high_bit и low_bit соответственно, если ВСЕ биты в in_data равны 0? в high_bit будет 0, в low_bit - 7. Это неверно, т.к. в этих битах 0. Да и вообще, если ищем СТАРШИЙ ненулевой бит, то логично перебирать от СТАРШИХ к МЛАДШИМ (i=7; i>=0;i--), а если ищем МЛАДШИЙ, то от младших к старшим. Видимо, в реализации ПЛИС это без разницы, т.к. все равно обработка идет параллельно для всей шины, но для классического программировани я и вообще чистоты стиля мой вариант мне кажется более правильным.
0 #4 nckm_ 04.04.2011 10:53
Цитирую Игорь:
for(i=7; i>=0; i=i+1)
если я правильно понимаю
for(начальное значение; условие выхода; операция над переменной)

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

исправил. конечно там минус должен быть, а не плюс..
+1 #3 Игорь 04.04.2011 10:28
for(i=7; i>=0; i=i+1)
если я правильно понимаю
for(начальное значение; условие выхода; операция над переменной)

при таком условии выхода цикл даже делать ничего не будет.
0 #2 Free 30.11.2010 09:15
Спасибо понравилось !
0 #1 Henolala 20.11.2010 08:11
Офигенная новость.. Щас добавлю вашу страничку в закладки

Добавить комментарий


Защитный код
Обновить


GitHub YouTube Twitter
Вы здесь: Начало Статьи о разном Введение в Verilog, Четвертый урок. Поведенческие блоки.