Verilog Codestyle

Инструменты пользователя

Инструменты сайта


verilog_guideline

Verilog Coding Style

Зачем это нужно

Дорогой читатель, coding style нужен для того чтобы код был красив и легко читаем.

Отступы и пробелы

Отступ - 2 пробела. Табы (\t) запрещены.

Отступ нужны для обозначения «вложения».

Пример:

if( a )
  b = c;

Пробелы

Пробел должен ставиться:

  • после открывающейся ( и перед закрывающейся скобкой ) в конструкциях if, for, always, function, task, сложных логических условиях
  • перед и после элементов, требующих двух операндов ( &, |, &&, ||, =, <=, +)
  • перед и после использованием переменных и констант.
Исключение: перед , и ; пробел не ставится.

Примеры:

if( a > c )
a = b && ( c > d );
a = ( b == c ) ? ( y ) : ( z );

<pagebreak>

Пустые строчки

Любые логические вещи необходимо отделять пустой строчкой. Под логической вещью понимается какой-то интерфейс или совокупность сигналов, которые имеют одинаковое назначение или цель.

Пример:

...
  input        clk_i,
  input  [1:0] mode_i,
  input        mode_en_i,
  output [7:0] data_o,
  output       data_val_o
...

Здесь мы видим, что есть три разные сущности:

  • синхроимпульс clk_i
  • настройка какого-то режима работы mode_i и его разрешения mode_en_i
  • сигнал выходных данных data_o и валидность этого сигнала data_valid_o

Для облегчения чтения необходимо делать пустые пробелы между сущностями:

...
  input        clk_i,
 
  input  [1:0] mode_i,
  input        mode_en_i,
 
  output [7:0] data_o,
  output       data_val_o
...

Выравнивание

Выравнивание необходимо для облегчения чтения кода. Выравнивание производится с помощью пробелов. Табы (\t) запрещены.

Необходимо выравнивать названия, размерность и комментарии по «логическим» колонкам.

Пример #1

Неправильно:

// BAD EXAMPLE
wire rd_stb; //buffer read strobe
reg [31:0] ram_data; //data from RAM block
reg [1:0]if_mode; //interface mode

Правильно:

// GOOD EXAMPLE
wire        rd_stb;    // buffer read strobe
reg  [31:0] ram_data;  // data from RAM block
reg  [1:0]  if_mode;   // interface mode

Пример #2

Неправильно:

// BAD EXAMPLE
input clk_i,
input rst_i,
input [31:0] data_i,
input data_valid_i,
output reg [7:0] data_o,
output data_valid_o

Правильно:

// GOOD EXAMPLE
input                            clk_i,
input                            rst_i,
 
input         [31:0]             data_i,
input                            data_valid_i,
 
output    reg [7:0]              data_o,
output                           data_valid_o

Примечание:

  • по умолчанию логические колонки «разделяются» одним пробелом, однако, допускается делать больше пробелов, если это улучшает читаемость (как в примере выше).

Пример #3

Неправильно:

// BAD EXAMPLE
assign next_len = len + 16'd8;
assign next_pkt_len = pkt_len + 16'd4;

Правильно:

// GOOD EXAMPLE
assign next_len     = len     + 16'd8;
assign next_pkt_len = pkt_len + 16'd4;

Пример #4

Неправильно:

// BAD EXAMPLE
always @( posedge clk_i ) begin
  pkt_data_d1 <= pkt_data_i;
  pkt_empty_d1 <= pkt_empty_i;
end

Правильно:

// GOOD EXAMPLE
always @( posedge clk_i ) begin
  pkt_data_d1  <= pkt_data_i;
  pkt_empty_d1 <= pkt_empty_i;
end

Примеры использования конструкций и операторов

Логические выражения

  • Необходимо использовать логическое И/ИЛИ/НЕ (&&/||/!) при описании каких-то логических выражений, а не их побитовые аналоги (&/|/~).
  • Любые сравнения, отрицания, и пр. берутся в скобки.

Пример #1

wire data_ready;
assign data_ready = ( pkt_word_cnt > 8'd5 ) && ( !data_enable ) && ( pkt_len <= 16'd64 );

