目录
前言
3.2.5 Finite State Machines
3.2.5.17 Serial receiver(Fsm serial)
3.2.5.18 Serial receiver and datapath(Fsm serialdata)
3.2.5.19 Serial receiver with parity checking(Fsm serialdp)
结语
HDLbits网站链接
今天终于来到了这几道题,其中第三道题博主不知道尝试了多少次,就是成功不了,真的是折磨啊......最后success才明白作者的意图是mealy型状态机,而我一直用的moore型状态机,多加了几个状态,导致一直不成功。这里的第三题也是博主完成的最后一道题,看到success时候的小小成就感还是很不错的,终于搞定全部题目~O(∩_∩)O~。好啦,下面就开始更新答案吧。
module top_module(
input clk,
input in,
input reset, // Synchronous reset
output done
);
parameter IDLE = 4'd0, START = 4'd1, S1 = 4'd2, S2 = 4'd3, S3 = 4'd4, S4 = 4'd5;
parameter S5 = 4'd6, S6 = 4'd7, S7 = 4'd8, S8 = 4'd9, STOP = 4'd10, WAIT = 4'd11;
reg [3:0] current_state;
reg [3:0] next_state;
always@(posedge clk)begin
if(reset)begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end
always@(*)begin
case(current_state)
IDLE:begin
next_state = in ? IDLE : START;
end
START:begin
next_state = S1;
end
S1:begin
next_state = S2;
end
S2:begin
next_state = S3;
end
S3:begin
next_state = S4;
end
S4:begin
next_state = S5;
end
S5:begin
next_state = S6;
end
S6:begin
next_state = S7;
end
S7:begin
next_state = S8;
end
S8:begin
next_state = in ? STOP : WAIT;
end
STOP:begin
next_state = in ? IDLE : START;
end
WAIT:begin
next_state = in ? IDLE : WAIT;
end
default:begin
next_state = IDLE;
end
endcase
end
assign done = (current_state == STOP);
endmodule
第一道题目比较容易,题目中的in信号包含了一个起始位(0),8个数据位和一个停止位(1),开始in为1,也就是IDLE状态,当in为0是,进入START状态,然后经过8个周期,如果in为1,则进入STOP状态,接着如果in为1,进入第二轮START状态,否则进入IDLE状态。
这里博主使用了很多个状态,实际上可以用计数器来代替中间的8个状态,这里是8个周期,如果是100个、200个周期,需要100个、200个状态,显然不现实,这道题目是这三题目中的基础题,大家不用考虑那么多,直接完成就好了。
module top_module(
input clk,
input in,
input reset, // Synchronous reset
output [7:0] out_byte,
output done
); //
parameter IDLE = 3'd0, START = 3'd1, DATA = 3'd2;
parameter STOP = 3'd3, WAIT = 3'd4;
reg [3:0] current_state;
reg [3:0] next_state;
reg [3:0] counter;
reg [7:0] par_in;
always@(posedge clk)begin
if(reset)begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end
always@(*)begin
case(current_state)
IDLE:begin
next_state = in ? IDLE : START;
end
START:begin
next_state = DATA;
end
DATA:begin
if(counter == 4'd8)begin
if(in)begin
next_state = STOP;
end
else begin
next_state = WAIT;
end
end
else begin
next_state = DATA;
end
end
STOP:begin
next_state = in ? IDLE : START;
end
WAIT:begin
next_state = in ? IDLE : WAIT;
end
default:begin
next_state = IDLE;
end
endcase
end
always@(posedge clk)begin
if(reset)begin
done <= 1'b0;
out_byte <= 8'd0;
counter <= 4'd0;
end
else begin
case(next_state)
IDLE:begin
done <= 1'b0;
out_byte <= 8'd0;
counter <= 4'd0;
end
START:begin
done <= 1'b0;
out_byte <= 8'd0;
counter <= 4'd0;
end
DATA:begin
counter <= counter + 1'b1;
done <= 1'b0;
par_in[counter] <= in;
out_byte <= 8'd0;
end
STOP:begin
done <= 1'b1;
out_byte <= par_in;
end
WAIT:begin
done <= 1'b0;
out_byte <= 8'd0;
end
default:begin
done <= 1'b0;
out_byte <= 8'd0;
counter <= 3'd0;
end
endcase
end
end
endmodule
/*
//second way
module top_module(
input clk,
input in,
input reset, // Synchronous reset
output [7:0] out_byte,
output done
); //
parameter IDLE = 4'd0, START = 4'd1, S1 = 4'd2, S2 = 4'd3, S3 = 4'd4, S4 = 4'd5;
parameter S5 = 4'd6, S6 = 4'd7, S7 = 4'd8, S8 = 4'd9, STOP = 4'd10, WAIT = 4'd11;
reg [3:0] current_state;
reg [3:0] next_state;
reg [7:0] par_in;
always@(posedge clk)begin
if(reset)begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end
always@(*)begin
case(current_state)
IDLE:begin
next_state = in ? IDLE : START;
end
START:begin
par_in[0] = in;
next_state = S1;
end
S1:begin
par_in[1] = in;
next_state = S2;
end
S2:begin
par_in[2] = in;
next_state = S3;
end
S3:begin
par_in[3] = in;
next_state = S4;
end
S4:begin
par_in[4] = in;
next_state = S5;
end
S5:begin
par_in[5] = in;
next_state = S6;
end
S6:begin
par_in[6] = in;
next_state = S7;
end
S7:begin
par_in[7] = in;
next_state = S8;
end
S8:begin
next_state = in ? STOP : WAIT;
end
STOP:begin
next_state = in ? IDLE : START;
end
WAIT:begin
next_state = in ? IDLE : WAIT;
end
default:begin
next_state = IDLE;
end
endcase
end
assign done = (current_state == STOP);
assign out_byte = (current_state == STOP) ? par_in : 8'd0;
endmodule
*/
/*
//third way
module top_module(
input clk,
input in,
input reset, // Synchronous reset
output [7:0] out_byte,
output done
); //
parameter IDLE = 4'd0, START = 4'd1, S1 = 4'd2, S2 = 4'd3, S3 = 4'd4, S4 = 4'd5;
parameter S5 = 4'd6, S6 = 4'd7, S7 = 4'd8, S8 = 4'd9, STOP = 4'd10, WAIT = 4'd11;
reg [3:0] current_state;
reg [3:0] next_state;
reg [7:0] par_in;
always@(posedge clk)begin
if(reset)begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end
always@(*)begin
case(current_state)
IDLE:begin
next_state = in ? IDLE : START;
end
START:begin
next_state = S1;
end
S1:begin
next_state = S2;
end
S2:begin
next_state = S3;
end
S3:begin
next_state = S4;
end
S4:begin
next_state = S5;
end
S5:begin
next_state = S6;
end
S6:begin
next_state = S7;
end
S7:begin
next_state = S8;
end
S8:begin
next_state = in ? STOP : WAIT;
end
STOP:begin
next_state = in ? IDLE : START;
end
WAIT:begin
next_state = in ? IDLE : WAIT;
end
default:begin
next_state = IDLE;
end
endcase
end
always@(posedge clk)begin
if(reset)begin
done <= 1'b0;
out_byte <= 8'd0;
end
else begin
case(next_state)
IDLE:begin
done <= 1'b0;
out_byte <= 8'd0;
end
START:begin
done <= 1'b0;
out_byte <= 8'd0;
end
S1:begin
done <= 1'b0;
par_in[0] <= in;
out_byte <= 8'd0;
end
S2:begin
done <= 1'b0;
par_in[1] <= in;
out_byte <= 8'd0;
end
S3:begin
done <= 1'b0;
par_in[2] <= in;
out_byte <= 8'd0;
end
S4:begin
done <= 1'b0;
par_in[3] <= in;
out_byte <= 8'd0;
end
S5:begin
done <= 1'b0;
par_in[4] <= in;
out_byte <= 8'd0;
end
S6:begin
done <= 1'b0;
par_in[5] <= in;
out_byte <= 8'd0;
end
S7:begin
done <= 1'b0;
par_in[6] <= in;
out_byte <= 8'd0;
end
S8:begin
done <= 1'b0;
par_in[7] <= in;
out_byte <= 8'd0;
end
STOP:begin
done <= 1'b1;
out_byte <= par_in;
end
WAIT:begin
done <= 1'b0;
out_byte <= 8'd0;
end
default:begin
done <= 1'b0;
out_byte <= 8'd0;
end
endcase
end
end
endmodule
*/
这道题目是上一道题目的扩展,不仅需要输出done信号,还需要输出数据。
博主给出三种方法,各有利弊,下面我来一一分析。
博主最先想到的是方法二,因为需要输出数据,那么在状态转移条件中将in给一个寄存器,然后在STOP状态的时候给出这个数据不就好了吗?当然没有问题,于是方法二就成功了,这是最简单能够得到success的方法。但是,状态转移条件是用组合逻辑写的,par_in中的in信号是数据,如果将in直接给par_in,in在第一个周期为1,第二个周期还为1,那么即使成功,警告中会提醒你生成了锁存器,当然如果只为了做题的话,没什么问题。实际应用中还是尽量不要使用锁存器。
博主想要消灭warning,于是便想到了方法三。大家可以看到,博主在方法三种将par_in信号写在了时序逻辑中,也就是写在了第三段状态机中,锁存器就被消灭了。大家要注意的一定是,在方法二中,in的值第一次赋给par_in是在状态转移条件中current_state为START的时候,而在方法三中,in的值第一次赋给par_in是在状态机第三段中next_state为S1的时候,大家可以想一下,这里next_state为S1,实际上此时的current_state为什么呢?为START。这就是为什么in给par_in赋值的状态不相同了。还有,在方法三中,博主在除了STOP外的每个状态,将done信号和out_byte信号都置为0。
但是博主又想,这里只有1个字节数据还好,用8个状态是可以的,如果换成了10个字节、100个字节,难道要80、800个状态吗?不可能呀,于是便诞生了方法一,也是博主最想让大家使用的方法。大家可以好好看一下,博主这里将8个数据状态定义成了一个DATA状态,不用定义S1、S2、S3什么的了,在状态机的第三段,博主将除了DATA状态的其他状态时的counter都清零,只有到了DATA状态时开始计数,顺便将par_in中的位数用counter代替,这样只需要增加counter和out_byte的位宽,就可以实现任意宽度数据的输出。方法一的状态转移条件中DATA状态有一个判定,就是counter == 4'd8的条件下,只要改变counter的计数值,DATA状态就可以保持counter个周期。这个方法非常巧妙,也是计数器加状态机的组合,希望大家好好体会。这道题真是非常有意思。
注意:博主的方法三种done信号和out_byte信号都在always中赋值,但是信号端口却没有reg,这里是不对的,因为这个网站有时候对这种语法检查的不是很好,实际应用中一定要在端口列表中将它们定义为output reg。
module top_module(
input clk,
input in,
input reset, // Synchronous reset
output [7:0] out_byte,
output done
); //
parameter IDLE = 4'd0, START = 4'd1, S1 = 4'd2, S2 = 4'd3, S3 = 4'd4;
parameter S4 = 4'd5, S5 = 4'd6, S6 = 4'd7, S7 = 4'd8, S8 = 4'd9;
parameter PARITY = 4'd10, STOP = 4'd11, WAIT = 4'd12;
reg [3:0] current_state;
reg [3:0] next_state;
reg [7:0] par_in;
wire odd;
always@(posedge clk)begin
if(reset)begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end
always@(*)begin
case(current_state)
IDLE:begin
next_state = in ? IDLE : START;
end
START:begin
next_state = S1;
par_in[0] = in;
end
S1:begin
next_state = S2;
par_in[1] = in;
end
S2:begin
next_state = S3;
par_in[2] = in;
end
S3:begin
next_state = S4;
par_in[3] = in;
end
S4:begin
next_state = S5;
par_in[4] = in;
end
S5:begin
next_state = S6;
par_in[5] = in;
end
S6:begin
next_state = S7;
par_in[6] = in;
end
S7:begin
next_state = S8;
par_in[7] = in;
end
S8:begin
next_state = PARITY;
end
PARITY:begin
next_state = in ? STOP : WAIT;
end
STOP:begin
next_state = in ? IDLE : START;
end
WAIT:begin
next_state = in ? IDLE : WAIT;
end
default:begin
next_state = IDLE;
end
endcase
end
always@(posedge clk)begin
if(reset)begin
done <= 1'b0;
end
else if(next_state == STOP && odd == 1'b1)begin
done <= 1'b1;
end
else begin
done <= 1'b0;
end
end
always@(posedge clk)begin
if(reset)begin
out_byte <= 8'd0;
end
else if(next_state == STOP && odd == 1'b1)begin
out_byte <= par_in;
end
else begin
out_byte <= 8'd0;
end
end
wire en;
//assign en = (reset == 1'b1 || current_state == IDLE || current_state == STOP || current_state == WAIT);
assign en = (reset == 1'b1 || next_state == IDLE || next_state == START);
// New: Add parity checking.
parity u_parity(
.clk (clk ),
.reset (en ),
.in (in ),
.odd (odd )
);
endmodule
这道题真的把博主折磨到了o(╥﹏╥)o,当时用了个各种方法,加了各种状态,就是不成功......况且完成这道题目,博主的全部题目就都完成了,所以当时的心态就是全心全意搞这一道题。可是有时候太过用力反而跑不远,一直搞不定这道题,于是博主就去搞项目了。过了两天想起来,试试在输出条件中加上odd看看怎么样,success!!!竟然成功了??!?!?!唉,我觉得自己的想法应该也没错,就是增加状态,如果没有odd怎么样,有odd怎么样,作者的意思呢就是在next_state == STOP的同时判断odd值,是一种mealy型状态机的思想,就是这种思想的不同,导致博主写的逻辑一直对不上作者给出的时序波形。
这道题目的原始波形其实是错的,博主向网站作者发了邮件之后作者改正了,原始波形如下图所示。大家看一下为什么原始波型是错误的,第一个输入0x4b有四个1,加上奇偶校验位的1,总共是五个,满足输出条件,但是0x62那里就出现问题了,0x62有三个1,奇偶校验位为0,应该是符合输出条件的,但是作者的原始图中没有输出。大家再看上面那个改正后的图,0x62没有变,但是奇偶校验位变成1了,这样总共就有四个1,不满足输出条件,done信号不拉高,不输出数据。
博主在完成这道题的时候还发了几封邮件请教了作者这道题目的测试向量,因为我看到很多in信号刚好在边沿变化,我看不太懂,作者给我回了信说这个网站中testbench里的测试向量大多是随机向量,所以有时候确实不是那么容易懂,然后还回了一个截图,如下图所示,我放出来让大家更好地理解题意。
这道题目博主没有尝试多种方法,只用了最简单的方法,大家可以用博主在题目二中提到的几种方法来完成题目三。
注意:这里作者给出了奇偶校验模块的module,大家直接调用就可以了。
就是第三道题目,花了我好久,真是心累呀~~~好在结果是好的,题目全部完成了。虽然这个网站题目总体不算很难,但是能够全部完成也会有一种小小的成就感。大家可以去做做感受一下。最后如果有什么地方有错误或者看不懂的,欢迎随时指正呦~
https://hdlbits.01xz.net/wiki/Main_Page