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


    碎碎念:

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

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

    目录

    1 模块功能

    2 模块代码

    3 模块思路

    4 TestBench与仿真结果


    1 模块功能

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

    2 模块代码

    1. //------------------------------------------------------------------------------
    2. // pulse_gen.sv
    3. // Konstantin Pavlov, pavlovconst@gmail.com
    4. //------------------------------------------------------------------------------
    5. // INFO ------------------------------------------------------------------------
    6. // Pulse generator module, ver.2
    7. //
    8. // - generates one or many pulses of given width and period
    9. // - generates constant HIGH, constant LOW, or impulse output
    10. // - features buffered inputs, so inputs can change continiously during pulse period
    11. // - generates LOW when idle
    12. //
    13. // - Pulse period is (cntr_max[]+1) cycles
    14. // - If you need to generate constant LOW pulses, then CNTR_WIDTH should allow
    15. // setting cntr_low[]>cntr_max[]
    16. //
    17. // Example 1:
    18. // let CNTR_WIDTH = 8
    19. // let cntr_max[7:0] = 2^CNTR_WIDTH-2 = 254, pulse period is 255 cycles
    20. // cntr_low[7:0]==255 then output will be constant LOW
    21. // 0
    22. // cntr_low[7:0]==0 then output will be constant HIGH
    23. //
    24. // Example 2:
    25. // let CNTR_WIDTH = 9
    26. // let cntr_max[8:0] = 255, pulse period is 256 cycles
    27. // cntr_low[8:0]>255 then output will be constant LOW
    28. // 0
    29. // cntr_low[8:0]==0 then output will be constant HIGH
    30. //
    31. // In Example 2 constant LOW state can be acheived also by disabling start
    32. // condition or holding reset input, so cntr_low[8:0] and cntr_max[8:0]
    33. // can be left 8-bit-wide actually
    34. /* --- INSTANTIATION TEMPLATE BEGIN ---
    35. pulse_gen #(
    36. .CNTR_WIDTH( 8 )
    37. ) pg1 (
    38. .clk( clk ),
    39. .nrst( nrst ),
    40. .start( 1'b1 ),
    41. .cntr_max( 255 ),
    42. .cntr_low( 2 ),
    43. .pulse_out( ),
    44. .start_strobe,
    45. .busy( )
    46. );
    47. --- INSTANTIATION TEMPLATE END ---*/
    48. module pulse_gen #( parameter
    49. CNTR_WIDTH = 32
    50. )(
    51. input clk, // system clock
    52. input nrst, // negative reset
    53. input start, // enables new period start
    54. input [CNTR_WIDTH-1:0] cntr_max, // counter initilization value, should be > 0
    55. input [CNTR_WIDTH-1:0] cntr_low, // transition to LOW counter value
    56. output logic pulse_out, // active HIGH output
    57. // status outputs
    58. output logic start_strobe = 1'b0,
    59. output busy
    60. );
    61. logic [CNTR_WIDTH-1:0] seq_cntr = '0;
    62. logic seq_cntr_0;
    63. assign seq_cntr_0 = (seq_cntr[CNTR_WIDTH-1:0] == '0);
    64. // delayed one cycle
    65. logic seq_cntr_0_d1;
    66. always_ff @(posedge clk) begin
    67. if( ~nrst) begin
    68. seq_cntr_0_d1 <= 0;
    69. end else begin
    70. seq_cntr_0_d1 <= seq_cntr_0;
    71. end
    72. end
    73. // first seq_cntr_0 cycle time belongs to pulse period
    74. // second and further seq_cntr_0 cycles are idle
    75. assign busy = ~(seq_cntr_0 && seq_cntr_0_d1);
    76. // buffering cntr_low untill pulse period is over to allow continiously
    77. // changing inputs
    78. logic [CNTR_WIDTH-1:0] cntr_low_buf = '0;
    79. always_ff @(posedge clk) begin
    80. if( ~nrst ) begin
    81. seq_cntr[CNTR_WIDTH-1:0] <= '0;
    82. cntr_low_buf[CNTR_WIDTH-1:0] <= '0;
    83. start_strobe <= 1'b0;
    84. end else begin
    85. if( seq_cntr_0 ) begin
    86. // don`t start if cntr_max[] is illegal value
    87. if( start && (cntr_max[CNTR_WIDTH-1:0]!='0) ) begin
    88. seq_cntr[CNTR_WIDTH-1:0] <= cntr_max[CNTR_WIDTH-1:0];
    89. cntr_low_buf[CNTR_WIDTH-1:0] <= cntr_low[CNTR_WIDTH-1:0];
    90. start_strobe <= 1'b1;
    91. end else begin
    92. start_strobe <= 1'b0;
    93. end
    94. end else begin
    95. seq_cntr[CNTR_WIDTH-1:0] <= seq_cntr[CNTR_WIDTH-1:0] - 1'b1;
    96. start_strobe <= 1'b0;
    97. end
    98. end // ~nrst
    99. end
    100. always_comb begin
    101. if( ~nrst ) begin
    102. pulse_out <= 1'b0;
    103. end else begin
    104. // busy condition guarantees LOW output when idle
    105. if( busy &&
    106. (seq_cntr[CNTR_WIDTH-1:0] >= cntr_low_buf[CNTR_WIDTH-1:0]) ) begin
    107. pulse_out <= 1'b1;
    108. end else begin
    109. pulse_out <= 1'b0;
    110. end
    111. end // ~nrst
    112. end
    113. 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与仿真结果

    1. //------------------------------------------------------------------------------
    2. // pulse_gen_tb.sv
    3. // Konstantin Pavlov, pavlovconst@gmail.com
    4. //------------------------------------------------------------------------------
    5. // INFO ------------------------------------------------------------------------
    6. // testbench for pulse_gen.sv module
    7. `timescale 1ns / 1ps
    8. module pulse_gen_tb();
    9. logic clk200;
    10. initial begin
    11. #0 clk200 = 1'b0;
    12. forever
    13. #2.5 clk200 = ~clk200;
    14. end
    15. // external device "asynchronous" clock
    16. logic clk33;
    17. initial begin
    18. #0 clk33 = 1'b0;
    19. forever
    20. #15.151 clk33 = ~clk33;
    21. end
    22. logic rst;
    23. initial begin
    24. #0 rst = 1'b0;
    25. #10.2 rst = 1'b1;
    26. #5 rst = 1'b0;
    27. //#10000;
    28. forever begin
    29. #9985 rst = ~rst;
    30. #5 rst = ~rst;
    31. end
    32. end
    33. logic nrst;
    34. assign nrst = ~rst;
    35. logic rst_once;
    36. initial begin
    37. #0 rst_once = 1'b0;
    38. #10.2 rst_once = 1'b1;
    39. #5 rst_once = 1'b0;
    40. end
    41. logic nrst_once;
    42. assign nrst_once = ~rst_once;
    43. logic [31:0] DerivedClocks;
    44. clk_divider #(
    45. .WIDTH( 32 )
    46. ) cd1 (
    47. .clk( clk200 ),
    48. .nrst( nrst_once ),
    49. .ena( 1'b1 ),
    50. .out( DerivedClocks[31:0] )
    51. );
    52. logic [31:0] E_DerivedClocks;
    53. edge_detect ed1[31:0] (
    54. .clk( {32{clk200}} ),
    55. .anrst( {32{nrst_once}} ),
    56. .in( DerivedClocks[31:0] ),
    57. .rising( E_DerivedClocks[31:0] ),
    58. .falling( ),
    59. .both( )
    60. );
    61. logic [31:0] RandomNumber1;
    62. c_rand rng1 (
    63. .clk( clk200 ),
    64. .rst( 1'b0 ),
    65. .reseed( rst_once ),
    66. .seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 1) ),
    67. .out( RandomNumber1[15:0] )
    68. );
    69. c_rand rng2 (
    70. .clk( clk200 ),
    71. .rst( 1'b0 ),
    72. .reseed( rst_once ),
    73. .seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 2) ),
    74. .out( RandomNumber1[31:16] )
    75. );
    76. logic start;
    77. initial begin
    78. #0 start = 1'b0;
    79. #100 start = 1'b1;
    80. #20 start = 1'b0;
    81. end
    82. // Modules under test ==========================================================
    83. // simple static test
    84. /*pulse_gen #(
    85. .CNTR_WIDTH( 8 )
    86. ) pg1 (
    87. .clk( clk200 ),
    88. .nrst( nrst_once ),
    89. .start( start ),
    90. .cntr_max( 15 ),
    91. .cntr_low( 0 ),
    92. .pulse_out( ),
    93. .busy( )
    94. );
    95. */
    96. logic [31:0] in_high_width = '0;
    97. logic out;
    98. logic out_rise;
    99. // random test
    100. pulse_gen #(
    101. .CNTR_WIDTH( 8 )
    102. ) pg1 (
    103. .clk( clk200 ),
    104. .nrst( nrst_once ),
    105. .start( start ),
    106. .cntr_max( 16 ),
    107. .cntr_low( {4'b0,RandomNumber1[3:0]} ),
    108. .pulse_out( out ),
    109. .busy( )
    110. );
    111. edge_detect out_ed (
    112. .clk( clk200 ),
    113. .anrst( nrst_once ),
    114. .in( out ),
    115. .rising( out_rise ),
    116. .falling( ),
    117. .both( )
    118. );
    119. always_ff @(posedge clk200) begin
    120. if( ~nrst_once ) begin
    121. in_high_width[31:0] <= 1'b0;
    122. end else begin
    123. if( out_rise ) begin
    124. in_high_width[31:0] <= in_high_width[31:0] + 1'b1;
    125. end
    126. end
    127. end
    128. // PWM test
    129. /*pulse_gen #(
    130. .CNTR_WIDTH( 8 )
    131. ) pg1 (
    132. .clk( clk200 ),
    133. .nrst( nrst_once ),
    134. .start( 1'b1 ),
    135. .cntr_max( 15 ),
    136. .cntr_low( {4'b0,in_high_width[3:0]} ),
    137. .pulse_out( out ),
    138. .busy( )
    139. );*/
    140. 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信号的产生就非常巧妙,我认为很有趣。

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


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

  • 相关阅读:
    转置卷积理论解释(输入输出大小分析)
    软件项目管理课后习题——第7章软件项目的质量管理与配置管理
    队列题目:设计循环双端队列
    UDS协议发展历史
    Postgres数据库使用any和all判断数组解决IN和NOT IN条件参数超限的问题
    golang 求立方根
    自动切换背景的登录页面
    JavaWeb 项目 --- 博客系统(前后分离)
    Java项目实战《苍穹外卖》 二、项目搭建
    030-从零搭建微服务-消息队列(二)
  • 原文地址:https://blog.csdn.net/Alex497259/article/details/126278221