§ Триггеры на логике

В предыдущей статье я разобрал основные понятия, такие как провода, регистры и модули. На самом деле, регистры можно только симулировать при помощи проводов, если создать триггеры. К примеру, простой D-триггер будет выглядеть так:
1module dtrig(input d, input c, output q);
2
3  wire   w1 = ~(d & c);
4  wire   w2 = ~(w1 & c);
5  wire   w3 = ~(w2 & q);
6  assign q  = ~(w1 & w3);
7
8endmodule
Это D-триггер, который срабатывает по верхнему уровню и он состоит из 4 NAND. Но, во-первых, это слишком простой триггер, во-вторых, уже занимает большое количество логических элементов, в-третьих, не задействует уже встроенные возможности в логических ячейках, в-четверых, не очень то наглядно это все выглядит.
В целом говоря, по всем пунктам неэффективно. А если намного более простой путь. Но сначала про always.

§ Блоки always

Есть такие моменты, когда надо работать с триггерами. Для этого и были созданы блоки always. С их помощью можно определять то, по каким правилам будут записаны данные в триггеры. Общий случай:
1always @(<блок чувствительности>)
2begin
3...
4end
Блок чувствительности (хоть и странно звучит), это список проводов или выходов регистров, а также правила, по которым будет срабатывать все, что находится в begin .. end. Если же запись в регистр или действие происходит в 1 оператор, то писать begin/end не нужно. Они выполняют ту же самую роль скобок в С++ или, аналогично Паскалю, работает по тому же принципу.
Возьмем пример:
1reg  a;
2wire b = 1'b1;
3
4always @(posedge clock)
5  a <= b;
Тут происходит следующее - на ПОЗИТИВНОМ фронте (то есть, когда провод clock перешел из 0 в 1), происходит защелкивание (flip-flop) значения с провода b в регистр a.
Причем, обращу внимание на <= эта конструкция значит, что происходит именно синхронное защелкивание, что значит, что все что находится в блоке always, сработает в один и тот же момент.
  • posedge clock — блок срабатывает только на позитивном фронте clock
  • negedge clock — срабатывает на негативном
Если честно, кроме posedge/negedge мне больше ничего не требовалось. Поэтому я далее и буду использовать именно такой метод записи.
Есть также такая конструкция always @*, которая означает, что блок будет выполнен абсолютно всегда, вне зависимости от значения любых проводов.
Еще пример:
1reg a;
2reg b;
3
4always @(posedge clock)
5begin
6   a <= b; // Защелка b -> a
7   b <= a; // Защелка a -> b
8end
Теперь надо кое-то сказать важного. Дело в том, что раз тут <= это значит, что обе защелку пройдут именно одновременно. Это важно! Потому что, если в a=5 и в b=3, то следующим значением будет a=5, b=3!
Почему так — именно потому, что запись происходит одновременно, а не последовательно. Если бы запись шла последовательно, то тогда сначала в a=3, а потом в b=a=3 и по итогу было бы a=b=3.

§ if в always

Есть еще интересный трюк:
1always @(posedge c)
2begin
3  a <= 1'b0;
4  if (c) a <= b;
5end
Несмотря на синхронную логику, в a будет записано именно значение b, но только тогда, когда c=1. Если же c=0, то в a будет записан 0.
Здесь я впервые применил оператор if. Это оператор, как и оператор case, может использоваться исключительно только в блоках always. Вне этих блоков эти операторы работать не будут.
Также, стоит сказать что оператор = использовать можно:
1always @*
2begin
3  a = b;
4  if (c) begin a = b; end
5end
В данном случае, это значения регистрам присваиваться будут не на позитивном фронте, как раньше, а всегда. Такой способ удобен, потому что позволяет писать код с использованием различных операторов, которые нельзя использовать вне always, таких как if или case.

§ case в always

Существует также весьма полезный и нужный оператор case. Его синтаксис такой:
1case (<провод>)
2<значение>: <блок>
3<значение>: <блок>
4...
5<значение>: <блок>
6default: <блок>
7endcase
Дело в том, что case выступает в роли сложного мультиплексора, который выполняет тот или иной блок в зависимости от того, какое было значение на проводе. Приведу пример:
1case (w)
2
3  2'b00: a <= 1'b1;
4  2'b01: begin a <= 1'b0; end
5  default: a <= w;
6
7endcase
То есть, если w=2'b00, то тогда выполняется a <= 1'b1, при w=2'b01 выполняется a <= 1'b0, а при любом другом значений, которое не входит в объявленные случаи, выполняется a <= w.
Есть другие варианты case это casex и casez. С их помощью можно проверять не точное значение провода, а по шаблону. casex/casez отличаются только использованием букв "x" (casex) или "?" (casez) в значениях. Приведу пример:
1casez (w2)
2
3  4'b00_00: a <= 1'b1; // 1-е условие
4  4'b1?_?0: a <= 2'h2; // 2-е условие
5  default:  a <= w2;   // 3-е условие
6
7endcase
Если w2 = 4'b0000, то тогда выполняется условие 1. Если же w2 равно 1000, 1010, 1100, 1110 (один из 4 вариантов), то выполняется второе условие, ну и при любых других значениях — условие 3.