Verilog: 按键控制基于SPI协议M25P16读写操作系统设计(三)读写及控制模块

目录

一.  读模块(read_flash)

1. 模块端口信号

2. 状态机

3. 关键点

4. read_flash模块代码

5. read_flash模块仿真

二. 写模块(write_flash)

1. 模块端口信号

2. 状态机

3. 关键点

A. 标志信号:

a. 区分WREN 跳到SE或PP,代码实现如下

b. 区分RDSR分跳到WREN或IDLE,代码实现如下

B. 读状态寄存器WEL与WIP位判断

C. 使用byte计数器计数字节传输量(同read_flash)

D. 命令及操作延时

4. write_flash模块代码

5. write_flash模块仿真

三. 读写控制模块(flash_control)

 1. 代码实现逻辑

2. flash_control模块仿真


一.  读模块(read_flash)

      读模块实现两个功能:读芯片ID,及读数据,分别用两个信号控制

1. 模块端口信号

input/outputnamedescription
input

rd_id

读ID请求信号 from flash_control
input

rd_data

读数据请求信号 from flash_control
input

rd_done

一字节传输完成信号 from spi_interface

input

rx_data[7:0]

读出ID或数据 from spi_interface

output

rd_req

读请求信号 to spi_interface

output

tx_data[7:0]

发出命令、地址 to spi_interface

output

seg_data[23:0]

输出读出数据 to flash_control

2. 状态机

      状态机分为空闲(IDLE),读ID(RDID),读数据(RDDATA),完成(DONE)四个状态,状态转移图如图2.1所示。

图1.2.1  read_flash状态转移图 

状态转移条件:

IDLE TO RDID :系统处于IDLE且收到rd_id信号

RDID TO DONE :系统处于RDID且传输完所有字节(1byte命令+3byteID)

IDLE TO RDDATA:系统处于IDLE且收到rd_data信号

RDDATA TO DONE:系统处于RDDATA且传输完所有字节(1byte命令+ 3byte地址+n字节数据,n>=1)

DONE TO IDLE :系统处于DONE状态(DONE位过渡状态)

3. 关键点

     使用byte计数器计数字节传输量,每个状态(RDID/RDDATA)字节传输量不同,当spi_interface模块传回done信号有效时,计数值加一,代码实现如下:

 

//****************************************************************
//--cnt_bytes
//**************************************************************** 
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_bytes <= 8'd0;
        end 
        else if(add_cnt_bytes)begin 
            if(end_cnt_bytes)begin 
                cnt_bytes <= 8'd0;
            end
            else begin 
                cnt_bytes <= cnt_bytes + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_bytes = state_c != IDLE && rd_done;
    assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == byte_sum;

4. read_flash模块代码

/**************************************功能介绍***********************************
Description:  flash m25p16芯片读控制模块		
Change history:  2023年7月13日20:34:55  
*********************************************************************************/

`include "param.v"

//---------<模块及端口声名>------------------------------------------------------
module flash_read( 
    input				clk		,
    input				rst_n	,
    //key
    input		    	rd_id   ,//读ID指令
    input		        rd_data	,//读数据指令
    //spi_interface
    input               rd_done ,//一字节传输完成信号 from spi_interface
    input		[7:0]	rx_data ,//读出数据          from spi_interface
    output		    	rd_req	,//读请求信号         to spi_interface
    output		[7:0]	tx_data	,//发出命令、地址     to spi_interface
    //output
    output      [23:0]  seg_data //输出读出数据
    //output              seg_vld  //读出数据有效信号
);								 
//---------<参数定义>--------------------------------------------------------- 
    //状态机状态编码
    localparam          IDLE         = 4'b0001  ,//空闲状态
                        RDID         = 4'b0010  ,//读设备ID状态
                        RDDATA       = 4'b0100  ,//读数据状态
                        DONE         = 4'b1000  ;//读操作完成状态 
      
//---------<内部信号定义>-----------------------------------------------------
    reg         [23:0]  init_addr      ;//首地址寄存
    //输出信号寄存
    reg                 req_reg        ;//读请求信号寄存
    reg         [7:0]   tx_data_reg    ;
    reg         [23:0]  seg_data_reg   ; 
    //reg                 seg_vld_reg    ; 
    //字节计数
    reg         [8:0]   byte_sum       ;
    reg			[8:0]	cnt_bytes	   ;
    wire				add_cnt_bytes  ;
    wire				end_cnt_bytes  ;
   
    //状态机状态转移
    reg         [3:0]   state_c        ;//现态
    reg         [3:0]   state_n        ;//次态
    //状态机状态转移条件   
    wire                idle2rdid      ;
    wire                rdid2done      ;
    wire                idle2rddata    ;
    wire                rddata2done    ;
    wire                done2idle      ;

//****************************************************************
//--init_addr
//**************************************************************** 
    always @(*)begin 
         if(rd_req)begin
           init_addr = `INIT_ADDR;
         end 
         else begin 
           init_addr = 24'h0;
         end 
    end

//****************************************************************
//--req_reg
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            req_reg <= 1'b0;
        end 
        else if(rd_id || rd_data)begin 
            req_reg <= 1'b1;
        end 
        else if(state_c == DONE)begin 
            req_reg <= 1'b0;
        end 
    end

