状态机,只要C代码写过2年的人,估计无人不识君,稍微复杂的逻辑都可以借助状态机来简化问题。
为了方便,我们使用前面用过的一个例子,来说明状态机的应用,也就是说我们前面已经有意无意的用过状态机了。
我们以SPI的Slave接口,为例,来说明状态机的使用为了简化问题
1、我们没有把信号同步到本地时钟
2、把其他信号同步到SCK
3、我们把SPI暂时按照单向来分析
下面,我们分析SPI通讯
1、nCS高电平时候,总线是空闲的
2、nCS低电平时传输数据
3、满了8个bit,凑够了一个字节,要保存当前已经收到的字节,并准备收下一个;
nCS高电平的时候,我们称之为idel态(IDEL)
接受0~7逐个bit的时候,称之为bit接受态(BIT_RECV)
收满一个字节,称之为字节转存态(BYTE_SAVE)
我们开始画状态转移图<ignore_js_op> model SlaveSPI(input nCS, input SCK, input MOSI, output MISO);
model SlaveSPI(input nCS, input SCK, input MOSI, output MISO);
parameter IDEL = 0,
   BIT_RECV = 1, 
   BIT_SAVE = 2;
 reg[3:0]  bitcnt;
 reg[7:0]  shift_in; //写入
 reg[7:0]  shift_out;//读出
 reg[7:0]  data;
 reg[1:0]  state;
 reg[1:0]  next_state;
 always @(*) 
begin
 case(state)
 IDEL: 
   if(nCS==1'b1)
     next_state = IDEL; 
  else
     next_state = BIT_RECV;
 BIT_RECV: 
   if(nCS==1'b0)
   bgein 
     if(bitcnt<4'h8) 
     next_state = BIT_RECV;
     else 
     next_state = BYTE_SAVE;
   end 
  else 
     next_state = IDEL;
 BYTE_SAVE:
   if(nCS==1'b0) 
    next_state = BIT_RECV;
   else
     next_state = IDEL;
 defalut:
   next_state = IDEL;
 endcase
 end 
always @(posdge SCK)
 if(nCS)
   bitcnt=0; 
 else  
  state = next_state
 always @(posdge SCK)
 case(state)
 BIT_RECV:
   begin 
    bitcnt <= bitcnt+4'h1;
     shift_in <= {shift_in[6:0], MOSI};
    end
 BYTE_SAVE:
    begin
     bitcnt <= 4'h0;
     data <= deshift_in; 
  end 
endcase 
我用了所谓的三段式,来描述这个状态机,用了3个always语句第一个always用来描述状态转移的条件第二个always用来描述状态转移第三个always用来描述状态机的输出,也就是状态机实际要干的活与前面帖子(同步和异步设计)中的SPI代码相比,是不是冗长了很多。
冗长,并不代表着脱裤子放屁,自找麻烦。
您难道没有反显,代码很容易读懂了吗?没错,这就是空间换时间的策略,我们写更长的代码来增强可分析性。
状态机的代码撰写一般不复杂,复杂的是状态机的构建过程,这个过程中,我们要分析实际遇到的各个情况,同时把状态机进行优化,某些状态进行合并,某些状态去掉。
有人问,为何某些状态要去掉?这要说下FSM的来头了,有限状态机,是有限的状态机。
自然界实际的状态机,往往起状态的数量,是非常大的,直接建模使用简直是劳民伤财。
而且,现实的往往是无限的状态机,这根本无法用于工程实现,所以有限状态机就横空出世了。
正如,软件算法中的DAG(Directed Acyclic Graph)有向无环图,比纯粹的图有实用价值。
二叉树,比多叉树,也更容易实现。越说,软件和FPGA越近了。
可谓是天下大势,分久必合,合久必分。