Простые советы по стилю Verilog

free bad advice

Навеяно комментариями в блоге.

Вообще-то, в языке 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:

Altera Quartus II Adder

Запись @* обозначает, что блок выполняется всегда и непрерывно и бесконечно. Никакие значения нигде не хранятся. При изменении входных сигналов тут же меняется и сигнал отклик.

А как же слово 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, то мы прямо увидим запоминающие ячейки в виде синих прямоугольников регистров:

Altera Quartus II counter

 

Правило 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 и видим именно регистры тактируемые общим клоком:

edge sel
 

Еще раз. В блоках always @(posedge clk) старайтесь всегда писать только неблокирующие присвоения тем самым обозначая одновременность фиксации данных в реальных триггерах. Если будете писать или <= или = и смешивать их в одном блоке, то скорее запутаетесь в логике работы своей схемы.

Блокирующие и неблокирующие присвоения - это отдельная большая тема. Я уже как-то писал про это.

Таким образом, переходим к понятию конечного автомата – state machine.

Текущее состояние проекта-схемы хранится в регистрах, которые мы описываем переменными reg. Следующее состояние машины вычисляется комбинационными функциями, которые мы стараемся описывать с помощью wire и assign. Машина переходит из одного состояния в другое, например, по фронту тактовой частоты.

Если вы сможете некоторое время следовать такому стилю и этим трем простым правилам, то возможно вы сможете одновременно с написанием кода программы мысленно представлять себе фрагмент своей схемы.

Проверять проект легко. Если пользуетесь Altera Quartus II, то компилируете проект и смотрите, что получилось в виде схемы в RTL Viewer. Если представляли себе описываемую схему примерно так же, как ее Quartus показывает, то значит в правильном направлении двигаетесь.

Конечно, приведенные выше правила и ограничения довольно дурацкие. Это в какой-то момент даже вредные советы. Язык Verilog позволяет написать очень многое и весьма разными способами.

Однако, мне кажется, что такой стиль может оказаться полезным новичкам, изучающим Verilog. По крайней мере я этот стиль сам себе придумал, когда занялся изучением языка Verilog HDL и у меня у самого долгое время не было четкого представления как все работает.

 

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