//****************************************************************
//--byte_sum
//****************************************************************
    always @(*)begin 
         if(state_c == RDID)begin
            byte_sum = `RDID_BYTES;
         end 
         else if(state_c == RDDATA)begin 
            byte_sum = `RDDATA_BYTES;
         end 
         else begin 
            byte_sum = 9'b0;
         end 
    end
//****************************************************************
//--cnt_bytes
//**************************************************************** 
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_bytes <= 8'd0;
        end 
        else if(add_cnt_bytes)begin 
            if(end_cnt_bytes)begin 
                cnt_bytes <= 8'd0;
            end
            else begin 
                cnt_bytes <= cnt_bytes + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_bytes = state_c != IDLE && rd_done;
    assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == byte_sum;
    
//****************************************************************
//--状态机
//****************************************************************
    //第一段:时序逻辑描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
    
    //第二段:组合逻辑描述状态转移规律和状态转移条件
    always @(*) begin
        case(state_c)
            IDLE   : begin
                if(idle2rdid)begin
                   state_n = RDID; 
                end
                else if(idle2rddata)begin
                    state_n = RDDATA;
                end
                else begin
                    state_n = state_c;
                end
            end 
            RDID   : begin
                if(rdid2done)begin
                    state_n = DONE;
                end
                else begin
                    state_n = state_c;
                end
            end 
            RDDATA : begin
                if(rddata2done)begin
                    state_n = DONE;
                end
                else begin
                    state_n = state_c;
                end
            end 
            DONE   : begin
                if(done2idle)begin
                    state_n = IDLE;
                end
                else begin
                    state_n = state_c;
                end
            end 
            default : begin
                    state_n = state_c;
            end
        endcase
    end

    //状态转移条件
    assign  idle2rdid   = state_c == IDLE   && rd_id ;
    assign  rdid2done   = state_c == RDID   && end_cnt_bytes ;
    assign  idle2rddata = state_c == IDLE   && rd_data ;
    assign  rddata2done = state_c == RDDATA && end_cnt_bytes ;
    assign  done2idle   = state_c == DONE   && 1'b1 ;
                
    //第三段:描述输出,时序逻辑或组合逻辑皆可
                
//****************************************************************
//--tx_data_reg
//****************************************************************             
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            tx_data_reg <= 8'h0;
        end 
        else if(state_c == RDID)begin 
            case(cnt_bytes)
            0 : tx_data_reg <= `RDID;
      default : tx_data_reg <= tx_data_reg;
            endcase
        end 
        else if(state_c == RDDATA)begin
            case(cnt_bytes)
            0 : tx_data_reg <= `READ;
            1 : tx_data_reg <= init_addr[23:16];
            2 : tx_data_reg <= init_addr[15: 8];
            3 : tx_data_reg <= init_addr[ 7: 0];
      default : tx_data_reg <= tx_data_reg     ;
        endcase
        end
    end

//****************************************************************
//--seg_data_reg
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            seg_data_reg <= 24'b0;
        end 
        else if(state_c == RDID)begin 
            case(cnt_bytes)
            1 : seg_data_reg[23:16] <= rx_data;
            2 : seg_data_reg[15: 8] <= rx_data;
            3 : seg_data_reg[ 7: 0] <= rx_data;
      default : seg_data_reg <= seg_data_reg  ;
            endcase
        end 
        else if(state_c == RDDATA)begin
            case(cnt_bytes)
            4 : seg_data_reg[23:16] <= rx_data;
            5 : seg_data_reg[15: 8] <= rx_data;
            6 : seg_data_reg[ 7: 0] <= rx_data;
      default : seg_data_reg <= seg_data_reg  ;
            endcase
        end
    end

//****************************************************************
//--seg_vld_reg
//****************************************************************
    // always @(posedge clk or negedge rst_n)begin 
    //     if(!rst_n)begin
    //         seg_vld_reg <= 1'b0;
    //     end 
    //     else if(end_cnt_bytes)begin 
    //         seg_vld_reg <= 1'b1;
    //     end 
    //     else begin 
    //         seg_vld_reg <= 1'b0;
    //     end 
    // end

//****************************************************************
//--rd_req  tx_data  seg_data  seg_vld 
//****************************************************************
    assign  rd_req   = req_reg     ;
    assign  tx_data  = tx_data_reg ;
    assign  seg_data = seg_data_reg;
    //assign  seg_vld  = seg_vld_reg ;
    
endmodule

5. read_flash模块仿真

分别模拟读ID和读数据操作,调用m25p16从机模型及spi接口模块,仿真代码如下:

