Verilog 长脉冲转短脉冲

暑期学校要用FPGA写一个钟。在我的设计里,计数器的CLK由上一个计数器进位的上升沿充当。这将导致计数器的进位脉冲宽度和驱动其的CLK周期相同。如果该计数器表示小时,那么它的进位将1小时更新一次,即其进位的高电平将保持一个小时。

如果只是计时,这样可以满足要求。但是需要自己设置时间,意味着寄存器的更新还要能通过按钮的脉冲(称为ADD)控制。在我的设计里,实际更新寄存器信号是用复用器选择的CLK和ADD之一。问题来了,如果在CLK长达1小时的高电平期间,切换到ADD信号,再切换回去,就会多出一个上升沿,导致计数器无辜+1。因为由于ADD是按钮脉冲,故大多时候为低电平。如果在ADD为低电平的时候离开设置模式,即切换回长达1小时高电平的CLK信号,就会产生上升沿。

解决办法是写一个长脉冲转短脉冲的模块。

第一版,检测电平变化,CLK为高频,比如100Hz:

module POSEDGE_TO_PULSE(
    input CLK,
    input SIGNAL,
    output reg PULSE
);
    reg prev_SIGNAL;

    always @(posedge CLK) begin
        // 如果上一刻是低电平且此时为高电平才高
        PULSE <= !prev_SIGNAL && SIGNAL;
        prev_SIGNAL <= SIGNAL;
    end

    initial begin
        prev_SIGNAL = 0;
        PULSE = 0;
    end
endmodule

 但是用到进位上成了这样:

还是叫这个为上升沿延迟模块吧...重新写一个。

冥思苦想(想了很久很久!),想到一个:

module POSEDGE_TO_PULSE(
    input CLK,
    input SIGNAL,
    output reg PULSE
);
    reg bef;
    always @(posedge CLK) begin
        bef <= SIGNAL;
    end
    wire clk = CLK & SIGNAL;
    always @(posedge clk) begin
        PULSE = !bef;
    end
endmodule

 原理:由于CLK导致SIGNAL(时钟导致进位),所以SIGNAL的上升沿会落后于CLK的上升沿,利用了两个always的先后。但是这有一个缺点:输出为2个CLK周期长度。改进方法是增加采样密度,所以用双边沿采样技术,即两次异或。代码如下:

module POSEDGE_TO_PULSE(
    input CLK,
    input SIGNAL,
    output reg PULSE
);
    reg bef_pos;
    reg bef_neg;
    always @(posedge CLK) begin
        #1 bef_pos <= SIGNAL ^ bef_neg;
    end
    always @(negedge CLK) begin
        #1 bef_neg <= SIGNAL ^ bef_pos;
    end
    wire bef = bef_pos ^ bef_neg;

    wire clk = CLK & SIGNAL;
    always @(posedge clk) begin
        PULSE = !bef;
    end
endmodule

关于上述代码的"#1",是因为仿真的时候似乎有了竞争冒险,(没加的时候)导致寄存器没锁住,输出为X。加了之后就完全没有问题。

但是,实际上的延迟真的会让它正常工作吗?要改方法。似乎只要在clk上升沿之前更新bef即可。那不如在CLK的下降沿触发!

最终代码如下:

module POSEDGE_TO_PULSE(
    input CLK,
    input SIGNAL,
    output reg PULSE
);
    reg bef;
    always @(negedge CLK) begin
        bef <= SIGNAL;
    end
    wire clk = CLK & SIGNAL;
    always @(posedge clk) begin
        PULSE = !bef;
    end

    initial begin
        bef = 0;
        PULSE = 0;
    end
endmodule

 写一个test bench:

module pulse_test();
    reg clk;
    wire [3:0] s1, s2, s3;
    wire [2:0] c;
    COUNTER #(9) c1(clk, 0, 0, c[0], s1);
    COUNTER #(9) c2(c[0], 0, 0, c[1], s2);

    wire cc1;
    POSEDGE_TO_PULSE pc1(clk, c[1], cc1);
    COUNTER #(9) c3(cc1, 0, 0, c[2], s3);

    always #1 clk = ~clk;
    initial begin
        clk = 0;
    end
endmodule

 完美解决问题!

学数电以来最佩服自己的一次!真的想了好久! 

但是。。。对这个“长脉冲”还是有限制:必须覆盖一个上升沿和一个下降沿。万一输入的信号并不长怎么办呢?于是有了下面的更简单的代码:

module POSEDGE_TO_PULSE (
    input CLK,
    input SIGNAL,
    output PULSE
);
    reg x, y;
    always @(posedge SIGNAL) begin
        x <= ~x;
    end
    always @(posedge CLK) begin
        y <= x;
    end
    assign PULSE = y ^ x;

    initial begin
        x = 0;
        y = 0;
    end
endmodule

此刻,模块功能从“长脉冲转短脉冲”,成了“脉冲规格化”,对于间隔大的脉冲,其高电平长度都将小于一个CLK周期,而与脉冲到底多宽无关。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值