【verilog】循环与计数
0赞在verilog的世界里,循环与计数有着“暧昧的”关系。
不知道,读者有没有这种感觉——明明我想循环10次,结果怎么才9(却有11)次??
明明我想延时10个时钟,为什么只有9(却有11)??
这种不确定感觉,也曾经一度困扰这笔者,今天我们就让“循环”和“计数”做到心中
有数!
循环,编程中再常见不过了,verilog中如何实现循环?For?NO!For是不推荐使用的。
在verilog中推荐用状态机实现循环,做个实验,循环发送7个脉冲:
reg [3:0]i; reg [3:0]num1; always @(posedge CLK or negedge RSTn) if(!RSTn) begin i <= 4'd0; num1 <= 4'd0; plus <= 1'b0; end else case(i) 0://循环判断 begin if(num1 == 7)begin num1 <= 4'd0; i <= 4'd3; end //跳出循环 else i <= i + 1'b1; //进入循环 end 1://循环体 begin //具体操作内容 plus <= 1'b1; i <= i + 1'b1; end 2: begin plus <= 1'b0; num1 <= num1 + 1'b1;//循环次数加1 i <= 4'd0; end 3://循环出口 begin i <= i; end endcase
整个循环分为3个主体部分:
1、循环判断:判断——是继续进入循环体,还是跳出循环。
2、循环体:循环时,需要完成的任务。
3、循环出口:跳出循环的去处。
过程分析:
首先,num1是用来计算循环的次数,所以初始值为0,表示一开始的时候,一次也
没有循环。
其次,循环判断置于循环的最上方,进入循环体后,执行每次循环需完成的任务,任务
完成后,累加num1,跳回循环判断状态。直到num1等于7,才跳出循环。
我们发现,num1从头到尾,始终能反映循环的真实次数,num1等于几,就表示当前已经
循环了都少次,绝不含糊(这个就叫做循环不变式!)
那么当我们判断num1 == 7是,表明程序已经循环了7次(而不是模棱两可的,是第7次,还是已经7次了?)也就是说已经发了7个脉冲!此时跳出循环。
如此一来,循环次数,心中有数,num1 就是我们的指示标,仿真如下:
如果,感觉还不过瘾,我们看看,循环如何嵌套,我继续做实验,每个脉冲下再发3个脉冲。
思路基本是一样的,我先把仿真图贴出来如下,笔者想想如何用嵌套循环实现~~
实现程序如下:
reg [3:0]i; reg [3:0]num1; reg [3:0]num2; always @(posedge CLK or negedge RSTn) if(!RSTn) begin i <= 4'd0; num1 <= 4'd0; num2 <= 4'd0; plus <= 1'b0; pp <= 1'b0; end else case(i) 0://W判断 begin if(num1 == 7)begin num1 <= 4'd0; i <= 4'd6; end //跳出循环 else i <= i + 1'b1; //进入循环 end 1://W循环体 begin plus <= 1'b1; i <= i + 1'b1; end 2://N循环判断 begin if(num2 == 3)begin num2 <= 4'd0; i <= 4'd5; end //跳出循环 else i <= i + 1'b1; //进入循环 end 3://N循环体入口 begin pp <= 1'b1; i <= i + 1'b1; end 4://N循环体 begin pp <= 1'b0; i <= 4'd2; num2 <= num2 + 1'b1;//循环次数加1 end 5://W循环体,N入出口 begin plus <= 1'b0; i <= 4'd0; num1 <= num1 + 1'b1;//循环次数加1 end 6://W出口 begin i <= i; end endcase
W表示外层循环,N表示内层循环,为了可读性更强,我故意将内层循环缩进了~~
//------------------------------------------------------------------------------------------------------
好了循环,说完了,我们再来看看计数!
在驱动一些芯片的时候,我们往往会去写一些驱动时序。FPGA的时钟往往是比较快的,我们往往通过
计数来实现精准的延时。
举个例子,FPGA跑在100M时钟下(周期是10ns),一个芯片的读信号需要持续拉高40ns有效,
那么FPGA就需要将读信号置高4个周期。此时我们就要借助计数的力量了。
我来模拟一下这个程序:(计数,试验1)
reg [3:0]i; always @(posedge CLK or negedge RSTn) if(!RSTn) begin i <= 4'd0; C0 <= 4'd0; read <= 1'b0; end else case(i) 0: begin read <= 1'b1; if(C0 == 4)begin C0 <= 4'd0; i <= i + 1'b1; end else begin C0 <= C0 + 1'b1; end end 1: begin read <= 1'b0; i <= i + 1; end 2: begin read <= 1'b1; if(C0 == 4)begin C0 <= 4'd0; i <= i + 1'b1; end else begin C0 <= C0 + 1'b1; end end 3: begin read <= 1'b0; i <= i; end endcase
我们的意图是延时4个周期,但是实际却延时了五个周期,原因是归零的时候,用了一个周期。
我修改程序如下:(计数,试验2)
reg [3:0]i; always @(posedge CLK or negedge RSTn) if(!RSTn) begin i <= 4'd0; C0 <= 4'd0; read <= 1'b0; end else case(i) 0://第一次拉高 begin if(C0 == 4)begin C0 <= 4'd0; i <= i + 1'b1; read <= 1'b1;end else begin C0 <= C0 + 1'b1; read <= 1'b1;end end 1: begin read <= 1'b0; i <= i + 1; end 2://第二次拉高 begin if(C0 == 4)begin C0 <= 4'd0; i <= i + 1'b1; read <= 1'b1;end else begin C0 <= C0 + 1'b1; read <= 1'b1;end end 3: begin read <= 1'b0; i <= i; end endcase
此时,发现此时read是拉高了4个周期,与预期相符。但是你还得发现,此时两次拉高直接的间隔由一个
周期变成了两个周期。如果为此你感觉不爽,你可以继续改:(计数,试验3)
reg [3:0]i; always @(posedge CLK or negedge RSTn) if(!RSTn) begin i <= 4'd0; C0 <= 4'd0; read <= 1'b0; end else case(i) 0: begin read <= 1'b1; if(C0 == 3)begin C0 <= 4'd0; i <= i + 1'b1; end else begin C0 <= C0 + 1'b1; end end 1: begin read <= 1'b0; i <= i + 1; end 2: begin read <= 1'b1; if(C0 == 3)begin C0 <= 4'd0; i <= i + 1'b1; end else begin C0 <= C0 + 1'b1; end end 3: begin read <= 1'b0; i <= i; end endcase
小结一下:以上面这种形式,计数时,每次等于4的时刻,应该是停止计数的时刻,也是复位计数值的
时刻,也同时可以是状态跳转的时刻。这个“时刻”是会消耗一个周期的。你可以将其利用起来,如
试验3,好处是节约了一个时钟,弊端是可读性,就没有试验2好了。
我还是比较推荐试验2这种形式,因为他和之前讲的循环的思维是统一的(等于4是停止计数的时刻,
类似于,跳出循环的时刻)。
//---------------------------------------------------------------------------------------------------------------------
为了进一步,看清问题的本质,我们继续做两个试验:
reg [3:0]C0; always @(posedge CLK or negedge RSTn) if(!RSTn) begin C0 <= 4'd0; end else begin if(C0 == 4)begin C0 <= 4'd0; end else begin C0 <= C0 + 1'b1; end end
这是一个无限的循环,第一次间隔的是4个时钟周期,之后的间隔都是5个周期。因为第一次置0这个事情是复位完成的,或者说,初始值本就是0,而之后的却是需要一个周期去完成置0,所以间隔变成了5。
reg [3:0]C0; always @(posedge CLK or negedge RSTn) if(!RSTn) begin C0 <= 4'd0; end else begin if(C0 == 4)begin C0 <= 4'd1; end else begin C0 <= C0 + 1'b1; end end
这样的话,直接利用C0 == 4这个周期,完成4到1的转变,从而之后的间隔也都变成了4.
总结:
1、循环注意,循环不变式,利用它,做到循环次数心中有数。
2、计数注意,停止计数的时刻(计数复位的时刻),是会消耗时钟的,你可以将其利用起来,充当计数周期,
也可以让其作为单纯的复位时刻。
3、注意循环和计数的相似性。
技术讨论欢迎加群~~电子技术协会 362584474