三段式状态机
状态机是个好东西,写逻辑基本上都是避不开的,按照状态控制和输出是否在一起可以分成一段式、两段式和三段式,我们数电课上都学习过状态机的分析,只是说从来都没有写过,我一开始在学习Verilog和写了一些代码的时候都没有很好的和实际电路联系起来,但是状态机确实是很重要的,一种好的状态机风格可以让代码变得更简单,也不容易出错。
这个部分主要是参考了Clifford E. Cummings的 Synthesizable Finite State Machine Design Techniques Using the New SystemVerilog 3.0 Enhancements
先说说状态机的作用,众所周知FPGA的所有代码是并行运行的,但是很多事情并不是并行的,都是有自己的流程的,因此状态机的引入就是为了更好的叙述整个控制的流程。
状态机的书写风格
一段式
大家一开始会很自然的写成一段式状态机,状态随着输入输出信号不同来变化,Xilinx官方论坛的一个工作人员曾经说过:不同的状态机只是编码风格的问题,如果设计得正确的话效果是一样的,Vivado的综合工具还是很智能的,可以很好的综合逻辑中的状态机,但是作为一名工程师不能止步于这里,一个好的编码风格还是有必要的。当然,上面的帖子有人提出了不同的意见,有人喜欢一段式状态机,觉得很方便和智能,同时还可以避免设计不好带来的latch;也有人觉得一段式状态机不标准,有可能存在不能抵达的状态,同时可读性更差一些。
一段式状态机最大的缺点就是根本不能称之为完整的状态机,因为一个状态可能对应了好几种不同的输入输出,其实应该被拆分成好几个状态才对,而且这样子在分析的时候输出信号实际上是差了一个周期的,因为case判断的是时钟沿来临之前的那个时刻状态的值,这会导致很多的麻烦,写代码的时候也会很痛苦。当然也有好处,一段式状态机书写起来很简单,绝对不会出现latch。
两段式
两段式状态机:两段式状态机的优势在于状态的跳转是在时钟的控制下,每次写的时候只需要考虑在这个状态下的输出是什么,该进入的下一个状态是什么就可以了,标准的两段式状态机的第二段是组合逻辑,这是它的缺点(组合逻辑直接输出的意思是输出的信号可能是有毛刺的,也是不推荐的一种输出的代码风格)。下面十一个两段式状态机的代码,第二段的输出信号最好再用触发器锁一遍,保证下一个模块收到信号的时候不会出现毛刺。
module fsm_cc1_2
(output reg rd, ds,
input go, ws, clk, rst_n);
parameter IDLE = 2'b00,
READ = 2'b01,
DLY = 2'b11,
DONE = 2'b10;
reg [1:0] state, next;
always @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else
state <= next;
always @(state or go or ws) begin
next = 'bx;
rd = 1'b0;
ds = 1'b0;
case (state)
IDLE : if (go) next = READ;
else next = IDLE;
READ : begin
rd = 1'b1;
next = DLY;
end
DLY : begin
rd = 1'b1;
if (!ws) next = DONE;
else next = READ;
end
DONE : begin
ds = 1'b1;
next = IDLE;
end
endcase
end
endmodule
三段式
既然都用D触发器锁存了一遍了,那么不如直接上三段式状态机:第一段状态跳转,第二段用组合逻辑表示状态转移关系,第三段用表示信号的输入输出关系。下面的代码就是一个三段式状态机的例子,第二个always中的的ns = `bx我个人理解是为了让仿真和实测的结果是一致的,要是有人知道其他的原因烦请告诉我。
这样的三段式状态机非常的有效,写起来很轻松,我最开始写的就是一段式状态机,经常为了状态的跳转和输出不同而绞尽脑汁,当我用了三段式状态机之后,每个状态只干一件事,写起来就特别的轻松,如果是使用我的vim的话,写个fsm3,然后按F12可以直接调出三段式状态机的模板,如图所示。
module fsm_cc1_3
(output reg rd, ds,
input go, ws, clk, rst_n);
parameter IDLE = 2'b00,
READ = 2'b01,
DLY = 2'b11,
DONE = 2'b10;
reg [1:0] state, next;
always @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next;
always @(*) begin
next = 'bx;
case (state)
IDLE : if (go) next = READ;
else next = IDLE;
READ : next = DLY;
DLY : if (!ws) next = DONE;
else next = READ;
DONE : next = IDLE;
endcase
end
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
rd <= 1'b0;
ds <= 1'b0;
end
else begin
rd <= 1'b0;
ds <= 1'b0;
case (next)
READ : rd <= 1'b1;
DLY : rd <= 1'b1;
DONE : ds <= 1'b1;
endcase
end
endmodule
个人的看法
按照数字电路设计的思路来说
One-hot码
One-hot码是一个很有意思的内容,待补充
