碎碎念:
学习新知识的感觉还是比较快乐,下面要介绍的是一个全能的边沿检测器,可以识别上升、下降、以及同时检测两种边沿,不得不说人家的代码写得确实很优雅。
目录
1 模块功能
多通道的边沿检测器,可以同时检测到输入信号的上升沿、下降沿以及上升/下降沿。
2 模块代码
//------------------------------------------------------------------------------
// 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
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, 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会在对应通道产生一个周期的高电平;下降沿以及上升下降沿是同理的。
这就是本期的全部内容啦,我感觉一边学习一边总结真的是受益匪浅,开拓了我很多的思路。如果你喜欢我的文章,不要忘了点赞+收藏+关注,分享给身边的朋友哇~