Навеяно комментариями в блоге.
Вообще-то, в языке Verilog, который я всячески пропагандирую, действительно есть странные места, от которых у начинающих программистов происходит «вынос мозга». Часто путанница связана с wire и reg – где и когда их можно или нужно писать.
Все было бы более или менее понятно, если бы reg всегда обозначал одно и то же. Например, если бы reg в программе Verilog однозначно соответсвовал бы триггеру в схеме, то изучить язык было бы легче. Это имхо.
Увы, язык Verilog HDL «очень гибкий» и из-за этого не все так просто. Иногда (и часто), присвоение значения регистру reg не порождает тригер в схеме.
Например:
wire a;
wire b;
reg c;
always @*
c = a + b ;
В этом примере результат с – это комбинационная функция. В результирующей схеме в ПЛИС не будет никаких запоминающих регистров-триггеров, а будет только логика. Скомпилируйте такою конструкцию в Quartus II и посмотрите в Tools => Netlist Viewers => RTL Viewer:
Запись @* обозначает, что блок выполняется всегда и непрерывно и бесконечно. Никакие значения нигде не хранятся. При изменении входных сигналов тут же меняется и сигнал отклик.
А как же слово reg (register)? Мы вроде бы привыкли к тому, что в регистрах что-то хранится, как в регистрах процессора или микроконтроллера... А здесь, то хранится, то не хранится – не понятно..
Я предлагаю несколько простых дурацких правил, которые сильно ограничивают сам язык Verilog и его возможности, но зато делают код программы более понятным, читабельным и более соответствующим результирующей физической схеме цифрового устройства. Это моя трактовка – очень сильно «урезанный» Verilog.
Мастерам Verilog лучше бы этого не видеть. Предвижу полет гнилых яблок в мою сторону.
Правило 1.
Никогда не пишите отдельные комбинационные функции в always блоках.
Ну то есть, это чтоб вы не подумали, что в схеме будет запоминающий регистр-триггер с в который будет записан некоторый результат не нужно писать так, как в приведенном выше примере. Вместо этого напишите:
wire c;
assign c = a + b ;
В этом случае у нас получаются только чистые «провода» wire. Выходной сигнал получается из входных сигналов путем их преобразования логическими комбинационными функциями.
Комбинационные функции – это всякие простые И, ИЛИ, НЕТ, исключающее или. Еще сумматоры, вычитатели, сравниватели, мультиплексоры, декодеры. Вот их все в wire и с помощью assign и описываем. Любую комбинационную функцию можно легко описать с помощью wire и assign. Это вместо соответствующей конструкции always @*.
Даже когда логика сложная и есть условные вычисления всегда можно написать что то вроде мультиплексора:
wire c;
assign c = a ? (b+1) : (d<<4);
В этом примере в зависимости от значения a выбирается либо первое (b+1) либо второе (d<<4) вычисленное значение.
Еще с помощью проводов wire можно и нужно соединять модули между собой.
Правило 2.
Старайтесь описывать регистрами reg только те переменные, которые хотите чтоб были реально триггерами в результирующей схеме.
Представьте, что это именно те запоминающие ячейки, которые принимают значение и только по тактовому сигналу.
Например, счетчик:
reg [7:0]a;
always @(posedge clk)
a <= a+1;
Вот в результате из этого кода получится восемь регистров-триггеров в которые по тактовому сигналу @(posedge clk) записывается новое значение. Правда в этом примере так же есть комбинационная функция – сумматор, но выписывать его в отдельно с помощью wire и assign было бы совсем уныло.
Самое главное – здесь присвоение в переменную типа reg соответствует защелкиванию, фиксации данных в триггерах по фронту тактовой частоты. Подразумеваем регистр-триггер (flip-flop) – вот и описываем переменную типом reg.
Если такой код скомпилировать в Quartus II и посмотреть в RTL Viewer, то мы прямо увидим запоминающие ячейки в виде синих прямоугольников регистров:
Правило 3
В always блоке с тактовым сигналом типа @(posedge clk) всегда используйте присвоения только одного типа "неблокирующее присвоение" <=
Это будет обозначать, что все присвоения во всем проекте происходят одновременно. И это соответствует физической схеме, где все записи в регистры если и происходят, то по единому фронту тактовой частоты.
module test(
input wire clk,
input wire in,
output reg in_edge
);
reg [2:0]sr;
always @(posedge clk)
begin
sr <= {sr[1:0],in};
in_edge <= (sr[2:1]==2'b01);
end
endmodule
Регистр sr и in_edge тактируются одной частотой и защелкивание данных в них происходит одновременно. Псомотрим RTL Viewer и видим именно регистры тактируемые общим клоком:
Еще раз. В блоках always @(posedge clk) старайтесь всегда писать только неблокирующие присвоения тем самым обозначая одновременность фиксации данных в реальных триггерах. Если будете писать или <= или = и смешивать их в одном блоке, то скорее запутаетесь в логике работы своей схемы.
Блокирующие и неблокирующие присвоения - это отдельная большая тема. Я уже как-то писал про это.
Таким образом, переходим к понятию конечного автомата – state machine.
Текущее состояние проекта-схемы хранится в регистрах, которые мы описываем переменными reg. Следующее состояние машины вычисляется комбинационными функциями, которые мы стараемся описывать с помощью wire и assign. Машина переходит из одного состояния в другое, например, по фронту тактовой частоты.
Если вы сможете некоторое время следовать такому стилю и этим трем простым правилам, то возможно вы сможете одновременно с написанием кода программы мысленно представлять себе фрагмент своей схемы.
Проверять проект легко. Если пользуетесь Altera Quartus II, то компилируете проект и смотрите, что получилось в виде схемы в RTL Viewer. Если представляли себе описываемую схему примерно так же, как ее Quartus показывает, то значит в правильном направлении двигаетесь.
Конечно, приведенные выше правила и ограничения довольно дурацкие. Это в какой-то момент даже вредные советы. Язык Verilog позволяет написать очень многое и весьма разными способами.
Однако, мне кажется, что такой стиль может оказаться полезным новичкам, изучающим Verilog. По крайней мере я этот стиль сам себе придумал, когда занялся изучением языка Verilog HDL и у меня у самого долгое время не было четкого представления как все работает.
Подробнее...