verilog代码结构

上来先写module xxx和endmodule,类似于c++中的int main和return 0.

常用的语句主要有三种:assign语句、always语句和底层模块调用语句。


module DFF(CLK,D,Q);//module 模块名 ([端口列表]);
	output Q;
	input CLK,D;//[端口信号声明;]
	reg Q;//[参数声明;]
	always @(posedge CLK)//always语句块
		Q <= D;
endmodule//endmodule

assign语句

连续赋值语句,基本格式:  assign 赋值目标 = 表达式
例如:

assign y = a;//把a的值赋给y
assign y = a&b;//把a与b的值赋给y

        之所以称为连续赋值语句是指其总是处于激活状态,只要表达式中的操作数有变化,立即进行计算和赋值。(与连续赋值语句对应的另一种语句称为过程赋值语句-always)
        赋值目标必须是wire型的,wire可以理解为电路间的连线。

表达式运算

不只是有“1”和“0”还有“x”。
大多数与c++一样,几个少见的如case等于/不等于,结果只能是0或1。

1 == x  //结果为x
1 === x //结果为0
x == x  //结果为x
x === x //结果为1

还有就是拼接复制运算符(对于4'b1001是什么东西,可跳转到数字表示一节)

Y = {4'b1001,2'b11} //100111
Y = {4{2'b01}} //01010101

always语句块

always语句块又叫过程块

基本格式:
always @ (敏感信号条件表)
        各类顺序语句;
例如:

always @(posedge CLK)
		Q = D;

always语句本身不是单一的有意义的一条语句,而是和下面的语句一起构成一个语句块,称之为过程块;过程块中的赋值语句称过程赋值语句;

该语句块不是总处于激活状态,当满足激活条件时才能被执行,否则被挂起,挂起时即使操作数有变化,也不执行赋值,赋值目标值保持不变;

赋值目标必须是reg型的。

激活条件

激活条件由敏感信号条件表决定,当敏感条件满足时,过程块被激活。敏感条件有两种,一种是边沿敏感,一种是电平敏感。

边沿敏感:

(posedge clk) //信号上升沿到来
(negedge clk) //信号下降沿到来

电平敏感:
(a,b,c)  当a,b,c中有一个发生变化被激活

begin end

module adder (a,b,cin,s,cout)
    input a,b,cin;
    output s,cout;
    reg s,cout;
always @ (a,b,cin)
begin
    s=a^b^cin;
    cout=(a&b)|(a&cin)|(b&cin);
    /*
    类似于c++中的if语句不加{}就只能执行紧跟着if下面的那一句,后面的不执行了
    */*
end
endmodule

always语句块中如果有多条赋值语句必须将其用begin end包括起来,assign语句中没有begin end。

在仿真时,begin和end之间的语句执行顺序如何?begin end之间的赋值语句有阻塞赋值和非阻塞赋值之分。

阻塞赋值:语句顺序执行,前面的执行完才能执行后面;

begin
    m=a*b;
    y=m;
end
当m=a*b 执行完才能执行y=m 。
当m赋值完成后,才能执行y的赋值,y得到的是m的新值。

非阻塞赋值:所有语句并行执行。

begin
    m<=a*b;
    y<=m;
end
m=a*b 和y=m并行执行 。
m和y的赋值并行执行,y得到的是m的旧值。

阻塞赋值的实质:右边表达式的计算和对左边寄存器变量的赋值是一个统一的原子操作中的两个动作,这两个动作之间不能再插入其他任何动作。

非阻塞赋值的实质:首先按顺序计算右边表达式的值,但是并不马上赋值,而是要等到过程结束时再按顺序赋值。

底层模块与门原语调用

底层模块调用

该电路是由两个D触发器和一个或门构成的,设计思路之一是先设计底层电路D触发器,然后再设计顶层电路,在顶层电路中可调用底层模块。

底层模块描述:

module DFF(CLK,D,Q)
    output reg Q;
    input CLK,D;
always @ (posedge CLK)
    Q<=D;
endmodule

顶层模块描述:
为了调用底层模块,需要加两个内部变量d1和q1;
并给两次调用的模块进行命名;
调用时例化名不能省略。

module examp (clk,d,a,q)
    output  q;
    input clk,d,a;

    wire d1;
    wire q1;

DFF dff1(.CLK(clk),.D(d1),.Q(q1));
DFF dff2(q1,d,q);//最好不要用,看似代码简单,实际很麻烦

or (d1,a,q);

endmodule

门原语调用

Verilog语言提供已经设计好的门,称为门原语,这些门可直接调用,不用再对其进行功能描述。

调用格式:

门原语名    实例名  (端口连接)

and (out, in1, in2);
端口连接中第一个是输出,其余是输入,输入个数不限。

常用的与或门

非门和缓冲器

not (OUT1, IN); 
buf b1_2out(OUT1, OUT2, IN);

端口列表中前面是输出,最后一个是输入,输出个数不限。

三态门:

bufif1 b1 (out, in, ctrl);
bufif0 b0 (out, in, ctrl);

notif1 n1 (out, in, ctrl);
notif0 n0 (out, in, ctrl);

端口列表中前面是输出,中间是输入,最后是使能端,输出个数不限。

verilog中的数据类型

线网类(net类)和变量类(variable类)

将一个信号定义成net型还是varible型,由以下两方面决定:

使用何种赋值语句对该信号进行赋值,如果是连续赋值或门原语赋值或例化语句赋值,则定义成net型;如果是过程赋值则定义成variable型。

对于端口信号来说,input信号和inout信号必须定义成net型的;output信号可以是net型的也可以是variable型的,决定于如何对其赋值。

大多数情况下,使用连续赋值和门原语赋值的语句为net型,使用过程赋值的语句通常定义为variable型。

数字表示以及逻辑值

无符号数的表示方法: <位宽>’<进制><数字>

有符号数的表示方法:<位宽>’< sb ><数字>

Verilog语言中的逻辑值有四种

1:逻辑1,高电平,数字1
0:逻辑0,低电平,数字0
x:不确定
z:高阻态

if语句

always @(posedge CLK)
  if (!RST) Q=0;
  else Q=Q+1;  

case语句

module MUX41 (a,b,c,d,s1,s0,y);
input a,b,c,d,s1,s0;
output reg y;
//reg y;
always @(a,b,c,d,s1,s0)
begin
	case ({s1,s0})
 	    2’b00 : y=a;
	    2’b01 : y=b;
	    2’b10 : y=c;
	    2’b11 : y=d;
             default : y=0;
	endcase
end
endmodule

verilog语言三种描述

结构化描述(也称门级描述)(全部用门原语和底层模块调用) 

数据流级描述(全部用assign语句)

行为级描述(全部用always语句配合if、case语句等)

结构化描述:

module mux4_to_1 (out, i0, i1, i2, i3, s1, s0);
output out;
input i0, i1, i2, i3;
input s1, s0;

wire s1n, s0n;
wire y0, y1, y2, y3;

not (s1n, s1);
not (s0n, s0);

and (y0, i0, s1n, s0n);
and (y1, i1, s1n, s0);
and (y2, i2, s1, s0n);
and (y3, i3, s1, s0);

or (out, y0, y1, y2, y3);
endmodule

数据流级描述:

module mux4_to_1  (out, i0, i1, i2, i3, s1, s0);

output out,
input i0, i1, i2, i3;
input s1, s0;

assign out = (~s1 & ~s0 & i0)|(~s1 & s0 & i1) |(s1 & ~s0 & i2) |(s1 & s0 & i3) ;
endmodule

行为级描述:

module mux4_to_1(out, i0, i1, i2, i3, s1, s0);
output out;
input i0, i1, i2, i3;
input s1, s0;
reg out;
always @(s1 or s0 or i0 or i1 or i2 or i3)
 begin
    case ({s1, s0})
       2'b00: out = i0;
       2'b01: out = i1;
       2'b10: out = i2;
       2'b11: out = i3;
       default: out = 1'bx;
     endcase
  end
endmodule

testbench仿真编写

为了更加直观的展示,将用一个简单的实例进行举例:

module text(
    input a,
    input b,
    output [2:0]y
    );
    assign y[0] = a&b;
    assign y[1] = ~(a&b);
    assign y[2] = a|b;
endmodule

我们根据上面学过的内容很容易的根据代码画出上面的电路图,但是为了测试电路图是否跟我们设计想象的一样,我们就需要仿真验证,因为我们需要再写一个下面的仿真程序进行演示

'timescale 1ns / 1ps //时间单位/时间精度


module text_tb( );//仿真模块的名字,随便起
    reg a,b;
    wire [2:0]y;
    //待测模块实例化,跟python中的类的调用相似
    text tsh(  //调用模块的名字+随便起个名字
        .a(a),
        .b(b),
        .y(y)
    );
    //激励信号的生成
    initial begin
       a = 0;b = 0;
       # 100;//100ns延时
       a = 0;b = 1;
       # 100;
       a = 1;b = 0;
       # 100;
       a = 1;b = 1;
       $finish;//结束仿真,不然一直进行
    end
    
endmodule

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