• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

FPGAFPGAspi的flash读写

武飞扬头像
EPCCcc
帮助1

一、SPI

SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
学新通

二、看spi–flash手册找关键

1.描述

学新通

16Mbit的存储空间

单扇区擦除或者整块擦除

用spi协议与flash读写

2.flash接口信号

学新通

C是串行时钟

D是数据

S是片选信号

3.SPI模式选择

flash只支持mode0和mode3两种模式
学新通

学新通

CPOL时钟相位

时钟的极性(CPOL)用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。当时钟极性为0时(CPOL=0),SCK信号线在空闲时为低电平;当时钟极性为1时(CPOL=1),SCK信号线在空闲时为高电平;

CPHA时钟极性

当时钟相位为1时(CPHA=1),在SCK信号线的第二个跳变沿进行采样;这里的跳变沿究竟是上升沿还是下降沿?取决于时钟的极性。当时钟极性为0时,取下降沿;当时钟极性为1时,取上升沿

  • CPOL=0,CPHA=0

学新通

  • CPOL=0,CPHA=1

    学新通

  • CPOL=1,CPHA=0

    学新通

  • CPOL=1,CPHA=1

    学新通

4.高字节MSB

学新通

MSB先,就是高字节先

5.指令

学新通

Instruction Code 说明
RDID 8’h9F 读ID
RDSR 8’h05 读寄存器判断最后一位是0(但我其实没有用过)
READ 8’h03 读数据
PP 8’h02 页擦除
SE 8’hD8 扇区擦除

6. 写使能时序

  • 1字节的指令

学新通

7.读ID时序

  • 1字节的指令
  • 3字节的ID数据

学新通

8.读寄存器时序(我没用到)

  • 1字节的指令
  • 2字节的数据

学新通

判断WIP BIT是否为0才能进行下一步(我的代码里没有用到)

学新通

9.读数据时序

  • 1字节的指令
  • 3字节的地址
  • 1字节的数据

学新通

10.页编程

  • 1字节的指令
  • 3字节的地址
  • 1字节的数据

学新通

11.扇区擦除

  • 1字节的指令
  • 3字节的地址

学新通

12.重要的时间

学新通

学新通

时间名称    
C_TIME 100ns 一个操作结束后到下一个操作片需要100ns,比如一个命令到下一个命令
PP_TIME 1.4-5ms 页编程的所需要的时间5ms
SE_TIME 1-3s 擦除所需要的时间3s

三、状态机设计

1.spi接口状态机

学新通

2.flash读状态机

学新通

3.flash写状态机

学新通

四、代码部分

1.spi_interface.v

module spi_interface(
    input           clk,
    input           rst_n,
    // 接口与主机
    input   [7:0]   din,
    input           req,
    output  [7:0]   dout,
    output          done,
    // 接口与flash
    input           miso,// 主机采样从机发送
    output          mosi,// 主机发送从机
    output          sclk,// 串行时钟
    output          cs_n // 片选信号
);

parameter   CPHA = 1,// 空闲状态高电平
            CPOL = 1;// 下降沿发送,上升沿采样

// 16分频或8分频或4分频 不能2分频
parameter   SCLK = 16,
            SCLK_BEFORE = SCLK/4,
            SCLK_AFTER  = SCLK*3/4;

// 状态机
localparam  IDLE  = 4'b0001,
            WAIT  = 4'b0010,
            DATA  = 4'b0100,
            DONE  = 4'b1000;

reg     [3:0]       state_c;
reg     [3:0]       state_n;

wire                idle2wait;
wire                wait2data;
wire                data2done;
wire                done2idle;

// bit计数器
reg     [2:0]       cnt_bit;
wire                add_cnt_bit;
wire                end_cnt_bit;

// 分频串行时钟计数器
reg     [4:0]       cnt_sclk;
wire                add_cnt_sclk;
wire                end_cnt_sclk;

// 寄存要发送的数据
reg                 spi_sclk;
reg     [7:0]       rx_data;
reg     [7:0]       tx_data;
reg                 spi_cn_n;
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(idle2wait)begin
                        state_n = WAIT;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        WAIT      :begin 
                    if(wait2data)begin
                        state_n = DATA;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        DATA      :begin 
                    if(data2done)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: state_n = IDLE;
    endcase
