FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值

本学习笔记主要参考小梅哥B站教学视频,网址如下:
https://www.bilibili.com/video/BV1va411c7Dz?p=1

使用的编译器为Vivado,HDL语言为verilog

一、从计数器到可控线性序列机

1.1 让LED按照亮0.25s,灭0.75s的状态循环亮灭。

思路:设置计数器计数到1s才清零。不用取反操作,而是在相应位置处拉高或者拉低电平。

设计文件:

module counter_led_0(
    Clk,
    Reset_n,
    Led

    );
    
    input Clk;
    input Reset_n;
    output reg Led;
    
    reg [25:0] counter;
    parameter MCNT = 50000000;
    
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter <= 0;
        else if(counter == MCNT-1)
            counter <=0;
        else
            counter <= counter + 1'b1;
            
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            Led <= 0;
        else if(counter == (MCNT/2+MCNT/4)-1) //0.75s处
            Led <= 1;
        else if(counter == MCNT-1)
            Led <= 0;
        
endmodule

测试文件:

`timescale 1ns/1ns

module counter_led_0_tb;

    reg Clk;
    reg Reset_n;
    wire Led;

    counter_led_1
    #(
    .MCNT(125000) //将参数调小一些
    )
    counter_led_1(
    .Clk(Clk),
    .Reset_n(Reset_n), 
    .Led(Led)

    );
    
    
    initial Clk = 1;
    always #10 Clk = !Clk;  //~按位取反,单位信号时与!等价
    
    initial begin
        Reset_n = 0;
        # 201;  //避开时钟上升沿,方便观察
        Reset_n = 1;
        #2000000000; //两秒钟
        $stop;
    end



endmodule

仿真结果:
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第1张图片
1.2 让LED灯按照指定的亮灭模式亮灭,亮灭模式未知,由用户随机指定。以0.25s为一个变化周期,8个变化状态为一个循环

思路:2秒为一个循环周期,拿出一个ctrl(输入端口)来控制LED的亮灭模式,

设计文件:

module counter_led_2(
    Clk,
    Reset_n,
    Ctrl,
    Led

    );
    
    input Clk;
    input Reset_n;
    input [7:0]Ctrl;
    output reg Led;
    
    reg [26:0] counter;
    parameter MCNT = 100000000;
    
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter <= 0;
        else if(counter == MCNT-1)
            counter <=0;
        else
            counter <= counter + 1'b1;
            
//    always@(posedge Clk or negedge Reset_n)
//        if(!Reset_n)
//            Led <= 0;
//        else if(counter == MCNT/8-1)
//            Led <= Ctrl[0];
//        else if(counter == MCNT*2/8-1)
//            Led <= Ctrl[1];
//        else if(counter == MCNT*3/8-1)
//            Led <= Ctrl[2];          
//        else if(counter == MCNT*4/8-1)
//            Led <= Ctrl[3]; 
//         else if(counter == MCNT*5/8-1)
//            Led <= Ctrl[4];
//        else if(counter == MCNT*6/8-1)
//            Led <= Ctrl[5];
//        else if(counter == MCNT*7/8-1)
//            Led <= Ctrl[6];          
//        else if(counter == MCNT-1)
//            Led <= Ctrl[7]; 
 
     always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            Led <= 0;
        else case(counter)                                            
            MCNT*1/8-1:Led <= Ctrl[0];
            MCNT*2/8-1:Led <= Ctrl[1];
            MCNT*3/8-1:Led <= Ctrl[2];
            MCNT*4/8-1:Led <= Ctrl[3];
            MCNT*5/8-1:Led <= Ctrl[4];
            MCNT*6/8-1:Led <= Ctrl[5];   
            MCNT*7/8-1:Led <= Ctrl[6];      
            MCNT*8/8-1:Led <= Ctrl[7]; 
            default Led <= Led;       
         endcase   
                 
endmodule

测试文件:

`timescale 1ns/1ns

module counter_led_2_tb;

    reg Clk;
    reg Reset_n;
    reg [7:0]Ctrl;
    wire Led;

    counter_led_2
    #(
    .MCNT(100000)
    )
    counter_led_2(
    .Clk(Clk),
    .Reset_n(Reset_n), 
    .Ctrl(Ctrl),
    .Led(Led)

    );
    
    
    initial Clk = 1;
    always #10 Clk = !Clk;  //~按位取反,单位信号时与!等价
    
    initial begin
        Reset_n = 0;
        Ctrl = 0;
        # 201;  
        Reset_n = 1;
        #2000;
        Ctrl = 8'b1000_0110;
        #2000000000; 
        $stop;
    end



endmodule

仿真结果:
从低位开始控制
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第2张图片
1.3 让LED灯按照指定的亮灭模式亮灭,亮灭模式未知,由用户随机指定。8个变化状态为一个循环,每个变化状态的时间值可以根据不同的应用场景选择

