肖恩 Wei's Blog

数字芯片工程师

0%

工业级RTL风格

代码风格很重要,让别人读懂,增加效率。

下面是简单的代码书写风格

RTL代码书写风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/* STYLE_NOTES begin
* Throughout this file, these STYLE_NOTES comment blocks will provide some
* explanation about the block of code that follows.
* This first comment block provides some general formatting info.
* Use // format for comments.
* Limit line length to 80 characters.
* Indent using 2 spaces per level. Don't use tabs.
* Organize sections consistently. (WIRES, then REGISTERS, then ...)
* The following block is a standard header template. Don't skimp on the
* description. Include $Id$ and other revision-control flags.
* STYLE_NOTES end */
//-------------------------------------------------------------------
//
// COPYRIGHT (C) 2007, your_company
//
// THIS FILE MAY NOT BE MODIFIED OR REDISTRIBUTED WITHOUT THE
// EXPRESSED WRITTEN CONSENT OF your_company
//
// your_company http://www.your_company.com
// your_company_address1 info@your_company.com
// your_company_address2 your_company_phone
// your_company_city_state_zip
//-------------------------------------------------------------------
// Title : coding_style
// Author : you
// Created : 01/01/2007
// Description : Where does this file get inputs and send outputs?
// What does the guts of this file accomplish, and how does it do it?
// What module(s) does this file instantiate?
//
// $Id$
//-------------------------------------------------------------------

`timescale 1ns / 1ps

/* STYLE_NOTES begin
* A generic "clk" named input makes the design portable. (It is attached to
* a more descriptive clk name at a higher level.)
* "i_" and "o_" are handy ways to label module inputs/outputs. Especially
* useful when viewing waveforms.
* "rst_n" listed last so you don't have to worry which line omits the comma.
* Use "_n" for active-low. "_l" looks too much like "_1".
* STYLE_NOTES end*/
module coding_style(
clk,
i_buf_req,
o_buf_ack,
i_proc_rdata,
o_proc_raddr,
rst_n
);

/* STYLE_NOTES begin
* Include your global constants header files here.
* STYLE_NOTES end*/
`include "coding_style.vh"

/* STYLE_NOTES begin
* Comment your I/O by block and by signal
* STYLE_NOTES end*/
input clk; // clock
input rst_n; // reset
// signals to/from another_module
input i_buf_req; // input buffer request
output o_buf_ack; // output buffer ack
// signals to/from a_different_module
input [`PROC_DWIDTH-1:0] i_proc_rdata; // processor read data
output [`PROC_AWIDTH-1:0] o_proc_raddr; // processor read address

/* STYLE_NOTES begin
* Use parameters for local variables like state machine state enumeration.
* STYLE_NOTES end*/
//
// PARAMETERS
//
// state machine for controlling something
parameter IDLE = 2'b00;
parameter READ_DATA = 2'b01;
parameter PUSH_DATA = 2'b10;
parameter WAIT_COMPLETE = 2'b11;

/* STYLE_NOTES begin
* Not necessary to re-declare outputs as wires.
* When using an always block for logic, wires will be minimal.
* STYLE_NOTES end*/
//
// WIRES
//
wire some_internal_wire; // passed between submodules
wire some_strobe; // passed between submodules

