状态机设计技巧和经验

状态机有Moore和Mealy两种类型,Moore状态机的输出与输入无关,Mealy状态机的输出与输入有关,这是我们在数电学到的内容。

那么在实际的工程中,如何选择状态机类型,设计时又有哪些需要注意的地方呢?本文将用一个实际的例子,给大家分享一些设计技巧和个人经验。

例题:在通信领域,数据会按照一定格式打包成报文(也称为帧)作为通信的基本单元。若某报文有数据帧和控制帧两种格式,设计一个报文检测模块,输入数据为din[7:0],输出数据为dout[7:0],输出有效指示信号dout_vld,输出报头指示信号dout_sop(dout输出type第一个字节时置1),输出报尾指示信号dout_eop(dout输出fcs最后一个字节时置1)

数据报文格式:10Byte报文头(header)+1Byte报文类型(type)+2Byte报文长度(length)+1Byte至65535Byte的报文数据(payload)+4Byte检验码(fcs)

控制报文格式:10Byte报文头(header)+1Byte报文类型(type)+64Byte报文数据(payload)+4Byte检验码(fcs)

header:连续收到80’h55d5_55d5_55d5_55d5_55d5

type:若为0表示是控制报文,否则为数据报文

length:数据报文才有length部分,长度为2Byte(length位宽为16bit,能表示的范围为0-65535)。length的值+1为多少,payload的字节数就有多少。如果length值为1277,那么表示payload有128个Byte

题目分析:状态分解要足够细,尽可能让状态与状态之间保持独立。本题可分为如下几个状态,HEAD(检测header),TYPE(检测type),LEN(检测length),DATA(检测data),FCS(检测fcs)。

先介绍一个FSM的设计规范,三段式。

第一段:组合逻辑,实现next_state

第二段:时序逻辑,实现current_state

第三段:组合逻辑,实现output

第一段:组合逻辑,实现next_state

always @* begin
  case(current_state)
    HEAD: begin
          if(header_match)  //检测到header
            next_state=TYPE;
          else
            next_state=HEAD;
          end
    TYPE: begin
            if(din==0)  //若为控制报文,则不需要接收length
              next_state=DATA;
            else        //为数据报文
              next_state=LEN;
          end
    LEN:  begin
            if(length_done)   //length接收完成
              next_state=DATA;
            else              //继续接收length
              next_state=LEN;
          end
    DATA: begin
            if(data_done)     //data接收完成
              next_state=FCS;
            else              //继续接收data
              next_state=DATA;
          end
    FCS:  begin
            if(fcs_done)      //fcs接收完成
              next_state=HEAD;
            else              //继续接收fcs
              next_state=FCS;
          end
  endcase
end

技巧总结:我们在设计时会发现所需的某些中间条件是未知的,遇到这种情况时坚定自己的思路,这些未知条件直接作为中间变量写出来即可。例如以上代码,在设计三段式的第一段时引入了header_match,length_done,date_done,fcs_done四个中间变量,这些信号后续将会依次设计。另外,这部分状态跳转逻辑推荐使用case语句描述。

第二段:时序逻辑,实现current_state

always @(posedge clk or negedge rstn) begin
  if(rstn==1’b0)
    current_state=IDLE;
  else
    current_state=next_state;
end

第三段:组合逻辑,实现output

assign dout_tmp       = din;
assign dout_vld_tmp   = (current_state!=HEAD);    //HEAD状态的数据无效
assign dout_sop_tmp   = (current_state==TYPE);    //type首字节
assign dout_eop_tmp   = (last_byte_of_fcs);       //fcs最后一个字节

技巧总结:以上代码,在设计第三段时又引入了一个中间变量last_byte_of_fcs。至此状态机部分设计就结束了,状态机让控制逻辑更清晰也容易维护。可以发现dout_vld_tmp和dout_sop_tmp信号只与当前状态有关所以这部分是Moore类型,而dout_eop_tmp信号的条件last_byte_of_fcs虽然我们还没设计,但简单分析可知其既与当前状态有关(必须是FCS状态)也与输入信号din是有关(输入数据din开始输入fcs的最后一个Byte),所以这部分应该是Mealy类型的。dout_tmp信号只与输入信号din有关,并不属于状态机的一部分。

如图,若有红色线路则为Mealy类型,否则为Moore类型,以某种角度来看,Moore状态机是Mealy状态机的一个特例。

对于同一个电路,既可以设计成Moore类型也可以设计成Mealy类型,主要是通过状态分解确定的,其功能上是等价的,同一个电路设计成Moore类型的状态会比Mealy多一些,所以Moore状态机的延时也会更长。

而Moore电路的输出与输入无关,输入信号的噪声被第二段寄存器隔开不会往下传递,可以让电路更稳定。

如果输入信号是有噪声的接口信号,不能将毛刺引入电路内部,就设计成更稳定的Moore类型;如果是内部状态机,就设计成延时更小的Mealy状态机