`timescale 1ns/1ns
    
module tb_flash_read();

//激励信号定义 
    reg                 clk	    ;
    reg                 rst_n   ;
    reg                 rd_id   ; 
    reg                 rd_data ;

    reg     [47:0]      assic_state_c;

    wire    [7:0]       rx_data ;	 
    wire	     		req 	;
    wire	[7:0]		tx_data ;
    wire    [23:0]      seg_data;
    wire                done    ;
    wire			    miso	;
    wire		        cs_n	;
    wire		        mosi	;
    wire		        sclk	;
//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

    pullup(miso);

    always @(*)begin 
        case (u1.state_c)
            u1.IDLE       : assic_state_c = "IDLE  ";
            u1.RDID       : assic_state_c = "RDID  ";
            u1.RDDATA     : assic_state_c = "RDDATA";
            u1.DONE       : assic_state_c = "DONE  ";
            default: ;
        endcase
    end

//模块例化
    flash_read u1( 
    /*input				*/.clk		(clk	  ),
    /*input				*/.rst_n	(rst_n	  ),
    /*input		    	*/.rd_id    (rd_id    ),//读ID指令
    /*input		        */.rd_data 	(rd_data  ),//读数据指令
    /*input             */.rd_done  (done     ),//一字节传输完成信号 from spi_interface
    /*input		[7:0]	*/.rx_data  (rx_data  ),//读出数据          from spi_interface
    /*output        	*/.rd_req 	(req      ),//读请求信号         to spi_interface
    /*output	[7:0]	*/.tx_data	(tx_data  ),//发出命令、地址     to spi_interface
    /*output    [23:0]  */.seg_data (seg_data )//输出读出数据
);

    spi_interface spi_interface_inst( 
    .clk  	(clk     ) ,
    .rst_n	(rst_n   ) ,
    .req    (req     ) ,//操作请求信号
    .miso   (miso    ) ,
    .din    (tx_data ) ,//输入数据(命令 地址  ID 写数据)
    .done   (done    ) ,//传输完成一个字节数据标志信号  
    .dout   (rx_data ) ,//接受到的flash数据串转并        
    .cs_n	(cs_n	 ) ,//主机发送片选信号
    .mosi	(mosi	 ) ,//主机发送数据  并转串
    .sclk	(sclk	 )  //主机产生时钟信号
);

    m25p16 m25p16_inst(
   /*input */.c       (sclk    ),
   /*input */.data_in (mosi    ),
   /*input */.s       (cs_n    ),
   /*input */.w       (        ),
   /*input */.hold    (        ),
   /*output*/.data_out(miso    )
    );

//产生时钟
    initial 		clk = 1'b0;
    always #(CLOCK_CYCLE/2) clk = ~clk;

//产生激励
    initial  begin 
        rst_n = 1'b1;
        rd_id = 1'b0;
        rd_data = 1'b0;
        #(CLOCK_CYCLE*2);
        rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        rst_n = 1'b1;
        #3;

        //模拟读取设备ID
        // rd_id = 1'b1;
        // #20;
        // rd_id = 1'b0;
        // #15000;
        
        //模拟读数据
        rd_data = 1'b1;
        #20;
        rd_data = 1'b0;
        #30000;

        $stop;


    end

endmodule 

a. 读id仿真结果

读出从机模式ID202015 

b. 读数据仿真结果

读出从机24'h01开始连续3处地址对应数据,ffffff。

二. 写模块(write_flash)

      写模块完成页编程(PP)操作,一般执行顺序为:写使能寄存(WREN),扇区擦除(SE),读状态寄存器(RDSR),页编程(PP)。

1. 模块端口信号

input/outputnamedescription
input

wr_en

写操作请求信号 from flash_control
input

wr_done

一字节传输完成信号 from spi_interface

input

rx_data[7:0]

读出数据(状态寄存器) from spi_interface

output

wr_req

写请求信号 to spi_interface

output

tx_data[7:0]

发出命令、地址 to spi_interface

output

seg_data[23:0]

输出读出数据(状态寄存器) to flash_control

2. 状态机

      状态机分为空闲(IDLE),写使能(WREN),扇区擦除(SE),读状态寄存器(RDSR),页编程(PP)五个状态,状态转移图如图2.1所示。

 

图2.2.1  write_flash状态转移图 

 

状态转移条件:

IDLE TO WREN:系统处于IDLE && 收到wr_en信号

WREN TO SE:系统处于WREN && 传输完所有字节(1byte命令)&& 延时结束 && 第一次到WREN状态(标志信号)

SE TO RDSR:系统处于SE && 传输完所有字节(1byte命令+3byte地址)&& 延时结束

RDSR TO WREN:系统处于RDSR && 传输完所有字节(1byte命令)&& 延时结束 && WIP = 0(无操作正在执行) && WEL = 0(无写使能被锁存) && 第一次到RDSR 状态(标志信号)

RDSR TO PP:系统处于RDSR && 传输完所有字节(1byte命令)&& 延时结束 && WIP = 0(无操作正在执行) && WEL = 1(写使能被锁存)

WREN TO PP:系统处于WREN && 传输完所有字节(1byte命令)&& 延时结束 && 第二次到WREN状态(标志信号)

PP TO RDSR :系统处于PP && 传输完所有字节(1byte命令+3byte地址)&& 延时结束

RDSR TO IDLE:系统处于RDSR && 传输完所有字节(1byte命令)&& 延时结束 && WIP = 0(无操作正在执行) && WEL = 0(无写使能被锁存) && 第二次到RDSR 状态(标志信号)

3. 关键点

WREN 与 RDSR跳转到其他状态的条件

A. 标志信号:

a. 区分WREN 跳到SE或PP,代码实现如下
//****************************************************************
//--wren_flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wren_flag <= 1'b0;
        end 
        else if(wren2se)begin 
            wren_flag <= 1'b1;
        end 
        else if(rdsr2idle)begin
            wren_flag <= 1'b0;
        end
    end
b. 区分RDSR分跳到WREN或IDLE,代码实现如下
//****************************************************************
//--rdsr_flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rdsr_flag <= 1'b0;
        end 
        else if(pp2rdsr)begin 
            rdsr_flag <= 1'b1;
        end 
        else if(rdsr2idle)begin
            rdsr_flag <= 1'b0;
        end
    end

B. 读状态寄存器WEL与WIP位判断

(WEL与WIP位定义详见  Verilog: 按键控制基于SPI协议M25P16读写操作系统设计(一)模块划分、原理分析及相关参数定义)

C. 使用byte计数器计数字节传输量(同read_flash)

D. 命令及操作延时

a. WREN及 RDSR延时至少100ns

b. SE 至少延时3s

c. PP至少延时5ms

代码实现如下:

/****************************************************************
//--time_delay
//****************************************************************
    always @(*)begin 
         if(state_c == WREN || state_c == RDSR)begin
           time_delay = `TIME_CMD;
         end 
         else if(state_c == SE)begin 
           time_delay = `TIME_SE;
         end 
         else if(state_c == PP)begin 
           time_delay = `TIME_PP;
         end
         else begin 
           time_delay = 28'd0 ;
         end 
    end

