SPI實(shí)現(xiàn)(verilog)

幫朋友寫了個(gè)比較簡(jiǎn)單的SPI MASTER 電路,現(xiàn)在公布源代碼柿汛,以便初學(xué)者學(xué)習(xí)之用;

1. 源代碼

module SPI_MASTER(
    input        clk     , //the FPGA input clock
    input        rst_n   , //the FPGA asynchronous reset
                 
    input        spi_sdi , //the SPI read input
    output reg   spi_sdo , //the SPI write output
    output reg   spi_sck , //the SPI clock
    output reg   spi_cs    //the SPI chip selection
);
//---------------------------------------------
// the following localparam need to configure to 
// fit defferent scenarios
//---------------------------------------------
localparam  CFG_REG0   = 32'h0123_4567; //the CFG reg0
localparam  CFG_REG1   = 32'h5555_5555; //the CFG reg0
localparam  CFG_REG2   = 32'hAAAA_AAAA; //the CFG reg0
localparam  CNT        = 16'd31       ; //the frequncy of spi_sck=clk/(CNT+1);
localparam  WR_CTRL    = 3'b111       ; //bit0:cfg_reg0 write enable
                                        //bit1:cfg_reg1 write enable
                                        //bit2:cfg_reg2 write enable
localparam  RD_CTRL    = 3'b111       ; //bit0:cfg_reg0 read enable
                                        //bit1:cfg_reg1 read enable
                                        //bit2:cfg_reg2 read enable

//---------------------------------------------
// the following localparam don't need configuration
//---------------------------------------------
localparam  CNT_DIV2   = (CNT>>1)     ; //the div clock trun around counter 
localparam  SPI_IDLE   = 3'd0         ;
localparam  SPI_TAG    = 3'd1         ;
localparam  SPI_ADDR   = 3'd3         ;
localparam  SPI_WDATA  = 3'd2         ;
localparam  SPI_TURN   = 3'd7         ;
localparam  SPI_RDATA  = 3'd6         ;

//---------------------------------------------
// REG defination
//---------------------------------------------
integer     i                ;
reg  [15:0] clock_cnt        ;
reg         spi_sck_raw      ;
reg  [5:0]  spi_fsm_ctrl     ;
reg  [2:0]  idx              ;

reg  [5:0]  spi_fsm_start_idx;
reg  [3:0]  spi_cur_st       ;
reg  [3:0]  spi_next_st      ;

reg  [4:0]  fns_cnt          ;
reg  [4:0]  fns_cnt_next     ;
reg  [31:0] spi_sdo_raw0     ;
reg  [31:0] spi_sdo_raw1     ;
reg  [31:0] rback_reg0       ;
reg  [31:0] rback_reg1       ;
reg  [31:0] rback_reg2       ;

//---------------------------------------------
// WIRE defination
//---------------------------------------------
wire        ck_rise          ;
wire        ck_fall          ;
wire        ck_jmp           ;
wire        spi_fsm_start    ;

wire        spi_fsm_start    ;
wire        addr_fns         ;
wire        wdata_fns        ;
wire        rdata_fns        ;
wire        trun_fns         ;

//---------------------------------------------
// MAIN FUNCTION
//---------------------------------------------
always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        clock_cnt<=16'b0;
    else if(spi_next_st!=SPI_IDLE) begin
        if(clock_cnt>=CNT)
            clock_cnt<=16'b0;
        else
            clock_cnt<=clock_cnt+1'b1;`
    end
    else 
       clock_cnt<=16'b0;
end

//spi clock keep 0 when there is no traction.
assign ck_rise=clock_cnt==CNT      ;
assign ck_fall=clock_cnt==CNT_DIV2 ;
assign ck_jmp =ck_rise | ck_fall   ;
always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        spi_sck<=1'b0;
    else if(spi_cur_st==SPI_IDLE && spi_next_st!=SPI_IDLE || ck_jmp)
        spi_sck<=~spi_sck;
    else if(spi_cur_st!=SPI_IDLE && spi_next_st==SPI_IDLE)
        spi_sck<=1'b0;
end

//chip selection keep 0 when there is no traction.
always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        spi_cs<=1'b0;
    else if(spi_next_st==SPI_IDLE)
        spi_cs<=1'b0;
    else
        spi_cs<=1'b1;
end

