§ Триггеры на логике
В предыдущей статье я разобрал основные понятия, такие как провода, регистры и модули. На самом деле, регистры можно только симулировать при помощи проводов, если создать триггеры. К примеру, простой 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
— срабатывает на негативном
Есть также такая конструкция
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.