//****************************************************************
//--delay_flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            delay_flag <= 1'b0;
        end 
        else if(end_cnt_bytes)begin 
            delay_flag <= 1'b1;
        end 
        else if(end_cnt_delay)begin 
            delay_flag <= 1'b0;
        end
    end

//****************************************************************
//--cnt_delay
//****************************************************************      
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_delay <= 'd0;
        end 
        else if(add_cnt_delay)begin 
            if(end_cnt_delay)begin 
                cnt_delay <= 'd0;
            end
            else begin 
                cnt_delay <= cnt_delay + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_delay = delay_flag;
    assign end_cnt_delay = add_cnt_delay && cnt_delay == time_delay;

4. write_flash模块代码

/**************************************功能介绍***********************************
Description:	flash m25p16芯片写控制模块	
Change history:  2023年7月14日  
*********************************************************************************/
 
`include "param.v"
   
//---------<模块及端口声名>------------------------------------------------------
module flash_write( 
    input				clk		       ,
    input				rst_n	       ,
    //key       
    input		    	wr_en          ,//写操作使能
    //spi_interface       
    input               wr_done        ,//一字节传输完成信号 from spi_interface
    input		[7:0]	rx_data        ,//读出数据(状态寄存器)  from spi_interface
    output		    	wr_req	       ,//读请求信号         to spi_interface
    output		[7:0]	tx_data	       ,//发出命令、地址     to spi_interface
    //output       
    output      [23:0]  seg_data        //输出读出数据
);								 
//---------<参数定义>--------------------------------------------------------- 
    //状态机状态编码     
    parameter           IDLE = 5'b00001,//空闲状态
                        WREN = 5'b00010,//写使能状态
                        SE   = 5'b00100,//扇区擦除状态
                        RDSR = 5'b01000,//读状态寄存器状态
                        PP   = 5'b10000;//页编程状态

//---------<内部信号定义>-----------------------------------------------------
    reg         [23:0]  init_addr      ;//首地址寄存
    //状态机参数定义  
    reg         [4:0]   state_c        ;//现态
    reg         [4:0]   state_n        ;//次态
    //状态机状态转移条件   
    wire                idle2wren      ;//
    wire                wren2se        ;//
    wire                wren2pp        ;//
    wire                se2rdsr        ;//
    wire                rdsr2pp        ;//
    wire                rdsr2wren      ;//
    wire                rdsr2idle      ;//
    wire                pp2rdsr        ;//
   
    reg         [8:0]   bytes_max      ;//byte计数器最大值
    reg			[8:0]	cnt_bytes	   ;
    wire				add_cnt_bytes  ;
    wire				end_cnt_bytes  ;
     
    reg         [27:0]  time_delay     ;//延时计数器最大值
    reg                 delay_flag     ;//延时计数器累加标志信号
    reg			[27:0]	cnt_delay	   ;
    wire				add_cnt_delay  ;
    wire				end_cnt_delay  ;

    reg                 wren_flag      ;//区分跳到SE或PP
    reg                 rdsr_flag      ;//区分跳到WREN或IDLE

    reg                 wip            ;//rxdata[0] 正在写入位
    reg                 wel            ;//rxdata[1] 写使能锁存器位

    //输出信号寄存  
    reg                 req_reg        ;
    reg         [7:0]   tx_data_reg    ;
    reg         [23:0]  seg_data_reg   ;

//****************************************************************
//--init_addr
//**************************************************************** 
    always @(*)begin 
         if(wr_req)begin
           init_addr = `INIT_ADDR;
         end 
         else begin 
           init_addr = 24'h0;
         end 
    end

