代码编写中verilog的设计规范

描述


	

5.2 代码编写中容易出现的问题

  • 在for-loop中包括不变的表达式 浪费运算时间
 for (i=0;i<4;i=i+1)
 begin
 Sig1 = Sig2;
 DataOut[i] = DataIn[i];
 end

for-loop中第一条语句始终不变,浪费运算时间.

  • 资源共享问题 条件算子中不存在 资源共享 ,如
 z = (cond) ? (a + b) : (c + d);

必须使用两个加法器; 而等效的条件if-then-else语句则可以资源共享 如

 if (Cond)
 z = a + b;
 else
 z = c + d;

只要加法器的输入端复用,就可以实现加法器的共享,使用一个加法器实现。

  • 由于组合逻辑的位置不同而引起过多的触发器综合 如下面两个例子
 module COUNT (AndBits, Clk, Rst);
 Output Andbits;
 Input Clk,
 Rst;
 Reg AndBits;
 //internal reg

Reg [2:0] Count;
 always @(posedge Clk) begin
 begin
 if (Rst)
 Count <= #u_dly 0;
 else
 Count <= #u_dly Count + 1;
 End //end if
 AndBits <= #u_dly & Count;
 End //end always
 endmodule

在进程里的变量都综合成触发器了,有4个;

 module COUNT (AndBits, Clk, Rst);
 Output AndBits;
 Input Clk,
 Rst;
 Reg AndBits;
 //internal reg
 Reg [2:0] Count;
 always @(posedge Clk) begin //synchronous
 if (Rst)
 Count <= #u_dly 0;
 else
 Count <= #u_dly Count + 1;
 End //end always
 always @(Count) begin //asynchronous
 AndBits = & Count;
 End //end always
 Endmodule //end COUNT

组合逻辑单开,只有3个触发器.

  • 谨慎使用异步逻辑
 module COUNT (Z, Enable, Clk, Rst);
 Output [2:0] Z;
 Input Rst,