思路:此时的MCNT不再是一个定值,而是可以指定的Time,所以之前的写法已经不再使用了。前面是化整为零(先计数整体的时间,然后切分为八段),后面可以是凑零为整(先计数每一小段的时间,然后一起凑成一个整周期)。
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第3张图片
第一个计数器在第一小段循环计数基本时间,计满一次让第二个计数器加1,计满八次后,清零重新开始。

设计文件:

module counter_led_3(
    Clk,
    Reset_n,
    Ctrl,
    Time,
    Led

    );
    
    input Clk;
    input Reset_n;
    input [7:0]Ctrl;
    input [31:0]Time;
    output reg Led;
    
    reg [31:0] counter;
    
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter <= 0;
        else if(counter == Time-1)
            counter <=0;
        else
            counter <= counter + 1'b1;
            
    reg [2:0] counter2;       
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter2 <= 0;
        else if(counter == Time - 1)
            counter2 <= counter2 + 1'b1;
            
            
 
     always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            Led <= 0;
        else case(counter2)                                            
            0:Led <= Ctrl[0];
            1:Led <= Ctrl[1];
            2:Led <= Ctrl[2];
            3:Led <= Ctrl[3];
            4:Led <= Ctrl[4];
            5:Led <= Ctrl[5];   
            6:Led <= Ctrl[6];      
            7:Led <= Ctrl[7]; 
            default Led <= Led;       
         endcase   
                 
endmodule

测试文件:

`timescale 1ns/1ns

module counter_led_3_tb;

    reg Clk;
    reg Reset_n;
    reg [7:0]Ctrl;
    reg [31:0]Time;
    wire Led;

    counter_led_3 counter_led_3(
    .Clk(Clk),
    .Reset_n(Reset_n), 
    .Ctrl(Ctrl),
    .Time(Time),
    .Led(Led)

    );
    
    
    initial Clk = 1;
    always #10 Clk = !Clk;  //~按位取反,单位信号时与!等价
    
    initial begin
        Reset_n = 0;
        Ctrl = 0;
        Time = 0;
        # 201;  
        Reset_n = 1;
        #2000;
        Time = 2500;
        Ctrl = 8'b1000_0110;
        #2000000000; 
        $stop;
    end



endmodule

仿真结果:
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第4张图片

1.4 每隔10ms,让LED灯的一个8状态循环执行一次(每个状态的变化时间值小一点,方便测试,比如设置为10us)

思路:之前的代码counter2只要跳出复位状态,就一直地在计数。应该有一个控制作用。counter也是一直在计数。
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第5张图片

控制counter,让它每10ms计数一次。引入一个计数开关信号,En,高电平期间,counter计数,低电平时,counter不计数为0.

设计文件:

module counter_led_4(
    Clk,
    Reset_n,
    Ctrl,
    Time,
    Led

    );
    
    input Clk;
    input Reset_n;
    input [7:0]Ctrl;
    input [31:0]Time;
    output reg Led;
    
    
    reg [31:0] counter; //计数一个状态的基本时间
    reg EN;
    reg [2:0] counter2;       //计数状态数   
                
    reg [18:0] counter0;       //计数外部周期,10ms周期定时器
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter0 <= 0;
        else if(counter0 == 500000 - 1)
            counter0 <= 0;
        else
            counter0 <= counter0 + 1'b1;  
             
 
        
        always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            EN <= 0;
         else if(counter0 == 0) //把EN拉高
            EN <= 1;
         else if(counter2 == 7) //&&(counter == Time - 1)
            EN <= 0;         
     
    
    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter <= 0;
        else if(EN)begin
            if(counter == Time-1)
                counter <=0;
            else
                counter <= counter + 1'b1;
         end
         else
            counter <= 0;
            

    always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            counter2 <= 0;
        else if(EN)begin
        if(counter == Time - 1)
            counter2 <= counter2 + 1'b1;
        end
        else 
            counter2 <= 0;
            
            
 
     always@(posedge Clk or negedge Reset_n)
        if(!Reset_n)
            Led <= 0;
        else 
        case(counter2)                                            
            0:Led <= Ctrl[0];
            1:Led <= Ctrl[1];
            2:Led <= Ctrl[2];
            3:Led <= Ctrl[3];
            4:Led <= Ctrl[4];
            5:Led <= Ctrl[5];   
            6:Led <= Ctrl[6];      
            7:Led <= Ctrl[7]; 
            default Led <= Led;       
         endcase
                 
endmodule

测试文件:

`timescale 1ns/1ns

