碎碎念:
学习新知识的感觉还是比较快乐,下面要介绍的是一个全能的边沿检测器,可以识别上升、下降、以及同时检测两种边沿,不得不说人家的代码写得确实很优雅。
目录
多通道的边沿检测器,可以同时检测到输入信号的上升沿、下降沿以及上升/下降沿。
- //------------------------------------------------------------------------------
- // edge_detect.sv
- // published as part of https://github.com/pConst/basic_verilog
- // Konstantin Pavlov, pavlovconst@gmail.com
- //------------------------------------------------------------------------------
-
- // 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
重点分析一下其中我认为值得关注的地方。
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模块,大大提升了代码在使用过程中的灵活性。
- //------------------------------------------------------------------------------
- // edge_detect_tb.sv
- // Konstantin Pavlov, pavlovconst@gmail.com
- //------------------------------------------------------------------------------
-
- // 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
其中复用了前几期提到过的模块:
仔细阅读发现TestBench其实也有很多值得学习的地方,这里只提一点。
通过将生成的伪随机数作为边沿检测的输入信号,这样可以很方便的对所需要检测的模块进行更加具有说服力的验证。
从仿真结果中(最后四个信号,是我从并行信号中提取出来的,便于观察现象)可以看出,对于其中任意一路信号,当出现上升沿时,rise会在对应通道产生一个周期的高电平;下降沿以及上升下降沿是同理的。
这就是本期的全部内容啦,我感觉一边学习一边总结真的是受益匪浅,开拓了我很多的思路。如果你喜欢我的文章,不要忘了点赞+收藏+关注,分享给身边的朋友哇~