层次化和参数化设计

层次化设计

Verilog的模块可以嵌入其他模块,形成层次化的嵌套关系。某一个模块要包含其他子模块,必须实例化子模块。实例化一个模块的方法是:

<模块名> <模块实例名> ( <端口映射> );

端口映射的方法有两种,一种是位置映射法,一种是名称映射法。下面举例说明。假设某一设计需要将寄存器的输入输出与总线连接,为此需要将寄存器通过三态缓冲器连接输入输出,如图 1。寄存器可以用D触发器模块实例化,三态缓冲器模块也在前面讲过,包含这两个子模块的顶层模块代码如下例 1

20100223212645
图 1. 数据寄存器和三态缓冲器
例 1. 模块的实例化
module TOP
(
  inout Data,
  input Clock, Reset, OE
);

wire buf_in;
D_FF D_FF_inst (.Q(buf_in), .D(Data), .Clock(Clock), .Reset(Reset));
Buffer Buffer_inst (Data, buf_in, OE);

endmodule

三态缓冲器模块Buffer的端口映射采用的是位置映射法,触发器模块D_FF的端口映射采用的是名称映射法。位置映射法要求按照子模块端口声明的顺序与顶层模块的信号连接;而名称映射法通过显式地给出子模块的端口名称与顶层模块的信号连接,不一定要按照子模块端口声明的顺序。名称映射法具有较好的可读性和灵活性,推荐采用。

参数化设计

参数的声明及传递

参数化可以提高代码的可重用性。比如,的三态缓冲器的数据宽度是1位的,如果在设计中需要8位的数据宽度,就需要再设计一个8位的三态缓冲器。采用参数化的设计方法,就不需要再设计一个8位的三态缓冲器,只需要在实例化时指明数据宽度就可以了。

参数声明的关键字是parameter。下面的例子采用了Verilog-2001的参数声明语法。

例 2. 参数化的三态缓冲器
module Buffer
#(parameter SIZE = 1)
(
  output [SIZE-1:0] Out,
  input [SIZE-1:0] In,
  input Enable
);

assign Out = Enable ? In : \{SIZE\{1'bz}};

endmodule

实例化为8位的三态缓冲器的方法如下:

Buffer #(8) Buffer_inst (.Out(out), .In(F), .Enable(en));

参数的传递也可以采用名称映射,如下:

Buffer #(.SIZE(8)) Buffer_inst (.Out(out), .In(F), .Enable(en));

如果实例化时没有指明参数值,则使用模块内声明的参数缺省值。

同样地,D触发器也可以采用参数化的设计(这里不再给出,留给读者练习),那么将例 1改为8位数据宽度就很容易,见下例。

例 3. 实例化时重定义参数值
module TOP
(
inout [7:0] Data,
input Clock, Reset, OE
);

wire [7:0] buf_in;
D_FF #(8) D_FF8 (.Q(buf_in), .D(Data), .Clock(Clock), .Reset(Reset));

Buffer #(.SIZE(8)) Buffer_inst (Data, buf_in, OE);

endmodule

局部参数

参数除了用于实例化时重定义参数值,在模块内部需要符号常量时,也可以用参数来定义。但是Verilog-1995不能从关键字上区分这两种用途,全都使用parameter。在IP核设计中,所有的参数都暴露给外部,会给使用者造成困惑,也容易造成一些错误。所以Verilog-2001增加了关键字localparam表示局部参数。下面是一个例子。

例 4. 用锁存器实现的存储器
module RAM
#(parameter ADDR_SIZE = 4, DATA_SIZE = 8)
(
  output [DATA_SIZE-1:0] Q,
  input [DATA_SIZE-1:0] Data,
  input [ADDR_SIZE-1:0] Addr,
  input WR
);

localparam MEM_DEPTH = 1<<ADDR_SIZE;
reg [DATA_SIZE-1:0] mem [0:MEM_DEPTH-1];

always @ * begin
  if (WR) mem[Addr] = Data ;
end

assign Q = (WR) ? 0 : mem[Addr];

endmodule

在上例中,存储单元的个数MEM_DEPTH是局部参数,在外部不能直接改变该参数值;但是它从参数ADDR_SIZE计算得到,所以是间接地被改变。如果将MEM_DEPTH也作为一个parameter,不仅给使用者增加了负担,不恰当的参数赋值还可能带来不希望的后果。

generate结构

generate结构可以在一个循环中重复创建多个实例,或者有条件地创建实例。

generate循环结构

例 5是一个循环创建多个实例的例子,实例化的模块是[exa-11]的D触发器。需要指出的是,多个实例的创建是在综合工具进行逻辑综合的时候,也就是说,在综合之后硬件就已经生成,并不是在运行的时候动态地创建硬件电路。generate循环结构有几个要求:

(1) 循环变量必须用genvar事先声明,它仅仅存在于综合期间,仿真时并不存在,所以不能在循环体以外被引用。

(2) for循环必须命名,因此也必须有begin…​end,即使只有一句。

例 5. 用多个D触发器串联构成移位寄存器
module ShiftRegister
#(parameter SIZE = 4)
(
input D,
input Clk,
input Reset,
output Q
);

wire [SIZE : 0] q;
assign q[0] = D;
assign Q = q[SIZE];

generate
  genvar i;
  for(i=1; i<=SIZE; i=i+1)
  begin : dff
    D_FF gen_inst(.D(q[i-1]), .Clock(Clk), .Reset(Reset), .Q(q[i]));
  end
endgenerate

endmodule

在generate块中并非只能对模块实例化,也可以直接包含assign语句、always语句。见下例。

例 6. 格雷码到二进制码的转换
module gray2bin
#(parameter SIZE = 8)
(
  output [SIZE-1:0] bin;
  input [SIZE-1:0] gray
);

genvar i;
generate
  for (i=0; i<SIZE; i=i+1)
  begin:bit
    assign bin[i] = ^gray[SIZE-1:i];
  end
endgenerate

endmodule

generate条件结构

在generate结构中可以加入if-else语句或case语句,实现有条件地生成代码。下面的例 7根据不同的条件生成乘法器实例,如果数据宽度小于8位,生成先行进位(CLA)乘法器;否则,生成华莱士(WALLACE)乘法器。

例 7. 有条件地生成模块实例
module Multiplier
#(parameter A_WIDTH = 8, B_WIDTH = 8)
(
  input [A_WIDTH-1:0] A,
  input [B_WIDTH-1:0] B,
  output [A_WIDTH+B_WIDTH-1:0] Product
);

generate
  if((A_WIDTH < 8) || (B_WIDTH < 8))
    CLA_multiplier #(A_WIDTH,B_WIDTH) u1(A, B, Product);
  else
    WALLACE_multiplier #(A_WIDTH,B_WIDTH) u1(A, B, Product);
endgenerate

endmodule