综述
有时会纠结于计数模块的复位的判断逻辑if(cnt == NUM-1'b1)
,不知何时需要减一操作,经常需要通过仿真图才能放心,这是非常被动的设计思路,所以,我打算彻底解决这个问题,在代码阶段就能完全掌控模块的功能和时序,刚好项目中遇见了适合的素材,值得记录。
核心观点:
1、定时器和计数器的概念是不等价的,虽然它们都是基于计数功能;
2、定时器是基于周期触发加一操作,基于其自身计数完成触发复位操作;定时器的计数时序是:1、2、… 、NUM-1、0;复位需要等量时钟个数,每一位占据等量时钟;
3、计数器是基于其他变量的判断逻辑触发加一和复位操作操作;计数器的计数时序是:1、2、…、NUM(0);复位发生在最后一位多个时钟里的最后一个或数个时钟;每一位可能占据不同的时钟长度;
这些概念比较空泛,接下来用例子来观察一下。
定时器
接下来,先看看定时器,将设计一个定时器,需求是:每10个时钟产生一个1clk的脉冲信号;
功能模块:
module counter_10(
input clk, //10M
input rst_n,
output O_Pulse_Sig //输出脉冲信号
);
reg rpulse_sig;
reg [3:0] cnt_10;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_10 <= 4'b0;
rpulse_sig <= 1'b0;
end
else if(cnt_10 == 10-1'b1)begin //计数寄存器复位逻辑
cnt_10 <= 4'b0;
rpulse_sig <= 1'b1;
end
else begin
cnt_10 <= cnt_10+1'b1;
rpulse_sig <= 1'b0;
end
end
assign O_Pulse_Sig = rpulse_sig;
endmodule
Testbench:
module counter_10_tb();
//时钟与复位
reg CLK; //10M
reg RST_N; //系统复位
initial begin
RST_N = 0; #2 RST_N = 1;
CLK = 0; forever #50 CLK = ~CLK;
end
//功能模块例化
wire O_Pulse_Sig;
counter_10 counter_10_inst(
.clk (CLK), //10M
.rst_n (RST_N),
.O_Pulse_Sig(O_Pulse_Sig) //输出脉冲信号
);
//激励 无
endmodule
来看一下波形图:
在这里计数寄存器的复位判断是cnt_10 == 10-1'b1
,两个游标的时间间隔是1052ns-52ns=1000ns
刚好是10个时钟,功能时序正确;
定时器的计数寄存器cnt_10
的触发条件是时钟,准确的说,是周期性的时钟上升沿,系统复位后,cnt_10
为零,52ns位置到来第一个时钟沿,计数寄存器进行加一操作,其未来值为1,接着依次2、3、4、5、6、7、8、9,之后,我们必须留出一个时钟给模块进行两个操作:
1、拉高输出脉冲信号,
2、复位计数寄存器,
而这个留出来的时钟就是第十个时钟,这也就是核心观点2;
按照我的定义,那些常见的cnt <= cnt+1'b1;
其实大多都是定时器!只不过是基于这个计数功能,大家就称之为“计数器”了,试着想一想,周期性的触发加一操作,等计数到了一个值就复位并重新计数,这个过程不就是表盘吗?秒针的周期是秒,到了59变成(60)0,时针的周期是3600个秒,到了23变成(24)0,每计数1天,就报个脉冲信号,这不就是上述仿真的定时器吗?所以,我更倾向于叫它基于计数功能的定时器。
计数器
项目中遇见一个实例,简单的说就是在一定时间内,判断输入的信号是125MHz或25MHz,我的思路是:对主时钟120M、待检测的125M或25M进行约分:24:25:5,如果我在24个主时钟下检测到5个txuserclk2
的上升沿,那么当前就是模式1;否则就配置为模式2,配置信号也就是speed_is_10_100
和speed_is_100
。
module freq_detect(
input I_sys_clk_120m,
input I_sys_rst_n,
input S_quad0_txuserclk2_out_2,
output speed_is_10_100_2,
output speed_is_100_2
);
(*mark_debug*)reg S_speed_is_10_100_2 ;
(*mark_debug*)reg S_speed_is_100_2 ;
reg txuserclk2_out_2_reg;
reg txuserclk2_out_2_reg_reg;
wire txuserclk2_out_2_pedge;
always@(posedge I_sys_clk_120m or negedge I_sys_rst_n)begin //±ßÑؼì²â
if(!I_sys_rst_n)begin
txuserclk2_out_2_reg <= 0;
txuserclk2_out_2_reg_reg<= 0;
end
else begin
txuserclk2_out_2_reg <= S_quad0_txuserclk2_out_2;
txuserclk2_out_2_reg_reg <= txuserclk2_out_2_reg;
end
end
assign txuserclk2_out_2_pedge = (~txuserclk2_out_2_reg_reg & txuserclk2_out_2_reg)?1:0;
(*mark_debug*)reg [4:0] cnt_sys_clk_2;
(*mark_debug*)reg [2:0] cnt_pedge_2;
always@(posedge I_sys_clk_120m or negedge I_sys_rst_n)begin
if(!I_sys_rst_n)begin
cnt_sys_clk_2 <= 0;
cnt_pedge_2 <= 0;
end
else begin
if(cnt_sys_clk_2 == 24-1)begin
cnt_sys_clk_2 <= 0;
cnt_pedge_2 <= 0;
if(cnt_pedge_2 == 5)begin //100M
S_speed_is_10_100_2 <= 1;
S_speed_is_100_2 <= 1;
end
else begin //1000M
S_speed_is_10_100_2 <= 0;
S_speed_is_100_2 <= 0;
end
end
else if(txuserclk2_out_2_pedge)begin
cnt_pedge_2 <= cnt_pedge_2+1'b1;
cnt_sys_clk_2 <= cnt_sys_clk_2+1'b1;
S_speed_is_10_100_2 <= S_speed_is_10_100_2;
S_speed_is_100_2 <= S_speed_is_100_2;
end
else begin
cnt_pedge_2 <= cnt_pedge_2;
cnt_sys_clk_2 <= cnt_sys_clk_2+1'b1;
S_speed_is_10_100_2 <= S_speed_is_10_100_2;
S_speed_is_100_2 <= S_speed_is_100_2;
end
end
end
assign speed_is_10_100_2 = S_speed_is_10_100_2;
assign speed_is_100_2 = S_speed_is_100_2;
endmodule
仿真的激励:
module freq_detect_tb();
reg CLK;
reg RST_N;
initial begin
RST_N = 0; #20 RST_N = 1;
CLK = 1; forever #4167 CLK = ~CLK;
end
reg S_quad0_txuserclk2_out_2;
initial begin
S_quad0_txuserclk2_out_2 = 1;
forever #4000 S_quad0_txuserclk2_out_2 = ~S_quad0_txuserclk2_out_2; //20000 4000
end
wire speed_is_10_100_2;
wire speed_is_100_2;
//instance
freq_detect freq_detect_U0(
.I_sys_clk_120m (CLK),
.I_sys_rst_n (RST_N),
.S_quad0_txuserclk2_out_2 (S_quad0_txuserclk2_out_2),
.speed_is_10_100_2 (speed_is_10_100_2),
.speed_is_100_2 (speed_is_100_2)
);
endmodule
看看波形图
125M输入:
25M输入:
模块中,每24个主时钟代码段是定时器,检测到上升沿计数的才是计数器!所以它们的计数时序有些不同,以百兆为例,定时器的计数触发是基于主时钟的上升沿,当其复位时,需要一个等长的时钟数完成计数寄存器的复位;而计数器的计数和复位触发是基于逻辑判断,计数寄存器每位占据的时间可能并不等长,这也是核心观点3。关键分析两个游标175.020ns-208.370ns的时序就很清楚了。
总结
怎么来理解这个呢,我举个栗子,模型是一个特殊的赛跑比赛,规则是在一定时间内,谁跑的圈数多谁赢,对于博尔特来说,他需要两个计数寄存器c1、c2
,c1
用来计数时间走了多久,当规定时间到了,c1
就在最后一拍完成复位,这一拍的长度跟“分辨率”有关,精度是秒的,那复位就用一秒,精度是毫秒的,那就用了一毫秒,但包括复位在内的计数过程,每一位都是等长的;c2
用来计数跑了多少圈,它的触发逻辑是博尔特到达终点线!所以,博尔特可能有的圈跑的快、有的慢、也有可能一直匀速,所以c2
的每一位所用时常是可能不相等的,而c2
计数停止和复位则是发生在c1
计数完成!复位只占了最后一个数据位的一部分;所以,这下就清楚哪个是定时器哪个是计数器了吧!
这是个很简单的例子,但我觉得挺重要的,以前也碰到过,但都是一股脑儿的叫计数器,所以理所当然的以为时序是一样的,教材也是、网上所谓的模板也是,哈哈,还是建议自己动手试试,分析过后会有更合理的理解。