├── .gitignore ├── README.md └── sources ├── Makefile ├── assign-reg.v ├── assign-wire.sv ├── assign-wire.v ├── reset.v ├── tri-state-vector.v └── tri-state.v /.gitignore: -------------------------------------------------------------------------------- 1 | *.syn -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # verilog-coding-standard 2 | 3 | 本文档是 Verilog 和 SystemVerilog 推荐编程规范。 4 | 5 | # 规范要求 6 | 7 | ## VCS-001 信号名称采用 `snake_case` 8 | 9 | 信号名称通常采用 `snake_case`,即变量名全小写,单词之间用下划线分隔。 10 | 11 | ## VCS-002 信号极性为低有效用 `_n` 后缀表示 12 | 13 | 对于复位和使能信号,例如 `rst` 和 `we`,如果添加了 `_n` 后缀,表示值为零时生效(低有效,Active Low),值为一时不生效;如果没有添加 `_n` 后缀,表示值为一时生效(高有效,Active High),值为零时不生效。详细解释见下面的表格: 14 | 15 | | 信号名称 | 极性 | 1'b1 | 1'b0 | 16 | | -------- | ------ | ------ | ------ | 17 | | rst | 高有效 | 复位 | 不复位 | 18 | | rst_n | 低有效 | 不复位 | 复位 | 19 | | we | 高有效 | 写入 | 不写入 | 20 | | we_n | 低有效 | 不写入 | 写入 | 21 | 22 | 当代码中需要混合使用 `rst` 和 `rst_n` 的时候,采用以下的方式来转换: 23 | 24 | ```sv 25 | module test( 26 | input rst_n 27 | ); 28 | wire rst; 29 | 30 | // GOOD 31 | assign rst = ~rst_n; 32 | 33 | // GOOD 34 | // Verilog 35 | always @(*) begin 36 | rst = ~rst_n; 37 | end 38 | 39 | // System Verilog 40 | always_comb begin 41 | rst = ~rst_n; 42 | end 43 | 44 | endmodule 45 | ``` 46 | 47 | ## VCS-003 选择 Verilog 或者 SystemVerilog 的功能子集 48 | 49 | 推荐使用 SystemVerilog,但不推荐使用 SystemVerilog 的高级语法,因为工具可能不支持。 50 | 51 | ## VCS-004 需要寄存器时用 `reg`,纯组合逻辑用 `wire` 52 | 53 | 推荐对于所有组合逻辑产生的信号,都采用 `wire` 类型;对于所有寄存器,都采用 `reg` 类型。不推荐使用 `logic` 类型。 54 | 55 | 严格来说,Verilog 和 SystemVerilog 不允许对 `wire` 类型进行 Procedural Assignment,也就是在 `always` 块中进行赋值,但很多环境中这个约束可以不遵守。笔者认为这是 Verilog 的一个设计里比较失败的一点。如果采用 Vivado 等不允许对 `wire` 进行 Procedural Assignment 的 EDA 软件,可以考虑都用 `reg` 类型,然后通过名字来区分:`r_` 开头都是寄存器,`w_` 开头都是组合逻辑。 56 | 57 | ```sv 58 | // GOOD 59 | wire c; 60 | always_comb begin 61 | c = a + b; 62 | end 63 | 64 | // BAD 65 | reg c; 66 | always_comb begin 67 | c = a + b; 68 | end 69 | 70 | // GOOD 71 | reg c; 72 | always_ff @(posedge clock) begin 73 | c <= a + b; 74 | end 75 | 76 | // BAD 77 | wire c; 78 | always_ff @(posedge clock) begin 79 | c <= a + b; 80 | end 81 | ``` 82 | 83 | 对于常量信号,请使用 `wire` 类型并用 `assign` 进行赋值,而不要用 `reg`: 84 | 85 | ```sv 86 | // GOOD 87 | wire one; 88 | assign one = 1'b1; 89 | 90 | // BAD 91 | reg one; 92 | always @(*) begin 93 | one = 1'b1; 94 | end 95 | ``` 96 | 97 | ## VCS-005 信号仅在一个 `always` 块中赋值 98 | 99 | 通常情况下,一个信号只会在一个 `always` 块中赋值。 100 | 101 | ## VCS-006 组合逻辑采用 `always @(*)` 或者 `always_comb` 块或者 `assign` 编写 102 | 103 | 组合逻辑的 `always` 块,使用以下的写法: 104 | 105 | ```sv 106 | // Verilog 107 | always @(*) begin 108 | c = a + b; 109 | end 110 | 111 | // System Verilog 112 | always_comb begin 113 | c = a + b; 114 | end 115 | 116 | // GOOD 117 | assign c = a + b; 118 | ``` 119 | 120 | ## VCS-007 组合逻辑 `always` 块中仅使用阻塞赋值 121 | 122 | 表示组合逻辑的 `always` 块中所有的赋值请使用阻塞赋值(`=`)。 123 | 124 | ## VCS-008 组合逻辑 `always` 块中保证每个分支都进行赋值 125 | 126 | 如果使用了条件语句 `if`,需要保证信号在每个可能的分支途径下都进行了赋值。 127 | 128 | ```sv 129 | // GOOD 130 | always_comb begin 131 | if (reset_n) begin 132 | c = a + b; 133 | end else begin 134 | c = 1'b0; 135 | end 136 | end 137 | 138 | // BAD 139 | always_comb begin 140 | if (reset_n) begin 141 | c = a + b; 142 | end 143 | end 144 | ``` 145 | 146 | 请不要列举敏感信号: 147 | 148 | ```sv 149 | // BAD 150 | always @ (b, c) begin 151 | a = b + c; 152 | end 153 | ``` 154 | 155 | ## VCS-009 时序逻辑在 `always @(posedge clock)` 或者 `always_ff @(posedge clock)` 块中实现 156 | 157 | 当需要表示时序逻辑时,使用以下的写法: 158 | 159 | ```sv 160 | // Verilog 161 | always @(posedge clock) begin 162 | c <= a + b; 163 | end 164 | 165 | // System Verilog 166 | always_ff @(posedge clock) begin 167 | c <= a + b; 168 | end 169 | ``` 170 | 171 | ## VCS-011 时序逻辑 `always` 块中仅使用非阻塞赋值 172 | 173 | 时序逻辑 `always` 块中,所有的赋值请使用非阻塞赋值(`<=`)。 174 | 175 | ## VCS-012 不要使用下降沿触发,特殊协议除外 176 | 177 | 通常情况下,请不要使用下降沿触发: 178 | 179 | ```sv 180 | // BAD: do not use negedge 181 | always @ (negedge clock) begin 182 | end 183 | ``` 184 | 185 | ## VCS-013 不要使用非时钟/复位信号的边沿触发 186 | 187 | 通常情况下,不要使用除了时钟和复位以外的信号做边沿触发 188 | 189 | ```sv 190 | // BAD: do not use non-clock/reset signals 191 | always @ (posedge signal) begin 192 | end 193 | ``` 194 | 195 | ## VCS-014 时序逻辑中不要使用时钟信号 196 | 197 | 请不要在时序逻辑中使用时钟信号: 198 | 199 | ```sv 200 | // BAD 201 | always @ (posedge clock) begin 202 | if (clock) begin 203 | a <= 1; 204 | end 205 | end 206 | ``` 207 | 208 | ## VCS-015 使用同步复位而不是异步复位 209 | 210 | 对于 FPGA,请使用同步复位: 211 | 212 | ```sv 213 | // Verilog 214 | always @(posedge clock) begin 215 | if (reset) begin 216 | c <= 1'b0; 217 | end else begin 218 | c <= a + b; 219 | end 220 | end 221 | 222 | // System Verilog 223 | always_ff @(posedge clock) begin 224 | if (reset) begin 225 | c <= 1'b0; 226 | end else begin 227 | c <= a + b; 228 | end 229 | end 230 | ``` 231 | 232 | ## VCS-016 三态门拆分成三个信号后使用 233 | 234 | 在涉及与外设双向通信的信号时,需要使用三态门。使用三态门时,通过以下的代码拆分为三个信号: 235 | 236 | ```sv 237 | module tri_state_logic( 238 | inout signal_io 239 | ); 240 | wire signal_i; 241 | wire signal_o; 242 | wire signal_t; 243 | 244 | assign signal_io = signal_t ? 1'bz : signal_o; 245 | assign signal_i = signal_io; 246 | endmodule 247 | ``` 248 | 249 | 其中 `signal_o` 表示输出,`signal_i` 表示输入,`signal_t` 为高时进入高阻态,`signal_t` 为低时信号输出。其余代码不操作 `signal_io` 信号。 250 | 251 | 推荐在顶层模块实现三态门信号的拆分,再按照需要把三个拆分后的信号接到内层模块。 252 | 253 | ## VCS-017 不要在内部模块中使用 `inout` 254 | 255 | FPGA 内部的模块之间请不要使用 `inout`,仿真环境除外。 256 | 257 | ## VCS-018 不要在变量声明处赋值 258 | 259 | 不要在变量声明处赋值,因为不同的类型赋值的意义不同: 260 | 261 | ```sv 262 | // Wire 263 | wire signal = 1; 264 | // Equals to 265 | wire signal; 266 | assign signal = 1; 267 | 268 | // Reg 269 | reg signal = 1; 270 | // Equals to 271 | reg signal; 272 | initial signal = 1; 273 | ``` 274 | 275 | 不建议在声明处赋值,而是采用等价的写法,来区分不同的语义。 276 | 277 | 在 FPGA 中,寄存器可以有初始值,即在 FPGA 进行配置时复位到初始值,但通常情况下还需要在自定义的 reset 信号有效时复位。 278 | 279 | ## VCS-019 用 localparam 命名状态机的各个状态 280 | 281 | 编写状态机的时候,用 `localparam` 命名各个状态: 282 | 283 | ```sv 284 | // GOOD 285 | localparam sInit = 2'd0; 286 | localparam sIdle = 2'd1; 287 | localparam sWork = 2'd2; 288 | localparam sDone = 2'd3; 289 | 290 | reg [1:0] state; 291 | ``` 292 | 293 | 如果仿真工具不支持在波形中显示为对应的状态名称,可以采用以下的方法: 294 | 295 | ```sv 296 | `ifndef SYNTHESIS 297 | reg [39:0] state_string; // 40 bits = 5 byte 298 | 299 | always @ (*) begin 300 | case(state) 301 | sInit: state_string = "sInit"; 302 | sIdle: state_string = "sIdle"; 303 | sWork: state_string = "sWork"; 304 | sDone: state_string = "sDone"; 305 | default: state_string = "?????"; 306 | endcase 307 | end 308 | `endif 309 | ``` 310 | 311 | 此时在仿真波形中,`state_string` 信号就可以看到状态的名称了。 312 | 313 | # 其他可参考的 Verilog 编程规范 314 | 315 | - [数字逻辑设计实验文档](https://lab.cs.tsinghua.edu.cn/digital-design/doc/) 316 | - [Verilog Coding Standard](http://fpgacpu.ca/fpga/verilog.html) 317 | - [lowRISC Verilog Coding Style](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md) -------------------------------------------------------------------------------- /sources/Makefile: -------------------------------------------------------------------------------- 1 | VERILOG_SOURCES=$(shell find . -type f -name '*.v') 2 | SYSTEMVERILOG_SOURCES=$(shell find . -type f -name '*.sv') 3 | 4 | TARGETS=$(patsubst %,%.syn,$(VERILOG_SOURCES) $(SYSTEMVERILOG_SOURCES)) 5 | 6 | all: $(TARGETS) 7 | 8 | %.syn: % 9 | yosys -b verilog -o $@ -p 'synth_xilinx' $^ 10 | 11 | clean: 12 | rm -rf */*.syn -------------------------------------------------------------------------------- /sources/assign-reg.v: -------------------------------------------------------------------------------- 1 | module test( 2 | input clock, 3 | input a, 4 | input b, 5 | output c 6 | ); 7 | 8 | reg reg_a; 9 | reg reg_b; 10 | 11 | initial begin 12 | reg_a = 0; 13 | reg_b = 0; 14 | end 15 | 16 | reg reg_c; 17 | assign c = reg_c; 18 | 19 | always @(posedge clock) begin 20 | reg_a <= a; 21 | reg_b <= b; 22 | reg_c <= reg_a + reg_b; 23 | end 24 | endmodule -------------------------------------------------------------------------------- /sources/assign-wire.sv: -------------------------------------------------------------------------------- 1 | module test( 2 | output c 3 | ); 4 | 5 | wire a; 6 | wire b; 7 | 8 | always_comb begin 9 | a = 0; 10 | b = 0; 11 | c = a + b; 12 | end 13 | endmodule -------------------------------------------------------------------------------- /sources/assign-wire.v: -------------------------------------------------------------------------------- 1 | module test( 2 | output c 3 | ); 4 | 5 | wire a; 6 | wire b; 7 | 8 | always @(*) begin 9 | a = 0; 10 | b = 0; 11 | c = a + b; 12 | end 13 | endmodule -------------------------------------------------------------------------------- /sources/reset.v: -------------------------------------------------------------------------------- 1 | module test( 2 | input rst_n 3 | ); 4 | wire rst; 5 | 6 | // GOOD 7 | assign rst = ~rst_n; 8 | endmodule -------------------------------------------------------------------------------- /sources/tri-state-vector.v: -------------------------------------------------------------------------------- 1 | module tri_state_logic( 2 | inout [31:0] signal_io, 3 | output [31:0] signal_i, 4 | input [31:0] signal_o, 5 | input signal_t, 6 | ); 7 | assign signal_io = signal_t ? 32'bz : signal_o; 8 | assign signal_i = signal_io; 9 | endmodule -------------------------------------------------------------------------------- /sources/tri-state.v: -------------------------------------------------------------------------------- 1 | module tri_state_logic( 2 | inout signal_io, 3 | output signal_i, 4 | input signal_o, 5 | input signal_t, 6 | ); 7 | assign signal_io = signal_t ? 1'bz : signal_o; 8 | assign signal_i = signal_io; 9 | endmodule --------------------------------------------------------------------------------