当前位置 博文首页 > zhoujianjayj的博客:Verilog代码规范(三) -- assign & al

    zhoujianjayj的博客:Verilog代码规范(三) -- assign & al

    作者:[db:作者] 时间:2021-08-30 22:26

    这一节简单聊聊 assign & always & for 三种语句中会出现的代码规范问题。


    代码规范(三)

    一、assign语句

    1. 数值的实际位宽大于指定的位宽会导致截位。如果截掉的bit中非全零,可能会错;(Spyglass?Assign Rules W19)

    - 2'b111: 发生截位,会报W19;
    - 2'b011:默认不会报,但是若设置了strict,则会报W19
    - 2'd15, 1’d3:发生截位,会报W19;
    - 2'd03, 4'd0014:虽然发生截位,但是不报W19,因为截断位都是0;
    - 3'hF, 2'o7:会报错
    - 3'h1, 2'o1:虽截位但是不报错;
    

    2. assign赋值两边的位宽不匹配(Spyglass Rule RHS>LHS, W164a)(Spyglass Rule RHS<LHS, W164b)

    对于W164a,会导致溢出、截位,造成数据丢失:

    //Example 1
    wire a,b;
    assign a = b + 4 ; //Violation reported with strict
    
    //Example 2
    A [11:0] = { 3’b000, b[9:0] } ; //no violation,leading zeros can ignore;
    

    对于W164b,允许出现,综合会自动补零,但是不建议出现。

    位宽的不匹配主要是出现在运算后,这部分在这里不赘述;

    3. 除了testbench,不要在赋值中使用delay,因为不可综合;(Spyglass Assign Rules W280, W257)

    a <= #10 b;
    

    4. 时序逻辑块中使用非阻塞赋值,组合逻辑块中使用阻塞赋值;(Assign Rules W336)

    module fbosc1(y1,y2,clk,rst);
    	output y1, y2;
    	input  clk, rst;
    	reg    y1, y2;
    
    	always @(posedge clk pr posedge rst)
    		if(rst) y1=0;
    		else y1=y2;
    
    	always @(posedge clk or posedge rst)
    		if(rst) y2=1;
    		else y2=y1;
    
    endmodule
    

    理论上,两个always块是并行执行的。若复位信号从1到0解复位:

    若第一个always块的有效时钟沿比第二个always块的时钟沿几个皮秒(时钟树误差)到达,则y1和y2都会取1;

    若第一个always块的有效时钟沿比第二个always块的时钟沿几个皮秒(时钟树误差)到达,则y1和y2都会取0;

    这说明,这个模块会产生冒险和竞争。同时也不能保证综合处争取的逻辑,是的前仿和后仿产生不同的结果;

    注意两点:

    1)在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构;

    (2)在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构;

    ?

    二、always语句

    1. 一个always语句内不使用多个时钟边沿。换句话说,一个always只能有一个时钟;Example:

    always begin            
    	@(posedge CLK1)       
    		QA <= A;
    	@(posedge CLK2)
    		QA <= B;
    end
    

    2. 一个always块内,只对一个变量赋值,或者对非常相关的一类信号赋值,避免不相关的信号同时赋值。Example:

    always @(*)                     //多个信号在一个always块中,不提倡
    if(A==1 && B==0) begin
    	DOUT = 1;
    	EOUT = ZIN;
    	FOUT = 0;
    	GOUT = 0;
    end
    else if(A==0 && B==0) begin
    	DOUT = 0;
    	EOUT = YIN;
    	FOUT = 0;
    	GOUT = 0;
    end
    else if(A==0 && B==0) begin
    	DOUT = 0;
    	EOUT = YIN;
    	FOUT = 1;
    	GOUT = 0;
    end
    else if(C==1) begin
    	DOUT = 1;
    	EOUT = ZIN;
    	FOUT = 1;
    	GOUT = 1;
    end
    else begin
    	DOUT = 1;
    	EOUT = ZIN;
    	FOUT = 1;
    	GOUT = 0;
    end
    
    always @(*)                     //拆分成多个always块,不容易产生latch
    if(A==1) begin
    	DOUT = 1;
    	EOUT = ZIN;
    end
    else begin
    	DOUT = 0;
    	EOUT = YIN;
    end
    
    always @(*) begin                   //
    	if(B==1) 
    		FOUT = 1;
    	else 
    		FOUT = 0;
    end
    
    always @(*) begin                   //
    	if(A==1 && B==1 && C==1) 
    		GOUT = 1;
    	else 
    		GOUT = 0;
    end
    

    3. 使用组合逻辑时,统一使用“always@(*)”的形式。防止括号里面的防止敏感信号列不全;Exapmle:

    always @(A, B, C) //很容易列漏掉敏感信号
    
    always @(*)       //有效避免漏列敏感信号
    

    4. 模块内禁止同一信号在不同的always逻辑块中赋值;(Spyglass Assign Rules W505)Example:

    always @(posedge clk or negedge res_n)
    begin
    	if(!res_n)
    		q <= 4'h0
    	else if (b_en) begin
    		q <= q << 1;                  //
    	end
    end
    
    //
    always @(posedge clk or negedge res_n)
    begin
    	if(!res_n)
    		q <= 4'h0
    	else if (a_en) begin
    		q[0] <= si;
    	end
    end
    

    5. 禁止在时序电路中对同一信号重复赋值;(MultipleDriver Rules W415)Example:

    always @(posedge clk or negedge res_n)
    begin
    	if(!res_n)
    		q <= 4'h0
    	else if (en) begin
    		q <= q << 1;                  //对同一信号赋值不可取
    		q[0] <= si;
    	end
    end
    
    //
    always @(posedge clk or negedge res_n)
    begin
    	if(!res_n)
    		q <= 4'h0
    	else if (en) begin
    		q <= {q[2:0],si};                  //
    	end
    end
    

    ?

    6. 组合逻辑和时序逻辑不可混杂使用。时序逻辑就是流水,组合逻辑就是每个流水阶段要做的事情。不要混在一起;???????

    7. 信号被always组合逻辑块使用,但是没有在敏感列表中定义。使用*而非列表可以避免此问题。???????

    8. 信号处于敏感列表,但不是所有的bit都被用到。使用*而非列表可以避免此问题。

    9. 确保敏感列表里面的信号在always中没有被修改。使用*而非列表可以避免此问题。

    10. 敏感列表中想表达or,却出现了|或者||。使用*而非列表可以避免此问题。

    不难看出,对于组合逻辑的always块中 的敏感列表,写*而非列表可以避免很多不必要的错误,而这些error/warning在spyglass检查中,也都会有所体现。???????

    ?

    三、for语句

    1. for循环语句可能被终止,但是综合不会,综合会把for循环全部条件下的电路翻译出来;

    reg  [3:0] i;
    wire [2:0] tmp;
    assign tmp = A-1;
    
    always @(*) begin
    	for (i=0; i<8;I=i+1)
    		if(i > tmp)
    			s[i] = 1'b1;
    		else
    			s[i] = 1'b0
    end
    

    2. 如果需要for语句,for语句可以转换成case语句来实现

    wire [2:0] tmp;
    assign tmp = A-1;
    
    always @(*) begin
    	case(tmp)
    		3'b000: s=8'b0000_0000;
    		3'b001: s=8'b1111_1110;
    		3'b010: s=8'b1111_1100;
    		3'b011: s=8'b1111_1000;
    		3'b100: s=8'b1111_0000;
    		3'b101: s=8'b1110_0000;
    		3'b110: s=8'b1100_0000;
    		3'b111: s=8'b1000_0000;
    		default: s=8'b0000_0000;
    	endcase
    end
    

    3. 在Verilog中除了在Testbench(仿真测试激励)中使用for循环语句外,但在RTL级编码中却很少使用for循环语句。

    主要原因:for循环会被综合器展开为所有变量情况的执行语句,每个变量独立占用寄存器资源,每条执行语句并不能有效地复用硬件逻辑资源,造成巨大的资源浪费。

    简单的说就是:for语句循环几次,就是将相同的电路复制几次,因此循环次数越多,占用面积越大,综合就越慢。

    在RTL硬件描述中,遇到类似的算法,推荐的方法是先搞清楚设计的时序要求,做一个reg型计数器。

    在每个时钟沿累加,并在每个时钟沿判断计数器情况,做相应的处理,能复用的处理模块尽量复用,即使所有的操作不能复用,也采用case语句展开处理。

    for(i=0;i<16;i++)
      DoSomething();
    
    reg [3:0] counter;
    always @(posedge clk)
      if(syn_rst)
        counter<=4'b0;
      else
        counter<=counter+1;
    always @(posedge clk)
      begin
        case(counter)
            4'b0000:
            4'b0001:
            ......
        default:
        endcase
      end
    

    ?

    ?

    cs