Description
本文主要是收集一些重要的Verilog coding style。一个好的coding style可以减少错误的发生,增加电路的效能,以及较好的可读性。
Text
The order of module signals
一个module signal顺序如下 (由左至右):
Input
clock signals(clk_*)
set/reset signals(set_*, rst_*)
enable/disble signals(en_*, dis_*)
read/write enable signals(we_*, re_*, rw_*)
control signals(i_*)
address signals(i_*)
data signals(i_*)
Output
clock signals(o_clk_*)
set/reset signals(o_set_*, o_rst_*)
enable/disable signals(o_en_*, o_dis_*)
control signals(o_*)
address signals(o_*)
data signals(o_*)
In/Out
control signals(io_*)
address signals(io_*)
data signals(io_*)
Naming Rule
以下的namign rule为个人使用的规则。
命名方式分类
底线分隔型:xxx_yyy_zzz
大写底线分隔型:XXX_YYY_ZZZ
首字大写型:AbcDefGhi
首字小写型:avcDefGhi
各种元素所使用的命名
文件名称:底线分隔型, Ex: xxx_yyy_zzz.v
module名称:底线分隔型, Ex: xxx_yyy_zzz
module instance名称:底线分隔型, Ex: xxx_yyy_zzz
local wire名称:底线分隔型:Ex: xxx_yyy_zzz
local reg名称:底线分隔型, Ex: xxx_yyy_zzz
input signal名称:前置i_的底线分隔型, Ex: i_xxx_yyy_zzz
output signal名称:前置o_的底线分隔型, Ex: o_xxx_yyy_zzz
input/output signal名称:前置io_的底线分隔型, Ex: io_xxx_yyy_zzz
常数名称:大写底线分隔型, `XXX_YYY_ZZZ
parameter参数名称:大写底线分隔型, Ex: XXX_YYY_ZZZ
block名称:大写底线分隔型, Ex: XXX_YYY_ZZZ
特殊讯号名称
单一的clock signal: clk
多个clock signal: clk_xxx
负缘触发的clock signal: clk_n, clk_xxx_n
单一的reset signal: rst
多个reset signal: rst_xxx
负缘触发的reset signal: rst_n , rst_xxx_n
单一的set signal: set
多个set signals: set_xxx
负缘触发的set signals: set_n, set_xxx_n
致能讯号: en_xxx
除能讯号: dis_xxx
Procedural Assignments
使用指引
在撰写sequential logic时,使用nonblocking assignment。
在撰写latches电路时,使用nonblocking assignment。
在always block中撰写conbinational logic时,使用blocking assignment。
在同一个always block中同时撰写sequential及combinational logic时,一律使用nonblocking assignment。
别在同一个always block中混合使用nonblocking及blocking assignment。
别在两个以上的always block中对同一个变量设定数值。
使用$strobe来显示由nonblocking assignment所给定的变量。
不要对assignment使用#0延迟设定。
在procedural assignment中,LHS一定要是reg型态。非procedural assignment一定是net的型态。
常用的style
Combination logic
在always block中,如果使用combination logic,应当使用blocking assignment.
// All inputs used within always block should be listed in sensitive list.
always @(in1 or in2 or in3)
begin
xxx = in1 ^ in2 & in3;
end
Sequential logic
在always block中,如果使用sequential logic,应当使用nonblocking assignment.
// Synchronous reset. The rst_n is NOT in sensitive list.
always @(posedge clk)
begin
if (~rst_n)
begin
xxx <= `INIT_VAL;
end
else
begin
xxx <= yyy;
end
end
// Asynchronous reset. The rst_n IS in sensitive list.
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
xxx <= `INIT_VAL;
end
else
begin
xxx <= yyy;
end
end
Delay的建模
Combination logic
建模没有delay时,使用blocking assignment(ex: a = b;)
建模有惯性(inertial) delay时(即glitch不会传到后面的电路中)。使用delayed evaluation blocking assignments(#10 a = b;).
建模传输(transport)delay时(即glitch也会一并传到后面的电路中)。使用delayed assignment nonblocking assignments(ex: a <= #10 b;).
Sequential logic
建模没有delay时,使用non-blocking assignments (ex: q <= d; ).
建模有delay时,使用delayed assignment nonblocking assignments(ex: q <= #10 d;).
Finite State Machine
Moore FSM: 输出与输入没有直接关系。
由两个always block构成,一个是sequential block用来处理状态的变化。另一个为combinational block用来处理状态与输入之间的关系。注意,在sequential block中应全部使用nonblocking assignment。在combinational block中应使用blocking assignment。在某些简单的case中,combinational block也可直接由continuous assignment来取代。下面的范例是一般简单的FSM。
// Sequential always block.
always @(posedge clk or posedge rst)
begin
if (rst)
state <= STATE_IDLE;
else
state <= next;
end
// Combinational always block.
always @(state or input1 or input2 ... or inputN)
begin
next = STATE_IDLE;
outputs = OUTPUT_IDLE:
case (state)
STATE_IDLE:
begin
... // the logic to determine the next state.
next = STATE_?????;
end
STATE_?????:
begin
... // the logic to determine the next state.
next = STATE_?????;
outputs = ?????; // The output of this state.
end
endcase
end
针对simplified one-hot encoding的FSM范例:
// Sequential always block.
always @(posedge clk or posedge rst)
begin
if (rst)
state <= n'b0;
state[STATE_DEFAULT] <= 1'b0;
else
state <= next;
end
// Combinational always block.
always @(state or input1 or input2 ... or inputN)
begin
next = n'b0;
outputs = OUTPUT_DEFAULT:
case (1'b1) // synopsys full_case parallel_case
state[STATE_DEFAULT]:
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
end
state[STATE_?????]:
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
outputs = ?????; // The output of this state.
end
// synopsys translate_off
default:
$display("Bad state!!");
// synopsys translate_on
endcase
end
针对simplified one-hot with zero-idle encoding的FSM:
// Sequential always block.
always @(posedge clk or posedge rst)
begin
if (rst)
state <= n'b0;
else
state <= next;
end
// Combinational always block.
always @(state or input1 or input2 ... or inputN)
begin
next = n'b0;
outputs = OUTPUT_DEFAULT:
case (1'b1) // synopsys full_case parallel_case
~|state: // IDLE
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
end
state[STATE_?????]:
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
outputs = ?????; // The output of this state.
end
// synopsys translate_off
default:
$display("Bad state!!");
// synopsys translate_on
endcase
end
Mealy FSM: 输出与输入有直接关系。
Mealy FSM的作法与上面的范例相类似。唯一的不同在于outputs的指定,需加上与input相关的逻辑判断。例如:
case(state) // synopsys parallel_case full_case
...
STATE_?????:
begin
...
if (input1 & input2)
outputs = ?????;
else
outputs = ?????;
end
// synopsys translate_off
default:
$display("Bad FSM.");
// synopsys translate_on
default
endcase
Datapath
参考:Coding Guidelines for Datapath Synthesis.
有号数的计算:若有需要关于有号数的计算,应当利用Verilog 2001所提供的signed及$signed()机制。
input signed [7:0] a, b;
output signed [15:0] o;
assign o = a * b;
or
input [7:0] a, b;
output [15:0] o;
wire signed [15:0] o_sgn;
assugb o_sgn = $signed(a) * $signed(b);
assign o = $unsigned(o_sgn);
正负号的扩展:应多加利用Verilog的implicity signed extension,避免手动进行转换。
input signed [7:0] a, b;
input signed [8:0] o;
assign o = a + b; // Verilog会自动进行符号的扩展。
有号数与无号数的混合计算:不要在同一个verilog叙述中进行有号数与无号数的计算。应该要分成个别独立的叙述。在一个verilog叙述中只要有一个无号数的操作数,整个算式将被当成无号数进行计算。
input [7:0] a;
input signed [7:0] b;
output signed [15:0] o;
// Don't do this: assign o = a * b;
// The $signed({1'b0, a}) can convert the unsigned number to signed number.
assign o = $signed({1'b0, a}) * b;
input signed [7:0] a;
output signed [15:0] o;
// Don't do this: assign o = a * 8'b10111111;
// Use $signed() system task
assign o = a * $signed(8'b10111111);
// or sb keyword.
assign o = a * 8'sb10111111;
part-select运算过后的操作数是无号数。就算是选择的范围包含整个register或wire。
input signed [7:0] a;
input signed [7:0] b;
output signed [15:0] o1, o2;
// Don't do this: assign o1 = a[7:0];
assign o1 = a;
// Don't do this: assign o2 = a[6:0] * b;
assign o2 = $signed(a[6:0]) + b;
Verilog的位宽度规则:技巧就是要善用LHS来限制位宽度。利用中介的讯号线来作为限制宽度用的LHS操作数。
在没有特别设定的状况下,Verilog会依据LHS的操作数宽度来决定RHS操作数的宽度。
input [7:0] a;
input [7:0] b;
output [8:0] o;
assign o = a + b; // 9 bits.
对一个表示式而言,最大宽度的操作数决定了整体的宽度。
input signed [3:0] a;
input signed [7:0] b;
output [11:0] o;
wire signed [11:0] o_sgn;
// Don't do this: assign o = $unsigned(a * b); 这将会是一个8 bit的运算。因为在刮号内的bits数是依据最大操作数b的宽度决定的。
assign o_sgn = a * b; // 12 bits。因为bit数是依据LHS宽度决定的。
assgign o = $unsigned(o_sgn);
input [7:0] a;
input [7:0] b;
input [7:0] c;
input [7:0] d;
output o;
wire [15:0] tmp1;
wire [15:0] tmp2;
// Don't do this: assign o = (a + b) > (c * d); 因为(a + b)及(c * d)都会是8 bit的结果。
assign tmp1 = a + b; // 16 bits.
assign tmp2 = c * d; // 16 bits.
assign o = tmp1 > tmp2; // 1 bit. |