SPI实现(verilog)

帮朋友写了个比较简单的SPI MASTER 电路,现在公布源代码,以便初学者学习之用;

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传输时钟相对模块工作时钟的分频系数;
  • CFG_REG0/CFG_REG1/CFG_REG2为要写入slave中3个32bit寄存器的内容;
  • WR_CTRL/RD_CTRL控制写入哪些寄存器和读出哪些寄存器;
  • 默认的流程为先写后读。

小结

可用其他软件接口代替localparam定义;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容