• FPGA学习笔记(六)—— 时序逻辑电路设计


    用always@(posedge clk)描述
           时序逻辑电路的基础——计数器在每个时钟的上升沿递增1)

       例1.四位计数器(同步使能、异步复位)

    // Module Name: counter_4bit
    // Description: 4bit异步复位同步使能二进制计数器
    
    module counter_4bit(
        input    clk,            //系统时钟信号
        input    rst,            //系统复位按键
        input    en,             //计数器使能端
        output   reg [3:0]q      //计数器计数值输出
        );
      //同步使能,异步复位
        always@(posedge clk,posedge rst)
        if(rst)
            q <= 0;
        else if(en)
            q <= q + 1'b1;       //计数器加一
    endmodule

       testbench测试代码如下:

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Module Name: counter_4bit_tb 
    // Description: 4bit计数器模块测试文件
    //////////////////////////////////////////////////////////////////////////////////
    `define clk_period 20   //宏定义时钟周期  
    module counter_4bit_tb();
        
        reg     clk;    //用于产生时钟信号
        reg     rst;    //用于产生复位信号
        reg     en;     //用于产生使能信号
        wire    [3:0]q; //计数器计数值输出
    
        //例化测试模块
      counter_4bit counter_4bit_test(
           .clk(clk),          //系统时钟信号
           .rst(rst),            //系统复位按键
           .en(en),            //计数器使能端
           .q(q)               //计数器计数值输出
            );
         
         //开始测试
         //生成时钟信号
         initial clk = 1;
         always #(`clk_period/2)  clk = ~clk;         //clk每5ns翻转一次,产生100M时钟信号
         
         initial begin
                rst = 1;    en = 0;
            #(`clk_period * 5 + 1 );   
                rst = 0;
            #(`clk_period * 5);
                en = 1;
            #(`clk_period *20);     //因为4bit计数16个clk就清零,所以延时20个时钟周期
                $stop;
          end
    endmodule

      测试结果如下:

       综合的电路图如下:

      计数器是我们设计的第一个时序逻辑电路,也是最基本、最重要的时序逻辑电路,由图中可以看到一个计数器由加法器和D触发器组成;

      特别要注意的一点,在用verilog描述计数寄存器加一的时候,我们没有先写一个加法器,然后例化调用,而是直接采用 q <= q + 1'b1这样描述加法器,这点在综合出的电路图也可以看出,加法器不再是门电路的组合,而是用一个圆圈替代,这时,数字逻辑设计的思想就又抽象了一层,不再是门电路的组合,而是这些具有逻辑功能的小方块的组合,所以,抽象的思想在数字逻辑设计中至关重要;

       在编写testbench仿真测试的时候,我们也不再像测试组合逻辑电路那样利用穷举法测试功能,而是利用时钟测试,比如测试4bit的加法器,一要测试它能不能在每个时钟沿加一,而要测试它计满时可不可以自动清零;所以,在测试时序逻辑电路的时候要准确把握clk时钟信号

       注:计数器只是基本电路,比如分频器、定时器、巴克码序列发生器在计数器基本电路上设计即可;
     
    例2.基本分频操作——分频器(闪烁灯:LED以1hz的频率闪烁)
        分频器是计数器的一大应用,在FPGA中没有像C语言中delay()这样的延时,那如何延时呢?可以通过分频器,将系统100M的高速时钟信号经过分频器,变为1hz的输出信号,这样,LED就会按1s的周期闪烁;
      1hz的时钟信号是每500ms翻转1次,而在100M的时钟下,1个clk是10ns,所以应该每计数 500_000_000/10 = 50_000_000 次输出信号翻转,就产生了1hz的信号;50_000_000大概估算需要一个26位的寄存器;又因为计数器是从0开始计数的,所以最大计数值应该为49_999_999,设计如下:
    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Create Date: 2018/05/22 20:32:26
    // Module Name: flashled 
    // Description: 产生一个时钟信号,让LED灯按1hz的频率闪烁
    //////////////////////////////////////////////////////////////////////////////////
     //`define SIMULATION      /*** 仿真时保留,板级验证时注释 ***/
    module flashled(
        input clk,          //时钟信号输入
        input rst,          //复位信号输入
        input en,           //使能信号输入
        output [15:0]led    //led信号输出
        );
        
         
         `ifdef SIMULATION      //仿真情况下
            parameter   CNT_MAX = 26'd49;
         `else                  //板级验证情况下
            parameter   CNT_MAX = 26'd49_999_999;
         `endif   
           
        reg [25:0]cnt;      //最大计数值49_999_999
        reg [15:0]led_clk;  //输出驱动led的1hz信号
       
        //计数器功能描述
        always@(posedge clk,posedge rst)
            if(rst)
                cnt <= 0;
            else if(en)begin
                if(cnt == CNT_MAX)
                    cnt <= 0;
                else
                    cnt <= cnt + 1'b1;
            end
        
        //产生1hz信号
        always@(posedge clk,posedge rst)
            if(rst)
                led_clk <= 0;
            else if(cnt == CNT_MAX)
                led_clk <= ~led_clk;    //每计满50_000_000个时钟周期(500ms),输出信号翻转一下
            else
                led_clk <=  led_clk;
        
        //经1hz信号输出到led
        assign led = led_clk;
     
    endmodule 
      testbench测试文件如下:
    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Module Name: flashled_tb
    // Description: flashled模块测试文件
    //////////////////////////////////////////////////////////////////////////////////
    `define clk_period 10   //100M时钟信号
    module flashled_tb();
        reg clk;          //用于产生时钟信号
        reg rst;          //用于产生复位信号
        reg en;           //用于产生使能信号
        wire [15:0]led;   //观察led输出
        
        //例化测试模块
        flashled flashled_test(
            .clk(clk),          //时钟信号输入
            .rst(rst),          //复位信号输入
            .en(en),           //使能信号输入
            .led(led)           //led信号输出
            );
        
        //产生时钟信号
        initial clk = 1;
        always #(`clk_period/2) clk = ~clk;
        
        //开始测试
        initial begin
            rst = 1;                 //复位;
            en  = 0;
        #(`clk_period * 5);
             rst = 0;
        #(`clk_period * 5);   
              en = 1;                //使能
        #(`clk_period * 50 * 5 );   //仿真情况下计数器每计50次翻转,所以应该延时50 * 5个时钟周次观察翻转情况
              $stop;      
       end
         
    endmodule
      测试结果如下:

      综合电路图如下:

      在编写testbench测试分频器模块时,由于计数器计数值为49_999_999 ,所以花费长时间,因为只需要测试功能,看模块计到设定值后会不会翻转信号,所以这样就是在浪费时间;

      为了加快仿真速度,可以在仿真时将最大值改为49,这样就非常快,为了方便,可以在verilog中使用条件编译:

      `ifdef SIMULATION        //仿真情况下
            parameter   CNT_MAX = 26'd49;
         `else                  //板级验证情况下
            parameter   CNT_MAX = 26'd49_999_999;
         `endif 
    例3.基本移位操作——verilog位操作(流水灯:16个LED循环左移)
          //1.取某一位直接操作
          wire [2:0]m;
          assign m = out[5:3];
    
            //2.循环移位(移位寄存器)
            reg [7:0] shift_a;
            always@(posedge clk)
                shift_a <= {shifta[0],shift[7:1]};
            
            reg [7:0] shift_a;
            wire data;
            always@(posedge clk)
              shift_a <= {data,shift[7:1]};
    
            reg [7:0] shift_a;
            wire data;
            always@(posedge clk)
              shift_a <= {shift[7:1],data};
    
           //3、拼接
            wire [3:0]x;
            wire [3:0]y;
            wire [7:0]z;
            wire [31:0]n;
    
            assign z = {x,y};
            assign n = {x,7{x}};

       verilog设计代码如下(计数器部分和分频器相同):

        //移位寄存器功能描述
        always@(posedge clk,posedge rst)
            if(rst)
                led_temp <= 26'h0001;
            else if(cnt == CNT_MAX)
                  led_temp <= {led_temp[14:0],led_temp[15]};      //循环左移
            else
                  led_temp <= led_temp;
         //输出到led
         assign led = led_temp; 

       testbench仿真测试文件和分频器相同;

       测试结果如下:

      综合出的电路图如下:

      例4.BCD计数器

       计数器是二进制的,但我们所熟悉的是十进制,所以4bit BCD计数器相当于十进制的计数器:从0计到9,然后进位,清零,设计图如下:

          

        BCD计数器常常应用在数码管显示中,比如要要是1234,在单片机中通常会用C语言中的除法和取余提取每一位送到数码管显示,但是在数字逻辑电路中,除法器很耗费资源且效率不高,所以不采用除法,那如何进行显示呢?这个时候就可以将1234转换为对应的BCD码:0001_0010_0011_0100;这样只需要每次送入一个4bitBCD码就可以显示(如果以1ms的频率刷新,就是数码管动态扫描显示,也是下一个例子);

        verilog代码:

    //////////////////////////////////////////////////////////////////////////////////// Module Name: BCDcounter
    // Description: BCD计数器
    //////////////////////////////////////////////////////////////////////////////////
    module BCDcounter(
        input   clk,    //时钟信号输入
        input   rst,    //复位信号输入
        input   cin,    //进位信号输入
        output  cout,   //进位信号输出
        output  [3:0]q  //4bitBCD码输出 
        );
       reg  [3:0]cnt;   //计数器最大值为9,所以需要4bit计数器    
       
       //BCD计数器计数功能描述
       always@(posedge clk,posedge rst)
            if(rst)
                cnt <= 0;
            else if(cin)begin
                if(cnt == 4'd9)
                    cnt <= 0;
                else
                    cnt <= cnt + 1'b1;
            end
        
       assign cout = cin && (cnt == 4'd9);      /** 此处不能使用时序逻辑,需要在计满9的时候同步输出cout信号,然后在下次清零是cout恢复为0 **/
        
       assign q = cnt;      //BCD计数值输出
                     
    endmodule

      testbench测试文件如下:

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Module Name: BCDcounter_tb
    // Description: BCDcounter模块测试文件
    //////////////////////////////////////////////////////////////////////////////////
    `define clk_period 10   //100M时钟信号
    module BCDcounter_tb();
       reg   clk;    //时钟信号输入
       reg   rst;    //复位信号输入
       reg   cin;    //进位信号输入
       wire  cout;   //进位信号输出
       wire  [3:0]q;  //4bitBCD码输出 
        
       //例化测试模块
       BCDcounter BCDcounter_test(
           .clk(clk),     //时钟信号输入
          .rst(rst),      //复位信号输入
           .cin(cin),     //进位信号输入
           .cout(cout),   //进位信号输出
           .q(q)          //4bitBCD码输出 
           );
       //产生100M时钟信号
        initial clk = 1;
        always #(`clk_period/2) clk = ~clk;
        
        //开始测试
        initial begin
            rst = 1;    //复位
            cin = 0;    //无进位
        #(`clk_period * 2);
            rst = 0;    
        #(`clk_period * 2);
            cin = 1;    //产生进位,计数器开始计数
        #(`clk_period * 15);      //1个clk计一次数,总共计10次,所以延时15个clk观察波形
            $stop;
       end
     
    endmodule

      测试结果如下:

      综合电路图如下:

      至此一个4bitBCDcounter就设计完成了,一般使用时,都是通过级联实现多位BCD计数器,比如四个BCD计数器级联起来最大值为9999,可以很方便的应用在数码管动态显示上;

      verilog代码如下:

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Module Name: BCDcounter_top
    // Description: 4个4bit计数器进行级联,实现计数最大值为9999
    //////////////////////////////////////////////////////////////////////////////////
    
    module BCDcounter_top(
            input clk,          //时钟信号输入
            input rst,          //复位信号输入
            input cin,          //进位信号输入
            output cout,        //进位信号输出
            output [15:0]q      //BCD码输出
        );
        
        wire cout0,cout1,cout2;     //4个BCD计数器级联之间连接线
        wire [3:0]q0,q1,q2,q3;      //每个BCD计数器的输出
        
        assign q = {q3,q2,q1,q0};   //拼接成16位输出
        
        //例化4个BCD计数器
        BCDcounter BCDcounter0(
            .clk(clk),        //时钟信号输入
            .rst(rst),        //复位信号输入
            .cin(cin),        //进位信号输入
            .cout(cout0),     //进位信号输出
            .q(q0)             //4bitBCD码输出 
            );
        BCDcounter BCDcounter1(
            .clk(clk),    //时钟信号输入
            .rst(rst),    //复位信号输入
            .cin(cout0),  //进位信号输入
            .cout(cout1), //进位信号输出
            .q(q1)         //4bitBCD码输出 
                );
        BCDcounter BCDcounter2(
            .clk(clk), //时钟信号输入
            .rst(rst),        //复位信号输入
                  .cin(cout1),        //进位信号输入
                  .cout(cout2),      //进位信号输出
                  .q(q2)             //4bitBCD码输出 
                    );
        BCDcounter BCDcounter3(
                  .clk(clk),        //时钟信号输入
                  .rst(rst),        //复位信号输入
                  .cin(cout2),        //进位信号输入
                  .cout(cout),      //进位信号输出
                  .q(q3)             //4bitBCD码输出 
                   );
    endmodule

      testbench测试文件与之前一样,但是需要增大延时时长,因为要计数到9999;

      测试结果如下:

      

      综合电路图如下:

      

      例5.数码管动态扫描显示

       

       设计图如上图,在数码管静态显示的基础上只需要添加一个分频器,将系统时钟分频为1ms的时钟信号,然后每1ms同时改变段选和位选,刷新显示,这就是数码管动态显示的原理;位选信号已经由2-4译码器控制,只有输入2位选择信号,所以还需一个四选一数据选择器,和2-4译码器共享一个选择信号sel,这样只需要改变sel的值就可以了;

      verilog代码如下(这是顶层文件,还需要之前):

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Module Name: Dynamic_seg_display 
    // Description: 4位数码管动态扫描显示
    //////////////////////////////////////////////////////////////////////////////////
    `define SIMULATION      /*** 仿真时保留,板级验证时注释 ***/
    module Dynamic_seg_display(
            input  clk,                  //时钟信号
            input  rst,                  //复位信号
            input  [15:0]data_display,   //16位待显示数据显示(4个BCD码)
            output [6:0]segments,        //数码管段码
            output [3:0]wei_sel          //数码管位码
        );
          `ifdef SIMULATION      //仿真情况下            
           parameter   CNT_MAX = 26'd49;
        `else                  //板级验证情况下
           parameter   CNT_MAX = 26'd99_999;
        `endif   
          
        reg [22:0]cnt;                //最大计数值4_999_999,需要23位计数器
        reg [1:0]wei;                 //选择哪一位显示
        wire [3:0]data_display_temp;  //显示信号传输
      
       //计数器功能描述
       always@(posedge clk,posedge rst)
           if(rst)
               cnt <= 0;
           else if(cnt == CNT_MAX)
               cnt <= 0;
           else
               cnt <= cnt + 1'b1;
      
       //控制数码管动态扫描显示(每隔1ms加一,刷新段选和位选)
       always@(posedge clk,posedge rst)
           if(rst)
               wei <= 2'b00;
           else if(cnt == CNT_MAX)
               wei <= wei + 1'b1;    //每计满100_000个时钟周期(1ms),位选加一,切换下一位
           else
               wei <=  wei;
        //例化四选一多路器,选择输入哪一位显示段码信号
                 mux4 mux4_1(
                     .sel(wei),
                     .data_in(data_display),
                     .data_out(data_display_temp)        
                     );
       //例化数码管译码模块
       seg_display seg_display0(
                  .data_display(data_display_temp),      //数码管待显示数据
                  .wei(wei),               //选择哪一位显示
                   .segments(segments),           //数码管段码
                   .wei_sel(wei_sel)             //数码管位码
                   );
                
       
    endmodule
        //四选一多路器
    module mux4(
        input        [1:0]sel,
        input        [15:0]data_in,
        output reg   [3:0]data_out        
        );
        always@(*)
            case(sel)
                //对应位
                4'h0:       data_out  = data_in[3:0];
                4'h1:       data_out  = data_in[7:4];
                4'h2:       data_out  = data_in[11:8];
                4'h3:       data_out  = data_in[15:12];
                default:    data_out  = 4'bz;
            endcase
    endmodule

      testbench测试代码如下:

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////// Module Name: Dynamic_seg_display_tb
    // Description: 数码管动态扫描显示控制模块测试文件
    //////////////////////////////////////////////////////////////////////////////////
    `define clk_period 10   //100M时钟信号
    
    module Dynamic_seg_display_tb();
           reg  clk;                  //时钟信号
           reg  rst;                  //复位信号
           reg  [15:0]data_display;   //16位待显示数据显示(4个BCD码)
           wire [6:0]segments;        //数码管段码
           wire [3:0]wei_sel;         //数码管位码
           
           //例化测试模块
           Dynamic_seg_display Dynamic_seg_display_test(
                   .clk(clk),                   //时钟信号
                   .rst(rst),                   //复位信号
                  .data_display(data_display),  //16位待显示数据显示(4个BCD码)
                   .segments(segments),         //数码管段码
                  .wei_sel(wei_sel)             //数码管位码
               );
          //产生100M时钟信号
          initial   clk = 1;
          always #(`clk_period/2)   clk = ~clk;
          
          //开始测试
          initial begin
                rst = 1;
                data_display = 16'h4321;    //数码管上显示"1234"
          #(`clk_period * 2);
                rst = 0;
          #(`clk_period * 50 * 5);         //计50次数刷新一下,观察5次
                $stop;
          end
    endmodule

       测试结果如下:

      综合后的电路图如图:

     注:

      将上一例中四个级联BCD计数器的16bit输出和该例中数码管动态扫描控制模块的16bit输入接起来,控制BCD计数的频率为1hz,就形成一个简易计时器,从0000一直计到9999;

      verilog代码如下(这是顶层文件,底层模块调用之前的数码管动态扫描模块和4个级联BCD计数器模块):

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Module Name: easyclock
    // Description: 简易数字钟:按1hz的频率从0000-9999显示
    //////////////////////////////////////////////////////////////////////////////////
    //`define SIMULATION      /*** 仿真时保留,板级验证时注释 ***/
    module easyclock(
        input clk,                   //时钟信号输入
        input rst,                   //复位信号输入
        output [6:0]segments,        //数码管段码
        output [3:0]wei_sel          //数码管位码
    
        );
       `ifdef SIMULATION      //仿真情况下
            parameter   CNT_MAX = 26'd49;
        `else                  //板级验证情况下
            parameter   CNT_MAX = 26'd49_999_999;
        `endif   
           
            reg  clk_1hz;               //1hz时钟信号
            reg  [25:0]cnt;                   //计数器计数寄存起(用于分频器)
            wire [15:0]data_display;    //待显示数据
        
        //计数器
        always@(posedge clk,posedge rst)
            if(rst)
                 cnt <= 0;
            else  if(cnt == CNT_MAX)
                 cnt <= 0;
            else
                 cnt <= cnt + 1'b1;
        //产生1hz信号
         always@(posedge clk,posedge rst)
           if(rst)
               clk_1hz <= 0;
           else if(cnt == CNT_MAX)
               clk_1hz <= ~clk_1hz;    //每计满50_000_000个时钟周期(500ms),输出信号翻转一下
           else
                clk_1hz <=  clk_1hz;
        
        //例化4个BCD计数器
        BCDcounter_top BCDcounter_top0(
               .clk(clk_1hz),             /** 因为BCD需要按1hz计数,所以需要一个1hz时钟信号 **/
               .rst(rst),           //复位信号输入
               .cin(1'b1),          //进位信号输入
               .cout(),             //进位信号输出
               .q(data_display)     //BCD码输出
            );
        //例化数码管动态扫描显示控制模块
        Dynamic_seg_display Dynamic_seg_display0(
                .clk(clk),                      //时钟信号
                .rst(rst),                     //复位信号
                .data_display(data_display),   //16位待显示数据显示(4个BCD码)
                .segments(segments),           //数码管段码
                .wei_sel(wei_sel)              //数码管位码
            );
         
    endmodule

      综合电路图如下:

      例6.74HC595驱动

       在上一例中讲述了数码管动态扫描的设计,但是仅仅是4位数码管显示,就占用了8+4个IO,如果是8段8位数码管,就会占用16个IO,造成IO浪费,所以可以采用74HC595串转并芯片两片级联,只采用3个IO完成8段8位数码管的显示;

        verilog描述如下:

       

    //////////////////////////////////////////////////////////////////////////
    //module: 74HC595驱动模块
    //description:    将输入的16位数据串行输出到74HC595
    //////////////////////////////////////////////////////////////////////////
    module HC595drive(
        input  clk,                //50M系统时钟
        input  rst,                //低电平复位
        input  [7:0]seg,        //8bit段选信号
        input  [7:0]sel,        //8bit位选信号
        output reg DIO,        //74HC595数据引脚
        output reg SHCP,        //74HC595移位脉冲引脚
        output reg STCP         //74HC595输出脉冲引脚
        );
        
        reg clk_595;
        reg [1:0]divid_cnt;
        reg [4:0]count;
        
        //产生74HC595工作时钟12.5M
        always@(posedge clk,negedge rst)
            if(!rst)begin
                divid_cnt <= 0;
                clk_595 <= 0;
            end
            else if(divid_cnt == 2'd3)begin
                clk_595 <= 1;
                divid_cnt <= 0;
            end
            else begin
                divid_cnt <= divid_cnt + 1'b1;
                clk_595 <= 0;
            end
        
        //对74HC595工作时钟clk_595计数
        always@(posedge clk,negedge rst)
            if(!rst)
                count <= 0;
            else if(clk_595 == 1)begin            //一共串行发送16bit数据
                if(count == 5'd31)
                    count <= 0;
                else 
                    count <= count + 1'b1;
                end
            else
                count <= count;        //count清零
        
        //功能描述
        always@(posedge clk,negedge rst)
            if(!rst)begin
                DIO  <= 0;
                SHCP <= 0;
                STCP <= 0;
            end
            else 
                case(count)
                    5'd0:        begin     DIO <= seg[7];    SHCP <= 0;    STCP <= 1;     end    //HEX_DP,输出
                    5'd1:        begin                       SHCP <= 1;    STCP <= 0;     end    //移位
                    5'd2:        begin     DIO <= seg[6];    SHCP <= 0;    STCP <= 0;     end    //HEX_G
                    5'd3:        begin                       SHCP <= 1;                    end        
                    5'd4:        begin     DIO <= seg[5];    SHCP <= 0;                    end    //HEX_F
                    5'd5:        begin                       SHCP <= 1;                    end    
                    5'd6:        begin     DIO <= seg[4];    SHCP <= 0;                    end    //HEX_E
                    5'd7:        begin                       SHCP <= 1;                    end    
                    5'd8:        begin     DIO <= seg[3];    SHCP <= 0;                    end    //HEX_D
                    5'd9:        begin                       SHCP <= 1;                    end    
                    5'd10:      begin     DIO <= seg[2];    SHCP <= 0;                    end    //HEX_C
                    5'd11:      begin                       SHCP <= 1;                    end    
                    5'd12:      begin     DIO <= seg[1];    SHCP <= 0;                    end    //HEX_B
                    5'd13:      begin                       SHCP <= 1;                    end    
                    5'd14:      begin     DIO <= seg[0];    SHCP <= 0;                    end    //HEX_A
                    5'd15:      begin                       SHCP <= 1;                    end                    
                    5'd16:      begin     DIO <= sel[7];    SHCP <= 0;                    end    //第7位
                    5'd17:      begin                       SHCP <= 1;                    end    
                    5'd18:      begin     DIO <= sel[6];    SHCP <= 0;                    end    //第6位
                    5'd19:      begin                       SHCP <= 1;                    end    
                    5'd20:      begin     DIO <= sel[5];    SHCP <= 0;                    end    //第5位
                    5'd21:      begin                       SHCP <= 1;                    end                    
                    5'd22:      begin     DIO <= sel[4];    SHCP <= 0;                    end    //第4位
                    5'd23:      begin                       SHCP <= 1;                    end    
                    5'd24:      begin     DIO <= sel[3];    SHCP <= 0;                    end    //第3位
                    5'd25:      begin                       SHCP <= 1;                    end    
                    5'd26:      begin     DIO <= sel[2];    SHCP <= 0;                    end    //第2位
                    5'd27:      begin                       SHCP <= 1;                    end    
                    5'd28:      begin     DIO <= sel[1];    SHCP <= 0;                    end    //第1位
                    5'd29:      begin                       SHCP <= 1;                    end    
                    5'd30:      begin     DIO <= sel[0];    SHCP <= 0;                    end    //第0位
                    5'd31:      begin                       SHCP <= 1;                    end        
                endcase 
    endmodule 

        testbench如下:

     

    `timescale 1ns/1ps
    //////////////////////////////////////////////////////////////////////////
    //module: 74HC595驱动测试模块
    //////////////////////////////////////////////////////////////////////////
    `define clk_period 20        //50M系统时钟
    
    module HC595drive_tb();
        
        reg  clk;                //50M系统时钟
        reg  rst;                //低电平复位
        reg  [7:0]seg;            //8bit段选信号
        reg  [7:0]sel;            //8bit位选信号
        wire DIO;                //74HC595数据引脚
        wire SHCP;                //74HC595移位脉冲引脚
        wire STCP;                 //74HC595输出脉冲引脚
        
        //例化测试模块
        HC595drive HC595drive_test(
            .clk(clk),                
            .rst(rst),                
            .seg(seg),        
            .sel(sel),        
            .DIO(DIO),        
            .SHCP(SHCP),        
            .STCP(STCP)         
        );
        
        //产生50M时钟信号
        initial clk = 1;
        always #(`clk_period / 2)    clk = ~clk;
        
        //开始测试
        initial begin
            rst = 0;            //复位
            seg = 8'hc0;      //显示数字"1" 
            sel = 8'ha5;      //10100101
        #(`clk_period * 5);    
            rst = 1;
        #(`clk_period * 32 * 4);        //传送16位需要32个clk,clk又被进行4分频
        #(`clk_period * 10);
            $stop;
        end
    endmodule

      测试结果如下:

     例7.定时器

        玩过单片机的小伙伴都很熟悉,单片机的基本外设就是定时器,定时器的核心也是计数器,设定工作方式(单次计数和循环计数)和定时值后,就开始计数,计满之后产生计数慢标志信号,提示设定的定时时间到达;

        所以,如果要用计数器来设计一个定时器,需要实现的功能有:

        1、定时时间参数通过一个端口输入,调节该值可以修改定时时间;

        2、设置一个计数模式控制信号,当该信号为1时为循环定时模式,当该信号为0时为单次定时模式;

        3、设置一个计数启动信号,在循环定时模式下,该信号为高电平使能计时,为低电平则停止计时;当单次计数模式下,该信号的一个单基准时钟周期的脉冲使能一次定时;

        4、输出计数器定时计数值,该值用于产生特定占空比的方波;

        设计图如下:
        

      verilog代码如下:

    module    timer(
            input  clk,                       //50M时钟信号
            input  rst,                       //低电平复位
            input  [31:0]timer_value,         //定时值
            input  mode,                      //定时器模式选择
            input  en,                        //定时器使能控制端
            output [31:0]cnt_value,           //定时器计数值实时输出
            output full_flag                  //定时器计满输出标志位
    );
        
            reg [31:0]cnt;            //32bit计数器
            reg oneshot;              //在单次定时模式中,使能端一个脉冲就启动,但是计数器需要en保持为1才会计数,所以需要另外一个单次使能信号,当外部一个脉冲时,它会变为1,保持至定时结束;
            
            //功能描述
            assign full_flag = (cnt == timer_value)?1'b1:1'b0;        //输出计满标志位
            assign cnt_value = cnt;                                   //实时输出计数值
            
            always@(posedge clk,negedge rst)
                if(!rst)
                    cnt <= 0;
                else if(mode)begin             //mode = 1,循环定时模式
                    if(en && cnt < timer_value)    
                        cnt <= cnt +1'b1;      //使能状态下且计数值未满,加一
                    else
                        cnt <= 0;              //其余情况下cnt清零
                end
                else begin                     //mode = 0,单次定时模式
                    if(oneshot)
                        cnt <= cnt + 1'b1;
                    else
                        cnt <= 0;
                end
    
            //利用外部一个脉冲产生oneshot信号供定时器单次定时模式下使用
            //功能:当外部en产生一个高脉冲时,它会变为1,保持至定时结束;
            always@(posedge clk,negedge rst)
                if(!rst)
                    oneshot <= 0;
                else if(en)
                    oneshot <= 1'b1;
                else if(cnt >= timer_value)    //计满清零
                    oneshot <= 0;
    endmodule 

      testbench测试文件如下:

    `timescale 1ns/1ps
    `define clk_period 20
    
    module timer_tb();
    
            reg  clk;                       //50M时钟信号
            reg  rst;                       //低电平复位
            reg  [31:0]timer_value;         //定时值
            reg  mode;                      //定时器模式选择
            reg  en;                        //定时器使能控制端
            wire [31:0]cnt_value;           //定时器计数值实时输出
            wire full_flag;                 //定时器计满输出标志位
            
            //例化测试模块
            timer timer_test(
             .clk(clk),                       //50M时钟信号
             .rst(rst),                       //低电平复位
             .timer_value(timer_value),       //定时值
             .mode(mode),                     //定时器模式选择
             .en(en),                         //定时器使能控制端
             .cnt_value(cnt_value),           //定时器计数值实时输出
             .full_flag(full_flag)            //定时器计满输出标志位
    );
    
            //产生50M时钟信号
            initial clk = 1;
            always #(`clk_period / 2) clk = ~clk;
            
            //开始测试
            initial begin
                rst  = 0;                //系统复位
                mode = 0;                //首先测试模式0,单次计数模式
                en   = 0;                //未使能
                timer_value = 32'd10;    //定时器计数值为10
            #(`clk_period * 2);
                rst = 1;
            #(`clk_period * 2);
                en  = 1;                 //产生一个高脉冲,使能定时器
            #(`clk_period * 2);
                en  = 0;
            #(`clk_period * 15);         //系统计数10个clk会输出计满flag,在延时观察5个clk观察定时器是否停止
                mode = 1;                //开始测试定时器模式1,循环计数模式
            #(`clk_period);
                en = 1;                  //使能定时器
            #(`clk_period * 10 * 5);     //系统计数10个clk会输出计满flag,一共观察5次flag
                en = 0;                  //停止使能
            #(`clk_period * 10);         //等待10个clk,观察定时器是否已停止工作
                $stop;                   //停止测试
            end
    endmodule 

       测试结果如下:

      单次计数模式测试:

      循环计数模式测试:

      综合电路图如下:

      定时器至此就设计结束,定时器有很多应用,比如产生PWM驱动无源蜂鸣器,驱动直流电机调速,驱动舵机,驱动步进电机,驱动RGBLED变色,总之利用定时器可以很方便产生可变PWM,利用好PWM也是很深的学问,如有兴趣自行深入;

      

     

      

     

     

     

     

     

        

        

     

  • 相关阅读:
    《JavaScript高级程序设计》读书笔记(十):本地对象Date
    JavaScript计算字符串中每个字符出现的次数
    JavaScript单元测试ABC
    ASP.NET MVC3 AJAX 上传图片示例
    canvas标签的width和height以及style.width和style.height的区别
    分享一个自定义的 console 类,让你不再纠结JS中的调试代码的兼容
    《JavaScript高级程序设计》阅读笔记(十四):继承机制的实现
    从此不再惧怕URI编码:JavaScript及C# URI编码详解
    Levenshtein算法的JavaScript实现
    《JavaScript高级程序设计》阅读笔记(十五):浏览器中的JavaScript
  • 原文地址:https://www.cnblogs.com/Mculover666/p/9077228.html
Copyright © 2020-2023  润新知