之前的那个讲解配的是一个八位串行输入,总感觉怪怪的,这次我们看一个并行输入的例子。
检测一个不定长度的串中01011字串的个数并在一个数码管上显示。
我们有一个控制输入的按键开关,还有一个异步清零&状态归零的复位键。
一位数码管最大显示数也就是0Fh,相当于十六进制的15,所以我们需要一个四位数的计数器。
将计数部分拿出来作为一个单独的模块,将四位数使用数码管显示的模块也单独拿出来当然这里的数码管显示没有之前那么复杂。
信号还需要一个除颤模块(点击查看之前的讲解),我也尝试过没有除颤,状态会发生跳动,确实是不稳定。(除颤还需要一个1KHZ的分频子模块)
主模块只需要将这些模块串起来,然后调整中间变量即可。
另外我们这个例子是不可覆盖的,如果是101这样的序列我们还需要考虑一下是否是覆盖的
(10101算几个?)
所以主要的部分还是计数的状态机。
这里先使用Moore来进行。(主模块还添加一个led灯判断状态正确与否)
之前说了,上一篇讲状态机的博客采用的是单个状态变量,其实我们也可以用两个变量来实现,即现态和次态分开,但是在输入的过程中因为我们的状态受输入的控制,所以现态是比输入晚一拍的,所以我们不需要两个状态即可实现。
我们也说过正经的状态机都是一个时序部分(状态转换),两个组合(输入输出),但是我们这老不当人的,因为是拨码开关的输入,所以我们还是需要将拨码开关信号作为时钟信号来进行处理。
我们是可以将任意信号作为时钟端进行处理,但是如果是像这样的将输入控制使能端作为always块唯一的posedge,那么就会显示一个报错:
[fil[Place 30-574] Poor placement for routing between an IO pin and BUFG. If this sub optimal condition is acceptable for this design, you may use the CLOCK_DEDICATED_ROUTE constraint in the .xdc file to demote this message to a WARNING. However, the use of this override is highly discouraged. These examples can be used directly in the .xdc file to override this clock rule.
< set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets set_i_IBUF] >
set_i_IBUF_inst (IBUF.O) is locked to IOB_X1Y34
and set_i_IBUF_BUFG_inst (BUFG.I) is provisionally placed by clockplacer on BUFGCTRL_X0Y0
每一个人的报错可能不一样,但是内容相差不大,原因就是因为将信号作为了时钟信号,所以我们需要在约束文件中添加尖括号里面的内容:
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets set_i_IBUF]
`timescale 1ns / 1ps
module count(
input clk_i, //时钟信号
input rst_i, //重置使能,高有效
input set_i, //输入使能
input data_i,
output [3:0]number, //输出的个数
output [2:0]state_o //led灯,判断一下状态转换
);
reg in = 1'b1; //读入的数据,初始化为1防止1011
reg if_in1 = 1'b0; //输入端使能
reg if_in2 = 1'b0;
reg if_add1 = 1'b0; //自增使能
reg if_add2 = 1'b0;
reg [3:0]counter = 4'b0000; //下面四句为处理输出变量赋初值问题的
reg [2:0]state = 3'b001;
assign number = counter;
assign state_o = state;
always @(posedge set_i) //读入
begin
in <= data_i;
if_in1 <= ~if_in1;
end
//状态转换,因为是异步清零,所以需要将rst信号也放上去
always @(posedge clk_i or posedge rst_i)
begin
if(rst_i)
begin
state <= 3'b001;
end
else if(if_in2 != if_in1)
begin
if_in2 <= if_in1;
case({in,state})
4'b0000:state <= 3'b000;
4'b0001:state <= 3'b000;
4'b0010:state <= 3'b011;
4'b0011:state <= 3'b000;
4'b0100:state <= 3'b011;
4'b0101:state <= 3'b000;
4'b1000:state <= 3'b010;
4'b1001:state <= 3'b001;
4'b1010:state <= 3'b001;
4'b1011:state <= 3'b100;
4'b1100:state <= 3'b101;
4'b1101:state <= 3'b001;
default;
endcase
if(state == 3'b101)
if_add1 <= ~if_add1; //表示需要计数器自增
else;
end
else;
end
always @(posedge clk_i)
begin
if(rst_i)
counter <= 4'b0000;
else if(if_add1 != if_add2)
begin
counter <= counter+1'b1;
if_add2 <= if_add1;
end
else
counter <= counter;
end
endmodule
最让人头疼的应该就是if_in和if_add两组变量了,我们分别来讲解。
if_in的功能是判断是否有输入,因为我们说过每一次读入只能发生一次状态改变,所以我们需要设置一个变量作为使能信号给状态转变always块,但是这样我们就需要在两个模块都修改使能信号的值,所以我的方式就是设置两个变量实现在两个模块中进行修改。
同样,递增信号也是如此,我们的输出模块是时钟信号,但是当我们进入状态5没有下一个输入就会一直保持,导致一次会进行很多次的自增,所以我们也需要这样一组变量进行处理。
那两个assign语句,是因为我们的输入变量没有合适的在always语句块初始化条件,所以我创建了两个新的变量来进行修改、赋值。
显示模块就是简单的数码管显示,看懂了上面链接的博客应该就没什么问题了。
这里我们采用的是输入四位数据,然后进行一个译码即可。
`timescale 1ns / 1ps
module light(
input [3:0]number,
output [3:0]en,
output reg [7:0]led_o
);
assign en = 4'b0001;
always@(*)
begin
case (number)
4'b0000 : led_o = 8'b1111_1100;
4'b0001 : led_o = 8'b0110_0000;
4'b0010 : led_o = 8'b1101_1010;
4'b0011 : led_o = 8'b1111_0010;
4'b0100 : led_o = 8'b0110_0110;
4'b0101 : led_o = 8'b1011_0110;
4'b0110 : led_o = 8'b1011_1110;
4'b0111 : led_o = 8'b1110_0000;
4'b1000 : led_o = 8'b1111_1110;
4'b1001 : led_o = 8'b1111_0110;
4'b1010 : led_o = 8'b1110_1110;
4'b1011 : led_o = 8'b0011_1110;
4'b1100 : led_o = 8'b0001_1010;
4'b1101 : led_o = 8'b0111_1010;
4'b1110 : led_o = 8'b1001_1110;
4'b1111 : led_o = 8'b1000_1110;
default;
endcase
end
endmodule
顶层模块:(真没啥了,不想说了)
`timescale 1ns / 1ps
module ex5_plus(
input rst_n_i,
input clk_i,
input set_i,
input data_i,
output [3:0]leden_o,
output [7:0]ledcx_o,
output [2:0]led
);
wire [3:0]number;
wire set;
chuchan cc(set_i,clk_i,set);
count counter(clk_i,rst_n_i,set,data_i,number,led);
light lighter(number,leden_o,ledcx_o);
endmodule
如果真的是下板验证,会发现在正确输入后板子不会有反应,这是因为我们采用的是非阻塞赋值的方式,需要等到下一个时钟上升沿(这里的时钟信号是set_i),所以我们需要再次进行输入,才能看到计数器的变化。
led灯显示的是状态转变,可以考虑看一下。
我们在之前的思路其实也差不多,只要将上面的几个细节想清楚,剩下的都还好,所以我们也是直接上代码:
`timescale 1ns / 1ps
module count(
input clk,
input rst,
input set,
input data,
output [3:0]number,
output [2:0]state
);
//设置初始状态初始状态
reg [2:0]led = 3'b001 ;
reg [3:0]num = 4'b0000;
assign state = led;
assign number= num;
reg if_in1 = 1'b0; //输入端使能
reg if_in2 = 1'b0;
reg if_add1 = 1'b0; //自增使能
reg if_add2 = 1'b0;
reg in = 1'b1;
//读入语句块
always @(posedge set)
begin
in <= data;
if_in1 <= ~if_in1;
end
//状态转换,因为是异步清零,所以需要将rst信号也放上去
always @(posedge clk or posedge rst)
begin
if(rst)
begin
led <= 3'b001;
end
else if(if_in2 != if_in1)
begin
if_in2 <= if_in1;
case({in,led})
4'b0000:led <= 3'b000;
4'b0001:led <= 3'b000;
4'b0010:led <= 3'b011;
4'b0011:led <= 3'b000;
4'b0100:led <= 3'b011;
4'b1000:led <= 3'b010;
4'b1001:led <= 3'b001;
4'b1010:led <= 3'b001;
4'b1011:led <= 3'b100;
4'b1100:led <= 3'b001;
default;
endcase
if(led == 3'b100 && in == 1'b1)
if_add1 <= ~if_add1;
else;
end
else;
end
always @(posedge clk)
begin
if(rst)
num <= 4'b0000;
else if(if_add1 != if_add2)
begin
num <= num+1'b1;
if_add2 <= if_add1;
end
else
num <= num;
end
endmodule
这里因为是mealy型,需要同时看输入和状态,所以我们不需要等一个周期才能看到数码管的变化。
(其实也相当于再次按下输入,因为在自增的过程中状态从4到了1)