赞
踩
碎碎念:
明明是周四,这周竟然不开组会_(:з)∠)_
那我可以继续愉快地学习人家的代码了,这篇博客介绍的是脉冲发生器,脉冲和Killer Queen是不是很配呢hhh
目录
通过设置参数cntr_max与cntr_low,可以产生任意周期数与占空比的脉冲信号。
- //------------------------------------------------------------------------------
- // 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
-
在明确了整个模块的功能之后,也就比较看出主要思路就是利用计数器的原理,利用对计数值的判断,来控制输出脉冲的信号,下面来具体进行说明。
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。
- //------------------------------------------------------------------------------
- // 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.复用之前的随机数模块
同样对之前的随机数模块以及时钟分频电路进行了复用,从而提高测试的说服力。同时使用到了边沿检测器,用来对输出的脉冲进行上升沿检测。对随机数产色模块以及边沿检测不了解的,可以跳转:
同时注意到,为了生成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信号的产生就非常巧妙,我认为很有趣。
最终证明,波形的展示与我们的分析是保持一致的~
这就是本期的全部内容啦,如果你喜欢我的文章,不要忘了点赞+收藏+关注,分享给身边的朋友哇~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。