Verilog:【3】边沿检测器(edge_detect.sv)

碎碎念:

学习新知识的感觉还是比较快乐,下面要介绍的是一个全能的边沿检测器,可以识别上升、下降、以及同时检测两种边沿,不得不说人家的代码写得确实很优雅。

目录

1 模块功能

2 模块代码

3 模块思路

4 TestBench与仿真结果


1 模块功能

多通道的边沿检测器,可以同时检测到输入信号的上升沿、下降沿以及上升/下降沿。

2 模块代码

//------------------------------------------------------------------------------
// edge_detect.sv
// published as part of https://github.com/pConst/basic_verilog
// Konstantin Pavlov, [email protected]
//------------------------------------------------------------------------------

// INFO ------------------------------------------------------------------------
// Edge detector, ver.4
//
// (new!) Added WIDTH parameter to simplify instantiating arrays of edge detectors
// (new!) Made reset to be asynchronous
//
// Added parameter to select combinational implementation (zero clocks delay)
//                        or registered implementation (one clocks delay)
//
// In case when "in" port has toggle rate 100% (changes every clock period)
//    "rising" and "falling" outputs will completely replicate input
//    "both" output will be always active in this case
//


/* --- INSTANTIATION TEMPLATE BEGIN ---

edge_detect #(
  .WIDTH( 32 ),
  .REGISTER_OUTPUTS( 1'b1 )
) in_ed (
  .clk( clk ),
  .anrst( 1'b1 ),
  .in( in[31:0] ),
  .rising( in_rise[31:0] ),
  .falling(  ),
  .both(  )
);

--- INSTANTIATION TEMPLATE END ---*/


module edge_detect #( parameter
  bit [7:0] WIDTH = 1,                 // signal width
  bit [0:0] REGISTER_OUTPUTS = 1'b0    // 0 - comb. implementation (default)
                                       // 1 - registered implementation
)(
  input clk,
  input anrst,

  input [WIDTH-1:0] in,
  output logic [WIDTH-1:0] rising,
  output logic [WIDTH-1:0] falling,
  output logic [WIDTH-1:0] both
);

// data delay line
logic [WIDTH-1:0] in_d = '0;
always_ff @(posedge clk or negedge anrst) begin
  if ( ~anrst ) begin
    in_d[WIDTH-1:0] <= '0;
  end else begin
    in_d[WIDTH-1:0] <= in[WIDTH-1:0];
  end
end

logic [WIDTH-1:0] rising_comb;
logic [WIDTH-1:0] falling_comb;
logic [WIDTH-1:0] both_comb;
always_comb begin
  rising_comb[WIDTH-1:0] = {WIDTH{anrst}} & (in[WIDTH-1:0] & ~in_d[WIDTH-1:0]);
  falling_comb[WIDTH-1:0] = {WIDTH{anrst}} & (~in[WIDTH-1:0] & in_d[WIDTH-1:0]);
  both_comb[WIDTH-1:0] = {WIDTH{anrst}} & (rising_comb[WIDTH-1:0] | falling_comb[WIDTH-1:0]);
end

