Verilog:【4】脉冲发生器(pulse_gen.sv)

碎碎念:

明明是周四,这周竟然不开组会_(:з)∠)_

那我可以继续愉快地学习人家的代码了,这篇博客介绍的是脉冲发生器,脉冲和Killer Queen是不是很配呢hhh

目录

1 模块功能

2 模块代码

3 模块思路

4 TestBench与仿真结果


1 模块功能

通过设置参数cntr_max与cntr_low,可以产生任意周期数与占空比的脉冲信号。

2 模块代码

//------------------------------------------------------------------------------
// pulse_gen.sv
// Konstantin Pavlov, pavlovconst@gmail.com
//------------------------------------------------------------------------------

// INFO ------------------------------------------------------------------------
// Pulse generator module, ver.2
//
// - generates one or many pulses of given width and period
// - generates constant HIGH, constant LOW, or impulse output
// - features buffered inputs, so inputs can change continiously during pulse period
// - generates LOW when idle
//
// - Pulse period is (cntr_max[]+1) cycles
// - If you need to generate constant LOW pulses, then CNTR_WIDTH should allow
//   setting cntr_low[]>cntr_max[]
//
// Example 1:
//      let CNTR_WIDTH = 8
//      let cntr_max[7:0] = 2^CNTR_WIDTH-2 = 254, pulse period is 255 cycles
//      cntr_low[7:0]==255              then output will be constant LOW
//      0<cntr_low[7:0]<=cntr_max[7:0]  then output will be generating pulse(s)
//      cntr_low[7:0]==0                then output will be constant HIGH
//
// Example 2:
//      let CNTR_WIDTH = 9
//      let cntr_max[8:0] = 255, pulse period is 256 cycles
//      cntr_low[8:0]>255               then output will be constant LOW
//      0<cntr_low[8:0]<=cntr_max[8:0]  then output will be generating pulse(s)
//      cntr_low[8:0]==0                then output will be constant HIGH
//
//      In Example 2 constant LOW state can be acheived also by disabling start
//      condition or holding reset input, so cntr_low[8:0] and cntr_max[8:0]
//      can be left 8-bit-wide actually



/* --- INSTANTIATION TEMPLATE BEGIN ---

pulse_gen #(
  .CNTR_WIDTH( 8 )
) pg1 (
  .clk( clk ),
  .nrst( nrst ),

  .start( 1'b1 ),
  .cntr_max( 255 ),
  .cntr_low( 2 ),

  .pulse_out(  ),

  .start_strobe,
  .busy(  )
);

--- INSTANTIATION TEMPLATE END ---*/

module pulse_gen #( parameter
  CNTR_WIDTH = 32
)(
  input clk,                          // system clock
  input nrst,                         // negative reset

  input start,                        // enables new period start
  input [CNTR_WIDTH-1:0] cntr_max,    // counter initilization value, should be > 0
  input [CNTR_WIDTH-1:0] cntr_low,    // transition to LOW counter value

  output logic pulse_out,                   // active HIGH output

  // status outputs
  output logic start_strobe = 1'b0,
  output busy
);


logic [CNTR_WIDTH-1:0] seq_cntr = '0;

