Дорогой читатель, 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
) запрещены.
Необходимо выравнивать названия, размерность и комментарии по «логическим» колонкам.
Неправильно:
// 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
Неправильно:
// 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
Примечание:
Неправильно:
// 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 @( 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
&&
/||
/!
) при описании каких-то логических выражений, а не их побитовые аналоги (&
/|
/~
).wire data_ready; assign data_ready = ( pkt_word_cnt > 8'd5 ) && ( !data_enable ) && ( pkt_len <= 16'd64 );
always @( * ) 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
В синтезируемой логике настоятельно не рекомендуется использовать конструкцииcasex
иcasez
Заметьте, что здесь выровнено по 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 @( 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
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 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
._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, интерфейса - один файл. Название файла - название этого модуля.
.v
.sv
.vh
.svh
.vhd
_pkg
: func_pkg.sv
.vh
. Пример defines.vh
.Общее:
always @( * )
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
Категорически запрещается
Каждый модуль описывается в отдельном файле.
Файл 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
Т.е.:
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 ) );
_inst0
, _inst1
( либо просто 0
, 1
) и т.д. Однако иногда названия получается слишком длинные и засчет вложенности модулей иерархический путь может быть очень длинным, что приведет к неудобству смотрения в ModelSim или TimeQuest. Поэтому допускается инстансы(!) модулей типа: main_engine
сокращать до me
. Однако без необходимости этим не злоупотреблять.Используется для генерации множества инстансов одного модуля, например, для создания конвейера.
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;
reg
и wire
(за исключением тех случаев, когда необходимы множественные драйверы). Это позволит выявить ошибки, связанные с множественным назначением на этапе компиляции и ошибки, вызванные отсутствием инициализации (так как reg
инициализируется неопределенным значением).`define
) должны определяться в отдельном файле либо в модуле верхнего уровня иерархии.