数字IC-1.9 吃透通信协议中状态机的代码编写套路

目录

  • 一、前言
  • 二、例子预备知识
  • 三、时序法WE命令代码举例
  • 四、逻辑法BE命令代码举例
  • 五、测试原代码文件
  • 六、关于通用指令集状态机编写的思考与疑问
  • 七、分块式命令框架设计小例(两法混用且验证六的问题)

一、前言

本文针对verilog HDL语言编写不同通信协议时,需要对不同命令状态机进行代码实现的方法进行了浅薄的探讨。作者水平有限,还请大家指正。

文中利用SPI协议对flash发送全擦除指令为例子,分别探讨以下两种不同编写方法的优劣及具体实现方式:1、利用always语句块方法得到状态跳变条件或时间等待标志进行协议端口输出的写法(下文称 “逻辑法”);2、使用cnt计数器,在驱动时钟频率至少是spi_clk时钟频率两倍以上的前提下,对时序的不同时刻行为进行具体相应描述的编写方法(下文称 “时序法”)。

两者比较的结论

逻辑法优点-后续工程中定制化修改更加方便,协议通信描述逻辑更加清晰具体,更能够抽象化规律化描述协议通信时的行为。缺点-编写不同协议行为之间的跳变限制逻辑设计较为复杂,代码仿真调节次数较多,需要反复考虑不同寄存器之间的延迟问题以提前空出延时余量,满足时序高效紧凑需求。(特点:小块时序逻辑更多,布线更简单,面积更小,速度稍慢

时序法优点-编写新思路简单清晰,开发速度快,可以简单有效地定位到bug存在位置,代码执行效率高。缺点-后期定制化其他功能修改的地方多,不容易看懂时序之间跳变的规律,不能够很客观的描述协议行为序列变化之间的关系。(特点:程序块较少,但一个case里计数器递进判断条件较多,布线由于比较集中,因此比较复杂,面积较大,速度更快

使用建议逻辑法更适用于已知固定长度,固定格式,一小段命令一小段命令发送命令或者接收状态的协议。对于非定常连续触发的协议或者一条命令内通信线时序变化非常复杂的协议,则推荐使用时序法来进行编写。 但是在实际工程运用中,其实它们并没有很明确的分界线,一般都是配合使用,博主的菜鸡经验是,连续信号耦合强且该信号持续期间有状态跳变的用逻辑法,连续信号只存在一个状态内且多线信号变化较为复杂的用时序法。

二、例子预备知识

        本文利用spi协议与flash芯片进行通信,向它发送全部扇区擦除指令。该指令由两个部分组成,一个是写使能部分WE,一个是全擦除命令BE部分。时序图如下。

数字IC-1.9 吃透通信协议中状态机的代码编写套路_第1张图片

 本文将使用硬件表述语言,分别使用两种方法来描述上述时序。其中WE写使能(8'h06)是用时序法实现的,BE全擦除(8'hc7)是用逻辑法实现的。

三、时序法WE命令代码举例

时序

         由上图可以看到,当spi_clk为上升沿时从机就会通过mosi对数据进行采样,因此要在上升沿之间的低电平时期就要把需要发出的命令数据放在mosi线上了(这是由于组合逻辑的非阻塞赋值延时一个时钟周期决定的)。

        注意: 此时spi_cmd_reg的命令8'n06是不变的,这是由于下面代码中命令直接寻址发送的编写方法决定的。

源码

        时序法通过case语句在每个时刻对spi协议的各个数据线行为跳变进行直接操控,使得这种方法编写起来简单有效。代码执行效率高,不需要在不同数据线之间设计相互承接的变化触发条件。但是这个方法对于人的阅读来说不够客观。

w_en_100ns : begin
                    case(cnt_son_stage)
                        7'd0 : begin 
                            spi_cs  <= 1'b0;
                            spi_clk <= 1'b0;
                            spi_mosi    <= spi_cmd_reg[7];
                        end
                        7'd1 : spi_clk <= 1'b1;
                        7'd2 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[6];
                        end
                        7'd3 : spi_clk <= 1'b1;
                        7'd4 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[5];
                        end
                        7'd5 : spi_clk <= 1'b1;
                        7'd6 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[4];
                        end
                        7'd7 : spi_clk <= 1'b1;
                        7'd8 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[3];
                        end
                        7'd9 : spi_clk <= 1'b1;
                        7'd10 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[2];
                        end
                        7'd11: spi_clk <= 1'b1;
                        7'd12: begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[1];
                        end
                        7'd13: spi_clk <= 1'b1;
                        7'd14: begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[0];
                        end
                        7'd15: spi_clk <= 1'b1;
                        7'd16: begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[0];
                        end
                        7'd17: begin
                            spi_cs        <= 1'b1;
                            spi_mosi      <= 1'b0;
                        end
                        7'd20: begin
                            cnt_son_stage <= 7'd127;
                            sondone       <= 1'b1;
                        end
                        default: ;	
                    endcase
                end

四、逻辑法BE命令代码举例

时序

        由上图可以看到,当spi_clk为上升沿时从机就会通过mosi对数据进行采样,因此要在上升沿之间的低电平时期就要把需要发出的命令数据放在mosi线上了(这是由于组合逻辑的非阻塞赋值延时一个时钟周期决定的)。

        注意: 此时spi_cmd_reg的命令8'nca是改变的,这是由于下面代码中命令是通过高位移位循环地址发送的编写方法决定的。

源码

        逻辑法通过少数的if条件判断语句对时序的行为进行间接触发操控,使得这种方法编写起来不仅需要分析和编写不同时序段协议应该干什么,还需要在每个时段下分析此时不同信号线之间的触发关系。代码描述信号变化规律比较客观易懂,有利于后期修改与人员阅读。

cmd_cyc : begin
                        if(cnt_son_stage == 7'd20) begin // 完成 
                            sondone       <= 1'b1;
                            cnt_son_stage <= 7'd127;
                        end
                        else if(cnt_son_stage == 7'd1) begin
                            spi_cs      <= 1'b0;
                            spi_clk     <= 1'b0;
                            spi_mosi    <= spi_cmd_reg[7];
                            spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
                        end
                        else if(cnt_son_stage > 7'd1 && cnt_son_stage < 7'd18) begin
                                spi_clk  <= ~spi_clk;
                            if(spi_clk) begin 
                                spi_mosi    <= spi_cmd_reg[7];
                                spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
                            end
                            else begin 
                                spi_mosi    <= spi_mosi   ;
                                spi_cmd_reg <= spi_cmd_reg;
                            end  
                        end
                        else  begin
                            spi_cs        <= 1'b1 ;
                            spi_cmd_reg   <= spi_cmd_reg;
                            spi_mosi      <= 1'b0;
                            spi_clk       <= 1'b0;
                        end
                end

五、测试原代码文件

`timescale  1ns/1ns
`define		WEL_CMD	        8'h06		// 写使能命令
`define		C_ERA_CMD 	    8'hc7		// 全部擦除命令

module tb_test();
    reg     sys_clk     ;   //模拟时钟信号
    reg     sys_rst_n   ;   //模拟复位信号
    
    reg              spi_start   ;//spi开启使能。
    reg              spi_cs      ;//SPI从机的片选信号,低电平有效。
	reg              spi_clk     ;//主从机之间的数据同步时钟。
	reg              spi_mosi    ;//数据引脚,主机输出,从机输入。
    reg  [ 7:0]      spi_cmd     ;
    
    reg  [ 2:0]     cnt_stage     ;
    reg  [ 6:0]     cnt_son_stage ;
    reg  [ 7:0]     spi_cmd_reg   ;//FLAH操作指令
    
    initial begin
            sys_clk     =   1'b1;
            sys_rst_n   <=  1'b0;
            spi_start   <=  1'b0;
            #100
            sys_rst_n   <=  1'b1;
            #300
            spi_start    <=  1'b1;
            //spi_addr_reg <=  ;//FLASH地址
            #(20*47)
            spi_start    <=  1'b0;
            #300
            spi_start    <=  1'b1;
            //spi_addr_reg <=  ;//FLASH地址
            #(20*47)
            spi_start    <=  1'b0;
            #600
            $stop();
    end
    
    
    always # 10 sys_clk <= ~sys_clk; // 50M hz
    
    reg     clk_25M; // 读时钟最大20MHZ,这里将在状态机里降频到12.5Mhz
    wire    clk_drive;
    reg     spi_start_reg;
    
    // 时钟
    always @(posedge sys_clk or negedge sys_rst_n ) begin
        if(!sys_rst_n)
            clk_25M       <=  1'b0;
        else 
            clk_25M     <= ~clk_25M;
    end
    
    assign clk_drive = clk_25M;
    
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_start_reg <= 1'b0;
        end
        else
            spi_start_reg <= spi_start;
    end
    
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_cmd     <=  8'b0;
        end
        else if(spi_start && !spi_start_reg) 
            if( spi_cmd == 8'b0)
                spi_cmd     <=  8'hc7;
            //else if ( spi_cmd == 8'h06)
            //    spi_cmd     <=  8'hc7;
        else
            spi_cmd     <=  spi_cmd;
    end
    
    // 状态机状态
    localparam IDLE         = 7'b000_0001;//空闲状态
    localparam WEL          = 7'b000_0010;//写使能状态
    localparam C_ERA        = 7'b000_1000;//全局擦除
               
    
    // 命令输出状态
    localparam idle_stage   = 8'b0000_0001,
               w_en_100ns   = 8'b0000_0010,
               cmd_cyc      = 8'b0000_0100,
               adds_cyc     = 8'b0000_1000, 
               end_6ns      = 8'b0001_0000, 
               w_n_bit      = 8'b0010_0000, 
               r_n_bit      = 8'b0100_0000,
               busy_8b      = 8'b1000_0000;
               
    reg    [7:0]  cur_stage ; //当前阶段    
    reg           stdone    ; //命令完成标志
    reg           sondone   ; //子命令阶段完成标志 
    reg    [6:0]  cur_state ; //状态机当前状态
    reg    [6:0]  next_state; //状态机下一状态
    
    //(三段式状态机)同步时序描述状态转移
    always @(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) 
            cur_state  <= IDLE;
        else 
            cur_state <= next_state;
    end
    
    always@(*) begin
        next_state = IDLE;
        case(cur_state)
            IDLE : begin
                if(spi_start && spi_cmd == `WEL_CMD)
                    next_state = WEL;
                else if(spi_start && spi_cmd == `S_ERA_CMD)
                    next_state = S_ERA;
                else
                    next_state=IDLE;
            end
            
            WEL: begin
                if(stdone)
                    next_state = IDLE;
                else   
                    next_state = WEL;
			end
            
            C_ERA: begin		
                if(stdone)
                    next_state = IDLE;
                else
                    next_state = C_ERA;
            end
            
            default: next_state=IDLE;	
        endcase
    end
    
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_cs        <= 1'b1 ;
            spi_clk       <= 1'b0 ;
            spi_mosi      <= 1'b0 ;	
            spi_cmd_reg   <= 8'b0 ;
            
            cur_stage     <= idle_stage;
            cnt_stage     <= 3'b0;
            cnt_son_stage <= 7'b0;
            sondone       <= 1'b0;
            stdone        <= 1'b0;
        end
        else begin
            cur_stage <= idle_stage;
            stdone    <= 1'b0;
            if(sondone) begin
                cnt_stage <= cnt_stage + 1'b1;
                sondone   <= 1'b0;
            end
            else  begin
                cnt_stage <= cnt_stage;
                sondone   <= sondone;
            end
            
            case(cur_state)   
                IDLE : begin
                    cur_stage <= idle_stage;
                    cnt_stage <= 3'b0;
                    spi_cs   <= 1'b1;
                end
                
                WEL: begin
                    case(cnt_stage) // mosi
                        3'd0 :   begin
                            cur_stage <= w_en_100ns;
                            if(cnt_son_stage == 7'd1)
                                spi_cmd_reg  <= `WEL_CMD ;
                            else
                                spi_cmd_reg  <= spi_cmd_reg;
                        end
                        3'd1 :   begin
                            stdone    <= 1'b1;
                            cnt_stage <= 3'd2; 
                        end   
                        default: ;	
                    endcase
                end
                
                C_ERA : begin
                    case(cnt_stage) // mosi
                        3'd0 :   begin
                            cur_stage <= w_en_100ns;
                            if(spi_cs)
                                spi_cmd_reg  <= `WEL_CMD ;
                            else
                                spi_cmd_reg  <= spi_cmd_reg;
                        end
                        3'd1 :   begin
                            cur_stage <= cmd_cyc;
                            if(spi_cs)
                                spi_cmd_reg  <= `C_ERA_CMD ;
                            else
                                spi_cmd_reg  <= spi_cmd_reg;
                        end
                        3'd2 :  cur_stage <= end_6ns;
                        3'd3 :   begin
                            stdone    <= 1'b1;
                            cnt_stage <= 3'd4; 
                        end   
                        default: ;	
                    endcase
                end
                default: ;	
            endcase
            
            
            if(spi_start || cnt_son_stage != 7'd127)
                cnt_son_stage <= cnt_son_stage + 1'b1;
            else
                cnt_son_stage <= cnt_son_stage;
                    
            case(cur_stage)   
                idle_stage : begin
                    spi_clk  <= 1'b0;
                    spi_mosi <= 1'b0;
                    
                    cnt_son_stage    <= 7'b0;
                end
                
                w_en_100ns : begin
                    case(cnt_son_stage)
                        7'd0 : begin 
                            spi_cs  <= 1'b0;
                            spi_clk <= 1'b0;
                            spi_mosi    <= spi_cmd_reg[7];
                        end
                        7'd1 : spi_clk <= 1'b1;
                        7'd2 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[6];
                        end
                        7'd3 : spi_clk <= 1'b1;
                        7'd4 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[5];
                        end
                        7'd5 : spi_clk <= 1'b1;
                        7'd6 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[4];
                        end
                        7'd7 : spi_clk <= 1'b1;
                        7'd8 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[3];
                        end
                        7'd9 : spi_clk <= 1'b1;
                        7'd10 : begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[2];
                        end
                        7'd11: spi_clk <= 1'b1;
                        7'd12: begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[1];
                        end
                        7'd13: spi_clk <= 1'b1;
                        7'd14: begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[0];
                        end
                        7'd15: spi_clk <= 1'b1;
                        7'd16: begin 
                            spi_clk  <= 1'b0;
                            spi_mosi <= spi_cmd_reg[0];
                        end
                        7'd17: begin
                            spi_cs        <= 1'b1;
                            spi_mosi      <= 1'b0;
                        end
                        7'd20: begin
                            cnt_son_stage <= 7'd127;
                            sondone       <= 1'b1;
                        end
                        default: ;	
                    endcase
                end
                
                cmd_cyc : begin
                        if(cnt_son_stage == 7'd18) begin // 完成 
                            sondone       <= 1'b1;
                            cnt_son_stage <= 7'd127;
                        end
                        else if(cnt_son_stage == 7'd1) begin
                            spi_cs      <= 1'b0;
                            spi_clk     <= 1'b0;
                            spi_mosi    <= spi_cmd_reg[7];
                            spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
                        end
                        else if(cnt_son_stage > 7'd1 && cnt_son_stage < 7'd18) begin
                                spi_clk  <= ~spi_clk;
                            if(spi_clk) begin 
                                spi_mosi    <= spi_cmd_reg[7];
                                spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
                            end
                            else begin 
                                spi_mosi    <= spi_mosi   ;
                                spi_cmd_reg <= spi_cmd_reg;
                            end  
                        end
                        else  begin
                            spi_cs        <= 1'b1 ;
                            spi_cmd_reg   <= spi_cmd_reg;
                            spi_mosi      <= 1'b0;
                            spi_clk       <= 1'b0;
                        end
                end
                
                end_6ns : begin
                    if(cnt_son_stage == 7'd6) begin // 完成 
                        sondone       <= 1'b1;
                        cnt_son_stage <= 7'd127;
                    end
                    else  begin
                            spi_cs        <= 1'b1 ;
                            spi_cmd_reg   <= 8'b0;
                            spi_mosi      <= 1'b0;
                            spi_clk       <= 1'b0;
                    end
                end
                default: ;	
            endcase
        end
    end

endmodule

六、关于通用指令集状态机编写的思考与疑问

        在我平时编写通信协议与器件进行通讯的过程中,会遇到这么一个问题。当项目使用到的命令数量较少时,我们可以直接对每条命令利用时序法进行描述,并设计状态机跳转来实现。可是当项目使用的命令数很多时,如果还是像上面的方法那么处理,将会产生很多的代码冗余和寄存器资源浪费,那么这种情况下用什么方法编写更合适呢?

        相信读者心里各自有自己答案,希望大家在评论区多多指点。作者学习ic不久,提出一下自己不成熟的想法。如上面的test bench源码所展示的结构,我尝试将不同命令差分成更小的共性小块,然后实现这些小块,不同的命令只要按照自己的需要使用这些小块进行顺序组合就可以实现了。这样是否能够做到多命令兼容呢?理论上应该可行。

数字IC-1.9 吃透通信协议中状态机的代码编写套路_第2张图片 数字IC-1.9 吃透通信协议中状态机的代码编写套路_第3张图片 

        如上图的全擦除命令与扇区擦除命令,其实他们可以拆成几个不同的通用小块:1、写使能和它后面的100ns延时。2、5ns开头和8个spi_clk周期的命令发送阶段。3、器件操作地址的24位宽地址输出阶段。4、最后5ns的数据结尾完成阶段。使用上述四个小块就可以组合成两条不同的操作指令了。如有不当之处,还请各位多多指正。

七、分块式命令框架设计小例(两法混用且验证六的猜想)

        针对上述想法,本人进行了实际实验。验证了自己的一部分猜想,同时也获得了一些思考。

        直接上结论再上例子!

分析:上述的分小快命令组成不同大命令的想法是可行的,它可以让人看或者写代码时鲁棒性显著增加,但是这也付出了一定的代价,即状态跳转次数增加导致的功耗增加(状态数量增加)、需要设计而外的组合电路来实现状态依据不同的条件实时跳变,虽然增加了面积可是要比每条命令都具体实现要小很多。

总结该框架可以缩短开发时间,降低代码维护成本,减低占用面积,但是会增加功耗以及布线难度。又由于芯片设计中的硬件功耗及速度成本是远大于人力成本与面积成本的,因此该框架在实际工程使用中的实际意义不强。

新猜想:那么有没有既代码可读维护性高,又功耗小且高速的协议命令状态机设计方法呢?其实我们可以引入引入规范化的代码编写格式和编写更全面的统一函数库来管理来实现这一目标。甚至我们可以设置一些脚本文件,利用脚本文件兼顾代码的可读和可维护新,用脚本文件自动拼接含有小块时序逻辑结构的真正硬件描述代码来兼顾功耗与效率,最后进行小范围人为调整即可。

预备知识:在上述执行spi的flash全擦除指令C_ERA_CMD即图46-16时,其实还需要一个命令R_STA_REG_CMD来等待从机miso线返回flash已经擦除完毕的,以便于主机知道何时开始发送下一条命令。一次我们下面将基于一次完整的flash全擦除过程实现例子,即写使能WREN -》扇区全擦除RE -》等待查询寄存器选中05h -》等待miso线上的每8位的最低位为b[0]=0时,数据全擦除过程结束。

1.

数字IC-1.9 吃透通信协议中状态机的代码编写套路_第4张图片

2.

数字IC-1.9 吃透通信协议中状态机的代码编写套路_第5张图片

 时序图:

实例代码

`timescale  1ns/1ns

`define		WEL_CMD	        8'h06		// 写使能命令
`define		S_ERA_CMD 	    8'h20		// 扇区擦除命令
`define		C_ERA_CMD 	    8'hc7		// 全部擦除命令
`define		READ_CMD 	    8'h03		// 读扇区命令          
`define		WRITE_CMD	    8'h02		// 写扇区命令
`define		R_STA_REG_CMD	8'h05		// 等待写忙信号结束空闲命令

module tb_test();
    reg     sys_clk     ;   //模拟时钟信号
    reg     sys_rst_n   ;   //模拟复位信号
    
    reg              spi_start   ; //spi开启使能。
    reg              spi_cs      ; //SPI从机的片选信号,低电平有效。
	reg              spi_clk     ; //主从机之间的数据同步时钟。
	reg              spi_mosi    ; //数据引脚,主机输出,从机输入。
    reg              spi_miso    ; 
    reg  [ 7:0]      spi_cmd     ;
    
    reg  [ 2:0]     cnt_stage     ;
    reg  [ 6:0]     cnt_son_stage ;
    reg  [ 7:0]     spi_cmd_reg   ;//FLAH操作指令
    reg             stdone    ; //命令完成标志
    reg             son_done  ; //子命令阶段完成标志 
    
    initial begin
            sys_clk     =  1'b1;
            sys_rst_n   =  1'b0;
            #100
            sys_rst_n   =  1'b1;
            #6500
            $stop();
    end
    
    // 模拟命令订阅行为 spi_start的实现
    reg [4:0] cnt_spi_start;
    always @(posedge sys_clk or negedge sys_rst_n ) begin
        if(!sys_rst_n) begin
            spi_start     <=  1'b0;
            cnt_spi_start <=  4'b0;
        end
        else if(stdone) begin
            spi_start     <=  1'b0;
            cnt_spi_start <=  4'd10;
        end
        else if(cnt_spi_start<=3'd7) begin
            spi_start     <=  1'b1;
            cnt_spi_start <=  cnt_spi_start + 1'b1;
        end
        else
            cnt_spi_start <=  cnt_spi_start;
    end
    
    //提供系统时钟
    always # 10 sys_clk <= ~sys_clk; // 50M hz
    
    // 驱动状态机的降频时钟生成
    reg     clk_25M; // 读时钟最大20MHZ,这里将在状态机里降频到12.5Mhz
    wire    clk_drive;
    always @(posedge sys_clk or negedge sys_rst_n ) begin
        if(!sys_rst_n)
            clk_25M       <=  1'b0;
        else 
            clk_25M     <= ~clk_25M;
    end
    
    assign clk_drive = clk_25M;
    
    // 模拟主机上层模块命令发送
    reg     spi_start_reg;
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_start_reg <= 1'b0;
        end
        else
            spi_start_reg <= spi_start;
    end
    
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_cmd     <=  8'b0;
        end
        else if(spi_start && !spi_start_reg) 
            if( spi_cmd == 8'b0)
                spi_cmd     <=  8'hc7;
            else
                spi_cmd     <=  8'hff;
            //else if ( spi_cmd == 8'h06)
            //    spi_cmd     <=  8'hc7;
        else
            spi_cmd     <=  spi_cmd;
    end
    
    
    // 命令状态机状态设定
    localparam IDLE         = 7'b000_0001;//空闲状态
    localparam WEL          = 7'b000_0010;//写使能状态
    localparam S_ERA        = 7'b000_0100;//扇区擦除状态
    localparam C_ERA        = 7'b000_1000;//全局擦除
    localparam READ         = 7'b001_0000;//读状态
    localparam WRITE        = 7'b010_0000;//写状态
    localparam R_STA_REG    = 7'b100_0000;//轮询寄存器,等待忙结束状态
               
    
    // 小命令块输出状态设定
    localparam idle_stage   = 8'b0000_0001,
               w_en_100ns   = 8'b0000_0010,
               cmd_cyc      = 8'b0000_0100,
               adds_cyc     = 8'b0000_1000, 
               end_6ns      = 8'b0001_0000, 
               w_n_bit      = 8'b0010_0000, 
               r_n_bit      = 8'b0100_0000,
               miso_8b      = 8'b1000_0000;
               
    reg    [7:0]  cur_stage ; //当前阶段    
    reg    [6:0]  cur_state ; //状态机当前状态
    reg    [6:0]  next_state; //状态机下一状态
    reg    [7:0]  miso_8bufe; //数据接收缓存
    
    // 模拟从机应答
    reg [5:0] cnt_miso_data;
    always@(posedge spi_clk or negedge sys_rst_n) begin
        if(!sys_rst_n) 
            cnt_miso_data   <=  6'b0;
        else if(cnt_miso_data >= 6'd25 || cur_stage != miso_8b) 
            cnt_miso_data   <=  cnt_miso_data;
        else if(cur_stage == miso_8b)
            cnt_miso_data   <=  cnt_miso_data + 1'b1;
        else
            cnt_miso_data   <=  cnt_miso_data;
    end
    
     always@(negedge spi_clk or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_miso     <= 1'b1;
        end
        else if(cur_stage == miso_8b ) begin
            case(cnt_miso_data)
                6'd7  :  spi_miso <= 1'b1;
                6'd15 :  spi_miso <= 1'b1;
                6'd23 :  spi_miso <= 1'b0;
                default: spi_miso <= 1'b1;
            endcase
        end
        else 
            spi_miso <=  1'b1;
    end
    
    // 使用时序逻辑生成spi时钟
    reg  [4:0] cnt_clk;
    reg        spi_clk_sta;
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            cnt_clk <= 5'b0;
            spi_clk <= 1'b0;
        end
        else if(spi_clk_sta) begin
            spi_clk <= ~spi_clk;
            cnt_clk <= cnt_clk + 1'b1;
        end
        else begin
            cnt_clk <= 5'b0;
            spi_clk <= 1'b0;
        end
    end
    
    
    //(三段式状态机)
//第一段:同步时序描述状态转移
    always @(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) 
            cur_state  <= IDLE;
        else 
            cur_state <= next_state;
    end
    
//第二段:组合逻辑描述命令状态跳变
    always@(*) begin
        next_state = IDLE;
        case(cur_state)
            IDLE : begin
                if(spi_start && spi_cmd == `WEL_CMD)
                    next_state = WEL;
                else if(spi_start && spi_cmd == `C_ERA_CMD)
                    next_state = C_ERA;
                else if(spi_start && spi_cmd == `S_ERA_CMD)
                    next_state = S_ERA;
                else if(spi_start && spi_cmd == `READ_CMD)
                    next_state = READ;
                else if(spi_start && spi_cmd == `WRITE_CMD)
                    next_state = WRITE;
                else if(spi_start && spi_cmd == `R_STA_REG_CMD)
                    next_state = R_STA_REG;
                else
                    next_state=IDLE;
            end
            
            WEL: begin
                if(stdone)
                    next_state = IDLE;
                else   
                    next_state = WEL;
			end
            
            S_ERA: begin
				if(stdone)
					next_state = R_STA_REG;
				else
					next_state = S_ERA;
            end
            
            C_ERA: begin		
                if(stdone)
                    next_state = IDLE;
                    //next_state = R_STA_REG;
                else
                    next_state = C_ERA;
            end
            
            READ: begin 		
                if(stdone)
                    next_state=IDLE;
                else
                    next_state=READ;
            end
            
            WRITE: begin		
                if(stdone)
                    next_state = R_STA_REG;
                else
                    next_state=WRITE;
            end
            
            R_STA_REG: begin		
                if(stdone)
                    next_state=IDLE;
                else
                    next_state=R_STA_REG;
            end
            
            default: next_state=IDLE;	
        endcase
    end
    
    //命令块状态转移计数
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            cnt_stage     <= 3'b0;
        end
        else if(son_done ||  stdone)
            cnt_stage <= cnt_stage + 1'b1;
        else if(cur_state==IDLE)
            cnt_stage <= 3'b0;
        else
            cnt_stage <= cnt_stage;
    end
    
    //命令块组合逻辑由条件变化状态跳变
    reg pro_continue;
    always@(*) begin
            // 命令子模块订阅
            case(cur_state)   
                IDLE : begin
                    cur_stage     = idle_stage;
                    spi_cmd_reg   = 8'b0 ;
                    stdone        = 1'b0;
                    pro_continue  = 1'b1;
                end
                
                WEL: begin
                    case(cnt_stage) // mosi
                        3'd0 :   begin
                            cur_stage = w_en_100ns;
                            if(cnt_son_stage == 7'd1)
                                spi_cmd_reg  = `WEL_CMD ;
                            else
                                spi_cmd_reg  = spi_cmd_reg;
                        end
                        3'd1 :   begin
                            stdone    = 1'b1;
                        end   
                        default: ;	
                    endcase
                end
                
                C_ERA : begin
                    case(cnt_stage) // mosi
                        3'd0 :   begin
                            cur_stage = w_en_100ns;
                            if(spi_cs)
                                spi_cmd_reg  = `WEL_CMD ;
                            else
                                spi_cmd_reg  = spi_cmd_reg;
                        end
                        3'd1 :   begin
                            cur_stage <= cmd_cyc;
                            if(spi_cs)
                                spi_cmd_reg  = `C_ERA_CMD ;
                            else
                                spi_cmd_reg  = spi_cmd_reg;
                        end
                        3'd2 :  cur_stage = end_6ns; 
                        3'd3 :   begin
                            pro_continue  = 1'b0;
                            cur_stage     = cmd_cyc;
                            if(spi_cs)
                                spi_cmd_reg  = `R_STA_REG_CMD ;
                            else
                                spi_cmd_reg  = spi_cmd_reg;
                                
                            if(son_done) cur_stage = miso_8b;
                            else cur_stage = cur_stage;
                        end
                        3'd5 :   begin
                            stdone    = 1'b1;
                        end   
                        default: ;	
                    endcase
                end
                default: begin
                    stdone        = 1'b0;
                    spi_cmd_reg   = 8'b0;
                    pro_continue  = 1'b1;
                end
            endcase
    end

//第三段:由状态机状态,有时序逻辑进行spi接口的跳变输出    
    always@(posedge clk_drive or negedge sys_rst_n) begin
        if(!sys_rst_n) begin
            spi_cs        <= 1'b1 ;
            spi_mosi      <= 1'b0 ;	
            //spi_addr_reg  <= 24'b0;
            //spi_data_reg  <= 8'b0 ;
            
            cnt_son_stage <= 7'b0;
            miso_8bufe    <= 8'hff;
            son_done      <= 1'b0;
            spi_clk_sta   <= 1'b0;
        end
        else begin
            son_done   <= 1'b0;
            
            //命令子模块执行
            if(spi_start || (cnt_son_stage != 7'd127))
                cnt_son_stage <= cnt_son_stage + 1'b1;
            else
                cnt_son_stage <= cnt_son_stage;
                    
            case(cur_stage)   
                idle_stage : begin
                    spi_mosi <= 1'b0;
                    //spi_addr_reg <= spi_addr;
                    //spi_data_reg <= spi_data;
                    cnt_son_stage    <= 7'b0;
                    spi_clk_sta   <= 1'b0;
                end
                
                w_en_100ns : begin
                    case(cnt_son_stage)
                        7'd0 : begin 
                            spi_cs  <= 1'b0;
                            spi_mosi    <= spi_cmd_reg[7];
                            spi_clk_sta <= 1'b1;
                        end
                        7'd2 :  spi_mosi <= spi_cmd_reg[6];
                        7'd4 :  spi_mosi <= spi_cmd_reg[5];
                        7'd6 :  spi_mosi <= spi_cmd_reg[4];
                        7'd8 :  spi_mosi <= spi_cmd_reg[3];
                        7'd10:  spi_mosi <= spi_cmd_reg[2];
                        7'd12:  spi_mosi <= spi_cmd_reg[1];
                        7'd14: begin 
                            spi_mosi <= spi_cmd_reg[0];
                            spi_clk_sta   <= 1'b0;
                        end
                        7'd15: begin
                            spi_cs        <= 1'b1;
                            spi_mosi      <= 1'b0;
                        end
                        7'd16: begin
                            cnt_son_stage <= 7'd127;
                            son_done       <= 1'b1;
                        end
                        default: ;	
                    endcase
                end
                
                cmd_cyc : begin
                    case(cnt_son_stage)
                        7'd0 : begin 
                            spi_cs  <= 1'b0;
                            spi_mosi    <= spi_cmd_reg[7];
                            spi_clk_sta   <= 1'b1;
                        end
                        7'd2 : spi_mosi <= spi_cmd_reg[6];
                        7'd4 : spi_mosi <= spi_cmd_reg[5];
                        7'd6 : spi_mosi <= spi_cmd_reg[4];
                        7'd8 : spi_mosi <= spi_cmd_reg[3];
                        7'd10: spi_mosi <= spi_cmd_reg[2];
                        7'd12: spi_mosi <= spi_cmd_reg[1];
                        7'd14: spi_mosi <= spi_cmd_reg[0];
                        7'd16: begin 
                            son_done      <= 1'b1;
                            spi_mosi      <= 1'b0;
                            
                            if(pro_continue) begin
                                cnt_son_stage <= 7'd127;
                                spi_cs        <= 1'b1;
                                spi_clk_sta   <= 1'b0;
                            end
                            else begin
                                cnt_son_stage <= 7'b0;
                                spi_cs        <= 1'b0;
                                spi_clk_sta   <= spi_clk_sta;
                            end
                        end
                        default: ;
                    endcase                        
                end
                
                end_6ns : begin
                    if(cnt_son_stage == 7'd3) begin // 完成 
                        cnt_son_stage <= 7'd127;
                    end
                    else if(cnt_son_stage == 7'd2)
                        son_done       <= 1'b1;
                    else  begin
                            spi_cs        <= 1'b1 ;
                            spi_mosi      <= 1'b0;
                    end
                end
                
                miso_8b : begin // miso 接收数据线传回的8bit信号
                    case(cnt_son_stage)
                        7'd0 : begin 
                            spi_cs    <= 1'b0;
                            spi_clk_sta <= 1'b1;
                            miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        end
                        7'd2 : miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd4 : miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd6 : miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd8 : miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd10: miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd12: miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd14: miso_8bufe  <= {miso_8bufe[6:0],spi_miso};
                        7'd15: begin     
                            if(!spi_miso && !pro_continue) begin
                                cnt_son_stage <= 7'd16;
                                son_done      <= 1'b1;
                                spi_cs        <= 1'b1;
                                spi_clk_sta   <= 1'b0;    
                            end
                            else begin 
                                    cnt_son_stage  <= spi_clk_sta ? 7'd0 : 7'd127;
                                    son_done       <= 1'b0;
                            end    
                        end
                        7'd16: cnt_son_stage <= 7'd127;  
                        default: ;	
                    endcase
                end
                default: ;	
            endcase
        end
    end
    
endmodule

你可能感兴趣的:(数字IC学习之旅,fpga开发,数字ic,通信协议,代码实例,问题讨论)