//the FSM ctrl:ctrl the SPI_FSM to run;
always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0) begin
        spi_fsm_ctrl<={READ_CTRL,WRITE_CTRL};
        idx         <=3'b0;
    end
    else if(spi_fsm_ctrl!=6'b0) begin
        for(i=5;i>=0;i=i-1) begin
            if(spi_fsm_ctrl[i])
                idx<=i;
        end
        if(spi_fsm_done) begin
            spi_fsm_ctrl<=spi_fsm_ctrl & (~spi_fsm_start_idx);
        end
    end
end

assign  spi_fsm_start=(spi_fsm_ctrl!=6'b0) && (idx!=3'd0);
always@(*) begin
    spi_fsm_start_idx     =6'b0;
    spi_fsm_start_idx[idx]=1'b1;
end

//the FSM;
always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        spi_cur_st<=SPI_IDLE;
    else
        spi_cur_st<=spi_next_st;
end
always@(*) begin
    spi_next_st=spi_cur_st;
    case(spi_cur_st)
        SPI_IDLE: if(spi_fsm_start) begin 
                      spi_next_st=SPI_TAG;
                  end
        SPI_TAG : if(ck_rise) begin 
                      spi_next_st=SPI_ADDR;
                  end
        SPI_ADDR: if(ck_rise&&addr_fns) begin 
                      if(spi_fsm_start_idx[2:0]!=3'b0)
                          spi_next_st=SPI_WDATA;
                      else
                          spi_next_st=SPI_TURN;
                  end
        SPI_WDATA:if(ck_rise&&wdata_fns) begin 
                      spi_next_st=SPI_IDLE;
                  end
        SPI_TRUN: if(ck_rise&&turn_fns) begin
                      spi_next_st=SPI_RDATA;
                  end
        SPI_RDATA:if(ck_rise&&rdata_fns) begin
                      spi_next_st=SPI_IDLE;
                  end
    endcase
end

always@(*) begin
    fns_cnt_next=fns_cnt;
    if(spi_cur_st!=spi_next_st)
        fns_cnt_next=5'd0;
    else if(spi_next_st!=IDLE && ck_rise)
        fns_cnt_next=fns_cnt_next+1'b1;
end

always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        fns_cnt<=5'd0;
    else 
        fns_cnt<=fns_cnt_next;
end
assign addr_fns =spi_cur_st==SPI_ADDR  & fns_cnt==5'd7 ;
assign wdata_fns=spi_cur_st==SPI_WDATA & fns_cnt==5'd31;
assign rdata_fns=spi_cur_st==SPI_RDATA & fns_cnt==5'd31;
assign turn_fns =spi_cur_st==SPI_TRUN  & fns_cnt==5'd1 ;

always@(*) begin
    case(spi_fsm_start_idx[2:0])
        3'b010 : spi_sdo_raw0=CFG_REG1;
        3'b100 : spi_sdo_raw0=CFG_REG2;
        default: spi_sdo_raw0=CFG_REG0;
    endcase
    spi_sdo_raw1=spi_sdo_raw0<<fns_cnt_next;
end

always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        spi_sdo<=1'd0;
    else if(spi_next_st=SPI_WDATA)
        spi_sdo<=spi_sdo_raw1[31];
    else
        fns_sdo<=1'b0;
end
//store the read back data to 3 regs;
always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0) begin
        rback_reg0<=32'd0;
        rback_reg0<=32'd0;
        rback_reg0<=32'd0;
    end
    else if(spi_cur_st=SPI_RDATA && ck_fall) begin
        if(spi_fsm_start_idx[5:3]==3'b001)
            rback_reg0<={rback_reg0[30:0],spi_sdi};
        if(spi_fsm_start_idx[5:3]==3'b010)
            rback_reg1<={rback_reg1[30:0],spi_sdi};
        if(spi_fsm_start_idx[5:3]==3'b100)
            rback_reg2<={rback_reg2[30:0],spi_sdi};
    end
end

endmodule

2.使用方法

使用前需要更改以下CODE:

//---------------------------------------------
// the following localparam need to configure to 
// fit defferent scenarios
//---------------------------------------------
localparam  CFG_REG0   = 32'h0123_4567; //the CFG reg0
localparam  CFG_REG1   = 32'h5555_5555; //the CFG reg0
localparam  CFG_REG2   = 32'hAAAA_AAAA; //the CFG reg0
localparam  CNT        = 16'd31       ; //the frequncy of spi_sck=clk/(CNT+1);
localparam  WR_CTRL    = 3'b111       ; //bit0:cfg_reg0 write enable
                                        //bit1:cfg_reg1 write enable
                                        //bit2:cfg_reg2 write enable
localparam  RD_CTRL    = 3'b111       ; //bit0:cfg_reg0 read enable
                                        //bit1:cfg_reg1 read enable
                                        //bit2:cfg_reg2 read enable

  • CNT為SPI傳輸時(shí)鐘相對(duì)模塊工作時(shí)鐘的分頻系數(shù);
  • CFG_REG0/CFG_REG1/CFG_REG2為要寫入slave中3個(gè)32bit寄存器的內(nèi)容油吭;
  • WR_CTRL/RD_CTRL控制寫入哪些寄存器和讀出哪些寄存器击蹲;
  • 默認(rèn)的流程為先寫后讀。

小結(jié)

可用其他軟件接口代替localparam定義婉宰;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歌豺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子心包,更是在濱河造成了極大的恐慌类咧,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟹腾,死亡現(xiàn)場(chǎng)離奇詭異痕惋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)娃殖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門值戳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炉爆,你說我怎么就攤上這事堕虹。” “怎么了叶洞?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵鲫凶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我衩辟,道長(zhǎng)螟炫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任艺晴,我火速辦了婚禮昼钻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘封寞。我一直安慰自己然评,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布狈究。 她就那樣靜靜地躺著碗淌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抖锥。 梳的紋絲不亂的頭發(fā)上亿眠,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音磅废,去河邊找鬼纳像。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拯勉,可吹牛的內(nèi)容都是我干的竟趾。 我是一名探鬼主播憔购,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼岔帽!你這毒婦竟也來了玫鸟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤山卦,失蹤者是張志新(化名)和其女友劉穎鞋邑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體账蓉,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年逾一,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铸本。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遵堵,死狀恐怖箱玷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情陌宿,我是刑警寧澤锡足,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站壳坪,受9級(jí)特大地震影響舶得,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爽蝴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一沐批、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝎亚,春花似錦九孩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至梅惯,卻和暖如春宪拥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背个唧。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工江解, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徙歼。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓犁河,卻偏偏與公主長(zhǎng)得像鳖枕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桨螺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容