Оглавление
§ Триггеры на логике
В предыдущей статье я разобрал основные понятия, такие как провода, регистры и модули. На самом деле, регистры можно только симулировать при помощи проводов, если создать триггеры. К примеру, простой D-триггер будет выглядеть так:
module DLatch(input d, input c, output q);
wire w1 = ~(d & c);
wire w2 = ~(w1 & c);
wire w3 = ~(w2 & q);
assign q = ~(w1 & w3);
endmodule
Это D-триггер, который срабатывает по верхнему уровню и он состоит из 4 NAND. Но, во-первых, это слишком простой триггер, во-вторых, уже занимает большое количество логических элементов, в-третьих, не задействует уже встроенные возможности в логических ячейках, в-четверых, не очень то наглядно это все выглядит.
В целом говоря, по всем пунктам неэффективно. А если намного более простой путь. Но сначала про always.
§ Блоки always
Есть такие моменты, когда надо работать с триггерами. Для этого и были созданы блоки always. С их помощью можно определять то, по каким правилам будут записаны данные в триггеры. Общий случай:
always @(<блок чувствительности>)
begin
...
end
Блок чувствительности (хоть и странно звучит), это список проводов или выходов регистров, а также правила, по которым будет срабатывать все, что находится в begin .. end. Если же запись в регистр или действие происходит в 1 оператор, то писать begin / end не нужно. Они выполняют ту же самую роль скобок в С++ или, аналогично Паскалю, работает по тому же принципу.
Возьмем пример:
reg a;
wire b = 1'b1;
always @(posedge clock)
a <= b;
Тут происходит следующее – на ПОЗИТИВНОМ фронте (то есть, когда провод clock перешел из 0 в 1), происходит защелкивание (flip-flop) значения с провода b в регистр a.
Причем, обращу внимание на <= эта конструкция значит, что происходит именно синхронное защелкивание (неблокирующее присваивание), что значит, что все что находится в блоке always, сработает в один и тот же момент.
posedge clock – блок срабатывает только на позитивном фронте clock
negedge clock – срабатывает на негативном
Если честно, кроме posedge/negedge мне больше ничего не требовалось. Поэтому я далее и буду использовать именно такой метод записи.
Есть также такая конструкция always @*, которая означает, что блок будет выполнен абсолютно всегда, вне зависимости от значения любых проводов.
Еще пример:
reg a;
reg b;
always @(posedge clock)
begin
a <= b;
b <= a;
end
Теперь надо кое-то сказать важного. Дело в том, что раз тут <= это значит, что обе защелку пройдут именно одновременно. Это важно! Потому что, если в a=5 и в b=3, то следующим значением будет a=5, b=3!
Почему так – именно потому, что запись происходит одновременно, а не последовательно. Если бы запись шла последовательно, то тогда сначала в a=3, а потом в b=a=3 и по итогу было бы a=b=3.
§ Блоки if в always
Есть еще интересный трюк:
always @(posedge c)
begin
a <= 1'b0;
if (c) a <= b;
end
Несмотря на синхронную логику и неблокирующее присваивание, в a будет записано именно значение b, но только тогда, когда c=1. Если же c=0, то в a будет записан 0.
Здесь я впервые применил оператор if. Это оператор, как и оператор case, может использоваться исключительно только в блоках always. Вне этих блоков эти операторы работать не будут.
Также, стоит сказать что оператор = использовать можно:
always @*
begin
a = b;
if (c) begin a = b; end
end
В данном случае, это значения регистрам присваиваться будут не на позитивном фронте, как раньше, а всегда. Такой способ удобен, потому что позволяет писать код с использованием различных операторов, которые нельзя использовать вне always, таких как if или case.
§ Блоки case в always
Существует также весьма полезный и нужный оператор case. Его синтаксис такой:
case (<провод>)
<значение>: <блок>
<значение>: <блок>
...
<значение>: <блок>
default: <блок>
endcase
Дело в том, что оператор case выступает в роли сложного мультиплексора, который выполняет тот или иной блок в зависимости от того, какое было значение на проводе. Приведу пример:
case (w)
2'b00: a <= 1'b1;
2'b01: begin a <= 1'b0; end
default: a <= w;
endcase
То есть, если w=2'b00, то тогда выполняется a <= 1'b1, при w=2'b01 выполняется a <= 1'b0, а при любом другом значений, которое не входит в объявленные случаи, выполняется a <= w.
Есть другие варианты case это casex и casez. С их помощью можно проверять не точное значение провода, а по шаблону. casex/casez отличаются только использованием букв x (casex) или ? (casez) в значениях. Приведу пример:
casez (w2)
4'b00_00: a <= 1'b1;
4'b1?_?0: a <= 2'h2;
default: a <= w2;
endcase
Если w2 = 4'b0000, то тогда выполняется условие 1. Если же w2 равно 1000, 1010, 1100, 1110 (один из 4 вариантов), то выполняется второе условие, ну и при любых других значениях – условие 3.