FPGA学习 阶段一:Verilog HDL 简介与基本语法(2)

FPGA学习 阶段一:Verilog HDL 简介与基本语法(2)

1. 模块及其结构

Verilog的基本设计单元是“模块”block,一部分描述接口,另一部分描述逻辑功能,比如:

module block(a,b,c,d);
    input  a,b;     //描述接口
    output c,d;
    
    assign c = a | b;   //描述逻辑功能
    assign d = a & b;

endmodule

以上是一个简单的模块,首先描述了四个接口a,b,c,d
8KGwEq.png
之后利用assign语句描述了接口之间的逻辑关系,即c等于a或b,d等于a且b
这样一个完整的模块就定义好了
在这里插入图片描述

1.1 功能定义

功能定义部分有三种方法,每个逻辑功能定义之间是并行的关系,即每个功能没有先后顺序,都是同时进行,但每个功能内部中是按照顺序执行的

  1. assign语句
    assign语句通常用来描述组合逻辑,即可以完整用逻辑门结构来表示的逻辑
  2. always语句
    always语句不但能描述组合逻辑,还可以用来描述时序逻辑
  3. 例化实例元件
    如:and #2 u1(q,a,b)定义了一个输入为a和b,输出为q的与门

1.2 模块的调用

在模块调用时,信号通过模块端口在模块之间传递

module seg_led_static_top (
    input               sys_clk  ,       // 系统时钟
    input               sys_rst_n,       // 系统复位信号(低有效)

    output    [5:0]     sel      ,       // 数码管位选
    output    [7:0]     seg_led          // 数码管段选

);

//parameter define
parameter  TIME_SHOW = 25'd25000_000;    // 数码管变化的时间间隔0.5s

//wire define
wire       add_flag;                     // 数码管变化的通知信号

//调用模块

//每隔0.5s产生一个时钟周期的脉冲信号
time_count #(                   //列出调用的模块名
.MAX_NUM(TIME_SHOW)             //列出模块中的参数并重命名
) u_time_count(
    .clk        (sys_clk  ),    //将底层信号传到上层格式: .被调用模块信号名    (顶层信号名)
    .rst_n      (sys_rst_n),    
    .flag       (add_flag )
);
//每当脉冲信号到达时,使数码管显示的数值加1
seg_led_static u_seg_led_static (
    .clk        (sys_clk  ), 
    .rst_n      (sys_rst_n),

    .add_flag   (add_flag ), 
    .sel        (sel      ),
    .seg_led    (seg_led  )
);

//被调用模块
module time_count(
    input           clk     ,   // 时钟信号
    input           rst_n   ,   // 复位信号

    output   reg    flag        // 一个时钟周期的脉冲信号
);

//parameter define
parameter  MAX_NUM = 25000_000; // 计数器最大计数值

//reg define
reg [24:0] cnt;                 // 时钟分频计数器

利用逻辑结构框图,上述模块之间的调用以及信号连接可以表示如下:
8KBFL8.png

2. 结构语句

结构语句有initialalways

2.1 initial语句

initial语句在模块中只执行一次
它常用于test bench(测试文件)的编写,用来产生仿真测试信号(激励信号),或者用于对储存器变量赋初值:

initial
begin
    sys_clk         <=1'b0;
    sys_rst_n       <=1'b0;
    touch_key       <=1'b0; //全部拉低
    #20 sys_rst_n   <=1'b1; //20个单位时间(20ns)后拉高电平
    #10 touch_key   <=1'b1;
    #30 touch_key   <=1'b0;
    #110 touch_key  <=1'b1;
    #30 touch_key   <=1'b0;
end

always #10 sys_clk  <= ~sys_clk //每10ns对sys_clk取反一次,也就是说产生周期为20ns,频率为50Mhz的时钟

上面的例子画出波形图如下:
在这里插入图片描述
可以看到,实际仿真的波形与我们设定的完全相符,起始状态全部为低电平,sys_clk以周期为20ns为单位不断进行电平反转,20ns后sys_rst_n被拉高,再经过10ns后touch_key被拉高…