Пример #2

always @( * ) begin
  if( data_enable && ( fifo_bytes_empty >= pkt_size ) )
  ...
end

Пример #3

assign start_stb_o = ( ( state == RED_S    ) && ( next_state != IDLE_S  ) ) ||
                     ( ( state == YELLOW_S ) && ( next_state != GREEN_S ) );

'if-else'

if( a > 5 )
  d = 5;
else
  d = 15;

'if-else' вместе с 'begin/end'

if( a > 5 ) begin
  c = 7;
  d = 5;
end
else begin
    c = 3;
    d = 7;
end

Вложенный 'if-else'

if( a > 5 )
  c = 7;
else
  if( a > 3 )
    c = 4;
  else 
    c = 5;

Тернарный оператор '?'

assign y = ( a > c ) ? ( d ) : ( e );

Обращаю внимание, что условие и переменные d, e взяты в круглые скобки.

Для облегчения чтения (и проверки/написания) допускается (и во многих случаях рекомендуется) писать в две строки:

assign y = ( a > c ) ? ( cnt_gt_zero ):
                       ( cnt_le_zero );

'case'

Каждый из вариантов описывается в своем begin/end блоке, отделяя варианты друг от друга пустой строкой.

case( opcode[1:0] )
  2'b00:
    begin
      next_state = OR_S;
    end
 
  2'b01:
    begin
      next_state = AND_S;
    end
 
  2'b10:
    begin
      next_state = NOT_S;
    end
 
  2'b11:
    begin
      next_state = XOR_S;
    end
 
  default:
    begin
      next_state = AND_S;
    end
endcase

Если case простой и не подразумевает никаких вложенных конструкций в begin/end блоке, то допускается делать так (для уменьшения строк и текста):

case( opcode[1:0] )
  2'b00:   next_state = OR_S;
  2'b01:   next_state = AND_S;
  2'b10:   next_state = NOT_S;
  2'b11:   next_state = XOR_S;
  default: next_state = AND_S;
endcase
В синтезируемой логике настоятельно не рекомендуется использовать конструкции casex и casez

Заметьте, что здесь выровнено по next_state для облегчения чтения.

`function` и `task`

function int calc_sum( input int a, int b );
  return a + b;
endfunction
task receive_pkt( output packet_t pkt );
  ...
endtask

При большом количестве аргументов допустимо сводить к описанию в столбик:

task some_magic( 
  input   int        a,
  input   bit [31:0] data,
  output  packet_t   pkt 
);
...
endtask

Однако, если у вас большое количество аргументов, возможно что-то вы делаете не так…

Еще один пример

if( condition1 )
  begin
    for( int i = 0; i < 10; i = i + 1 )
      statement1;
  end
else
  begin
    if( condition2 )
      statement2;
    else
      if( condition3 )
        statement3;
      else
        statement4;
  end

Комментарии

Комментарии пишутся на английском языке. После знака комментария // ставится один пробел.

Желательно писать комментарии перед тем блоком, который вы хотите пояснить:

// current packet word number
always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    pkt_word <= 16'd0;
  else
    // reset counter at last valid packet word
    if( pkt_valid && pkt_eop )
      pkt_word <= 'd0;
    else
      if( pkt_valid )
        pkt_word <= pkt_word + 1'd1;

Примечание:

  • для описания комментариев необходимо пользоваться здравым смыслом и классическим рассуждением «лучший комментарий тот, которого нет». Пример, который расположен выше, показывает где необходимо располагать комментарии, но не надо комментировать каждый блок и каждую строчку (с этой точки зрения пример не совсем корректен).

Наименование переменных и модулей

  • Стиль названия переменных, функций, тасков и модулей - snake_case, т.е. слова или префиксы разделяются _, и всё пишется маленькими буквами.
    • Пример:
      • pkt_anlz
      • is_ptp_pkt
      • super_task
  • Большими буквами (но так же через _) пишутся только константы, параметры, дефайны, состояния FSM.
    • Пример:
      • parameter A_WIDTH = 9;
      • define CRC_LEN 32'd4
  • Имя переменной должно отражать ее назначение. Следует избегать чрезмерно длинных и, особенно, чрезмерно коротких названий (rd_addr лучше чем ra).
  • Названия портов должны содержать суффикс _i для входных, _o для выходных, _io для двунаправленных сигналов.

