基于Verilog语言的可维护性设计技术
【注】本文内容主体部分直接翻译参考文献[1]较多内容,因此本文不用于任何商业目的,也不会发表在任何学术刊物上,仅供实验室内部交流和IC设计爱好者交流之用。
“曲意而使人喜,不若直节而使人忌;无善而致人誉,不如无恶而致人毁” ——《菜根谭》
【摘要】 本文以VerilogHDL为例,从可重用性、代码可扩展性、可读性、变量本地化、参数(parameter)和宏(`define)的对比以及封装子程序的角度探讨了可维护性设计应遵守的几条基本原则。
【关键词】设计重用; 可维护性设计; 本地化; 参数; 宏
前言
随着集成电路制造技术的发展,对设计提出了更多的挑战,随着设计复杂度的增加,又提出了片上系统(SoC)的概念。为了加速设计收敛,设计重用、可测性设计、可验证性设计和可维护性设计得到了更多重视。本文以VerilogHDL为例,对可维护性设计进行了初步探讨。
1、设计重用与可维护性设计 设计重用是一个很大的概念,严格来讲,可验证性设计和可维护性设计都在设计重用之列。可维护性设计的目的本身就是便于设计重用,便于让后来人读懂前人所写的代码,但设计重用包括的内容更广泛。 设计重用讲的是设计总体风格而不是设计的细节,“it is about forests, rather than trees”。
最重要的概念:本地化(locality)——一定要尽量使问题本地化,这样有利于问题的排除。对于大的设计尤为重要。
三条基本原则
-
尽量采用全同步设计,将输入和输出寄存,这样有利于将时序优化本地化。
-
采用bottom-up验证方法,保证每个子模块的功能是正确的,然后再进行整体验证。
-
花时间设计一个好的设计规范,力图使这样的规范具有好的体系结构和模块划分,这样有利于本地化的有效实现。
一个好的设计应该包括:完备的文档、好风格的代码、详尽的注释、良好易用的验证环境和鲁棒的脚本。 影响设计重用的障碍本质上说是管理和企业文化;开发和管理内部IP资源是最大的重用挑战。 关于设计重用这里不再讨论,否则就有喧宾夺主之嫌了。
2、关于代码可扩展性的一点讨论 代码可扩展性实际上也包括在设计重用之内。代码可扩展性或称可扩展性设计要求便于向将来新的设计过渡。比如64位的PCI标准出来的时候,现行的32位PCI接口设计模块可以通过比较简单的改动而变为64位,而不需要一切从头再来。一般设计中通过定义参数和宏来便于修改。
典型例子就是总线: `define WORD 16 `define DWORD 32 reg [`WORD-1:0] intruction reg [`DWORD-1:0] data_bus,addr_bus;
当然代码可扩展性不仅仅包括参数和宏的使用,如下例: module tri_buf(in,out, ena); parameter WIDTH=8; input [WIDTH-1:0] in; output[WIDTH-1:0] out; input ena; assign out= ena ? in : ’bz; endmodule
这是一段有潜在问题的代码,Verilog将高位扩展为0来匹配输出,所以当WIDTH>32时,上述代码是有问题的,仿真的时候甚至都看不出来,因此不利于可扩展性。象这样的问题可以用Verilog lint等工具来检查。
3、可读性最重要 可读性最重要。许多设计者有个习惯:追求尽量用短的代码来完成同样的功能,国内许多考试中也要求用少于多少行的代码完成某某功能(很不好的倾向)。
对于HDL,我不推荐这样的风格,因为综合工具会帮助你完成优化工作。
我的建议是:同样的代码可长可短,不要为了减少行数而影响可读性。如下例: Module rrarb(request,grant,reset,clk); Input [1:0] request; Output[1:0]grant; Input reset; Input clk; Wire winner; Reg last_winner; Reg [1:0] grant; Wire[1:0] next_grant;
Assign next_grant[0]=~reset&(request[0] & (~request[1]|last_winner)); Assugn next_grant[1]=~reset& (request[1] &(~request[0]| ~last_winner)); Assign winner=~reset & ~next_grant[0] &(last_winner | next_grant[1]);
Always @(posedge clk)Begin Last_winner=winner; Grant=next_grant; End
endmodule
当request[1:0]=2’b00,时,last_winner会发生什么变化?上面的代码可读性较之下面的代码差很多。
Module rrarb(request,grant,reset,clk); Input [1:0] request; Input reset; Input clk;
Output[1:0]grant;
Wire winner; Reg last_winner; Reg [1:0] grant;
Always @(posedge clk)Begin If(reset) begin Grant<=2’b00; Last_winner<=0; End Else begin Grant<=2’b00; If(request!=2’b00) begin:find_winner Reg winner;//模块内的局部变量 Case(request) 2’b01:winner<=0; 2’b10:winner<=1; 2’b11:if (last_winner==1’b0) winner<=1; else winner<=0; default:winner<=0; endcase grant[winner] <=1’b1; last_winner<=winner; end end endmodule
上述两个代码的综合结果差不多。而且上面第一段代码在always块内采用阻塞赋值的方法也存在潜在问题。
4、要有好的注释风格 要有好的注释风格。减少注释的行数跟节约代码的行数一样,我不推荐。像代码有好坏一样,注释也有好坏之分。比如: //increment addr addr<=addr+1;
上例中的注释就是一句废话,因为地址加1是不言自明的。这样的注释反而会影响可读性。好的注释应该给出该段代码的使用目的,它能够给不熟悉这段代码的人以信息。如下例:
//In burst mode,the bytes are written in consecutive addresses.
//Need to access the next address to verify that the next byte was properly saved. Addr<=addr+1;
关于如何注释我在设计重用里面讲过,这里不再重复,请参考[2]。
5、应尽量将声明本地化 这种办法有点类似于C++中封装的概念,应尽量保持变量的可见性在必要的范围内,不要产生不必要的交互。 如下例: integer I;
always begin for(I=0;I<32;I=I+1) begin … end end
always begin for(I=15;I>=0;I=I-1) begin … end end
对于两段代码来讲,I是全局的,他们存在的的不必要的交互会产生不可预料的结果。
在verilog里面,你可以将I定义到always块里面,使I只对当前块可见。如下例:
always begin:block1 integer I; for(I=0;I<32;I=I+1) begin … end end
always begin:block2 integer I; for(I=15;I>=0;I=I-1) begin … end end
在Verilog里面其它使变量本地化的方法是采用function 和task。Function和task中定义的变量只对其内部可见。 Task send; Input [7:0] data; Reg parity; Begin … end endtask
function [31:0] average; input [31:0] val1; input[31:0] val2; reg [32:0] sum; begin sum=val1+val2; average=sum/2; end endfunction;
另外,还要提一句关于`define VS parameter。
一般设计中都要有一个头文件,里面用`define定义了一些宏。而模块中又有可能用parameter定义一些参数。
两者的区别是一个是全局的,一个是本地的。设计者可以根据需要进行定义。定义成本地参数可以避免同其他模块的变量名字冲突。定义成全局的宏则可以在整个设计的各个文件中使用。千万不可为了方便全部定义成宏。
6.子程序封装 封装有利于程序的可维护性。如果程序中多次用到一段代码,应根据实际情况尽量将它定义为function、task或module。这样做可以减少代码长度,提高可维护性和可验证性。下面以testbench为例说明。
通常一个SoC设计中,需要首先设计和验证各个子模块,然后才进行系统级验证,因此要多次用到testbench。每个testbench中都要初始化时钟、进行系统复位等。这些多次用到的代码不如写在单独的一个文件(不妨称作basic.v)中,各个testbench只要使用include将他们包括进来就可以直接使用了。 下面是我在最近的863项目中写的basic.v,去掉一些注释后的内容,可以直接copy使用。 要求将时钟定义为clk,复位管脚定义为rst,高有效。
//start of basic.v event ENDSIM; // CYCLE monitor and end-of-simulation checker. task monitor_cycles; input max_cycles; integer max_cycles; integer cycles; begin cycles = 0; fork // Count cycles. forever begin @(posedge clk); cycles = cycles + 1; end // Watch for max cycles. If we detect max cycles then throw our testbench ENDSIM event. // begin wait (cycles == max_cycles); $display ("MAXIMUM CYCLES EXCEEDED!"); ->ENDSIM; end join end endtask // Reset task reset; begin rst = 1; #200; rst = 0; $display ("End RESET."); end endtask // Drive the clock input task drive_clock; begin clk = 0; forever begin #(`CLKLO) clk = 1; #(`CLKHI) clk = 0; end end endtask
// ************* BASIC CONFIDENCE Test Tasks ************** // // BASIC CONFIDENCE Test. // // This task will fork off all the other necessary tasks to cause reset, drive the clock, etc. etc. // // task clk_rst_gen_and_stop_at; input cycle_num; begin $display ("designed by Chenxi. Email:chenxiee@mails.tsinghua.edu.cn"); fork // Capture data capture_data; // Run the clock drive_clock; // Do a reset reset; begin monitor_cycles(cycle_num); end // Catch end of simulation event due to max number of cycles or pattern from PIC code. begin @(ENDSIM); // Catch the event. $display ("End of simulation signalled. Killing simulation in a moment."); #0; // Let anything else see this event... $stop; end join end endtask //end of basiv .v
在testbench中可以直接例化使用。
//somemodule_tb.v module somemodule_tb; `include “basic.v” initial begin // ** This is our top-level "Basic Confidence" test. … //generatr rst,clk,and stop at 5000 cycles clk_rst_gen_and_stop_at(5000); … end … endmodule //End of somemodule_tb.v
【注意】basic.v不能够单独编译,直接编译testbench就可以了。上述方法也在可综合的代码中使用。 参考文献[1]推荐的另外一种办法是将上述basic.v用模块封装起来。如下例:
module basic; integer warnings; integer errors; initial begin warnings=0; errors=0; end task warning; input[80*8:1] msg; begin $write(“Warning at %t: %s”,$time,msg); warnings=warnings+1; end endtask task terminate; begin $write(“Simulation Completed\n”); $stop; end … endmodule
调用办法:
module somemodule_tb; initial begin … if(…) basic.warning(”Unexpected response detected”); … basic.terminate; end endmodule
7、结论
本文VerilogHDL为例,从可重用性与可维护性的关系、代码可扩展性设计、尽量增加可读性和使变量本地化、参数(parameter)和宏(`define)的对比以及封装子程序的角度探讨了可维护性设计应遵守的几条基本原则。作者的HDL coding经验不多,权当抛砖引玉。 “公平正论不可犯手,一犯手则玷污终身”,“小处不渗漏,暗处不欺隐,末路不怠荒,才是真正英雄”,因此一定要依照以上原则进行可维护性设计!,不要试图降低师弟妹们对你的崇拜程度。 欢迎大家提出宝贵意见。联系方式: QQ:51559222 参考文献 [1]Janick Bergeron,WRITING TESTBGENCHES –Functional Verification of HDL Models, England:KLUWER ACADEMIC PUBLISHERS [2] 陈曦 设计重用 中国:清华大学 [3] 陈曦的设计文件 basic.v
本文来自:我爱研发网(52RD.com) - R&D大本营 详细出处:http://www.52rd.com/Blog/Archive_Thread.asp?SID=417