【问题标题】:Frequency Counter in VerilogVerilog中的频率计数器
【发布时间】:2021-10-26 10:25:14
【问题描述】:

尝试在 Verilog 中实现频率计数器。 我需要的是一个时钟输入、一个计数输出和一个复位输入。 行为应该是这样的:

  • 内部 reg 或线变量对应用于时钟输入的时钟脉冲进行计数
  • 每当一个复位脉冲到来时,它应该将计数值传送到输出中,直到下一个时钟脉冲到来,并复位内部变量。

下面给出一个简单的代码:

module counter(

input rst,
input clk,

output [31:0] countout
//output [31:0] count
);

wire rst;
wire clk;

reg [31:0] countout=0;
reg [31:0] count=0; 

always @ (posedge clk)
    begin
        count = count + 1'b1;
    end

always @ ( posedge rst )
    begin
        countout = count;
    end

always @ ( negedge rst )
    begin
        count = 0;
    end
endmodule

但是,Vivado 不允许这样做,因为计数变量是一个多驱动网络。显然,count 不能在 2 个不同的 always 块中更改。你知道我该如何实现吗?谢谢。

【问题讨论】:

    标签: verilog counter vivado


    【解决方案1】:

    我在下面重写了你的代码。它是根据行业惯例重写的。

    module counter(
    
    input rst,
    input clk,
    
    output wire [31:0] countout // should be wire for assign.
    //output [31:0] count
    );
    
    wire rst;
    wire clk;
    
    reg [31:0] countout; // = 0 -- do not initialize it here
    reg [31:0] count; // = 0 
    
      always @ (posedge clk or negedge rst)
        begin
          if (rst == 0) 
            count <= 0;
          else
            count <= count + 1'b1;
        end
    
    assign  countout = count;
    
    endmodule
    

    正如您所提到的,综合工具不支持从 2 个不同的始终块驱动“计数”,并在模拟中创建比赛。为避免这种情况,变量的所有驱动都必须使用单个始终块来完成,如我的示例所示。

    根据您的代码,您尝试为计数器实现异步重置。在我的示例中,此功能是使用 rst 的否定和块中的 if 语句的常见做法实现的。

    您不需要使用任何额外的触发器来驱动“countout”输出。一个简单的assign 语句就足够了。在 Verilog 中,您只需要确保 lhs 是 wire

    而且,您应该在创建 flops 时使用非阻塞赋值 &lt;=

    另外,不要在声明时初始化变量。这也会创建多个驱动程序,并且可能无法合成。

    在处理 verilog 时,请记住您描述的是由设备和连接组成的硬件。每个 always 块本质上定义了一个带有输入和输出的单独设备。 'assign' 和模块端口表示这些设备之间的连接。

    下面是一个简单的测试平台,可以帮助您测试您的计数器:

    module tb;
      reg rst;
      reg clk;
      wire[31:0] count;
      integer i;
    
      counter counter(rst, clk, count);
      
    initial begin 
      
      // print counter
      $monitor(count);
      
      clk = 0;
      
      // toggle reset
      rst = 1;
      #5 rst = 0;
      #5 rst = 1;
      
      // run 5 clock cycles
      for (i = 0; i < 10; i = i+1) 
        #5 clk = ~clk;
      
      // done
      $finish;
    end
    endmodule
    

    【讨论】:

    • 如我所见,这个版本不缓冲countout,所以它的值不能按照提问者的方式使用。
    • @Sasszem 无需缓冲计数。它只是作为寄存器的计数与世界其他地方之间的连接。在 OP 的代码中,它应该在 posedge rst 失败,这是没有意义的。这将导致 countout 在重置完成后停留在“0”,并且在此之后根本不更新。
    • 不,这在他/她的代码中是有意义的,因为它是在上升沿写入的,而计数器在下降沿清零,因此 之后存储该值。需要存储一个值,否则您以后无法处理它,因为它会被重置或更改。
    • @Sasszem 你说的是count 还是countout
    • countout 在上升沿更新为count,在后者被清除为 0 之前。提问者的原始代码将此作为缓冲区保存,因此在计数之间使用它很好,因为它存储最后一个计数值。这个答案的代码没有相同的行为,因为countout 被分配给count(分配给 reg 甚至有效吗?),因此必须在模块之外完成相同的逻辑。
    【解决方案2】:

    您的设计存在几个问题。

    首先,vivado 会找到 3 个时钟:

    • clk 摆姿势
    • rst 摆姿势
    • rst 下降沿。

    我认为你只需要一个时钟。

    那么你应该像这样写你的过程:

    always @ (posedge clk)
    begin
            // your code here
    end
    

    其次,你应该使用非阻塞赋值&lt;=进行同步寄存器。

    然后,据我了解,rst 的行为并不是真正的重置,而是一个简单的输入«按钮»。并且您需要知道是否出现边缘。

    我认为您应该将 rst 的混淆名称更改为 btn 之类的名称。你应该检测上升沿和下降沿保存以前的值:

    input btn;
    
    reg btn_old, btn_rise, btn_fall;
    
    always @ (posedge clk)
    begin
        btn_rise <= 1'b0;
        btn_fall <= 1'b0;
        if(!btn_old && btn) begin
            btn_rise <= 1'b1;
        end
        if(btn_old && !btn) begin
            btn_fall <= 1'b1;
        end
        btn_old <= btn;
    

    然后您可以使用btn_fallbtn_rise 作为您的计数器。

    【讨论】:

    • 这使得重置或按钮是同步的,这可能不是提问者想要的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多