generate
  if( REGISTER_OUTPUTS==1'b0 ) begin

    // combinational outputs, no delay
    always_comb begin
      rising[WIDTH-1:0] = rising_comb[WIDTH-1:0];
      falling[WIDTH-1:0] = falling_comb[WIDTH-1:0];
      both[WIDTH-1:0] = both_comb[WIDTH-1:0];
    end // always

  end else begin

    // registered outputs, 1 cycle delay
    always_ff @(posedge clk or negedge anrst) begin
      if( ~anrst ) begin
        rising[WIDTH-1:0] <= '0;
        falling[WIDTH-1:0] <= '0;
        both[WIDTH-1:0] <= '0;
      end else begin
        rising[WIDTH-1:0] <= rising_comb[WIDTH-1:0];
        falling[WIDTH-1:0] <= falling_comb[WIDTH-1:0];
        both[WIDTH-1:0] <= both_comb[WIDTH-1:0];
      end // always
    end // if

  end // end else
endgenerate

endmodule

3 模块思路

重点分析一下其中我认为值得关注的地方。

1.bit数据类型

bit类型是System Verilog中新增的数据类型,用来表示一位的无符号整型数据,当然也可以像40行这样使用多位的定义。

2.数据延时线(53-61行)

如果有过编写边沿检测器的经验,应该对这个相当熟悉。利用D触发器的结构,将待检测的输入数据in延时一个周期存储到in_d中,通过分析本周期和上一周期的信号,即可判断出待检测信号的边沿状况。

3.边沿检测逻辑(63-70行)

这部分就是本模块的算法核心部分,通过检测复位信号anrst、本周期输入信号in以及上一周期输入信号in_d这三个信号的情况,来检测是否出现了边沿。为了实现多通道的检测,这里作者使用了位运算符,从而可以同时对多个通道进行检测。

边沿类型 anrst in in_d
上升沿 1 1 0
下降沿 1 0 1
上升/下降沿 1 - -

如果需要检测上升/下降沿,只需要将上升沿与下降沿的检测结果按位或处理即可。

4.always_comb(76-80行)

参考博客:Systemverilog always_comb 过程块

always_comb是System Verilog中用来指定综合结果是组合逻辑的语法,因此其不需要指定敏感表,同时在仿真的零时刻会进行自动求值,从而不会出现Verilog中没有触发信号导致仿真前期出现高阻态的情况,这种零时刻求值确实是很关键。

5.generate(72-98行)

参考博客:Verilog中generate的使用 - 知乎 (zhihu.com)

generate模块在这里是用来构造条件结构,可以看到73行对REGISTER_OUTPUTS这一变量的值进行了检测,当其为0时,输出则使用always_comb构建组合逻辑电路;而当其值为1时,则会调用always_ff构建D触发器电路,从而将输出信号延迟一个周期。

在这里使用generate模块,大大提升了代码在使用过程中的灵活性。

4 TestBench与仿真结果

//------------------------------------------------------------------------------
// edge_detect_tb.sv
// Konstantin Pavlov, [email protected]
//------------------------------------------------------------------------------

// INFO ------------------------------------------------------------------------
//

`timescale 1ns / 1ps

module edge_detect_tb();

logic clk200;
initial begin
  #0 clk200 = 1;
  forever
    #2.5 clk200 = ~clk200;
end

logic rst;
initial begin
  #10.2 rst = 1;
  #5 rst = 0;
  //#10000;
  forever begin
    #9985 rst = ~rst;
    #5 rst = ~rst;
  end
end

logic nrst;
assign nrst = ~rst;

logic rst_once;
initial begin       // initializing non-X data before PLL starts
  #10.2 rst_once = 1;
  #5 rst_once = 0;
end
initial begin
  #510.2 rst_once = 1;    // PLL starts at 500ns, clock appears, so doing the reset for modules
  #5 rst_once = 0;
end

logic nrst_once;
assign nrst_once = ~rst_once;

logic [31:0] DerivedClocks;
clk_divider #(
  .WIDTH( 32 )
) CD1 (
  .clk( clk200 ),
  .nrst( nrst_once ),
  .out( DerivedClocks[31:0] )
);

logic [15:0] RandomNumber1;
c_rand RNG1 (
  .clk( clk200 ),
  .rst( rst_once ),
  .reseed( 1'b0 ),
  .seed_val( DerivedClocks[31:0] ),
  .out( RandomNumber1[15:0] )
);

logic start;
initial begin
  #0 start = 1'b0;
  #100.2 start = 1'b1;
  #5 start = 1'b0;
end

// Module under test ==========================================================

logic [15:0] rise;
logic [15:0] fall;
logic [15:0] both;

edge_detect ED1[15:0] (
  .clk( {16{clk200}} ),
  .anrst( {16{nrst_once}} ),
  .in( RandomNumber1[15:0] ),
  .rising(rise),
  .falling(fall),
  .both(both)
);


endmodule

其中复用了前几期提到过的模块:

  1. Verilog:【1】时钟分频电路(clk_divider.sv)

  2. Verilog:【2】伪随机数生成器(c_rand.v)

仔细阅读发现TestBench其实也有很多值得学习的地方,这里只提一点。

通过将生成的伪随机数作为边沿检测的输入信号,这样可以很方便的对所需要检测的模块进行更加具有说服力的验证。

从仿真结果中(最后四个信号,是我从并行信号中提取出来的,便于观察现象)可以看出,对于其中任意一路信号,当出现上升沿时,rise会在对应通道产生一个周期的高电平;下降沿以及上升下降沿是同理的。


这就是本期的全部内容啦,我感觉一边学习一边总结真的是受益匪浅,开拓了我很多的思路。如果你喜欢我的文章,不要忘了点赞+收藏+关注,分享给身边的朋友哇~

猜你喜欢

转载自blog.csdn.net/Alex497259/article/details/126268298