|
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.
|