天天看點

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

文章目錄

  • 一、SPI
  • 二、看spi--flash手冊找關鍵
    • 1.描述
    • 2.flash接口信号
    • 3.SPI模式選擇
    • 4.高位元組MSB
    • 5.指令
    • 6. 寫使能時序
    • 7.讀ID時序
    • 8.讀寄存器時序(我沒用到)
    • 9.讀資料時序
    • 10.頁程式設計
    • 11.扇區擦除
    • 12.重要的時間
  • 三、狀态機設計
    • 1.spi接口狀态機
    • 2.flash讀狀态機
    • 3.flash寫狀态機
  • 四、代碼部分
    • 1.==spi_interface.v==
    • 2.==spi_read_ctrl.v==
    • 3.==spi_write_ctrl.v==
    • 4.==spi_control.v==
    • 5.==top.v==
    • 6.其他子產品
  • 五、仿真驗證
  • 六、上闆驗證
  • 七、總結

一、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,從裝置使能信号,由主裝置控制。

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

二、看spi–flash手冊找關鍵

1.描述

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

16Mbit的存儲空間

單扇區擦除或者整塊擦除

用spi協定與flash讀寫

2.flash接口信号

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

C是串行時鐘

D是資料

S是片選信号

3.SPI模式選擇

flash隻支援mode0和mode3兩種模式

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

CPOL時鐘相位

時鐘的極性(CPOL)用來決定在總線空閑時,同步時鐘(SCK)信号線上的電位是高電平還是低電平。當時鐘極性為0時(CPOL=0),SCK信号線在空閑時為低電平;當時鐘極性為1時(CPOL=1),SCK信号線在空閑時為高電平;

CPHA時鐘極性

當時鐘相位為1時(CPHA=1),在SCK信号線的第二個跳變沿進行采樣;這裡的跳變沿究竟是上升沿還是下降沿?取決于時鐘的極性。當時鐘極性為0時,取下降沿;當時鐘極性為1時,取上升沿

  • CPOL=0,CPHA=0
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
  • CPOL=0,CPHA=1
    【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
  • CPOL=1,CPHA=0
    【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
  • CPOL=1,CPHA=1
    【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

4.高位元組MSB

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

MSB先,就是高位元組先

5.指令

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
Instruction Code 說明
RDID 8’h9F 讀ID
RDSR 8’h05 讀寄存器判斷最後一位是0(但我其實沒有用過)
READ 8’h03 讀資料
PP 8’h02 頁擦除
SE 8’hD8 扇區擦除

6. 寫使能時序

  • 1位元組的指令
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

7.讀ID時序

  • 1位元組的指令
  • 3位元組的ID資料
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

8.讀寄存器時序(我沒用到)

  • 1位元組的指令
  • 2位元組的資料
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

判斷WIP BIT是否為0才能進行下一步(我的代碼裡沒有用到)

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

9.讀資料時序

  • 1位元組的指令
  • 3位元組的位址
  • 1位元組的資料
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

10.頁程式設計

  • 1位元組的指令
  • 3位元組的位址
  • 1位元組的資料
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

11.扇區擦除

  • 1位元組的指令
  • 3位元組的位址
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

12.重要的時間

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結
時間名稱
C_TIME 100ns 一個操作結束後到下一個操作片需要100ns,比如一個指令到下一個指令
PP_TIME 1.4-5ms 頁程式設計的所需要的時間5ms
SE_TIME 1-3s 擦除所需要的時間3s

三、狀态機設計

1.spi接口狀态機

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

2.flash讀狀态機

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

3.flash寫狀态機

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–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資料

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

讀資料

【FPGA】FPGA基于spi的flash讀寫一、SPI二、看spi–flash手冊找關鍵三、狀态機設計四、代碼部分五、仿真驗證六、上闆驗證七、總結

七、總結

這個spi我是自己實作的,但是實作的是最基本的三個功能,讀id,讀資料,寫資料,但是每次功能按鍵還要複位一下,肯定有bug,但是不改了,哈哈。這個我隻是給讀者提供思路,但是不要直接拿我這個bug代碼,日後我會上傳正确的好的代碼。

記得Verilog代碼要看時序,有時候一個時序對不上,真的,調試半天。像我寫資料的時候,因為串行時鐘的時序沒有對上相對應的狀态,晚了一個周期,按照spi協定的說明,應該是在邊沿發送資料,晚了一個周期,就導緻資料不對了,是以無論咋樣都沒寫進資料。

我寫部落格是我的思路,我的了解,也可能是我與老師的結合,不完全是老師的,是以有問題很正常。

繼續閱讀