//****************************************************************
//--状态机
//****************************************************************
    //第一段:时序逻辑描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
    
    //第二段:组合逻辑描述状态转移规律和状态转移条件
    always @(*) begin
        case(state_c)
            IDLE : begin
                if(idle2wren)begin
                    state_n = WREN;
                end
                else begin
                    state_n = state_c;
                end
            end 
            WREN : begin
                if(wren2se)begin
                    state_n = SE;
                end
                else if(wren2pp)begin
                    state_n =PP;
                end
                else begin
                    state_n = state_c;
                end
            end 
            SE   : begin
                if(se2rdsr)begin
                    state_n = RDSR;
                end
                else begin
                    state_n = state_c;
                end
            end 
            RDSR : begin
                if(rdsr2pp)begin
                    state_n = PP;
                end
                else if(rdsr2wren)begin
                    state_n =WREN;
                end
                else if(rdsr2idle)begin
                    state_n =IDLE;
                end
                else begin
                    state_n = state_c;
                end
            end 
            PP   : begin
                if(pp2rdsr)begin
                    state_n = RDSR;
                end
                else begin
                    state_n = state_c;
                end
            end
         default : begin
                    state_n = IDLE;
            end
        endcase
    end
                
    //第三段:描述输出,时序逻辑或组合逻辑皆可
                
    //状态转移条件
    assign  idle2wren = state_c == IDLE && wr_en                        ;
    assign  wren2se   = state_c == WREN && end_cnt_delay && !wren_flag  ;
    assign  wren2pp   = state_c == WREN && end_cnt_delay &&  wren_flag  ;
    assign  se2rdsr   = state_c == SE   && end_cnt_delay                ;
    //assign  rdsr2pp   = state_c == RDSR && end_cnt_delay && !wip &&  wel && !rdsr_flag;
    assign  rdsr2pp   = state_c == RDSR && end_cnt_delay &&  wip &&  wel && !rdsr_flag;//仿真专用
    assign  rdsr2wren = state_c == RDSR && end_cnt_delay && !wip && !wel && !rdsr_flag;
    //assign  rdsr2idle = state_c == RDSR && end_cnt_delay && !wip && !wel &&  rdsr_flag;
    assign  rdsr2idle = state_c == RDSR && end_cnt_delay && wip && wel &&  rdsr_flag;//仿真专用
    assign  pp2rdsr   = state_c == PP   && end_cnt_delay                ;     

//****************************************************************
//--wren_flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wren_flag <= 1'b0;
        end 
        else if(wren2se)begin 
            wren_flag <= 1'b1;
        end 
        else if(rdsr2idle)begin
            wren_flag <= 1'b0;
        end
    end

//****************************************************************
//--rdsr_flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rdsr_flag <= 1'b0;
        end 
        else if(pp2rdsr)begin 
            rdsr_flag <= 1'b1;
        end 
        else if(rdsr2idle)begin
            rdsr_flag <= 1'b0;
        end
    end

//****************************************************************
//--wip  wel
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wip <= 1'b0;
            wel <= 1'b0;
        end 
        else begin 
            wip <= rx_data[0];
            wel <= rx_data[1];
        end 
    end

//****************************************************************
//--bytes_max
//****************************************************************
    always @(*)begin 
         if(state_c == WREN)begin
           bytes_max = `WREN_BYTES;
         end 
         else if(state_c == SE)begin 
           bytes_max = `SE_BYTES; 
         end 
         else if(state_c == RDSR)begin 
           bytes_max = `RDSR_BYTES; 
         end 
         else if(state_c == PP)begin 
           bytes_max = `PP_BYTES; 
         end 
         else begin 
           bytes_max = 9'd0; 
         end 
    end

