暑期学校要用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周期,而与脉冲到底多宽无关。