已经掌握:
HDL代码经常在表达式和数组的边界使用常量。这些值在模块内是固定的,不可修改。一个很好的设计惯例是用符号常量取代这些hard literal,这样做可使代码清晰,便于后续维持及修改。在Verilog中,可以使用localparam(本地参数)来声明常量。比方说,我们可以声明一个数据总线的位宽及数据范围为:
localparam DATA_WIDYH = 8, DATA_RANGE = 2**DATA_WIDYH - 1;
或者定义一个符号端口名称:
localparam UART_PORT = 4'b0001, LCD_PORT = 4'b0010, MOUSE_PORT = 4'b0100;
声明中的表达式,如2**DATA_WIDTH-1,是在预编译时计算,因此它不会引用任何物理电路。一般来讲,我们使用大写字母来表示常量。
常量的使用可用实例来说明。考虑一个带进位的加法器的代码。为了加法运算被正确执行,可将输入的值手动扩展1位,并取和的最高位为进位。代码如下:
代码1 使用hard literal的加法器
module adder_carry_hard_lit ( input [3:0] a, input [3:0] b, output [3:0] sum, output cout // carry output ); // signal declaration wire [4:0] sum_ext; // body assign sum_ext = {1'b0, a} + {1'b0, b}; assign sum = sum_ext[3:0]; assign cout = sum_ext[4]; endmodule
代码所示为4位加法器。hard literal,即硬文字,如用来表示数据范围的3和4,wire [4:]及sum_ext[3:0],以及最高位sum_ext[4]。如果我们需要把它修改为8位的加法器,那么久需要手动修改这些hard literal。如果代码很复杂且多处都引用这些hard literal,那么修改起来,将是件痛苦的事情,同时也有可能带来不必要的错误。
为了提高代码的可读性,我们可以使用符号常量,比方说,用N来代表加法器的位数。修改后的代码如下所示:
代码2 使用常量的加法器
module adder_carry_local_par ( input [3:0] a, input [3:0] b, output [3:0] sum, output cout // carry output ); // constant declaration localparam N = 4, N1 = N - 1; // signal declaration wire [4:0] sum_ext; // body assign sum_ext = {1'b0, a} + {1'b0, b}; assign sum = sum_ext[N1:0]; assign cout = sum_ext[N]; endmodule
常量使代码更易于被理解和维持。
Verilog的模块可被例化以作为更大模块的一部分。Verilog提供了参数,来给模块传递信息。这种机制使得模块更加通用,也方便复用,因此它的功能很像常量。
在Verilog-2001中,参数的定义区可以加在模块的头部,即端口申明之前。其简单语法如下:
module [module_name] #( parameter [parameter_name]=[default_value], . . ., [parameter_name]=[default_value] ) ( . . . // I/O port declaration );
举个例子,前面的加法器代码中的加法器宽度可以修改为通过参数来指定的形式。
代码3 使用参数的加法器
module adder_carry_para #(parameter N=4) ( input [N-1:0] a, input [N-1:0] b, output [N-1:0] sum, output cout // carry output ); // constant declaration localparam N1 = N - 1; // signal declaration wire [N:0] sum_ext; // body assign sum_ext = {1'b0, a} + {1'b0, b}; assign sum = sum_ext[N1:0]; assign cout = sum_ext[N]; endmodule
N参数被声明的缺省值为4。当N被声明之后,它可以像常量一样,被用于端口声明和模块主体之中。
如果之后,此加法器被用作其他代码的一个组件,那么我们就可以在组件例化的时候,指定想要的值给参数,以废除缺省值。参数可以通过名称或顺序列表方案来指定。一般情况下,本人都是通过名称来指定参数。请看代码:
代码4 加法器例化样例
module adder_inst ( input [3:0] a4, b4, output [3:0] sum4, output c4, input [7:0] a8, b8, output [7:0] sum8, output c8 ); // instantiate 8-bit adder adder_carry_para #(.N(8)) a_inst1 ( .a(a8), .b(b8), .sum(sum8), .cout(c8) ); // instantiate 4-bit adder adder_carry_para a_inst2 ( .a(a4), .b(b4), .sum(sum4), .cout(c4) ); endmodule
参数提供了创建可伸展代码的机制,使得电路的“宽度”可以按照指定需求来调整。这样写代码,可以使得设计被更好地复用。
localparam关键词、头部声明、通过名称指定参数给模块传递信息都是Verilog-2001的新特性。在Verilog-1995中,参数实在头部之后声明,而且只能通过顺序列表方案或defparam语句来重定义。进一步讲,常量必须被声明为参数,尽管它不应该被重新定义。上述的加法器使用Verilog-1995语法来描述的代码如下:
代码5 使用Verilog-1995参数的加法器
module adder_carry_95(a, b, sum, cout); parameter N = 4; // parameter declared before the port parameter N1 = N - 1;// no localparam in Verilog-1995 input [N1:0] a, b; output [N1:0] sum; output cout; // signal declaration wire [N:0] sum_ext; // body assign sum_ext = {1'b0, a} + {1'b0, b}; assign sum = sum_ext[N1:0]; assign cout = sum_ext[N]; endmodule
当一个组件被例化是,参数仅可以使用顺序列表方案来重定义,如:
adder_carry_95 #(8,7) a_inst1 ( .a(a8), .b(b8), .sum(sum8), .cout(c8) );
或者使用defparam语句,如:
defparam a_inst2.N=8; defparam a_inst2.N1=7; adder_carry_95 a_inst2 ( .a(a8), .b(b8), .sum(sum8), .cout(c8) );
Verilog-1995的方案比较冗长麻烦,有可能产生一些微妙的错误,因此不推荐使用。
1 Pong P. Chu.FPGA Prototyping By Verilog Examples: Xilinx Spartan-3 Version.Wiley