接下来完成题目剩余部分,需要设计的内容为header_match,length_done,data_done,fcs_done,last_byte_of_fcs五个中间信号,以及dout,dout_vld,dout_eop,dout_sop四个输出信号,从易到难依次设计。

dout信号设计

always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    dout  <= 8'b0;
  else
    dout  <= din;
end

dout_vld信号设计

always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    dout_vld  <= 1'b0;
  else
    dout_vld <= dout_vld_tmp;
end

dout_sop信号设计

always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    dout_sop  <= 1'b0;
  else
    dout_sop <= dout_sop_tmp;
end

dout_eop信号设计

always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    dout_eop  <= 1'b0;
  else
    dout_eop <= dout_eop_tmp;
end

length_done信号设计

分析:由于din一次输入为1Byte,而length是2Byte,所以需要引入一个计数器length_cnt(初值为0,加1条件为current_state==LEN,结束值为1)

assign length_done = (length_cnt==1'b1);
always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    length_cnt <= 1'b0;
  else if(current_state==LEN)
    length_cnt <= ~length_cnt;  //用反相器替代加法器,节省面积
  else
    length_cnt <= 1'b0;
end

fcs_done信号设计

分析:由于din一次输入为1Byte,而fcs是4Byte,所以需要引入一个计数器fcs_cnt(初值为0,加1条件为current_state==FCS,结束值为3)

assign fcs_done = (fcs_cnt=='h3);
always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    fcs_cnt     <= 2'b0;
  else if(current_state==FCS)
    fcs_cnt     <= fcs_cnt + 1;
  else
    fcs_cnt     <= 2'b0;
end

last_byte_of_fcs信号设计

分析:current_state处于FCS,开始输入fcs的最后一个Byte,需要用到刚刚设计的fcs_cnt信号

assign last_byte_of_fcs = ((current_state==FCS) && (fcs_cnt==’h3) );

技巧总结:设计时从易到难,一根信号一根信号地设计,一个always块只设计一根信号,思路就会很清晰。现在还剩下header_match和data_done两根信号需要设计,这两根相较上面的信号要复杂一点。

data_done信号设计

分析:由于数据帧和控制帧的payload长度不同,控制报文的payload固定为64Byte,而数据报文的payload需要由length决定。对于长度不固定的计数,使用减1计数器比加1计数器更方便。

assign data_done=(data_cnt==0);
always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    data_cnt <= 'b0;
  else if(current_state==TYPE && din=0)   //type为0表示该报文为控制报文,payload固定为64Byte
    data_cnt <= 'd63;
  else if(current_state==LEN) begin       //若为数据报文,在LEN状态时
    if(length_cnt==0)
      data_cnt <= {data_cnt[7:0], din};   //将length的第一个Byte拼接到data_cnt的低8位保存起来
    else
      data_cnt <= {data_cnt[7:0], din}-1; //将length的第二个Byte拼接起来,并减1(因为计数器结束值为0)
  end else if(data_cnt!=0)
    data_cnt <= data_cnt - 1;             //DATA状态时,计算接收到的payload字节数
end

header_match信号设计

分析:在HEAD状态下,连续收到80’h55d5_55d5_55d5_55d5_55d5表示检测到报文头。由于数据是一个Byte一个Byte输入的,需要引入一个head_cnt计数器(初值为0,加1条件为current_state为HEAD状态并且输入数据匹配,结束值为9)。

如何确定现在的期望值是55还是d5?需要再引入一根内部信号head_d5,若为1代表期望值d5,为0代表期望值为55

注意如果输入数据不匹配,计数器不一定置0,例如输入55_d5_01_...,由于01不匹配所以计数器要置0,如果输入55_d5_55_55_...,第四个55不匹配但计数器应该置1而非置0

assign header_match=( head_cnt=='d9 && din==8'hd5 );
always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    head_cnt <= 0;
  else if(current_state==HEAD) begin
    if(head_d5==1) begin                  //期望值为d5
      if(din==8'hd5) begin
        if(head_cnt==9)                   //到达最大值
          head_cnt <= 0;
        else
          head_cnt <= head_cnt + 1;
      end else if(din==8'h55)             //输入为55但不是期望值,head_cnt置1而非置0
        head_cnt <= 1;
      else
        head_cnt <= 0;
    end else if(head_d5==0) begin         //期望值为55
      if(din==8'h55)
        head_cnt <= head_cnt + 1;
      else
        head_cnt <= 0;
    end
  end else
    head_cnt <= 0;
end
always @(posedge clk or negedge rstn) begin
  if(rstn==1'b0)
    head_d5 <= 0;                         //刚开始期望55
  else if(current_state==HEAD) begin
    if(head_d5==1'b0) begin
      if(din==8'h55)                      //期望为55时,输入为55
        head_d5 <= 1'b1;
    end else begin
      if(din==8'h55)                      //期望为d5时,输入为55
        head_d5 <= 1'b1;
      else                                //期望为d5时,输入非d5和55的其他值
        head_d5 <= 1'b0;
    end
  end else
    head_d5 <= 0;
end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值