• [文档].艾米电子 二进制计数器及其变体,Verilog


    对读者的假设

    已经掌握:

    内容

    1 free-running二进制计数器

    自由运行二进制计数器就是按照二进制形式不断循环计数。例如,4位的二进制计数器的从0000数到1111,然后翻回来重新数。

    代码1 free-runing二进制计数器

    module free_run_bin_counter
    #(parameter N=8)
    (
      // global clock and asyn reset
      input clk,
      input rst_n,
      // counter interface
      output max_tick,
      output [N-1:0] q
    );
    
    // signal declaration
    reg [N-1:0] r_reg;
    wire [N-1:0] r_next;
    
    // body
    // register
    always@(posedge clk, negedge rst_n)
      if(!rst_n)
        r_reg <= 0; // {N{1'b0}}
      else
        r_reg <= r_next;
        
    // next-state logic
    assign r_next = r_reg + 1'b1;
    //output logic
    assign q = r_reg;
    assign max_tick = (r_reg == 2**N-1) ? 1'b1 : 1'b0;
                    // r_reg == {N{1'b1}}
                    
    endmodule

    次态逻辑是一个自增器,即给寄存器的当前值加1。由于使用了“+”运算符,因此也暗示了当r_reg到达1111的时候之后,会翻回来变成0000。这个电路也包括一个输出状态信号,max_tick。每当计数器到达最大值——1111(等同于2^N-1),就会插入一个max_tick,即max_tick变为高电平。

    所谓tick即一个时刻,比方说我们把1分钟可以分为60个tick,那么每一秒都会产生一个tick。此处的max_tick正是这种意义的信号,相应的,具有同类属性的信号我们都会加上_tick这个后缀。tick信号常用于连接不同频率的时序电路。

    2 Universal二进制计数器

    通用二进制计数器,可递增或递减计数,亦可载入指定的值,也可被异步清零。其查找表如表1所示。注意rst_n和syn_clr信号的区别,前者是异步复位,且仅应该用于系统的初始化;后者为同步复位,只在时钟的上升沿被采样,可被用于一般的同步设计中。

    表1 通用二进制计数器的查找表

    syn_clr load en up q次态 操作
    1 - - - 00…00 异步清零
    0 1 - - d 并行载入
    0 0 1 1 q+1 递增计数
    0 0 1 0 q-1 递减计数
    0 0 0 - q 暂停

    代码2 通用二进制计数器

    module univ_bin_counter
    #(parameter N=8)
    (
      // global clock and asyn reset
      input clk,
      input rst_n,
      // counter interface
      input syn_clr,
      input load,
      input en,
      input up,
      input [N-1:0] d,
      output max_tick,
      output min_tick,
      output [N-1:0] q
    );
    
    // signal declaration
    reg [N-1:0] r_reg, r_next;
    
    // body
    // register
    always@(posedge clk, negedge rst_n)
      if(!rst_n)
        r_reg <= 0; // {N{1'b0}}
      else
        r_reg <= r_next;
        
    // next-state logic
    always@*
      if(syn_clr)
        r_next = 0;
      else if(load)
        r_next = d;
      else if(en & up)
        r_next = r_reg + 1'b1;
      else if(en & ~up)
        r_next = r_reg - 1'b1;
      else
        r_next = r_reg;
    //output logic
    assign q = r_reg;
    assign max_tick = (r_reg == 2**N-1) ? 1'b1 : 1'b0;
    assign min_tick = (r_reg == 0) ? 1'b1 : 1'b0;
                    
    endmodule

    按照查找表设计的次态逻辑,被放在一个always块内,并且使用if-else-if来控制所需优先性的操作。

    3 模-m计数器

    模-m计数器,从0计数到m-1,然后翻过来重新计数。代码3所示的参数化的模-m计数器有两个参数:M,指定计数的范围为[0, M-1];N,指定M个数需要多少位宽来存储,其值为大于或等于log2(M)的整数。

    代码3 模-m计数器(缺省为模-10)

    module mod_m_bin_counter
    #(
      parameter N=4  // number of bits in counter
      parameter M=10 // mod-M
     )
    (
      // global clock and asyn reset
      input clk,
      input rst_n,
      // counter interface
      output max_tick,
      output min_tick,
      output [N-1:0] q
    );
    
    // signal declaration
    reg [N-1:0] r_reg;
    wire [N-1:0] r_next;
    
    // body
    // register
    always@(posedge clk, negedge rst_n)
      if(!rst_n)
        r_reg <= 0;
      else
        r_reg <= r_next;
       
    // next-state logic
    assign r_next = (r_reg == (M-1)) ? 0 : r_reg + 1'b1;
    //output logic
    assign q = r_reg;
    assign max_tick = (r_reg == (M-1)) ? 1'b1 : 1'b0;
    assign min_tick = (r_reg == 0) ? 1'b1 : 1'b0;
                    
    endmodule

    次态逻辑由一个条件语句组成:如果计数器数到M-1,那么新的值就会被清零;否则它将自增。

    虽然N的值取决于M的值,但是有时计算N的值有点烦人,以后我们通过加入function来解决这个问题

    4 时序电路的testbench

    所谓testbench即模仿物理实验平台的程序。下面我们写一个万用计数器的testbench,当然也做作为其他时序电路的testbench的模板。关于testbench的更加复杂的话题以后会讨论。

    代码4 通用计数器的testbench

    `timescale 1ns/10 ps
    // the `timescale specifies that
    // the simulation time unit is 1 ns and
    // the simulalor timestep is 10 ps
    
    module univ_bin_counter_tb;
    
    // declaration
    localparam T = 20; // clock period
    reg clk, rst_n;
    reg syn_clr, load, en, up;
    reg [2:0] d;
    wire max_tick, min_tick;
    wire [2:0] q;
    
    // univ_bin_counter instaniation
    univ_bin_counter #(.N(3)) univ_bin_counter_inst
    (
      //
      .clk(clk),
      .rst_n(rst_n),
      //
      .syn_clr(syn_clr),
      .load(load),
      .en(en),
      .up(up),
      .d(d),
      .max_tick(max_tick),
      .min_tick(min_tick),
      .q(q)
    );
    
    // clock
    // 20 ns clock running forever
    always
    begin
      clk = 1'b1;
      #(T/2);
      clk = 1'b0;
      #(T/2);
    end
    
    // reset for the first half cycle
    initial
    begin
      rst_n = 1'b0;
      #(T/2);
      rst_n = 1'b1;
    end
    
    // other stimulus
    initial
    begin
      // ==== initial input ====
      syn_clr = 1'b0;
      load = 1'b0;
      en = 1'b0;
      up = 1'b1; // count up
      d = 3'b000;
      @(posedge rst_n); // wait reset to deassert
      @(negedge clk); // wait for one clock
      // ===== test load ====
      load = 1'b1;
      d = 3'd011;
      @(negedge clk);
      load = 1'b0;
      repeat(2) @(negedge clk); // wait for two clock
      // ==== test syn_clear ====
      syn_clr = 1'b1; // assert clear
      @(negedge clk);
      syn_clr = 1'b0;
      // ==== test up counter and pause ====
      en = 1'b1; // enable counter
      up = 1'b1; // count up
      repeat(10) @(negedge clk);
      en = 1'b0; // pause
      repeat(2) @(negedge clk);
      en = 1'b1;
      repeat(2) @(negedge clk);
      // ==== test down counter ====
      up = 1'b0;
      repeat(10) @(negedge clk);
      // ==== wait statement ====
      // continue wait until q=20
      wait(q==2);
      @(negedge clk);
      up = 1'b1;
      // continue until min_tick becomes 10
      @(negedge clk);
      wait(min_tick);
      @(negedge clk);
      up = 1'b0;
      // ==== absolute delay ====
      #(4*T); // wait for 80 ns
      en = 1'b0; // pause
      #(4*T); 
      // ==== stop simulation ====
      // return to interactive simulation mode
      $stop;
    end
    
    endmodule

    上述代码包括创建一个3位的计数器的例化模块表达式,以及生成时钟、复位和寄存器输入的三段表达式。

    通过一个always块来生成指定时钟:

    always
    begin
      clk = 1'b1;
      #(T/2);
      clk = 1'b0;
      #(T/2);
    end

    T代表每个时钟周期包含多少时间单元。可使用下面的表达式定义。

    localparam T = 20; // clock period

    注意时钟生成always块没事敏感列表,及永远重复执行下去。clk信号被交替地断言为1或0,且每个值持续半个周期。

    通过initial块来模拟复位信号:

    initial
    begin
      rst_n = 1'b0;
      #(T/2);
      rst_n = 1'b1;
    end 

    在仿真的开始,initial块会被执行一次。上面的语句表示rst_n信号被初始设置为0;然后在半个时钟周期后变为1。这个initial块代表“上电”情况,即上电后复位信号会被立即插入,以清零及初始化系统。注意:缺省情况下,变量值为未定态。使用复位短脉冲是一个很好的初始化系统的机制。

    第二个initial块用于生成其他输入信号的模拟。我们首先测试的是载入(load)和同步清零(syn_clr)操作,然后测试的是两个方向的计数。$stop用于强制仿真器停止仿真。

    在带有上升沿触发的触发器的同步电路中,输入信号必须在时钟的上升沿附近保持稳定,以满足建立时间和保持时间的时序约束。一个简单的方法可以实现在保持信号稳定的情况下变换信号:在时钟的1到0跳变情况下,改变信号的值。我们通过使用下面的语句来可以等待这个跳变。注意分号不要落下。

    @(negedge clk);

    negedge用于指定时钟信号的下降沿。注意:每一条这样的语句都带吧一个新的下降沿。在我们的模版中,我们一般使用这样的表达式来指定时间进程。比方说多个时钟周期,可以使用repeat表达式:

    repeat(10) @(negedge clk); // repeat 10 times

    同时我们还是用了一个类似的语句,来等待上电异步复位完成,即解除异步复位信号。之所以此处为posedge,因为代码中的异步复位信号是低电平有效,我们所等待的是其恢复为高电平的跳变。

    @(posedge rst_n); // wait reset to deassert

    在第二个initial块的后面,还有一些时序控制语句。我们可以等待指定的条件。比方说,等到“q=2”的时候。注意一条语句结尾,分号不要落下。

    wait(q==2);

    再比如等待一个信号跳变:

    wait(min_tick);

    抑或等待一段绝对时间,比如

    #(4*T); // wait for 80 ns

    如果修改了输入信号的值,我们需要确认输入的改变不是在时钟的上升沿发生的,因此我们应该在其后附加下面的语句。

    @(negedge clk);

    完成的上述testbench的前仿真波形如图1和图2所示。

    图1 通用计数器的前仿真波形

    图1 通用计数器的前仿真波形

     图2 通用计数器的前仿真波形片段

    图2 通用计数器的前仿真波形片段

    参考

    1 Pong P. Chu.FPGA Prototyping By Verilog Examples: Xilinx Spartan-3 Version.Wiley

    另见

    [与艾米一起学FPGA/SOPC].[逻辑实验文档连载计划]

     安德鲁® / CC BY 2.5     FPGA Run!
  • 相关阅读:
    Android——inflate 将一个xml中定义的布局找出来
    Android——显示单位px和dip以及sp的区别
    StrategyPattern (策略模式)
    Flyweight(享元模式)
    ComponentPattern (组合模式)
    Java子类属性继承父类属性
    BridgePattern(桥接模式)
    FacadePattern(门面模式)
    装饰者模式,适配器模式,代理模式区别
    DecoratorPattern(装饰器模式)
  • 原文地址:https://www.cnblogs.com/yuphone/p/1902664.html
Copyright © 2020-2023  润新知