07-从计数器到可控线性序列机
设计功能
使led每隔10ms进行8个状态的亮灭,亮灭的状态可以由用户控制,亮灭的时间长短也可以有用户控制。
代码
设计文件
1 module counter_led_4( 2 Clk, 3 Reset_n, 4 Ctrl, //LED状态控制 5 Time, //LED状态变化的时间 6 led 7 ); 8 input Clk; 9 input Reset_n; 10 input [7:0]Ctrl; 11 input [31:0]Time; 12 reg EN; 13 output reg led; 14 15 reg [18:0]counter0;//EN信号定时 16 reg [31:0]counter; 17 reg [2:0]counter2; 18 //EN信号10ms周期定时 19 always@(posedge Clk or negedge Reset_n)begin 20 if(!Reset_n) 21 counter0 <= 0; 22 else if(counter0 == 5000000-1) //10ms周期 23 counter0 <= 0; 24 else 25 counter0 <= counter0 + 1'd1; 26 end 27 //EN逻辑功能:EN保持高电平时LED工作。 28 always@(posedge Clk or negedge Reset_n)begin 29 if(!Reset_n) 30 EN <= 0; 31 else if(counter0 == 0) 32 EN <= 1; 33 //else if(counter2 == 7) //有问题的写法 34 else if((counter2 == 7)&&(counter == Time -1)) 35 EN <= 0; 36 end 37 //LED状态变化的时间,例如10us改变一次状态。 38 //tb中更改Time的值来实现。 39 always@(posedge Clk or negedge Reset_n)begin 40 if(!Reset_n) 41 counter <= 0; 42 else if(EN)begin 43 if(counter == Time -1) //10us定时 44 counter <= 0; 45 else 46 counter <= counter + 1'd1; 47 end 48 else 49 counter <= 0; //不在EN信号有效电平(高电平)期间,计数值一律清零。 50 end 51 //每隔10us 赋予LED不同的值 52 always@(posedge Clk or negedge Reset_n)begin 53 if(!Reset_n) 54 counter2 <= 0; 55 else if(EN)begin 56 if(counter == Time -1)//计时10usled控制信号的值+1。 57 counter2 <= counter2 + 1'd1; 58 end 59 else 60 counter2 <= 0;//不在EN信号有效电平(高电平)期间,计数值一律清零。 61 end 62 //每隔10us 赋予LED不同的值 63 always@(posedge Clk or negedge Reset_n)begin 64 if(!Reset_n) 65 led <= 0; 66 else case (counter2) 67 0:led <= Ctrl[7]; 68 1:led <= Ctrl[6]; 69 2:led <= Ctrl[5]; 70 3:led <= Ctrl[4]; 71 4:led <= Ctrl[3]; 72 5:led <= Ctrl[2]; 73 6:led <= Ctrl[1]; 74 7:led <= Ctrl[0]; 75 default:led <= led; 76 endcase 77 end 78 79 endmodule
测试文件
1 `timescale 1ns / 1ns 2 module counter_led_tb; 3 reg s_Clk; 4 reg s_Reset_n; 5 reg [7:0]s_Ctrl; 6 reg [31:0]s_Time; 7 wire s_led; 8 9 counter_led_4 counter_led_inst0( 10 .Clk(s_Clk), 11 .Reset_n(s_Reset_n), 12 .Ctrl(s_Ctrl), 13 .Time(s_Time), 14 .led(s_led) 15 ); 16 initial s_Clk = 1; 17 always #10 s_Clk = ~s_Clk; 18 initial begin 19 s_Reset_n = 0; 20 s_Ctrl = 0; 21 s_Time = 0; 22 #201; 23 s_Reset_n = 1; 24 #201; 25 s_Ctrl = 8'b1011_0010; 26 s_Time = 500; 27 #20000; 28 s_Ctrl = 8'b1010_0110; 29 #2000000000; 30 end 31 32 endmodule
仿真结果
正确的仿真结果
仿真结果中,led的亮灭状态与预期结果相同。
有问题的仿真分析
仿真结果中看不到LED的位7的状态,问题出在写成了设计文件第33行else if(counter2 == 7) //有问题的写法,这样的写法有问题,在仿真中led位7的状态不可见,放大时间轴其实可以看到led位7的状态,看不到的原因是只显示了两个时钟周期,时间太短了。
为什么会这样呢?
当counter == 499时,counter此时的值为6(不是7,因为这里是499其实时498+1变成499的,实际上在这个时钟上升沿进行采样判断时,counter的值是498,这是一个极小的时间,ns级别,所以counter2在进行判断的时候,并不符合499的条件,这其中涉及硬件内部结构,D触发器传递从输入到输出是需要一定时间的)。
接着到下一个时钟上升沿时,counter == 499,被清零,counter2判断符合条件计数值+1变成7,这时EN的值还是保持高电平,而不是低电平,原因和上面一样。
接着再到下一个时钟上升沿时,因为EN实际上在上升沿的瞬间还是高电平,所以counter还会+1,变成1,而counter2继续保持7。
最后再走一个时钟周期,这时候EN已经变成低电平了,counter、counter2都清零了。
那么如何解决这个问题?
上面分析到,位7只保持了两个时钟周期,而他需要保持多久?他需要保持10us,也就是500个时钟周期,所以可以利用counter,让他继续计数,计时满10us,才将EN拉低。修改代码为else if((counter2 == 7)&&(counter == Time -1))。
修改后的仿真后结果
当counter=499后,下一个时钟周期EN拉低,counter2和counter都清零了,注意这里的清零并不是由于EN起的作用(原理分析和前面所说的一样),而是由counter计数到499后,就会被自身规则清零,而couter2计数到7后,因为counter2是一个3位数据,加到7再加1就会溢出变成0。