Описание клоков

  • Для описания клоков используется префикс clk.
  • При объявлении портов модуля клоки описываются в самом начале.
  • Если необходимо указать, какой частоты предполагается этот клок, то необходимо использовать шаблон clk_XmABC, где:
    • X - значение частоты в МГц
    • ABC - оставшая часть (старшие три знака), если она не равна нулю.
      • Примеры:
        • 2.048 МГц → clk_2m048
        • 156.25 МГц → clk_156m25
        • 250 МГц → clk_250m
  • Большинству модулей без разницы, какой именно клок (конкретное точное значение) к нему приходит, поэтому если разрабатываете модуль, который будет работать на частоте 100 МГц, то для названия порта модуля следует использовать общее название clk_i, а где-то «сверху» подключить нужные 100 МГц:
    .clk_i    ( clk_100m      ),
    ....
  • Если нужна какая-то параметризация в зависимости от клока, то необходимо сделать параметр в модуле:
    parameter CLK_FREQ_HZ = 100_000_000;

И затем его использовать в модуле.

  • Все триггеры должны защелкиваться только по положительному фронту posedge. Исключение: требования физических интерфейсов (например, DDR).

Категорически запрещается:

  • использовать сигнал клока для описания условий или создания комбинационной логики, типа:
       assign my_super_clk = clk_i && ( cnt == 8'd4 );

Описание ресетов

Есть два типа ресета (сброса):

  • асинхронный rst
  • синхронный srst

Чаще всего это используется как вход для описываемого модуля:

...
 
  input     clk_i,
  input     rst_i,
 
...

Активным уровнем для сброса считаем 1, т.е. когда rst_i == 1'b1, то схема находится в ресете.

Описание триггера с асинхронным ресетом:

always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    cnt <= 8'd0;
  else
    ...

Описание триггера с синхронным ресетом:

always @( posedge clk_i )
  if( srst_i )
    cnt <= 8'd0;
  else
    ...

Описание триггера с синхронным и асинхронным ресетом:

always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    cnt <= 8'd0;
  else
    if( srst_i )
      cnt <= 8'd0;
    else
    ...

Для большинства модулей используется асинхронный сброс для приведения модуля в начальное состояние.

Категорически запрещается:

  • путать синхронный и асинхронный сброс как по заведению сигналов, так и по их наименованию
  • создавать модули без сбросов. Исключение: модули без триггеров/памяти (чистая комбинационная логика).
  • производить какие-то манипуляции с асинхронным сбросом, типа:
reg [7:0] cnt;
assign my_rst = rst_i || ( cnt == 8'd5 );
 
always @( posedge clk_i or posedge my_rst )
  if( my_rst )
    cnt <= 8'd0;
  else
    ...
  • Нельзя помещать в один always блок регистры со сбросом и без него:
always @( posedge clk ) 
  if ( reset ) 
    stage_1 <= 'h0;
  else 
     begin
       stage_1 <= input;
       stage_2 <= stage_1;
     end

В результате синтеза reset будет использован как enable-сигнал у регистра stage_2

Правильно:

always @( posedge clk ) 
  if ( reset ) 
    stage_1 <= 'h0;
  else
    stage_1 <= input;
 
always @( posedge clk )
  stage_2 <= stage_1;

Описание конечного автомата (FSM)

Для описания конечного автомата используется следующая схема (в интернете она ходит под названием FSM 3 process):

reg [1:0] state;  
reg [1:0] next_state;
 
localparam IDLE_S = 2'b00;
localparam RUN_S  = 2'b01;  
localparam WAIT_s = 2'b10; 
 
always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    state <= IDLE_S;
  else
    state <= next_state;
 
always @( * )
  begin
    next_state = state;
 
    case( state )
      IDLE_S:
        begin
          if( ... )
            next_state = RUN_S;
        end
 
      RUN_S:
        begin
          if( ... ) 
            next_state = WAIT_S;
          else
            if( )
              next_state = IDLE_S; 
        end
 
      WAIT_S:
        begin
          if( ... )
            next_state = RUN_S;
        end
 
      default:
        begin
          next_state = IDLE_S;
        end
 
    endcase
  end
 
// some logic, that generates output values on state and next_state signals

Пояснение:

  • state обозначает текущее состояние, next_state - то, которое будет в state на следующем такте.
  • IDLE_S - дефолтное состояние, поэтому его устанавливают во время асинхронного сброса и default в case-блоке. Для удобства считаем, что это состояние должно идти первым в списке parameter.
  • Имена состояний FSM описываются большими буквами и должны содержать суффикс _S (IDLE_S, TX_S, WAIT_S и т.д.).

Категорически запрещается:

  • в блоке, где происходит назначение на next_state делать назначение еще каких-либо сигналов
  • производить какие-либо другие операции, кроме операции сравнения (==, !=) с переменными state, next_state, например:
assign b = ( state > RED_S );
assign c = ( state + 3'd2 ) > ( IDLE_S );

Размещение исходников

Исходники размещаются в файлах. Правило простое: один модуль, package, интерфейса - один файл. Название файла - название этого модуля.

  • Расширения:
    • Verilog: .v
    • SystemVerilog: .sv
    • Verilog Header: .vh
    • SystemVerilog Header: .svh
    • VHDL: .vhd
  • Файлы содержащие только декларации package следует помечать окончанием _pkg: func_pkg.sv
  • Файлы содержащие только константами, функции, таски, которые подгружаются в исходник при помощи include, следует именовать расширением .vh. Пример defines.vh.

Описание логических блоков/элементов

Общее:

  1. В одном блоке желательно описывать присваивание только в одну переменную.
  2. Присваивания в переменную может происходить только в одном блоке.
  3. Желательно блоки разделять пустой строчкой (сверху и снизу).

Комбинационная логика

  • Для описания комбинационных схем можно использовать:
    • блок always @( * )
    • непрерывное (continuous) присваивание assign
  • Разрешается использовать только блокирующие присваивание =.

Пример:

always @( * )
  begin
    a = b + d;
  end

Категорически запрещается:

  • описывать логику при «создании» переменной:
wire a = b && d;
  • описывать начальные значения для комбинационной логики:
wire [7:0] a = 8'd5;
assign a = b + d;

Триггеры

  • используется блок always
  • разрешается использовать только неблокирующие присваивание (<=).

Пример:

wire [31:0] data_d1;
reg  [7:0]  cnt;
 
always @( posedge clk_i )
  begin
    data_d1 <= data_i;
  end
 
always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    cnt <= '0;
  else
    if( cnt == 8'h92 )
      cnt <= 'd0;
    else
      cnt <= cnt + 1'd1;

Создание триггеров с начальным ненулевым значением

reg [7:0] cnt;
 
always @( posedge clk_i or posedge rst_i )
  if( rst_i )
    cnt <= 8'd5;
  else
    ...

Защелки (латчи)

Используется блок always_latch.

Пример:

always @( * )
  begin
    if( en )
      data = data_i;
  end

Категорически запрещается

  • использовать/создавать латчи в синхронных схемах на FPGA. (но допустимо использовать их в ASIC проектах)

Описание и объявление модулей

Описание модуля

Каждый модуль описывается в отдельном файле.

Файл some_module.v:

module some_module #(
  parameter DATA_WIDTH = 32,
  parameter CNT_WIDTH  = 8
)(
  input                       clk_i,
  input                       rst_i,
 
  input      [DATA_WIDTH-1:0] data_i,
  input                       data_val_i,
 
  output reg [CNT_WIDTH-1:0]  cnt_o
);
 
// some code...
 
endmodule

Т.е.:

  • Описание параметров и сигналов начинается с отступа (2 пробела).
  • При описании параметров и сигналов всё выравнивается по колонкам:
    • название сигналов
    • квадратные скобки
    • зарезервированные слова типа parameter, input, output
    • знак = в параметрах
  • Сигналы не смешиваются в кучу. Те сигналы, которые формируют логический интерфейс должны отделятся пустой строкой.
  • Предпочтительно сначала описывать входные сигналы, потом выходные, однако, первочердным должен соблюдаться принцип интерфейсов, который описан выше.
  • Первым в описании сигналов должно идти описание клоков/синхросигналов и сбросов.

Инстанс модуля

some_module #(
  .DATA_WIDTH                             ( 64                ),
  .CNT_WIDTH                              ( 4                 )
) some_module (
  .clk_i                                  ( rx_clk            ),
  .rst_i                                  ( main_rst          ),
 
  .data_i                                 ( rx_data           ),
  .data_val_i                             ( rx_data_val       ),
 
  .cnt_o                                  ( cnt               )
);  
  • При подключении сигнала или переопределении параметра делается отступ (2 пробела).
  • Cкобки, точки, запятые выравниваются.
  • Для передачи параметров используется `#`, а не `defparam`.
  • Пустые строчки, которые отделяют логические интерфейсы (или сигналы, которые должны быть вместе) расположены так же как и в описании модуля.
  • Чаще всего зкземпляр модуля называется так же как и сам модуль; если экземпляров одного модуля несколько, то их названия дополняются суффиксами _inst0, _inst1 ( либо просто 0, 1 ) и т.д. Однако иногда названия получается слишком длинные и засчет вложенности модулей иерархический путь может быть очень длинным, что приведет к неудобству смотрения в ModelSim или TimeQuest. Поэтому допускается инстансы(!) модулей типа: main_engine сокращать до me. Однако без необходимости этим не злоупотреблять.

Конструкция "generate"

Используется для генерации множества инстансов одного модуля, например, для создания конвейера.

generate
  genvar i;
    for ( i = 0; i < 4; i = i + 1 ) 
      begin: par_gen
        parity8 parity(
          .data( wr_data[(i * 8) +: 8] ),
          .parity( wr_par[i] )
        );
      end
endgenerate

Описание переменных

Переменные в модуле «создаются» после описания сигналов модуля и до начала «работы» с этими сигналами.

  output logic abc_o
);
 
reg [7:0]  cnt;
wire       cnt_en;
 
reg [63:0] pkt_data;
// ... some more signals
 
// work with signal starts here
  • Связанные переменные, как и сигналы модуля необходимо логически отделять пустой строкой.
  • Допускается создавать сигналы «рядом» с тем местом, где они употребляются, например:
reg [2:0] vlan_mpls_cnt;
// more signals
 
// some code
always @( * )
  begin
    if( vlan_mpls_cnt > 2 )
      ...
  end
 
// calc vlan_mpls_cnt
wire [1:0] vlan_cnt;
wire [1:0] mpls_cnt;
 
assign vlan_cnt = vlan[0] + vlan[1] + vlan[2];
assign mpls_cnt = mpls[0] + mpls[1] + mpls[2];
 
assign vlan_mpls_cnt = vlan_cnt + mpls_cnt;

Прочее

  • При наличии двунаправленных шин, направления передачи должны развязываться на верхнем уровне иерархии (top-файле).
  • В RTL описаниях использовать типы reg и wire (за исключением тех случаев, когда необходимы множественные драйверы). Это позволит выявить ошибки, связанные с множественным назначением на этапе компиляции и ошибки, вызванные отсутствием инициализации (так как reg инициализируется неопределенным значением).
  • Настоятельно рекомендуется защелкивать в триггеры выходные сигналы модулей. Это повысит максимальную скорость работы устройства.
  • Все критичные к быстродействию узлы по возможности следует размещать в отдельном модуле. Это позволит оптимизировать его независимо от прочих узлов.
  • Все макроподстановки (`define) должны определяться в отдельном файле либо в модуле верхнего уровня иерархии.
  • Hard-code недопустим и, по возможности, модули должны быть параметризованы. Исключения возможны в тех случаях, когда параметризуемость делает программу менее читабельной и понятной. Главное требование - модуль не должен требовать допиливания после присвоения нового значения параметру, т.е. должен полностью сохранять функционал.
verilog_guideline.txt · Последние изменения: 2020/05/11 13:58 — hepoh