//****************************************************************
//--time_delay
//****************************************************************
    always @(*)begin 
         if(state_c == WREN || state_c == RDSR)begin
           time_delay = `TIME_CMD;
         end 
         else if(state_c == SE)begin 
           time_delay = `TIME_SE;
         end 
         else if(state_c == PP)begin 
           time_delay = `TIME_PP;
         end
         else begin 
           time_delay = 28'd0 ;
         end 
    end

//****************************************************************
//--cnt_bytes
//**************************************************************** 
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_bytes <= 'd0;
        end 
        else if(add_cnt_bytes)begin 
            if(end_cnt_bytes)begin 
                cnt_bytes <= 'd0;
            end
            else begin 
                cnt_bytes <= cnt_bytes + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_bytes = state_c != IDLE && wr_done;
    assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == bytes_max;

//****************************************************************
//--delay_flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            delay_flag <= 1'b0;
        end 
        else if(end_cnt_bytes)begin 
            delay_flag <= 1'b1;
        end 
        else if(end_cnt_delay)begin 
            delay_flag <= 1'b0;
        end
    end

//****************************************************************
//--cnt_delay
//****************************************************************      
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_delay <= 'd0;
        end 
        else if(add_cnt_delay)begin 
            if(end_cnt_delay)begin 
                cnt_delay <= 'd0;
            end
            else begin 
                cnt_delay <= cnt_delay + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_delay = delay_flag;
    assign end_cnt_delay = add_cnt_delay && cnt_delay == time_delay;

//****************************************************************
//--req_reg
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            req_reg <= 1'b0;
        end 
        else if(idle2wren || wren2se ||wren2pp || se2rdsr || rdsr2pp || rdsr2wren || pp2rdsr)begin 
            req_reg <= 1'b1;
        end 
        else if(end_cnt_bytes)begin
            req_reg <= 1'b0;
        end
    end

//****************************************************************
//--tx_datd_reg
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            tx_data_reg <= 'd0;
        end 
        else if(state_c == WREN)begin 
            case(cnt_bytes)
            0 : tx_data_reg <= `WREN;
      default : tx_data_reg <= tx_data_reg ;
            endcase
        end 
        else if(state_c == SE)begin
            case(cnt_bytes)
            0 : tx_data_reg <= `SE;
            1 : tx_data_reg <= init_addr[23:16];
            2 : tx_data_reg <= init_addr[15: 8];
            3 : tx_data_reg <= init_addr[ 7: 0];
      default : tx_data_reg <= tx_data_reg;
            endcase
        end
        else if(state_c == RDSR)begin
            case(cnt_bytes)
            0 : tx_data_reg <= `RDSR;
      default : tx_data_reg <= tx_data_reg ;
            endcase
        end
        else if(state_c == PP)begin
            case(cnt_bytes)
            0 : tx_data_reg <= `PP;
            1 : tx_data_reg <= init_addr[23:16];
            2 : tx_data_reg <= init_addr[15: 8];
            3 : tx_data_reg <= init_addr[ 7: 0];
            4 : tx_data_reg <= 8'h13;             //写入flash芯片数据
            5 : tx_data_reg <= 8'h14;             //写入flash芯片数据
            6 : tx_data_reg <= 8'h15;             //写入flash芯片数据
      default : tx_data_reg <= tx_data_reg;
            endcase
        end
        else begin
            tx_data_reg <= 8'h0;
        end
    end

//****************************************************************
//--seg_data_reg
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            seg_data_reg <= 24'h0;
        end 
        else if(req_reg && state_c == WREN)begin 
            seg_data_reg <= {8'haa,8'hff,`WREN};
        end 
        else if(req_reg && state_c == SE)begin
            if(cnt_bytes < 3)begin
                seg_data_reg <= {8'haa,8'hff,`SE};
            end
            else begin
                seg_data_reg <= init_addr;
            end
        end
        else if(req_reg && state_c == RDSR)begin
            case(cnt_bytes)
            1 : seg_data_reg[23:16] <= rx_data;
            2 : seg_data_reg[15: 8] <= rx_data;
            3 : seg_data_reg[ 7: 0] <= rx_data;
      default : seg_data_reg <= seg_data_reg  ;
            endcase
        end
        else if(req_reg && state_c == PP)begin
            if(cnt_bytes < 4)begin
                seg_data_reg <= {8'hdd,8'hff,`PP};
            end
            else if(cnt_bytes <6)begin
                seg_data_reg <= init_addr;
            end
            else begin
                seg_data_reg <= {8'h13,8'h14,8'h15};
            end
        end
    end

//****************************************************************
//--wr_req  tx_data  seg_data
//****************************************************************
    assign  wr_req   = req_reg;
    assign  tx_data  = tx_data_reg;
    assign  seg_data = seg_data_reg;


endmodule

5. write_flash模块仿真

模拟页写操作,仿真代码如下:

`timescale 1ns/1ns
    
module tb_flash_write();

//激励信号定义 
    reg                 clk	    ;
    reg                 rst_n   ;
    reg                 wr_en ;

    reg     [31:0]      assic_state_c;

    wire    [7:0]       rx_data ;	 
    wire	     		req 	;
    wire	[7:0]		tx_data ;
    wire    [23:0]      seg_data;
    wire                done    ;
    wire			    miso	;
    wire		        cs_n	;
    wire		        mosi	;
    wire		        sclk	;
//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

    // pullup(miso);

    always @(*)begin 
        case (u1.state_c)
            u1.IDLE     : assic_state_c = "IDLE";
            u1.WREN     : assic_state_c = "WREN";
            u1.SE       : assic_state_c = "SE  ";
            u1.RDSR     : assic_state_c = "RDSR";
            u1.PP       : assic_state_c = "PP  ";
            default: ;
        endcase
    end

//模块例化
    flash_write u1( 
    /*input				*/.clk		(clk	  ),
    /*input				*/.rst_n	(rst_n	  ),
    /*input		        */.wr_en 	(wr_en    ),//读数据指令
    /*input             */.wr_done  (done     ),//一字节传输完成信号 from spi_interface
    /*input		[7:0]	*/.rx_data  (rx_data  ),//读出数据          from spi_interface
    /*output        	*/.wr_req 	(req      ),//读请求信号         to spi_interface
    /*output	[7:0]	*/.tx_data	(tx_data  ),//发出命令、地址     to spi_interface
    /*output    [23:0]  */.seg_data (seg_data )//输出读出数据
);

    spi_interface spi_interface_inst( 
    .clk  	(clk     ) ,
    .rst_n	(rst_n   ) ,
    .req    (req     ) ,//操作请求信号
    .miso   (miso    ) ,
    .din    (tx_data ) ,//输入数据(命令 地址  ID 写数据)
    .done   (done    ) ,//传输完成一个字节数据标志信号  
    .dout   (rx_data ) ,//接受到的flash数据串转并        
    .cs_n	(cs_n	 ) ,//主机发送片选信号
    .mosi	(mosi	 ) ,//主机发送数据  并转串
    .sclk	(sclk	 )  //主机产生时钟信号
);

    m25p16 m25p16_inst(
   /*input */.c       (sclk    ),
   /*input */.data_in (mosi    ),
   /*input */.s       (cs_n    ),
   /*input */.w       (        ),
   /*input */.hold    (        ),
   /*output*/.data_out(miso    )
    );

//产生时钟
    initial 		clk = 1'b0;
    always #(CLOCK_CYCLE/2) clk = ~clk;

//产生激励
    initial  begin 
        rst_n = 1'b1;
        wr_en = 1'b0;
        #(CLOCK_CYCLE*2);
        rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        rst_n = 1'b1;
        #3;
        
        //模拟写数据
        wr_en = 1'b1;
        #20;
        wr_en = 1'b0;
        #(CLOCK_CYCLE*3000);

        $stop;


    end

endmodule 

仿真结果如下:

状态跳转正常,由于从机模型问题,读状态寄存器值做了修改,其他一切正常 

三. 读写控制模块(flash_control)

 1. 代码实现逻辑

      控制读写模块的选择,接收发送读写模块和spi接口模块间的数据。

代码实现如下:

/**************************************功能介绍***********************************
Description:	flash m25p16芯片(读写)控制模块	
Change history:  2023年7月14日  
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module flash_control( 
    input				clk		,
    input				rst_n	,
    input		[2:0]   key_en	,//按键使能信号 from key_filter
    input               done    ,//1字节传输完成信号
    input       [7:0]   rx_data ,
    output		    	req 	,//输出读写请求信号
    output      [7:0]   tx_data ,
    output		[23:0]  seg_data //输出数码管显示数据 to segment
);								 
//---------<参数定义>--------------------------------------------------------- 
    
//---------<内部信号定义>-----------------------------------------------------
    reg                 wr_en       ;
    reg                 rd_id       ;	
    reg                 rd_data     ;
    wire                rd_req      ;
    wire                wr_req      ;
    reg                 rd_done     ;
    reg                 wr_done     ;
    reg         [7:0]   rd_rx_data  ;
    reg         [7:0]   wr_rx_data  ;
    wire        [7:0]   rd_tx_data  ;
    wire        [7:0]   wr_tx_data  ;
    wire        [23:0]  rd_seg_data ;
    wire        [23:0]  wr_seg_data ;
    //输出信号寄存
    reg         [7:0]   tx_data_reg ;
    reg         [23:0]  seg_data_reg;
    
//****************************************************************
//--wr_en
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wr_en <= 1'b0;
        end 
        else if(key_en[0])begin 
            wr_en <= 1'b1;
        end 
        else begin 
            wr_en <= 1'b0;
        end 
    end

//****************************************************************
//--rd_id
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rd_id <= 1'b0;
        end 
        else if(key_en[1])begin 
            rd_id <= 1'b1;
        end 
        else begin 
            rd_id <= 1'b0;
        end 
    end

//****************************************************************
//--rd_data
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rd_data <= 1'b0;
        end 
        else if(key_en[2])begin 
            rd_data <= 1'b1;
        end 
        else begin 
            rd_data <= 1'b0;
        end 
    end

//****************************************************************
//--req
//****************************************************************
    assign  req = rd_req || wr_req;

//****************************************************************
//--wr_done  rd_done
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rd_done <= 1'b0;
            wr_done <= 1'b0;
        end 
        else if(rd_req)begin 
            rd_done <= done;
            wr_done <= 1'b0;
        end 
        else if(wr_req)begin
            rd_done <= 1'b0;
            wr_done <= done;
        end
        else begin
            rd_done <= 1'b0;
            wr_done <= 1'b0;
        end
    end

//****************************************************************
//--rd_rx_data  wr_rx_data
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
          rd_rx_data <= 8'h0;
          wr_rx_data <= 8'h0;
        end 
        else if(rd_req)begin 
          rd_rx_data <= rx_data;
          wr_rx_data <= 8'h0;          
        end 
        else if(wr_req)begin 
          rd_rx_data <= 8'h0;
          wr_rx_data <= rx_data;          
        end 
    end

//****************************************************************
//--tx_data_reg  tx_data
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            tx_data_reg <= 8'h0;
        end 
        else if(rd_req)begin 
            tx_data_reg <= rd_tx_data;
        end 
        else if(wr_req)begin 
            tx_data_reg <= wr_tx_data;
        end 
    end

    assign  tx_data = tx_data_reg;

//****************************************************************
//--seg_data_reg  seg_data
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            seg_data_reg <= 8'h0;
        end 
        else if(rd_req)begin 
            seg_data_reg <= rd_seg_data;
        end 
        else if(wr_req)begin 
            seg_data_reg <= wr_seg_data;
        end 
    end
    
    assign  seg_data = seg_data_reg;

//****************************************************************
//--模块例化调用
//****************************************************************  
    flash_read u_read( 
    /*input				*/.clk		(clk	    ),
    /*input				*/.rst_n	(rst_n      ),
    /*//key*/
    /*input		    	*/.rd_id    (rd_id      ),//读ID指令
    /*input		        */.rd_data	(rd_data    ),//读数据指令
    /*//spi_interface*/
    /*input             */.rd_done  (rd_done    ),//一字节传输完成信号 from spi_interface
    /*input		[7:0]	*/.rx_data  (rd_rx_data ),//读出数据          from spi_interface
    /*output	    	*/.rd_req	(rd_req	    ),//读请求信号         to spi_interface
    /*output	[7:0]	*/.tx_data	(rd_tx_data ),//发出命令、地址     to spi_interface
    /*//output*/
    /*output    [23:0]  */.seg_data (rd_seg_data)//输出读出数据
    ///*output            */.seg_vld  (seg_vld ) //读出数据有效信号
);

    flash_write u_write( 
    /*input				*/.clk		(clk	    ),
    /*input				*/.rst_n	(rst_n      ),
    /*//key*/
    /*input		    	*/.wr_en    (wr_en      ),//写操作请求
    /*//spi_interface*/
    /*input             */.wr_done  (wr_done    ),//一字节传输完成信号 from spi_interface
    /*input		[7:0]	*/.rx_data  (wr_rx_data ),//读出数据          from spi_interface
    /*output	    	*/.wr_req	(wr_req	    ),//读请求信号         to spi_interface
    /*output	[7:0]	*/.tx_data	(wr_tx_data ),//发出命令、地址     to spi_interface
    /*//output*/
    /*output    [23:0]  */.seg_data (wr_seg_data)//输出读出数据
);
    
endmodule

2. flash_control模块仿真

      调用spi接口模块和从机模型。

仿真代码如下:

`timescale 1ns/1ns
    
module tb_flash_control();

//激励信号定义 
    reg                 clk	    ;
    reg                 rst_n   ;
    reg     [2:0]       key_en  ;

    reg     [31:0]      write_state_c;
    reg     [47:0]      read_state_c ;

    wire    [7:0]       rx_data ;	 
    wire	     		req 	;
    wire	[7:0]		tx_data ;
    wire    [23:0]      seg_data;
    wire                done    ;
    wire			    miso	;
    wire		        cs_n	;
    wire		        mosi	;
    wire		        sclk	;
//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

    pullup(miso);

    always @(*)begin 
        case (u1.u_read.state_c)
            u1.u_read.IDLE       : read_state_c = "IDLE  ";
            u1.u_read.RDID       : read_state_c = "RDID  ";
            u1.u_read.RDDATA     : read_state_c = "RDDATA";
            u1.u_read.DONE       : read_state_c = "DONE  ";
            default: ;
        endcase
    end

    always @(*)begin 
        case (u1.u_write.state_c)
            u1.u_write.IDLE     : write_state_c = "IDLE";
            u1.u_write.WREN     : write_state_c = "WREN";
            u1.u_write.SE       : write_state_c = "SE  ";
            u1.u_write.RDSR     : write_state_c = "RDSR";
            u1.u_write.PP       : write_state_c = "PP  ";
            default: ;
        endcase
    end

//模块例化
    flash_control u1( 
    /*input				*/.clk	   (clk		),
    /*input				*/.rst_n   (rst_n	),
    /*input		[2:0]   */.key_en  (key_en	),//按键使能信号 from key_filter
    /*input             */.done    (done    ),//1字节传输完成信号
    /*input     [7:0]   */.rx_data (rx_data ),
    /*output	    	*/.req 	   (req     ),//输出读写请求信号
    /*output    [7:0]   */.tx_data (tx_data ),
    /*output	[23:0]  */.seg_data(seg_data) //输出数码管显示数据 to segment
);

    spi_interface spi_interface_inst( 
    .clk  	(clk     ) ,
    .rst_n	(rst_n   ) ,
    .req    (req     ) ,//操作请求信号
    .miso   (miso    ) ,
    .din    (tx_data ) ,//输入数据(命令 地址  ID 写数据)
    .done   (done    ) ,//传输完成一个字节数据标志信号  
    .dout   (rx_data ) ,//接受到的flash数据串转并        
    .cs_n	(cs_n	 ) ,//主机发送片选信号
    .mosi	(mosi	 ) ,//主机发送数据  并转串
    .sclk	(sclk	 )  //主机产生时钟信号
);

    m25p16 m25p16_inst(
   /*input */.c       (sclk    ),
   /*input */.data_in (mosi    ),
   /*input */.s       (cs_n    ),
   /*input */.w       (        ),
   /*input */.hold    (        ),
   /*output*/.data_out(miso    )
    );

//产生时钟
    initial 		clk = 1'b0;
    always #(CLOCK_CYCLE/2) clk = ~clk;

//产生激励
    initial  begin 
        rst_n = 1'b1;
        key_en = 3'b0;
        #(CLOCK_CYCLE*2);
        rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        rst_n = 1'b1;
        #3;
        
        //模拟读控制操作

        //读设备ID
        // key_en[1] = 1'b1;
        // #20;
        // key_en[1] = 1'b0;
        // #15000;
        //模拟读数据操作
        // key_en[2] = 1'b1;
        // #20;
        // key_en[2] = 1'b0;
        // #30000;

        // //模拟写数据
        key_en[0] = 1'b1;
        #20;
        key_en[0] = 1'b0;
        #(CLOCK_CYCLE*3000);
      
        $stop;


    end

endmodule 

读写控制模块仿真结果与读写模块仿真结果一致,不再赘述。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值