前言

放假在写verilog的时候,在写到串口数据再次向更高位的并口数据转化的时候,由于串口速率太慢而导致转换完以后的uart_rx_done高电平保持时间太长,而数据的下一走向的时钟速率太快,导致使用该信号作为进行每次的串口数据读取的使能信号出现了小小的问题,在信号保持高电平时候反复读取从而出现了问题。在简单的思考过后,产生了脉冲转换的一个想法,大致思路就是将长脉冲转换为窄脉冲,窄脉冲的宽度为当前时clk的一个周期,同时为了保证同步,转换前后的脉冲应该保证上升沿的同步,而不是转换后的脉冲被延时了一个周期。

至于代码嘛,优先选择前人的经验了,下为参考。

代码实现

一版实现

对信号延时一个周期,然后进行一个异或。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

20240810174023

图1 仿真结果1

实现的思路很简单,缺点是会延时一个时钟周期。在一些情况下不是什么大问题,但终究不是完美。

改进代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
initial begin
bef = 0;
PULSE = 0;
end
endmodule

20240810174036

图2 仿真结果2

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

20240810174048

图3 仿真结果3

终极改进

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

PS: 其实我并没有看懂这句话到底是什么意思,但是作者原文的意思大致就是上一版本的代码在转换过程存在一定的问题orz,所以有了下面的版本。说不定我的打黑工交上去的代码在联合仿真时候出现了问题也是没用最终版的原因。还是看看下面的代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

20240810174101

图4 仿真结果4

在这里我们关心的只有信号的上升沿,那么把信号的上升沿先转换成电平的跳变,然后做延时再进行取异或输出就可以了,这里下降沿被巧妙的规避了。
此刻,对于间隔大的脉冲,其高电平长度都将小于一个CLK周期,而与脉冲到底多宽无关。这样这个模块就可以应用到更多的地方了。

同样的使用VScode来进行简单的仿真,下为tb文件,相比高贵的Vivado多了几行代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//~ `New testbench
`include "POSEDGE_TO_PULSE.v"
`timescale 1ns / 1ps

module tb_POSEDGE_TO_PULSE;
// ptp Parameters
parameter PERIOD = 10;
// ptp Inputs
reg CLK = 0 ;
reg SIGNAL = 0 ;
// ptp Outputs
wire PULSE ;

initial
begin
forever #(PERIOD/2) CLK=~CLK;
end

ptp u_ptp (
.CLK ( CLK ),
.SIGNAL ( SIGNAL ),
.PULSE ( PULSE )
);
initial
begin
#15
SIGNAL <= 1;
#50
SIGNAL <= 0;
#10
SIGNAL <= 1;
#50
SIGNAL <= 0;
#10
$finish;
end
/*生成仿真所需的vcd文件*/
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb_POSEDGE_TO_PULSE);
end
endmodule