Enable,
 Clk;
 
 reg [2:0] Z;
 always @(posedge Clk) begin
 if (Rst) begin
 Z <= #u_dly 1'b0;
 end
 else if (Enable == 1'b1) begin
 If (Z == 3'd7) begin
 Z <= #u_dly 1'b0;
 End
 else begin
 Z <= #u_dly Z + 1'b1;
 end
 End
 Else ;
 End //end always
 Endmodule //end COUNT

是同步逻辑,而下例则使用了组合逻辑作时钟,以及异步复位.实际的运用中要加以避免.

 module COUNT (Z, Enable, Clk, Rst);
 Output [2:0] Z;
 Input Rst,
 Enable,
 Clk;
 Reg [2:0] Z;
 //internal wire
 wire GATED_Clk = Clk & Enable;
 always @(posedge GATED_Clk or posedge Rst) begin
 if (Rst) begin
 Z <= #u_dly 1'b0;
 end
 else begin
 if (Z == 3'd7) begin
 Z <= #u_dly 1'b0;
end
 else begin
 Z <= #u_dly Z + 1'b1;
 end
 End //end if
 End //end always
 Endmodule //end module
  • 对组合逻辑的描述有多种方式 其综合结果是等效的
 c = a &b;
 等效于
 c[3:0] = a[3:0] & b[3:0];
 等效于
 c[3] = a[3] & b[3];
 c[2] = a[2] & b[2];
 c[1] = a[1] & b[1];
 c[0] = a[0] & b[0];
 等效于
 for ( i=0; i<=3; i = i + 1)
 c[i] = a[i] & b[i];
可以选择简洁的写法.
  • 考虑综合的执行时间
通常会推荐将模块划分得越小越好, 事实上要从实际的设计目标, 面积和时序要求出发。好的时序规划和合适的约束条件要比电路的大小对综合时间的影响要大。要依照设计的目标来划分模块, 对该模块综合约束的scripts也可以集中在该特性上。要选择合适的约束条件, 过分的约束将导致漫长的综合时间。最好在设计阶段就做好时序规划 。通过综合的约束scripts来满足时序规划。这样就能获得既满足性能的结果 ,又使得综合时间最省 。从代码设计讲 ,500~5000行的长度是合适的。
  • 避免点到点的例外
所谓点到点例外 Point-to-point exception ,就是从一个寄存器的输出到另一个寄存器的输入的路径不能在一个周期内完成。多周期路径就是其典型情况 。多周期路径比较麻烦, 在静态时序分析中要标注为例外, 这样可能会因为人为因素将其他路径错误地标注为例外, 从而对该路径没有分析, 造成隐患。避免使用多周期路径, 如果确实要用 ,应将它放在单独一个模块, 并且在代码中加以注释。
  • 避免伪路径(False path)
伪路径是那些静态时序分析 STA 认为是时序失败, 而设计者认为是正确的路径。通常会人为忽略这些warning ,但如果数量较多时 ,就可能将其他真正的问题错过了。
  • 避免使用Latch
使用Latch必须有所记录, 可以用All_registers -level_sensitive来报告设计中用到的Latch 。不希望使用Latch时 ,应该对所有输入情况都对输出赋值, 或者将条件赋值语 句写全, 如在if语句最后加一个else, case语句加defaults。
  • 当你必须使用Latch时 ,为了提高可测性, 需要加入测试逻辑。
不完整的if和case语句导致不必要的latch的产生, 下面的语句中 DataOut会被综合成锁存器 。如果不希望在电路中使用锁存器, 它就是错误。
always @(Cond)
begin
 if (Cond)
 DataOut <= DataIn end
  • 避免使用门控时钟

使用门控时钟(Gated clock)不利于移植 ,可能引起毛刺, 带来时序问题 ,同时对扫描链的形成带来问题。门控钟在低功耗设计中要用到 ,但通常不要在模块级代码中使用 。可以借助于Power compiler来生成 ,或者在顶层产生。

  • 避免使用内部产生的时钟

在设计中最好使用同步设计。如果要使用内部时钟 ,可以考虑使用多个时钟。因为使用内部时钟的电路要加到扫描链中比较麻烦,降低了可测性, 也不利于使用约束条件来综合。

  • 避免使用内部复位信号。

模块中所有的寄存器最好同时复位。如果要使用内部复位, 最好将其相关逻辑放在单独的模块中, 这样可以提高可阅读性。

  • 如果确实要使用内部时钟, 门控时钟 ,或内部的复位信号 ,将它们放在顶层。

将这些信号的产生放在顶层的一个独立模块, 这样所有的子模块分别使用单一的时钟和复位信号。一般情况下内部门控时钟可以用同步置数替代。

6 附录

6.1 Module 编写示例

/* *
 Filename ﹕
 Author ﹕
 Description ﹕
 Called by ﹕ 
 Revision History ﹕mm/dd/yy
 Revision 1.0
 Email ﹕ M@sz.huawei.com.cn
 Company ﹕ Huawei Technology .Inc
 Copyright(c) 1999, Huawei Technology Inc, All right reserved
* */
 Module module_name(
  Output_ports, //comment ; port description
  Input_ports, //comment ; port description
  Io_ports, //comment ; port descripttion
  Clk_port, //comment ; port description
  Rst_port //comment ; port description
 );
 
 //port declarations
 Output [31:;0] Dataout;
 Input [31:0] Datain;
 Inout Bi_dir_signal;
 Input input1,
 Input2;
 
//interrnal wire/reg declarations
Wire [31:0] internal_data;
 Reg output_enable;
 
 //module instantiations , Self-build module
 Module_name1 Uinstance_name1(...);
 Module_name2 Uinstance_name2(...);
 
 // TSC4000 cell
 DTC12 V1 (.Clk(Clk), .CLRZ(Clr), .D(Data), .Q(Qout));
 
 //continuous assignment
 Assign Data_out = out_enable ? Internal_data : 32’hz;
 
 //always block
 Always @(input2)
 Begin
 ...
 End
 //function and task definitions
  Functiom [function_type] function_name;
   Declarations_of_inputs;
  [declarations_of_local_variables];
   Begin
    Behavirol_statement;
    Function_name = function_express;
   End
  Endfunction //end function_name
 Endmodule //end module_name 

6.2 testbench编写示例

 下面是一个格雷码的测试模块,
module TB_GRAY;
 reg Clock;
 reg Reset;
 wire [7:0] Qout;
 integer fout; //输出文件指针
 parameter CYC = 20;
 
GRAY DUT(.Clock(Clock),.Reset(Reset),.Qout(Qout));

initial
begin
  Clock = 1'b0;
  Reset =1'b1;
  #(5*CYC) Reset = 1'b0;
  #(5*CYC) Reset = 1'b1;
  #(5000*CYC)
  $fclose(fout);
  $finish;
end

 initial
 begin
  $shm_open("GRAY.shm");
  $shm_probe("AS");
  fout=$fopen("gray.dat");
 end
 
always #CYC Clock = ~ Clock;


//输出数据到文件gray.dat
always @(posedge Clock)
begin
  $fwrite(fout,"%d %b
",Qout,Qout);
end

endmodule
  1. 在testbench中避免使用绝对的时间,如#20,#15或#(CYC+15)等,应该在文件前面使用parameter定义一些常量,使得时间的定义象#(CYC+OFF0)的形式,便于修改。

  2. 观测结果可以输出到波形文件GRAY.shm ,或数据文件gray.dat 。生成波形文件可以用simwave观测结果 ,比较直观。而生成数据文件则既可以快速定位 ,也可以通过编写的小程序工具对它进行进一步的处理。

  3. 对大的设计的顶层仿真 ,一般不要对所有信号跟踪, 波形文件会很大, 仿真时间延长,可以有选择的观测一些信号。

审核编辑:郭婷


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分