Отступ - 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
) запрещены.
Необходимо выравнивать названия, размерность и комментарии по "логическим" колонкам.
Неправильно:
// 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
Неправильно:
// 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
Примечание:
- по умолчанию логические колонки "разделяются" одним пробелом, однако, допускается делать больше пробелов, если это улучшает читаемость (как в примере выше).
Неправильно:
// 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;
Неправильно:
// 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
- Необходимо использовать логическое И/ИЛИ/НЕ (
&&
/||
/!
) при описании каких-то логических выражений, а не их побитовые аналоги (&
/|
/~
). - Любые сравнения, отрицания, и пр. берутся в скобки.
logic data_ready;
assign data_ready = ( pkt_word_cnt > 8'd5 ) && ( !data_enable ) && ( pkt_len <= 16'd64 );
always_comb begin
if( data_enable && ( fifo_bytes_empty >= pkt_size ) )
...
end
assign start_stb_o = ( ( state == RED_S ) && ( next_state != IDLE_S ) ) ||
( ( state == YELLOW_S ) && ( next_state != GREEN_S ) );
if( a > 5 )
d = 5;
else
d = 15;
if( a > 5 ) begin
c = 7;
d = 5;
end else begin
c = 3;
d = 7;
end
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 );
Каждый из вариантов описывается в своем 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 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 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
- Verilog:
-
Файлы содержащие только декларации 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 недопустим и, по возможности, модули должны быть параметризованы. Исключения возможны в тех случаях, когда параметризуемость делает программу менее читабельной и понятной. Главное требование - модуль не должен требовать допиливания после присвоения нового значения параметру, т.е. должен полностью сохранять функционал.