/* STYLE_NOTES begin
* Use "_ff" for single-bit flops.
* Use "_reg" for multi-bit flops.
* Use "_next" for all combinatorial d-inputs assigned in always blocks.
* STYLE_NOTES end*/
//
// REGISTERS
//
reg [1:0] state_proc_reg; // processor state machine
reg [1:0] state_proc_next;
reg [`PROC_DWIDTH-1:0] proc_rdata_reg; // capture proc read data
reg buf_ack_ff; // pulse ack output
reg buf_ack_next;
reg some_level_ff; // toggle level signal
reg some_level_next;
reg [`PROC_AWIDTH-1:0] proc_raddr_reg; // read address output
reg [`PROC_AWIDTH-1:0] proc_raddr_next;
reg [`PROC_DWIDTH-1:0] some_pulse_bus_reg; // multi-bit output
reg [`PROC_DWIDTH-1:0] some_pulse_bus_next;

/* STYLE_NOTES begin
* Try to avoid complicated logic assignments in continuous assign
* statements. Nested &, |, and ?: notations are extremely hard to follow.
* For many designs, this section will simply assign the output wires.
* STYLE_NOTES end*/
//
// ASSIGNMENTS
//
assign o_buf_ack = buf_ack_ff;
assign o_proc_raddr = proc_raddr_reg;

/* STYLE_NOTES begin
* Only synchronous assigns done here. Always use non-blocking <=
* Use "#1" for protecting against issues when interfacing with bad code.
* Async reset values of "0" are by far the most common, and are not needed
* for FPGA (they consume unnecessary resources).
* STYLE_NOTES end*/
//
// SYNCHRONOUS
//
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
state_proc_reg <= #1 IDLE;
buf_ack_ff <= #1 0;
some_level_ff <= #1 1;
proc_raddr_reg <= #1 0;
end
else begin
state_proc_reg <= #1 state_proc_next;
buf_ack_ff <= #1 buf_ack_next;
some_level_ff <= #1 some_level_next;
some_pulse_bus_reg <= #1 some_pulse_bus_next;
proc_raddr_reg <= #1 proc_raddr_next;
proc_rdata_reg <= #1 i_proc_rdata;
end

/* STYLE_NOTES begin
* Do all your logic here, reads more like C code.
* Make sure your sensitivity list is complete.
* At the top of the always block, make all your default assignments to
* feedback flops or constants such as zeros.
* All assign statements here must be blocking =
* Note that the operation of this state machine makes no real sense!
* STYLE_NOTES end*/
//
// ASYNCHRONOUS
//
always @(state_proc_reg
or i_buf_req
or proc_rdata_reg
or some_level_ff
or proc_raddr_reg
) begin
state_proc_next = state_proc_reg;
some_level_next = some_level_ff;
proc_raddr_next = proc_raddr_reg;
buf_ack_next = 0;
some_pulse_bus_next = 0;

// state machine
case (state_proc_reg)

// Normal processing IDLE state. Wait for the next
// buffer request.
IDLE: begin // 0
if (i_buf_req) begin
buf_ack_next = 1; // assert buf_ack pulse
state_proc_next = READ_DATA;
end
end

// Some description of what the state does
READ_DATA: begin // 1
some_pulse_bus_next = `PULSE_BUS_VALUE;
state_proc_next = PUSH_DATA;
end

// Some description of what the state does
PUSH_DATA: begin // 2
state_proc_next = WAIT_COMPLETE;
end

// Some description of what the state does
WAIT_COMPLETE: begin // 3
if (proc_rdata_reg == `PROC_RDATA_TRIGGER) begin
proc_raddr_next = proc_raddr_reg + 1; // increment address
some_level_next = ~some_level_ff; // toggle some_level
state_proc_next = IDLE;
end
else begin
proc_raddr_next = proc_raddr_reg - 2; // decrement address by 2
end
end

default : begin
state_proc_next = IDLE;
end

endcase

end

/* STYLE_NOTES begin
* Always instantiate by port name, not order.
* Use short instance names to make waveform and synthesis debug easier.
* Note how "i_", "o_" and "rst_n" order are used here.
* STYLE_NOTES end*/
//
// INSTANTIATIONS
//
submodule_1 sub1(
.clk(clk),
.i_some_internal_wire(some_internal_wire),
.i_proc_rdata(i_proc_rdata),
.o_some_strobe(some_strobe),
.rst_n(rst_n)
);

submodule_2 sub2(
.clk(clk),
.i_some_strobe(some_strobe),
.i_some_level(some_level_ff),
.o_some_internal_wire(some_internal_wire),
.rst_n(rst_n)
);


endmodule // coding_style

以上是简单的代码规范

下面开始 引用搬运 一些好的设计作为规范学习

蜂鸟E203处理器核的RTL代码风格

1. 使用标准 DFF 模块例化生成寄存器

寄存器是数字同步电路中基本的单元。当使用 Verilog 进行数字电路设计时, 最常见的方式是使用 always块语法生成寄存器。本节介绍蜂鸟 E203 处理器核推荐的原则, 本原则来自严谨的工业级开发标准。

对于寄存器,避免直接使用always块编写,而应该采用模块化的标准 DFF 模块进行例化。示例如下所示, 除时钟(clk)和复位信号(rst_n)之外, 一个名为flg_dfflr的寄存器还有使能信号 flg_ena和输入(flg_nxt) /输出信号(flg_r)。

1
2
3
wire flg_r;
wire flg_nxt= ~flg_r;
wire flg_ena = (ptr_r == ('E203_OITF_DEPTH-1)) & ptr_ena;

//此处使用例化 sirv_gnrl_dfflr 的方式实现寄存器, 而不使用显式的 always块

1
sirv_gnrl_dfflr #(1) flg_dfflrs(flg_ena, flg_nxt, flg_r, clk, rst_n);

使用标准DFF 模块例化的好处以下:

  1. 便于全局替换寄存器类型。
  2. 便于在寄存器中全局插入延迟。
  3. 明确的load-enable 使能信号(如下例中的 flg _ena)可方便综合工具自动插入寄存器级别的门控时钟以降低动态功耗。
  4. 便于规避 Verilog 语法中if-else 不能传播不定态(x 或 z)的问题。(避免bug在仿真过程中被掩盖)

标准 DFF 模块是一系列不同的模块,列举如下:

  • sirv_gnrl_dfflrs: 带 load-enable使能信号、带异步 reset信号、复位默认值为1 的寄存器。
  • sirv_gnrl_dfflr:带load-enable使能信号、带异步 reset信号、复位默认值为0的寄存器。
  • sirv_gnrl_dffl: 带 load-enable使能信号、不带 reset信号的寄存器。
  • sirv_gnrl_dffrs:不带 load-enable使能信号、带异步 reset信号、复位默认值为1 的寄存器。
  • sirv_gnrl_dffr:不带load-enable使能信号、带异步 reset信号、复位默认值为0的寄存器。
  • sirv_gnrl_ltch: Latch模块。

标准 DFF 模块内部则使用 Verilog语法的 always块进行编写, 以 sirv_gnrl_dfflr为例,代码如下所示。由于 Verilog if-else 语法不能传播不定态, 因此对于 if条件中 lden信号为不定态的非法情况使用断言(assertion) 进行捕捉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module sirv_gnrl_dfflr # (
parameter DW= 32
) (
input lden,
input [DW-1:0] dnxt,
output [DW-1:0] qout,
input clk,
input rst_n
);

reg [DW-1:0] qout_r;

// 使用always块编写寄存器逻辑
always @(posedge clk or negedge rst_n)
begin : DFFLR_PROC
if (rst_n == 1'b0)
qout_r <= {DW{1'b0}};
else if (lden == 1'b1)
qout_r <= dnxt;
end

assign qout = qout_r;

// 使用 assertion 捕捉 lden信号的不定态
`ifndef FPGA_SOURCE
`ifndef SYNTHESIS
sirv_gnrl_xchecker # (
.DW(1)
) u_sirv_gnrl_xchecker ( //该模块内部是使用SystemVerilog编写的断言
.i_dat(lden),
.clk (clk)
);
`endif
`endif

endmodule

sirv_gnrl_xchecker模块的代码片段 ,

此模块专门捕捉不定态,一旦输入的i_dat出现不定态, 则会报错并终止仿真

1
2
3
4
5
6
7
8
9
10
11
12
module sirv_gnrl_xchecker # (
parameter DW= 32
) (
input [DW-1:0] i_dat,
input clk
);
CHECK_THE_X_VALUE:
assert property (@(posedge clk)
((^(i_dat)) !== 1'bx)
)
else $fatal ("\n Error: Oops, detected a X value!!! This should never happen. \n");
endmodule

2. 推荐使用 assign语法替代 if-else 和case语法

Verilog中的 if-else 和 case 语法存在两大缺点。

  • 不能传播不定态。
  • 会产生优先级的选择电路而非并行选择电路, 从而不利于优化时序和面积。

为了规避这两大缺点, 蜂鸟E203 处理器核推荐使用 assign 语法进行代码编写, 本原则来自严谨的工业级开发标准。

Verilog的 if-else不能传播不定态, 以如下代码片段为例。假设a的值为X(不定态),按照 Verilog语法它会将等效于a==0,从而让 out等于in2, 最终没有将X(不定态) 传播出去。这种情况可能会在仿真阶段掩盖某些致命的bug, 造成芯片功能错误。

1
2
3
4
if(a)
out = inl;
else
out = in2;

而使用功能等效的 assign语法,如下所示, 假设a的值为X(不定态), 按照 Verilog语法,则会将X(不定态) 传播出去, 从而让out也等于X。通过对X(不定态) 的传播,开发人员可以在仿真阶段将bug彻底暴露出来

1
assign out = a ? in1 : in2;

虽然现在有的EDA 工具提供的专有选项(例如 Synopsys VCS 提供的 xprop 选项)可以将 Verilog 原始语法中定义的“不传播不定态”的情形强行传播出来, 但是一方面, 不是所有的EDA 工具支持此功能; 另一方面,在操作中此选项也时常被忽视, 从而造成疏漏。

Verilog 的 Case语法也不能传播不定态,与问题一中的 if-else 同理。而使用等效的 assign 语法即可规避此缺陷。

Verilog 的if-else 语法会被综合成优先级选择电路, 面积和时序均没有得到充分优化, 如下所示。

1
2
3
4
5
6
7
8
if(sell)
out = in1[3:0];
else if (sel2)
out = in2[3:0];
else if (sel3)
out = in3[3:0];
else
out = 4'b0;

如果此处确实要生成一种优先级选择逻辑, 则推荐使用 assign 语法等效地写成如下形式, 以规避X(不定态)传播的问题。

1
2
3
4
assign out = sell ? in1[3:0] :
sel2 ? in2[3:0] :
sel3 ? in3[3:0] :
4'b0;

而如果此处本来要生成一种并行选择逻辑,则推荐使用 assign语法明确地使用“与或”逻辑, 代码如下。

1
2
3
assign out  = ({4{sel1}} & in1[3:0])
| ({4{sel2}} & in2[3:0])
| ({4{sel3}} & in3[3:0]) ;

使用明确的assign语法编写的“与或”逻辑一定能够保证综合成并行选择的电路。

同理, Verilog 的 case 语法也会被综合成优先级选择电路,面积和时序均未充分优化。有的EDA 综合工具可以提供注释(例如 synopsys parallel_case 和full_case)来使综合工具综出并行选择逻辑,但是这样可能会造成前后仿真不一致的严重问题,从而产生重大的 bug。因此在实际的工程开发中, 注意以下两点。

应该明令禁止使用EDA 综合工具提供的注释(例如 synopsys parallel_case 和 full_case)。

应该使用等效的 assign 语法设计电路。

3.其他若干注意事项

其他编码风格中的若干注意事项如下。

  • 由于带 reset信号的寄存器面积略大,时序稍微差一点, 因此在数据通路上可以使用不带reset信号的寄存器, 而只在控制通路上使用带 reset信号的寄存器。
  • 信号名应该避免使用拼音,使用英语缩写, 信号名不可过长, 但是也不可过短。代码即注释, 应该尽量让开发人员能够从信号名中看出其功能。
  • Clock和 Reset信号应禁止用于任何其他的逻辑功能, Clock 和 Reset信号只能接入DFF,作为其时钟和复位信号。

我的微信(请添加自我介绍)