Skip to content

Latest commit

 

History

History
779 lines (612 loc) · 27.5 KB

coding-style.md

File metadata and controls

779 lines (612 loc) · 27.5 KB

Verilog (SystemVerilog) 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 );

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

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

Пример:

...
  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
logic rd_stb; //buffer read strobe
logic [31:0] ram_data; //data from RAM block
logic [1:0]if_mode; //interface mode

Правильно:

// GOOD EXAMPLE
logic        rd_stb;    // buffer read strobe
logic [31:0] ram_data;  // data from RAM block
logic [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 logic [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  logic [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_ff @( posedge clk_i )
  begin
    pkt_data_d1 <= pkt_data_i;
    pkt_empty_d1 <= pkt_empty_i;
  end

Правильно:

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

Допускается:

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

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

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

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

Пример #1

logic data_ready;

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

Пример #2

always_comb 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

Заметьте, что здесь выровнено по 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_ff @( posedge clk_i or posedge rst_i )
  if( rst_i )
    pkt_word <= 16'd0; 
  else if( pkt_valid && pkt_eop ) // reset counter at last valid packet word
    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_ff @( posedge clk_i or posedge rst_i )
  if( rst_i )
    cnt <= 8'd0;
  else
    ...

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

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

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

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

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

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

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

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

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

enum logic [1:0] { IDLE_S,
                   RUN_S,
                   WAIT_S } state, next_state;

always_ff @( posedge clk_i or posedge rst_i )
  if( rst_i )
    state <= IDLE_S;
  else
    state <= next_state;

always_comb
  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-блоке. для удобства считаем считаем, что это состояние должно идти первым в enum списке.

  • Имена состояний 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.

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

Общее:

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

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

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

Пример:

always_comb begin
  a = b + d;
end

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

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

assign a = b + d;

Триггеры

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

Пример:

logic [31:0] data_d1;
logic [7:0]  cnt;

always_ff @( posedge clk_i )
  begin
    data_d1 <= data_i;
  end

always_ff @( posedge clk_i or posedge rst_i )
  if( rst_i )
    cnt <= '0;
  else if( cnt == 8'h92 )
    cnt <= 'd0;
  else
    cnt <= cnt + 1'd1;

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

logic [7:0] cnt = 8'd5;

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

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

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

Пример:

always_latch
  begin
    if( en )
      data = data_i;
  end

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

  • использовать/создавать латчи в синхронных схемах.

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

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

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

Файл some_module.sv:

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 logic [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. Однако без необходимости этим не злоупотреблять.

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

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

  output logic abc_o
);

logic [7:0]  cnt;
logic        cnt_en;

logic [63:0] pkt_data;
// ... some more signals

// work with signal starts here
  • Связанные переменные, как и сигналы модуля необходимо логически отделять пустой строкой.
  • Допускается создавать сигналы "рядом" с тем местом, где они употребляются, например:
logic [2:0] vlan_mpls_cnt;
// more signals

// some code
always_comb begin
  if( vlan_mpls_cnt > 2 )
    ...
end

// calc vlan_mpls_cnt
logic [1:0] vlan_cnt;
logic [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;

Прочее

  • Категорически запрещается реализовывать в устройствах сигналы с нулевым активным уровнем. Исключением могут быть стандартные интерфейсы, где такие сигналы изначально предусмотрены (sram interface, к примеру). Допускается наличие подобных сигналов на входах FPGA, но на верхнем уровне иерархии (в top-файле) они должны быть проинвертированы.

  • При наличии двунаправленных шин, направления передачи должны развязываться на верхнем уровне иерархии (top-файле).

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

  • Настоятельно рекомендуется защелкивать в триггеры выходные сигналы модулей. Это повысит максимальную скорость работы устройства.

  • Все критичные к быстродействию узлы по возможности следует размещать в отдельном модуле. Это позволит оптимизировать его независимо от прочих узлов.

  • Все макроподстановки (`define) должны определяться в отдельном файле либо в модуле верхнего уровня иерархии.

  • Hard-code недопустим и, по возможности, модули должны быть параметризованы. Исключения возможны в тех случаях, когда параметризуемость делает программу менее читабельной и понятной. Главное требование - модуль не должен требовать допиливания после присвоения нового значения параметру, т.е. должен полностью сохранять функционал.