Menu Close

Verilog 语言编写深入探讨

在Verilog程序设计中,关于模块,always过程,并行赋值语句,函数及task调用等等。如何声明,调用,嵌套及赋值等一系列问题,初学者往往无所适从。关于wire,reg类型的声明,初始化及赋值等也有一系列的问题需要注意。本文对上述提到的相关问题进行深入讨论。希望读者在理解之后举一反三,做到正确使用Verilog语法规则,使得代码清晰,简洁,准确。

 

1. 模块(module)的组织与正确使用

模块(module)是verilog工程的基础,所有的verilog程序都是在module中进行编写的,少量的宏定义可以在模块之外。一个文件可以只包含一个模块,也可以是一个文件包含多个模块。 一个模块不能分离存储到不同的文件中。

1)通常情况下一个文件(*.v)中包含一个module。但也可以在 同一个*.v 文件中,同时存在2个或者多个 module 模块。module 的格式请参考:Verilog 模块与端口。

2)在文件中,module 级别是最高一级的,其他的如always,initial,并发赋值等只能在module 模块下进行编写和使用。 和module 同一级别 的verilog语法声明也包括`timescale,  `include,  `define 等宏定义,如例1所示。

例1:

// MACRO can be defined outside module

`timescale 1ns/1ps  

`include "test.v"

`define PPP      

module aaa
(
     input clk,
     input xxxx,
     output kkk
);

reg [4:0] ttt;
wire [2:0] uuu;

// MACRO can be also defined inside module
`include "debug.v"
`define BBB
awlays @ (posedge clk)
begin
    ....
end

always @ (*)
begin
    ....
end

assign kkk = ...;
assign uuu = ...;

endmodule

module bbb
(
    ...
);
  ....
endmodule

3)module 和 module是同一级别的。 Verilog 语法中module 模块中不能再嵌套定义module了, 虽然Systemverilog 可以嵌套定义,但也有规则限制。

例2:

module aaa
(
    ....
);
.....

//ERROR: module nested define is not allowed in Verilog
module bbb
(
    ...
);
   ...
endmodule

ccc  ccc_inst
(
     .d (),
     .c ()
);

endmodule

 

从例1,例2的使用可以看出:

    • Verilog 语法中module 下不能在定义 其他module了,但是systemverilog 可以(nested module declarations)。但是verilog 和 systemverilog 在module中都可以例化其他模块(module)。
    • module 和 `include 是同一级别的, 即 `include可以写在module 模块之外。 当然, `include也可以写在模块内部。
    • module 和 `define 是同一级别的, 即 `define可以写在module 模块之外。 当然, `define也可以写在模块内部。
    • module 和 `timescale是同一级别的, 即 `timescale可以写在module 模块之外。 当然, `timescale也可以写在模块内部。但是很少将timescale写在module 内部声明。

4)module嵌套时的端口传递

    • 被例化的module 输入端口(input)传递关系,能否将变量传递给被例化模块的输入端口(input),关键看驱动模型。
      •  符合驱动模型传递如下:
        • 本级模块的input 类型端口
        • 本级模块的output 类型端口
        • 本级模块的定义的reg 类型变量
        • 本级模块中的wire 类型变量
      • 不符合驱动模型传递如下:
    • 被例化的module 输出端口(output)传递关系
      • 符合驱动模型传递如下:
        • 本级模块的output,但是有如下限制
          • 本级模块的output不能连接到多个被例化模块中的output端口上
          • 本级模块的output连接到1个被例化模块中的output端口上,不能在本级模块其他逻辑中赋值
        • 本机模块的wire,但是有如下限制
          • 本级模块的wire不能连接到多个被例化模块中的output端口上
          • 本级模块的wire连接到1个被例化模块中的output端口上,不能在本级模块其他逻辑中赋值
      • 不符合驱动模型传递如下:
        • 本级模块的reg 类型不能连接到被例化模块中的output 端口上
        • 本级模块中的output reg 类型端口,不能连接到被例化模块中的output端口上

2. 仿真文件编写

在仿真文件中, module 内部可以编写

  • reg声明
  • wire声明
  • task声明
  • function 声明,
  • module 例化,
  • generate,
  • `define,
  • `include,
  • `timescale,
  • parameter,
  • localparam,
  • always,
  • assign
  • initial
  • IP, primitive 例化等 。

也就是说这些声明,例化是同一级别的。

举例

`timescale 1ns/1ps
`include "test.v"
`define KKK  3
module tb();

reg clk;
reg clk_50 = 0;
reg rst = 1;

localparam PERIOD = 10;

always # clk_50 = ~clk_50;

initial
begin
    clk = 0;
    forever #(PERIOD/2) clk = ~ clk;
end

reg [3:0] cnt;
always @ (posedge clk or posedge rst)
if(rst) cnt <= 0;
else 
begin
    if(cnt == PEROID) cnt <= 0;
    else cnt <= cnt + 1;
end

wire ppp = (cnt == `KKK) ? 1'b1 : 1'b0;

initial
begin
    #2000;
    $stop;
end

endmodule

 

在模块中可以使用wire,reg等声明,但要注意reg 声明时不能使用变量赋初值,如果需要,只能使用wire声明。

例1:

reg b = 0;
reg c = 0;
reg a = b | c;

initial
begin
    b = 1;
    c = 0;
    #10;
    b = 0;
    c = 1;
end

wire k = b | c;

 