2.2 always语句

  • always语句一直不停的执行,如上的sys_clk信号,并不是取反一次就结束了,而是不停地进行每10ns取反一次。
  • always后紧跟着的是触发条件,触发条件可以是单个信号也可以是多个信号,多个信号中间用or连接,连接而组成的列表称为“敏感列表”
  • 通常使用的posedge是指上升沿触发,negedge是指下降沿触发
2.2.1 描述时序逻辑电路

时序逻辑电路中,任一时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态。或者说还与以前的输入有关,因此时序逻辑必须具备记忆功能

module flow_led(
    input               sys_clk  ,  //系统时钟
    input               sys_rst_n,  //系统复位,低电平有效
	 
    output  reg  [3:0]  led         //4个LED灯
    );

//reg define
reg [23:0] counter;

//计数器对系统时钟计数,计时0.2秒
always @(posedge sys_clk or negedge sys_rst_n) 
begin
    if (!sys_rst_n)
        counter <= 24'd0;
    else if (counter < 24'd1000_0000)
        counter <= counter + 1'b1;
    else
        counter <= 24'd0;
end

//通过移位寄存器控制IO口的高低电平,从而改变LED的显示状态
always @(posedge sys_clk or negedge sys_rst_n) 
begin
    if (!sys_rst_n)
        led <= 4'b0001;
    else if(counter == 24'd1000_0000) 
        led[3:0] <= {led[2:0],led[3]};
    else
        led <= led;
end

endmodule 

上面的例子很好理解:
如果遇到sys_clk上升沿或sys_rst_n下降沿则进入always语句,然后进行always中的条件判断等等…综合为逻辑框图,可用下图来表示:
在这里插入图片描述

2.2.2 组合逻辑电路

组合逻辑电路中,任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关
比如,我们可以用always表示一组逻辑门电路:

always @(a or b or c or d or e or f or g or h or p or m)
begin
    out1 = a ? (b + c) : (d + e);
    out2 = f ? (g + h) : (p + m);
end

这个逻辑是:
判断a是否为1?若a为1,则out1 = b + c;反之out1 = d + e;
判断f是否为1?若f为1,则out1 = g + h;反之out1 = p + m;

如果组合逻辑快语句的输入变量很多,那么编写敏感列表会很繁琐且容易出错,这时候可以用*来表示对后面语块中所有输入变量的变化都是敏感的

always @( * )
begin
    out1 = a ? (b + c) : (d + e);
    out2 = f ? (g + h) : (p + m);
end

2.3 赋值语句

在描述组合逻辑的 always 块中用阻塞赋值 = ,综合成组合逻辑的电路结构;
这种电路结构只与输入电平的变化有关系。
在描述时序逻辑的 always 块中用非阻塞赋值 <=,综合成时序逻辑的电路结构;
这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。

2.3.1 阻塞赋值

阻塞赋值和其他常用语言中赋值的效果相同,即计算RHS(Right Hand Side)并更新LHS(Left Hand Side),也就是说,在同一个always中,后面的赋值语句是在前一句赋值结束之后才开始赋值的,比如:

always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            a = 1;
            b = 2;
            c = 3;
        end
    else 
        begin
            a = 0;
            b = a;
            c = b;
        end
end

显然,执行完always后a,b,c都为0,这是因为阻塞时赋值先计算了a = 0,然后再处理了b = a,以此类推,b和c的值都和a相同了
在这里插入图片描述
那么有没有办法让b和c赋第一个begin中的值呢?

2.3.2 非阻塞赋值

非阻塞赋值可以看做两个步骤:

  • 赋值开始的时候计算RHS
  • 赋值结束的时候更新LHS
    通俗来讲,所谓非阻塞是指在计算一个非阻塞赋值的RHS以及更新LHS期间,允许其他非阻塞赋值语句同时计算RHS和更新LHS,也就是说,不同非阻塞赋值语句之间是同时进行的
always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            a <= 1;
            b <= 2;
            c <= 3;
        end
    else 
        begin
            a <= 0;
            b <= a;
            c <= b;
        end
end

执行以后,b=1,c=2,a=0,可见非阻塞赋值确实是并行的
8lTHK0.png
注意: 在同一个always块中不要既用非阻塞赋值又用阻塞赋值
不允许在多个always块中对同一个变量进行赋值!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值