end
    
assign idle2wait = state_c == IDLE && (req);
assign wait2data = state_c == WAIT && (1'b1);
assign data2done = state_c == DATA && (end_cnt_bit);
assign done2idle = state_c == DONE && (1'b1);

// bit计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 0;
    end 
    else if(add_cnt_bit)begin 
            if(end_cnt_bit)begin 
                cnt_bit <= 0;
            end
            else begin 
                cnt_bit <= cnt_bit   1;
            end 
    end
   else  begin
       cnt_bit <= cnt_bit;
    end
end 

assign add_cnt_bit = end_cnt_sclk;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 8 - 1;

// sclk计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_sclk <= 0;
    end 
    else if(add_cnt_sclk)begin 
            if(end_cnt_sclk)begin 
                cnt_sclk <= 0;
            end
            else begin 
                cnt_sclk <= cnt_sclk   1;
            end 
    end
   else  begin
       cnt_sclk <= cnt_sclk;
    end
end 

assign add_cnt_sclk = (state_c == DATA);
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == SCLK - 1;

// 16分频串行时钟 CPHA=1,CPOL=1
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        if(CPHA == 0)begin
            spi_sclk <= 1'b0;
        end
        else if(CPHA == 1)begin
            spi_sclk <= 1'b1;
        end
    end 
    else if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin 
        if(CPHA == 0)begin
            spi_sclk <= 1'b1;
        end
        else if(CPHA == 1)begin
            spi_sclk <= 1'b0;
        end
    end 
    else if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin 
        if(CPHA == 0)begin
            spi_sclk <= 1'b0;
        end
        else if(CPHA == 1)begin
            spi_sclk <= 1'b1;
        end
    end 
end

// 发送的数据mosi 高位MSB先 CPHA=1,CPOL=1
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_data <= 0;
    end 
    else if(CPOL == 0)begin
        if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin
            tx_data <= din;
        end
    end
    else if(CPOL == 1)begin
        if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin 
            tx_data <= din;
        end 
    end
end

// 接收的数据miso 高位MSB先 CPHA=1,CPOL=1
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rx_data <= 0;
    end 
    else if(CPOL == 0)begin
        if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin 
            rx_data[7-cnt_bit] <= miso;
        end 
    end
    else if(CPOL == 1)begin
        if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin 
            rx_data[7-cnt_bit] <= miso;
        end 
    end
end

assign mosi = tx_data[7-cnt_bit];
assign sclk = spi_sclk;
assign cs_n = ~req;
assign dout = rx_data;
assign done = (state_c == DONE);

endmodule
学新通

2.spi_read_ctrl.v

module spi_read_ctrl(
    input               clk,
    input               rst_n,
    input       [2:0]   key_out,
    input       [7:0]   din,
    input               done,
    output reg          req,
    output      [7:0]   dout,
    output reg  [23:0]  seg_data
);

localparam  RDID_CMD = 8'h9F,// 读ID指令
            RDDA_CMD = 8'h03,// 读数据指令
            RDDA_ADD = 24'h0;// 读数据地址

localparam  IDLE    = 7'b000_0001,
            RDIDCMD = 7'b000_0010,  
            RDID    = 7'b000_0100,   
            RDDACMD = 7'b000_1000,
            RDDAADD = 7'b001_0000, 
            RDDATA  = 7'b010_0000,  
            DONE    = 7'b100_0000;

reg     [6:0]       state_c;
reg     [6:0]       state_n;

wire                idle2rdidcmd   ;
wire                idle2rddacmd   ;
wire                rdidcmd2rdid   ;
wire                rdid2done      ;
wire                rddacmd2rddaadd;
wire                rddaadd2rddata ;
wire                rddata2done    ;
wire                done2idle      ;

// 字节计数器
reg     [2:0]       cnt_byte;
wire                add_cnt_byte;
wire                end_cnt_byte;

// 读id和读数据请求
reg                 rdid_req;
reg                 rdda_req;

reg     [7:0]       tx_data;
// 状态机
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(idle2rdidcmd)begin
                        state_n = RDIDCMD;
                    end
                    else if(idle2rddacmd)begin
                        state_n = RDDACMD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        RDIDCMD      :begin 
                    if(rdidcmd2rdid)begin
                        state_n = RDID;
                    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
        RDDACMD      :begin 
                    if(rddacmd2rddaadd)begin
                        state_n = RDDAADD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        RDDAADD      :begin 
                    if(rddaadd2rddata)begin
                        state_n = RDDATA;
                    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: state_n = IDLE;
    endcase
end
    
assign idle2rdidcmd     = state_c == IDLE       && (rdid_req);
assign idle2rddacmd     = state_c == IDLE       && (rdda_req);
assign rdidcmd2rdid     = state_c == RDIDCMD    && (end_cnt_byte);
assign rdid2done        = state_c == RDID       && (end_cnt_byte);
assign rddacmd2rddaadd  = state_c == RDDACMD    && (end_cnt_byte);
assign rddaadd2rddata   = state_c == RDDAADD    && (end_cnt_byte);
assign rddata2done      = state_c == RDDATA     && (end_cnt_byte);
assign done2idle        = state_c == DONE       && (1'b1);

// 字节计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_byte <= 0;
    end 
    else if(add_cnt_byte)begin 
            if(end_cnt_byte)begin 
                cnt_byte <= 0;
            end
            else begin 
                cnt_byte <= cnt_byte   1;
            end 
    end
   else  begin
       cnt_byte <= cnt_byte;
    end
end 

assign add_cnt_byte = (state_c != IDLE) && done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == (((state_c == RDIDCMD) || (state_c == RDDACMD) || (state_c == RDDATA))?(1-1):(3-1));


// 读id和读数据请求
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rdid_req <= 0;
        rdda_req <= 0;
        req <= 0;
    end 
    else if(key_out[0])begin 
        rdid_req <= 1'b1;
        req <= 1'b1;
    end 
    else if(key_out[1])begin 
        rdda_req <= 1'b1;
        req <= 1'b1;
    end 
    else if(state_c == DONE)begin
        req <= 1'b0;
        rdid_req <= 1'b0;
        rdda_req <= 1'b0;
    end
end

// 指令
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_data <= 0;
    end 
    else if(idle2rdidcmd)begin 
        tx_data <= RDID_CMD;
    end 
    else if(idle2rddacmd)begin 
        tx_data <= RDDA_CMD;
    end 
    else if(rddacmd2rddaadd)begin
        tx_data <= RDDA_ADD;
    end
end

// seg_data
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        seg_data <= 0;
    end 
    else if(state_c == RDID && add_cnt_byte)begin
        case(cnt_byte)
        0   :   seg_data[23:16] <= din;
        1   :   seg_data[15:8] <= din;
        2   :   seg_data[7:0] <= din;
        default: seg_data <= seg_data;
        endcase
    end
    else if(state_c == RDDATA && add_cnt_byte)begin 
        case(cnt_byte)
        0   :   seg_data[23:16] <= din;
        default: seg_data <= seg_data;
        endcase
    end 
    else begin
        seg_data <= seg_data;
    end
end

// assign req = rdid_req || rdda_req;
assign dout = tx_data;
endmodule
学新通

3.spi_write_ctrl.v

module spi_write_ctrl(
    input           clk,
    input           rst_n,
    input   [2:0]   key_out,
    input   [7:0]   din,
    input           done,
    output          req,
    output  [7:0]   dout
);

parameter   CMD_TIME = 10,// 第一个指令到下一个指令200ns等待时间
            PP_TIME  = 250_000,// PP可编程时间5ms
            SE_TIME  = 150_000_000;// SE擦除时间3s

parameter   WREN_CMD = 8'h06,
            SE_CMD   = 8'hD8,
            SE_ADD   = 24'h000000,
            RDSR_CMD = 8'h05,
            PP_CMD   = 8'h02,
            PP_ADD   = 24'h000000,
            DATA     = 8'h78;

// 状态机
localparam  IDLE        =10'b00000_00001,
            FIRWRENCMD  =10'b00000_00010,
            SECMD       =10'b00000_00100,
            SEADD       =10'b00000_01000,
            RDSRCMD     =10'b00000_10000,
            SECWRENCMD  =10'b00001_00000,
            PPCMD       =10'b00010_00000,
            PPADD       =10'b00100_00000,
            PPDATA      =10'b01000_00000,
            DONE        =10'b10000_00000;

reg     [9:0]       state_c;
reg     [9:0]       state_n;

wire                idle2firwrencmd     ;
wire                firwrencmd2secmd    ;
wire                secmd2seadd         ;
wire                seadd2rdsrcmd       ;
wire                rdsrcmd2secwrencmd  ;
wire                secwrencmd2ppcmd    ;
wire                ppcmd2ppadd         ;
wire                ppadd2ppdata        ;
wire                ppdata2done         ;
wire                done2idle           ;

// 字节计数器
reg     [1:0]       cnt_byte;
wire                add_cnt_byte;
wire                end_cnt_byte;

// 100ms一个命令到下一个命令的等待时间
reg     [3:0]       cnt_200ns;
wire                add_cnt_200ns;
wire                end_cnt_200ns;

// se擦除等待时间
reg     [27:0]      cnt_3s;
wire                add_cnt_3s;
wire                end_cnt_3s; 

// pp页编程等待时间
// reg                 cnt_5ms;
// wire                add_cnt_5ms;
// wire                end_cnt_5ms;

// 一个命令到下一个命令的等待标志
reg                 delay_flag;

// 寄存req
reg                 req_r;

// 寄存要发送的数据
reg     [7:0]       tx_data;

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(idle2firwrencmd)begin
                        state_n = FIRWRENCMD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        FIRWRENCMD      :begin 
                    if(firwrencmd2secmd)begin
                        state_n = SECMD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        SECMD      :begin 
                    if(secmd2seadd)begin
                        state_n = SEADD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        SEADD      :begin 
                    if(seadd2rdsrcmd)begin
                        state_n = RDSRCMD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        RDSRCMD      :begin 
                    if(rdsrcmd2secwrencmd)begin
                        state_n = SECWRENCMD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        SECWRENCMD      :begin 
                    if(secwrencmd2ppcmd)begin
                        state_n = PPCMD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        PPCMD      :begin 
                    if(ppcmd2ppadd)begin
                        state_n = PPADD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        PPADD      :begin 
                    if(ppadd2ppdata)begin
                        state_n = PPDATA;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        PPDATA      :begin 
                    if(ppdata2done)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: state_n = IDLE;
    endcase
end
    
assign idle2firwrencmd    = state_c == IDLE         && (key_out[2]);
assign firwrencmd2secmd   = state_c == FIRWRENCMD   && (end_cnt_200ns);
assign secmd2seadd        = state_c == SECMD        && (end_cnt_byte);
assign seadd2rdsrcmd      = state_c == SEADD        && (end_cnt_3s);
assign rdsrcmd2secwrencmd = state_c == RDSRCMD      && (end_cnt_200ns);
assign secwrencmd2ppcmd   = state_c == SECWRENCMD   && (end_cnt_200ns);
assign ppcmd2ppadd        = state_c == PPCMD        && (end_cnt_byte);
assign ppadd2ppdata       = state_c == PPADD        && (end_cnt_byte);
assign ppdata2done        = state_c == PPDATA       && (end_cnt_byte);
assign done2idle          = state_c == DONE         && (1'b1);

// 字节计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_byte <= 0;
    end 
    else if(add_cnt_byte)begin 
            if(end_cnt_byte)begin 
                cnt_byte <= 0;
            end
            else begin 
                cnt_byte <= cnt_byte   1;
            end 
    end
   else  begin
       cnt_byte <= cnt_byte;
    end
end 

assign add_cnt_byte = ((state_c != IDLE) && done);
assign end_cnt_byte = add_cnt_byte && cnt_byte == (((state_c == FIRWRENCMD) || (state_c == SECMD) || (state_c == RDSRCMD) || (state_c == SECWRENCMD) || (state_c == PPCMD) || (state_c == PPDATA))?(1-1):(3-1));

// 100ms一个命令到下一个命令的等待时间计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_200ns <= 0;
    end 
    else if(add_cnt_200ns)begin 
            if(end_cnt_200ns)begin 
                cnt_200ns <= 0;
            end
            else begin 
                cnt_200ns <= cnt_200ns   1;
            end 
    end
   else  begin
       cnt_200ns <= cnt_200ns;
    end
end 

assign add_cnt_200ns = (((state_c == FIRWRENCMD) || (state_c == RDSRCMD) || (state_c == SECWRENCMD)) && delay_flag);
assign end_cnt_200ns = add_cnt_200ns && cnt_200ns == CMD_TIME - 1;

// SE擦除时间2s
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_3s <= 0;
    end 
    else if(add_cnt_3s)begin 
            if(end_cnt_3s)begin 
                cnt_3s <= 0;
            end
            else begin 
                cnt_3s <= cnt_3s   1;
            end 
    end
   else  begin
       cnt_3s <= cnt_3s;
    end
end 

assign add_cnt_3s = ((state_c == SEADD) && delay_flag);
assign end_cnt_3s = add_cnt_3s && cnt_3s == SE_TIME - 1;

// 一个命令到下一个命令的等待延长标志
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        delay_flag <= 0;
    end 
    else if(end_cnt_byte)begin 
        delay_flag <= 1'b1;
    end 
    else if(end_cnt_200ns || end_cnt_3s)begin 
        delay_flag <= 1'b0;
    end 
    else begin
        delay_flag <= delay_flag;
    end
end

// req信号
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        req_r <= 1'b0;
    end 
    else if(idle2firwrencmd)begin 
        req_r <= 1'b1;
    end
    else if((state_c == FIRWRENCMD) && end_cnt_byte)begin 
        req_r <= 1'b0;
    end 
    else if(firwrencmd2secmd)begin
        req_r <= 1'b1;
    end
    else if(secmd2seadd)begin
        req_r <= 1'b1;
    end
    else if((state_c == SEADD) && end_cnt_byte)begin
        req_r <= 1'b0;
    end
    else if(seadd2rdsrcmd)begin
        req_r <= 1'b1;
    end
    else if((state_c == RDSRCMD) && end_cnt_byte)begin
        req_r <= 1'b0;
    end
    else if(rdsrcmd2secwrencmd)begin
        req_r <= 1'b1;
    end
    else if((state_c == SECWRENCMD) && end_cnt_byte)begin
        req_r <= 1'b0;
    end
    else if(secwrencmd2ppcmd)begin
        req_r <= 1'b1;
    end
    else if(ppcmd2ppadd)begin
        req_r <= 1'b1;
    end
    else if(ppadd2ppdata)begin
        req_r <= 1'b1;
    end
    else if(ppdata2done)begin
        req_r <= 1'b0;
    end 
end

// dout传输的数据
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_data <= 0;
    end 
    else if(state_c == FIRWRENCMD)begin 
        tx_data <= WREN_CMD;
    end 
    else if(state_c == SECMD)begin 
        tx_data <= SE_CMD;
    end 
    else if(state_c == SEADD)begin
        tx_data <= SE_ADD;
    end
    else if(state_c == RDSRCMD)begin
        tx_data <= RDSR_CMD;
    end
    else if(state_c == SECWRENCMD)begin
        tx_data <= WREN_CMD;
    end
    else if(state_c == PPCMD)begin
        tx_data <= PP_CMD;
    end
    else if(state_c == PPADD)begin
        tx_data <= PP_ADD;
    end
    else if(state_c == PPDATA)begin
        tx_data <= DATA;
    end
end

assign req = req_r;
assign dout = tx_data;
endmodule
学新通

4.spi_control.v

module spi_control(
    input           clk,
    input           rst_n,
    input   [2:0]   key_out,
    input   [7:0]   din,
    input           done,
    output  [7:0]   dout,
    output          req,
    output  [23:0]  seg_data
);

wire            rd_req;
wire            wr_req;
wire    [7:0]   rd_tx_data;
wire    [7:0]   wr_tx_data;

assign req = rd_req | wr_req;
assign dout = ({8{rd_req}} & rd_tx_data) | ({8{wr_req}} & wr_tx_data); 

// 读控制模块
spi_read_ctrl u_spi_read_ctrl(
    /* input            */.clk      (clk      ),
    /* input            */.rst_n    (rst_n    ),
    /* input   [2:0]    */.key_out  (key_out ),
    /* input   [7:0]    */.din      (din      ),
    /* input            */.done     (done     ),
    /* output           */.req      (rd_req   ),
    /* output  [7:0]    */.dout     (rd_tx_data),
    /* output reg  [23:0]*/.seg_data(seg_data)
);

// 写控制模块
spi_write_ctrl u_spi_write_ctrl(
    /* input            */.clk      (clk     ),
    /* input            */.rst_n    (rst_n   ),
    /* input   [2:0]    */.key_out  (key_out),
    /* input   [7:0]    */.din      (din     ),
    /* input            */.done     (done    ),
    /* output           */.req      (wr_req   ),
    /* output  [7:0]    */.dout     (wr_tx_data)
);
endmodule
学新通

5.top.v

module top(
    input           clk,
    input           rst_n,
    input   [2:0]   key_in, 
    output  [7:0]   seg_dig,
    output  [5:0]   seg_sel,
    input           miso,// 主机采样从机发送
    output          mosi,// 主机发送从机
    output          sclk,// 串行时钟
    output          cs_n // 片选信号
);

wire    [2:0]       key_out;
wire                req;
wire                done;
wire    [7:0]       rx_data;
wire    [7:0]       tx_data;
wire    [23:0]      seg_data;                     

// 按键消抖模块
key_filter u_key_filter(
    /* input                        */.clk      (clk    ),
    /* input                        */.rst_n    (rst_n  ),
    /* input         [KEY_W-1:0]    */.key_in   (key_in ),
    /* output  reg   [KEY_W-1:0]    */.key_out  (key_out)
);

// 数码管驱动
seg_driver u_seg_driver(
    /* input                        */.clk      (clk    ),
    /* input                        */.rst_n    (rst_n  ),
    /* input           [23:0]       */.data     (seg_data),
    /* output   reg    [7:0]        */.seg_dig  (seg_dig),
    /* output   reg    [5:0]        */.seg_sel  (seg_sel)
);

spi_control u_spi_control(
    /* input            */.clk      (clk     ),
    /* input            */.rst_n    (rst_n   ),
    /* input   [2:0]    */.key_out  (key_out ),
    /* input   [7:0]    */.din      (rx_data ),
    /* input            */.done     (done    ),
    /* output  [7:0]    */.dout     (tx_data ),
    /* output           */.req      (req     ),
    /* output  [23:0]   */.seg_data (seg_data)
);


spi_interface u_spi_interface(
    /* input            */.clk      (clk  ),
    /* input            */.rst_n    (rst_n),
    /* // 接口与主机 */
    /* input   [7:0]    */.din      (tx_data),
    /* input            */.req      (req  ),
    /* output  [7:0]    */.dout     (rx_data ),
    /* output           */.done     (done ),
    /* // 接口与flash */
    /* input            */.miso     (miso ),// 主机采样从机发送
    /* output           */.mosi     (mosi ),// 主机发送从机
    /* output           */.sclk     (sclk ),// 串行时钟
    /* output           */.cs_n     (cs_n ) // 片选信号
);
endmodule
学新通

6.其他模块

按键消抖模块
数码管驱动模块

五、仿真验证

这个还没仿真,仿真验证也是验证接口状态转移是否正确

六、上板验证

  • 按下key[2]写数据
  • 按下key[1]读数据
  • 按下key[0]读ID

读id数据
学新通
读数据
学新通

七、总结

这个spi我是自己实现的,但是实现的是最基本的三个功能,读id,读数据,写数据,但是每次功能按键还要复位一下,肯定有bug,但是不改了,哈哈。这个我只是给读者提供思路,但是不要直接拿我这个bug代码,日后我会上传正确的好的代码。
记得Verilog代码要看时序,有时候一个时序对不上,真的,调试半天。像我写数据的时候,因为串行时钟的时序没有对上相对应的状态,晚了一个周期,按照spi协议的说明,应该是在边沿发送数据,晚了一个周期,就导致数据不对了,所以无论咋样都没写进数据。
我写博客是我的思路,我的理解,也可能是我与老师的结合,不完全是老师的,所以有问题很正常。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfahii
系列文章
更多 icon
同类精品
更多 icon
继续加载