module counter_led_4_tb;

    reg Clk;
    reg Reset_n;
    reg [7:0]Ctrl;
    reg [31:0]Time;
    wire Led;

    counter_led_4 counter_led_4(
    .Clk(Clk),
    .Reset_n(Reset_n), 
    .Ctrl(Ctrl),
    .Time(Time),
    .Led(Led)

    );
    
    
    initial Clk = 1;
    always #10 Clk = !Clk;  //~按位取反,单位信号时与!等价
    
    initial begin
        Reset_n = 0;
        Ctrl = 0;
        Time = 0;
        # 201;  
        Reset_n = 1;
        #2000;
        Time = 2500;  
        Ctrl = 8'b1000_0110;
        #2000000000; 
        Time = 25000;  
        Ctrl = 8'b1010_0110;
        #200000000; 
        $stop;
    end



endmodule

注意:设计文件中该非阻塞赋值的就要非阻塞赋值,不然会出现不必要的错误。

观察仿真结果:
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第6张图片
发现counter2为7这一段时间很短,仅保持了两个时钟周期,导致Led的最后一位也只保留了两个时钟周期。

观察代码:
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第7张图片
counter2=7时,直接把EN关掉了,变为0。EN关掉后,counter2马上清除为0.

FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第8张图片
修改代码为
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第9张图片
等于7还不够,还要等到基本计时周期结束后,才将EN置为0.

仿真结果:
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第10张图片
显示正确

二、阻塞与非阻塞赋值

设计文件1

module block_nonblock(
    Clk,
    Reset_n,
    a,
    b,
    c,
    out
    );
    
    input Clk;
    input Reset_n;
    input a,b,c;
    output reg[1:0]out;
    
    reg [1:0] d;
//    always@(posedge Clk or negedge Reset_n)
//    if(!Reset_n)begin
//        out <= 2'b0;
//        d <= 0;
//    end
//    else begin
//        d <= a+b;
//        out <= d+c;
//        out <= a+b+c;
//    end
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        d <= 0;
    end
    else begin
        d <= a+b;
    end
    
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        out <= 2'b0;
    end
    else begin
        out <= d+c;
    end
       
    
endmodule

两段always对应的电路逻辑
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第11张图片
图1

如果是直接out <= a+b+c;

FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第12张图片
图2

设计文件2

module block_nonblock1(
    Clk,
    Reset_n,
    a,
    b,
    c,
    out
    );
    
    input Clk;
    input Reset_n;
    input a,b,c;
    output reg[1:0]out;
    
    reg [1:0] d;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        out = 2'b0;
        d = 0;
    end
    else begin
        out = d+c;
        d = a+b;

    end
    
       
    
endmodule

文件2生成的结构图
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第13张图片
图3

设计文件2中,改为非阻塞赋值,得到的结构图与2毫无区别。

修改:

    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        out <= 2'b0;
        d <= 0;
    end
    else begin
        out <= d+c;
        d <= a+b;

    end

调换顺序为

        d = a+b;
        out = d+c;

结构图
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第14张图片
图4

图4与图2是对应的。

测试文件

`timescale 1ns / 1ns

`define Clk_period 20

module block_nonblock_tb();

    reg Clk;
    reg Reset_n;
    reg a,b,c;
    
    wire [1:0]out;
    
    block_nonblock2 block_nonblock2(
    Clk,
    Reset_n,
    a,
    b,
    c,
    out
    );
    
    initial Clk = 1;
    always#(`Clk_period/2) Clk = ~Clk;
    
    initial begin
        Reset_n = 1'b0;
        a = 0;
        b = 0;
        c = 0;
        #(`Clk_period*200+1);
        Reset_n = 1'b1;
        #(`Clk_period*200);
        a = 0; b = 0; c = 0;
        #(`Clk_period*200);
        a = 0; b = 0; c = 1;              
        #(`Clk_period*200);
        a = 0; b = 1; c = 0;    
        #(`Clk_period*200);
        a = 0; b = 1; c = 1;  
        #(`Clk_period*200);
        a = 1; b = 0; c = 0;  
        #(`Clk_period*200);
        a = 1; b = 0; c = 1;  
        #(`Clk_period*200);
        a = 1; b = 1; c = 0; 
        #(`Clk_period*200);
        a = 1; b = 1; c = 1;   
        #(`Clk_period*200);
        #(`Clk_period*200);
        $stop;
    end  
    
endmodule

仿真结果
(阻塞赋值)
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第15张图片
c变为1时,结果值在下一个时钟上升沿更新。

(非阻塞赋值)
FPGA学习笔记(二)——从计数器到可控线性序列机、阻塞赋值与非阻塞赋值_第16张图片

这里出现了一个奇怪的现象,出现了一个短暂的0.

通过加入测试文件的信号
在这里插入图片描述
out在下一个时钟沿来时,d变为1时,才为1

可以修改为

        d <=#2 a+b;
        out <=#2 d+c;

避开时钟上下沿,模拟寄存器的传输延迟。

你可能感兴趣的:(FPGA学习笔记,fpga开发,计数器,阻塞赋值与非阻塞赋值,Verilog,Vivado)