logic seq_cntr_0;
assign seq_cntr_0 = (seq_cntr[CNTR_WIDTH-1:0] == '0);

// delayed one cycle
logic seq_cntr_0_d1;
always_ff @(posedge clk) begin
  if( ~nrst) begin
    seq_cntr_0_d1 <= 0;
  end else begin
    seq_cntr_0_d1 <= seq_cntr_0;
  end
end

// first seq_cntr_0 cycle time belongs to pulse period
// second and further seq_cntr_0 cycles are idle
assign busy = ~(seq_cntr_0 && seq_cntr_0_d1);


// buffering cntr_low untill pulse period is over to allow continiously
//  changing inputs
logic [CNTR_WIDTH-1:0] cntr_low_buf = '0;
always_ff @(posedge clk) begin
  if( ~nrst ) begin
    seq_cntr[CNTR_WIDTH-1:0] <= '0;
    cntr_low_buf[CNTR_WIDTH-1:0] <= '0;
    start_strobe <= 1'b0;
  end else begin
    if( seq_cntr_0 ) begin
      // don`t start if cntr_max[] is illegal value
      if( start && (cntr_max[CNTR_WIDTH-1:0]!='0) ) begin
        seq_cntr[CNTR_WIDTH-1:0] <= cntr_max[CNTR_WIDTH-1:0];
        cntr_low_buf[CNTR_WIDTH-1:0] <= cntr_low[CNTR_WIDTH-1:0];
        start_strobe <= 1'b1;
      end else begin
        start_strobe <= 1'b0;
      end
    end else begin
      seq_cntr[CNTR_WIDTH-1:0] <= seq_cntr[CNTR_WIDTH-1:0] - 1'b1;
      start_strobe <= 1'b0;
    end
  end // ~nrst
end

always_comb begin
  if( ~nrst ) begin
    pulse_out <= 1'b0;
  end else begin
    // busy condition guarantees LOW output when idle
    if( busy &&
        (seq_cntr[CNTR_WIDTH-1:0] >= cntr_low_buf[CNTR_WIDTH-1:0]) ) begin
      pulse_out <= 1'b1;
    end else begin
      pulse_out <= 1'b0;
    end
  end // ~nrst
end


endmodule

3 模块思路

在明确了整个模块的功能之后,也就比较看出主要思路就是利用计数器的原理,利用对计数值的判断,来控制输出脉冲的信号,下面来具体进行说明。

1.模块接口定义部分(58-73行)

包含一个内部寄存器宽度参数CNTR_WIDTH;五个输入端口分别是时钟clk、低电平复位信号nrst、新周期开始信号start、起点计数值cntr_max、跳变计数值cntr_low;三个输出端口分别是脉冲输出信号pulse_out、输出状态信号start_strobe、忙碌信号busy。

这里同样使用了logic类型,在前几期的内容也有涉及到。

2.中间变量定义(76-79行)

定义了存储计数值的寄存器seq_cntr、以及标志其是否为0的寄存器seq_cntr_0。

3.延时一个周期(81-89行)

利用always_ff构建D触发器,从而获得seq_cntr_0延时一周期后的信号seq_cntr_0_d1。

4.输出busy信号的逻辑(93行)

当本周期与上一周期计数值都是0的时候,此时表示不在输出有效时间段,即busy=0;当start恒等于1时,因为计数值一定会变化,因此busy始终都是1。

读者可以结合仿真结果来看,我认为这一设计还是比较巧妙的,利用组合逻辑与延时的处理,让busy信号和计数器的结果就没有出现那种会错开一周期的情况(我本人有时会遇到_(:з)∠)_)。

5.计数器运行逻辑(96-119行)

利用always_ff构建D触发器,来实现整个运行的逻辑。当seq_cntr_0=1时,表示此时计数值为0,因此作为脉冲的起点,开始修改cntr_low等参数。

重点关注107行,当start信号出现一周期的高电平,同时cntr_max信号是有效值(大于0)时,将cntr_max的值赋值给seq_cntr;将cntr_low的值赋值给cntr_low_buf;start_strobe置为到1(持续一周期),表示开始启动脉冲的发生器。

之后start信号变为低电平,此时开始每周期将计数器结果减1。

在这里,cntr_max就控制了脉冲发生器的周期;cntr_low相对于cntr_max的大小,就控制了输出的占比空。

6.脉冲信号输出逻辑(121-133行)

这一部分就是比较简单的组合逻辑啦,使用always_comb搭建。通过判断当前的计数值,来控制脉冲信号的输出。

从代码可以看出:seq_cntr大于等于cntr_low_buf时,输出是1;seq_cntr小于cntr_low_buf时,输出是0。

4 TestBench与仿真结果

//------------------------------------------------------------------------------
// pulse_gen_tb.sv
// Konstantin Pavlov, pavlovconst@gmail.com
//------------------------------------------------------------------------------

// INFO ------------------------------------------------------------------------
// testbench for pulse_gen.sv module


`timescale 1ns / 1ps

module pulse_gen_tb();

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

// external device "asynchronous" clock
logic clk33;
initial begin
  #0 clk33 = 1'b0;
  forever
    #15.151 clk33 = ~clk33;
end

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

logic nrst;
assign nrst = ~rst;

logic rst_once;
initial begin
  #0 rst_once = 1'b0;
  #10.2 rst_once = 1'b1;
  #5 rst_once = 1'b0;
end

logic nrst_once;
assign nrst_once = ~rst_once;

logic [31:0] DerivedClocks;
clk_divider #(
  .WIDTH( 32 )
) cd1 (
  .clk( clk200 ),
  .nrst( nrst_once ),
  .ena( 1'b1 ),
  .out( DerivedClocks[31:0] )
);

logic [31:0] E_DerivedClocks;
edge_detect ed1[31:0] (
  .clk( {32{clk200}} ),
  .anrst( {32{nrst_once}} ),
  .in( DerivedClocks[31:0] ),
  .rising( E_DerivedClocks[31:0] ),
  .falling(  ),
  .both(  )
);

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

c_rand rng2 (
  .clk( clk200 ),
  .rst( 1'b0 ),
  .reseed( rst_once ),
  .seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 2) ),
  .out( RandomNumber1[31:16] )
);

logic start;
initial begin
  #0 start = 1'b0;
  #100 start = 1'b1;
  #20 start = 1'b0;
end

// Modules under test ==========================================================

// simple static test
/*pulse_gen #(
  .CNTR_WIDTH( 8 )
) pg1 (
  .clk( clk200 ),
  .nrst( nrst_once ),

  .start( start ),
  .cntr_max( 15 ),
  .cntr_low( 0 ),

  .pulse_out(  ),
  .busy(  )
);
*/

logic [31:0] in_high_width = '0;
logic out;
logic out_rise;

// random test
pulse_gen #(
  .CNTR_WIDTH( 8 )
) pg1 (
  .clk( clk200 ),
  .nrst( nrst_once ),

  .start( start ),
  .cntr_max( 16 ),
  .cntr_low( {4'b0,RandomNumber1[3:0]} ),

  .pulse_out( out ),
  .busy(  )
);




edge_detect out_ed (
  .clk( clk200 ),
  .anrst( nrst_once ),
  .in( out ),
  .rising( out_rise ),
  .falling(  ),
  .both(  )
);

always_ff @(posedge clk200) begin
  if( ~nrst_once ) begin
     in_high_width[31:0] <= 1'b0;
  end else begin
    if( out_rise ) begin
      in_high_width[31:0] <= in_high_width[31:0] + 1'b1;
    end
  end
end

// PWM test
/*pulse_gen #(
  .CNTR_WIDTH( 8 )
) pg1 (
  .clk( clk200 ),
  .nrst( nrst_once ),

  .start( 1'b1 ),
  .cntr_max( 15 ),
  .cntr_low( {4'b0,in_high_width[3:0]} ),

  .pulse_out( out ),
  .busy(  )
);*/

endmodule

下面开始学习一下TestBench的写法,其中我认为值得关注的地方有这几点:

1.#号表示延迟

此前我很少见到使用小数进行延迟的,看来在具有特殊频率要求的情况下,这也是必要的。

2.复用之前的随机数模块

同样对之前的随机数模块以及时钟分频电路进行了复用,从而提高测试的说服力。同时使用到了边沿检测器,用来对输出的脉冲进行上升沿检测。对随机数产色模块以及边沿检测不了解的,可以跳转:

  1. Verilog:【1】时钟分频电路(clk_divider.sv)
  2. Verilog:【2】伪随机数生成器(c_rand.v)
  3. Verilog:【3】边沿检测器(edge_detect.sv)

 同时注意到,为了生成32位的随机数,使用了两个16位随机数的生成器,将结果进行了拼接,应该是有降低资源使用量的考虑,毕竟过于高位的乘法器综合起来还是开销比较大的。

最后可以注意到这一TestBench包含两个测试,测试1是单独一次脉冲的结果(即下图);测试2是代码中最后被注释的部分,由于start恒为1,因此会持续不断产生受到随机数调制的PWM信号。

下面来看一下测试1的仿真波形,可以看到cntr_max的值是16,cntr_low的值是13,当start信号来一个周期的高电平时,cntr_low将值存储到cntr_low_buff中。

seq_cntr开始从16依次减小到0;busy这段时间为高电平,表示正在输出信号;seq_cntr变为16时,start_strobe出现一个高电平,表示开始输出的起点;pulse_out在计数值大于等于13时,为高电平,其余为低电平。seq_cntr_0与seq_cntr_0_d1对busy信号的产生就非常巧妙,我认为很有趣。

最终证明,波形的展示与我们的分析是保持一致的~


这就是本期的全部内容啦,如果你喜欢我的文章,不要忘了点赞+收藏+关注,分享给身边的朋友哇~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex-YiWang

不要打赏,想要一个赞

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值