这里 reg a = b | c ; 是不正确的。  只能使用 wire k = b |c ; 或者是 wire k;  assign = b | c;  来替代。

例2:always 多处赋值

`timescale 1ns / 1ps
module altera_sim # 
(
    parameter ADD_SUB = 0
)
(
    input clk,
    output [7:0] led
);
        
reg [3:0] cnt = 0;    
    
always @ (posedge clk)
begin
    cnt <= cnt + 1;
end

reg [7:0] led_r = 0;
always @ (posedge clk)
if(cnt[3:0] == 0) led_r <= ADD_SUB ? led_r + 1 : led_r - 1;

//always @ (posedge clk)
//if(cnt[3:0] == 1) led_r <= led_r + 2;

assign led = led_r;
endmodule

 

`timescale 1ns / 1ps
module tb(

    );
    
reg clk = 0;
localparam CLK_PERIOD = 10;
initial
begin
    clk = 0;
    #200;
    forever
        #(CLK_PERIOD/2) clk = ~clk;
end

reg [3:0] b = 5;

initial
begin
    b = 0;
end

always @ (posedge clk) 
b <= ~b; 

always @ (posedge clk) 
b <= b + 1; 
initial
begin
    clk = 0;
    #2000;
    $stop;
end
    
wire [7:0] led;
altera_sim   #(.ADD_SUB (1))
top_inst
(
    .clk    (clk),
    .led    (led)
);

assign led = 8'h80;
endmodule

 

b 有4个地方赋值:

  • 声明的时候reg [3:0] b = 0;
  • 初始化的时候赋值 initial
  • always b <= ~b;
  • always b <= b + 1;

这样的设计,在仿真的时候,可以正常使用。 但是相同的逻辑不能在综合的时候使用。原因是: 2 次 always  进行不同的赋值综合的时候会报错。调用模块alter_sim中的led (output 端口) 和 assign led = 8’h80; 赋值相冲突,最终输出为led = xx;

 

例3:initial 中嵌套always, 不能正常编译。

reg [3:0] b = 5;

initial
begin
    b = 0;

    always @ (posedge clk)
        b <= ~b;
end

initial 中使用always 编译的时候会报错。但可以使用其他方式实现:

reg [3:0] b = 5;
initial
begin
    b = 0;

    while(1)
    begin
        @(posedge clk);
        b <= ~b;
    end
end

或者

reg [3:0] b = 5;
initial
begin
    b = 0;

    while(1)
    begin
        #10;
        b <= ~b;
    end
end

例4:always 中使用 initial 不能正常编译:

always @ (posedge clk)
begin
    initial
    begin
        b = 3;
    end

    b <= ~b;
end

 

例5:2 个模块的输出(output) 连接到一起了。

wire [7:0] led;
altera_sim # (.ADD_SUB(1))
top_inst
(
    .clk (clk),
    .led (led)
);

//assign led = 8'h80;

altera_sim # (.ADD_SUB(0))
top1_inst
(
    .clk (clk),
    .led (led)
);

例6:always 中不能使用assign

always @ (posedge clk)
begin
    assign c = a + b;
end

 

例7:always 中不能使用generate , 但是generate 中可以使用always。

`timescale 1ns/1ps

module tb();

genvar i;

always @ (posedge clk) 
generate 
for (i = 0; i < 4; i = i + 1)
begin:test_gen
        b <= b + 1;
end
endgenerate


endmodule

 

例8:forever, while, if, for, case 等必须在initial, always 下使用,不能独立存在。即使有些综合器没有报错。

`timescale 1ns/1ps
module tb();

reg clk = 0;

forever # clk = ~clk;

reg a = 0;
reg b = 1;

localparam kkk = 1;
if(kkk) 
wire c = a & b;
else
wire c = a | b;

endmodule

 

例9:`define parameter可以在端口定义中使用,define 可以定义端口的种类和变量宽度,parameter 只能定义端口的宽度。举例:

`define KKK  4

module aaa # 
(
    parameter D = 8
)
(
     input clk,
     
     input [`KKK - 1:0]a,
     input [D - 1:0]b,
`ifdef KKK
    ouput [D - 1:0] led
`else
    ouput [D - 1:0] testpin
`endif
);

.....
endmodule

例10:always 使用

always @ (posedge clk);
begin
    a <= a + 1;
end

; 代表当前的always 结束了, 比如在仿真中使用@(posedge clk); 所以之后的begin ….   end 和 always 再也没有任何关系了。在always 中一定会有一个真实的执行体,不会只有always ,说得直白一些就是always 下一定能找到一个“;”。 举例:

always @ (posedge clk)
if(reset) st <= 0;
else case (st)
0: st <= 1;
default : st <= 0;
endcase

 

 

第一级 第二级 第三级
module
wire声明
reg声明
task 声明
function 声明 不能使用 #10; 不能使用always@(posedge)等时序逻辑
generate
`define 可以定义端口数量,改变端口声明
`include
`timescale
parameter 可以定义端口宽度
localparam
always 不能使用使用initial,assign, 模块声明,例化 ,不能声明function, 但可以调用function
initial 不能使用使用always, assign, 模块声明,例化,不能声明function, 但可以调用function
assign 不能包括begin …. end, 只能使用逻辑,关系,等式,位操作等运算符
IP, primitive 例化
module 例化
`timescale
`define
`include

 

Posted in FPGA, FPGA 教材教案, Verilog, 教材与教案, 文章

发表回复

相关链接