Archive
工业级RTL风格
代码风格很重要,让别人读懂,增加效率。
下面是简单的代码书写风格
RTL代码书写风格
/* 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)。
wire flg_r;
wire flg_nxt= ~flg_r;
wire flg_ena = (ptr_r == ('E203_OITF_DEPTH-1)) & ptr_ena;
//此处使用例化 sirv_gnrl_dfflr 的方式实现寄存器, 而不使用显式的 always块
sirv_gnrl_dfflr #(1) flg_dfflrs(flg_ena, flg_nxt, flg_r, clk, rst_n);
使用标准DFF 模块例化的好处以下:
- 便于全局替换寄存器类型。
- 便于在寄存器中全局插入延迟。
- 明确的load-enable 使能信号(如下例中的 flg _ena)可方便综合工具自动插入寄存器级别的门控时钟以降低动态功耗。
- 便于规避 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) 进行捕捉。
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出现不定态, 则会报错并终止仿真
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, 造成芯片功能错误。
if(a)
out = inl;
else
out = in2;
而使用功能等效的 assign语法,如下所示, 假设a的值为X(不定态), 按照 Verilog语法,则会将X(不定态) 传播出去, 从而让out也等于X。通过对X(不定态) 的传播,开发人员可以在仿真阶段将bug彻底暴露出来
assign out = a ? in1 : in2;
虽然现在有的EDA 工具提供的专有选项(例如 Synopsys VCS 提供的 xprop 选项)可以将 Verilog 原始语法中定义的“不传播不定态”的情形强行传播出来, 但是一方面, 不是所有的EDA 工具支持此功能; 另一方面,在操作中此选项也时常被忽视, 从而造成疏漏。
Verilog 的 Case语法也不能传播不定态,与问题一中的 if-else 同理。而使用等效的 assign 语法即可规避此缺陷。
Verilog 的if-else 语法会被综合成优先级选择电路, 面积和时序均没有得到充分优化, 如下所示。
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(不定态)传播的问题。
assign out = sell ? in1[3:0] :
sel2 ? in2[3:0] :
sel3 ? in3[3:0] :
4'b0;
而如果此处本来要生成一种并行选择逻辑,则推荐使用 assign语法明确地使用“与或”逻辑, 代码如下。
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,作为其时钟和复位信号。