在FPGA设计中,计数器是非常常用的,没有计数器就无法处理时序,下面记录几种计数器的写法:
一、时序逻辑与组合逻辑分开
1.1、代码
//======================================================================
// --- 名称 : Count_1
// --- 作者 : 又菜又爱玩
// --- 日期 : 2024-8-28
// --- 描述 : 模10计数器,0到10循环累加
//======================================================================
module Count_1
(
input clk ,
input rst_n ,
output reg [ 3:0] cnt
);
//----------------------------------------------------------------------
//-- 组合电路
//----------------------------------------------------------------------
reg [ 3:0] cnt_n ;
always @(*)begin
if(cnt == 4'd9)
cnt_n = 4'd0;
else
cnt_n = cnt + 1'b1;
end
//----------------------------------------------------------------------
//-- 时序电路
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 4'b0;
else
cnt <= cnt_n;
end
endmodule
/*
//----------------------------------------------------------------------
//-- 组合电路也可以这样写
//----------------------------------------------------------------------
wire [ 3:0] cnt_n ;
assign cnt_n = (cnt==4'd9)? 4'd0 : cnt+1'b1;
*/
1.2、写法1的RTL视图
1.3:写法2的RTL视图
二、常用写法
2.1、代码
//======================================================================
// --- 名称 : Count_2
// --- 作者 : 又菜又爱玩
// --- 日期 : 2024-8-28
// --- 描述 : 模10计数器,0到10循环累加
//======================================================================
module Count_2
(
input clk ,
input rst_n ,
output reg [3:0] cnt
);
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 4'd0;
else if (cnt == 4'd9)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
endmodule
2.2、RTL视图
三、代码片段法
//======================================================================
// --- 名称 : Count_3
// --- 作者 : 又菜又爱玩
// --- 日期 : 2024-8_28
// --- 描述 : 模10计数器,0到10循环增加
//======================================================================
module Count_3
(
clk,
rst_n,
cnt
);
//---------------------<端口声明>---------------------------------------
input clk ;
input rst_n ;
output reg [3:0] cnt ;
//---------------------<信号定义>---------------------------------------
wire add_cnt ;
wire end_cnt ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 'd0;
else if(add_cnt)begin
if(end_cnt)
cnt <= 'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= cnt;
end
assign add_cnt = 1;
assign end_cnt = add_cnt && cnt==10-1;
endmodule
四、其它计数器
源自开源骚客《SDRAM那些事儿》系列教程。
要求:现在对OV5640摄像头进行上电控制,由数据手册得到上电时序图如下所示,现用Verilog实现其波形
4.1、代码片段法
module power_ctrl
//========================< 端口 >==========================================
(
//system --------------------------------------------
input wire clk , // 50MHz
input wire rst_n ,
//ov5640 --------------------------------------------
output reg ov5640_pwdn , // ov5640上电
output reg ov5640_rst_n , // ov5640复位
output reg power_done // power_ctrl全面有效,SCCB可以开始工作
);
//========================< 参数 >==========================================
localparam T2_6MS = 30_0000 ; // T2>5ms
localparam T3_2MS = 10_0000 ; // T3>1ms
localparam T4_21MS = 105_0000 ; // T4>20ms
//========================< 信号 >==========================================
reg [18:0] cnt_6ms ;
wire add_cnt_6ms ;
wire end_cnt_6ms ;
reg [16:0] cnt_2ms ;
wire add_cnt_2ms ;
wire end_cnt_2ms ;
reg [20:0] cnt_21ms ;
wire add_cnt_21ms ;
wire end_cnt_21ms ;
//==========================================================================
//== ov5640_pwdn
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_6ms <= 'd0;
else if(add_cnt_6ms) begin
if(end_cnt_6ms)
cnt_6ms <= 'd0;
else
cnt_6ms <= cnt_6ms + 1;
end
end
assign add_cnt_6ms = ov5640_pwdn == 1'b1;
assign end_cnt_6ms = add_cnt_6ms && cnt_6ms== T2_6MS-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ov5640_pwdn <= 1'b1;
end
else if(end_cnt_6ms) begin
ov5640_pwdn <= 1'b0;
end
end
//==========================================================================
//== ov5640_rst_n
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_2ms <= 'd0;
else if(add_cnt_2ms) begin
if(end_cnt_2ms)
cnt_2ms <= 'd0;
else
cnt_2ms <= cnt_2ms + 1'b1;
end
end
assign add_cnt_2ms = ov5640_rst_n == 1'b0 && ov5640_pwdn == 1'b0;
assign end_cnt_2ms = add_cnt_2ms && cnt_2ms== T3_2MS-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ov5640_rst_n <= 1'b0;
end
else if(end_cnt_2ms) begin
ov5640_rst_n <= 1'b1;
end
end
//==========================================================================
//== power_done
//==========================================================================
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_21ms <= 'd0;
else if(add_cnt_21ms) begin
if(end_cnt_21ms)
cnt_21ms <= 'd0;
else
cnt_21ms <= cnt_21ms + 1'b1;
end
end
assign add_cnt_21ms = power_done == 1'b0 && ov5640_rst_n == 1'b1;
assign end_cnt_21ms = add_cnt_21ms && cnt_21ms== T4_21MS-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
power_done <= 1'b0;
end
else if(end_cnt_21ms) begin
power_done <= 1'b1;
end
end
endmodule
仿真波形:
4.2、开源骚客计数器
module power_ctrl
//========================< 端口 >==========================================
(
//system --------------------------------------------
input wire clk , // 50MHz
input wire rst_n ,
//ov5640 --------------------------------------------
output wire ov5640_pwdn , // ov5640上电
output wire ov5640_rst_n , // ov5640复位
output wire power_done // power_ctrl全面有效,SCCB可以开始工作
);
//========================< 参数 >==========================================
localparam T2_6MS = 30_0000 ; // T2>5ms
localparam T3_2MS = 10_0000 ; // T3>1ms
localparam T4_21MS = 105_0000 ; // T4>20ms
//========================< 信号 >==========================================
reg [18:0] cnt_6ms ;
reg [16:0] cnt_2ms ;
reg [20:0] cnt_21ms ;
//==========================================================================
//== ov5640_pwdn
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_6ms <= 'd0;
end
else if(ov5640_pwdn == 1'b1) begin
cnt_6ms <= cnt_6ms + 1'b1;
end
end
assign ov5640_pwdn = (cnt_6ms >= T2_6MS) ? 1'b0 : 1'b1;
//==========================================================================
//== ov5640_rst_n
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_2ms <= 'd0;
end
else if(ov5640_rst_n == 1'b0 && ov5640_pwdn == 1'b0) begin
cnt_2ms <= cnt_2ms + 1'b1;
end
end
assign ov5640_rst_n = (cnt_2ms >= T3_2MS) ? 1'b1 : 1'b0;
//==========================================================================
//== power_done
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_21ms <= 'd0;
end
else if(power_done == 1'b0 && ov5640_rst_n == 1'b1) begin
cnt_21ms <= cnt_21ms + 1'b1;
end
end
assign power_done = (cnt_21ms >= T4_21MS) ? 1'b1 : 1'b0;
endmodule
可以看到开源骚客写法较为简洁,代码精简很多。仿真波形如下:
从两个仿真波形可以看到,代码片段法计满之后会进行清0,而开源骚客的方法在计满之后会进行保持。
参考资料
[1]小梅哥FPGA教程
[2]明德扬FPGA教程
[3]开源骚客《SDRAM那些事儿》