Отступ - 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 ) )
...
endassign 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;
endif( 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;
endfunctiontask 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_anlzis_ptp_pktsuper_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_2m048156.25 МГц -> clk_156m25250 МГц -> 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 недопустим и, по возможности, модули должны быть параметризованы. Исключения возможны в тех случаях, когда параметризуемость делает программу менее читабельной и понятной. Главное требование - модуль не должен требовать допиливания после присвоения нового значения параметру, т.е. должен полностью сохранять функционал.