Parametric Structure Design for SPI in FPGA

Parametric Structure Design for SPI in FPGA
Hello, hero! Welcome to the FPGA technology world. The world is vast, and meeting is fate. You can follow the FPGA technology world to get other interesting resources in the “Adventuring” and “Chivalry” sections, or have a drink and chat together.
Today, I bring you the parametric structure design for SPI in FPGA. Without further ado, let’s get started.

Parametric Structure Design for SPI in FPGA

To avoid rewriting the SPI driver every time, we parameterize it to make it as efficient as possible.What is the use of SPI master? You will find that the configuration of various peripheral chips is generally done through SPI, whether it’s three-wire or four-wire.What is the use of SPI slave? When the external host (CPU) needs to read the internal register values of the FPGA, it becomes very useful. The FPGA registers are akin to RAM, allowing the CPU to read and write data via SPI.The code is for reference only and should not be used for commercial purposes.

Parametric Structure Design for SPI in FPGA

SPI Slave

Features supported by SPI slave:

  • Support for three-wire or four-wire SPI, switchable via define.
  • Support for custom instruction length and frame length.
  • Customizable working clock, greater than twice the SPI clock.

The user only needs to modify: (1) the number of wires in SPI. (2) single frame length. (3) instruction length. (4) register allocation.

Note: The highest bit of the instruction indicates read/write, low indicates write and high indicates read, while the remaining bits indicate the address. The instruction is followed by the data port, and the sum of their widths is the length of a single SPI frame.

//`define SPI_LINE  //Is it three-wire SPI`define SPI_FRAME_WIDTH 16 //SPI frame length is 16`define SPI_INS_WIDTH 8    //SPI instruction length`timescale 1ns/1ps////module spi_slave (    input     i_clk               , //work clk    input     i_rst_n             ,        input     i_spi_clk           , //SPI clk    input     i_spi_cs            , //SPI cs
    `ifdef SPI_LINE                 // Conditional compilation    inout     io_spi_sdio              `else    input     i_spi_mosi          , //SPI mosi    output    o_spi_miso            //SPI miso    `endif          );// Bit width calculation functionfunction integer clogb2 (input integer depth);begin    for (clogb2=0; depth>0; clogb2=clogb2+1)         depth = depth >>1;                          endendfunctionreg r_cs = 1'b1; // One beat always @(posedge i_clk)begin    r_cs <= i_spi_cs;endreg [1:0] r_spi_clk_edge = 2'b00; // SPI clock edge detectionalways @(posedge i_clk)begin    r_spi_clk_edge <= {r_spi_clk_edge[0],i_spi_clk};end //alwaysreg [clogb2(`SPI_FRAME_WIDTH-1)-1:0] r_spi_cnt = 'd0;always @(posedge i_clk)begin    if (r_cs) // cs is high, reset to zero        r_spi_cnt <= 'd0;    else if (r_spi_clk_edge == 2'b10) // Count only on falling edge        r_spi_cnt <= r_spi_cnt + 'd1;end//// Instruction latchreg [`SPI_INS_WIDTH-1:0] r_ins = 'd0;always @(posedge i_clk)begin    if ((~r_cs) && (r_spi_clk_edge == 2'b01)) // Latch data on rising edge    begin        if ((r_spi_cnt >= 0) && (r_spi_cnt <= `SPI_INS_WIDTH-1))        `ifdef SPI_LINE                 // Conditional compilation            r_ins <= {r_ins[`SPI_INS_WIDTH-2:0],io_spi_sdio};          `else            r_ins <= {r_ins[`SPI_INS_WIDTH-2:0],i_spi_mosi};          `endif     end end//// Data writingreg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_data_rx = 'd0;always @(posedge i_clk)begin    if ((~r_cs) && (r_spi_clk_edge == 2'b01)) // Latch data on rising edge    begin        if (r_spi_cnt >= `SPI_INS_WIDTH)        `ifdef SPI_LINE            r_data_rx <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio};        `else            r_data_rx <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi};           `endif    endend//// User register definitionsreg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg0 = 'd0;reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg1 = 'd0;reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg2 = 'd0;reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg3 = 'd0;////always @(posedge i_clk,negedge i_rst_n)begin    if (~i_rst_n)    begin        r_reg0 <= 'd0;        r_reg1 <= 'd0;        r_reg2 <= 'd0;        r_reg3 <= 'd0;
    end    else if ((~r_ins[`SPI_INS_WIDTH-1]) && (r_spi_cnt == (`SPI_FRAME_WIDTH-1)) && (~r_cs) && (r_spi_clk_edge == 2'b01))    begin    `ifdef SPI_LINE        case (r_ins[`SPI_INS_WIDTH-2:0])            'd0:begin r_reg0 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end            'd1:begin r_reg1 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end            'd2:begin r_reg2 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end            'd3:begin r_reg3 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end
        endcase     `else        case (r_ins[`SPI_INS_WIDTH-2:0])            'd0:begin r_reg0 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end            'd1:begin r_reg1 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end            'd2:begin r_reg2 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end            'd3:begin r_reg3 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end
        endcase     `endif     end
end//// Register value readoutreg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_data_tx = 'd0;always @(posedge i_clk)begin    if (r_ins[`SPI_INS_WIDTH-1] && (~r_cs) && (r_spi_clk_edge == 2'b10))    begin         if (r_spi_cnt == (`SPI_INS_WIDTH-1))        begin            case (r_ins[`SPI_INS_WIDTH-2:0])                'd0:begin r_data_tx <= r_reg0; end                 'd1:begin r_data_tx <= r_reg1; end                 'd2:begin r_data_tx <= r_reg2; end                 'd3:begin r_data_tx <= r_reg3; end                 endcase         end        else             r_data_tx <= {r_data_tx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],1'b0};    end end
//// Read output`ifdef SPI_LINEassign io_spi_sdio = (r_ins[`SPI_INS_WIDTH-1]) ? (((r_spi_cnt>=`SPI_INS_WIDTH) && (r_spi_cnt<`SPI_FRAME_WIDTH)) ? r_data_tx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1] : 1'bz) : 1'bz;`else assign o_spi_miso = ((r_spi_cnt>=`SPI_INS_WIDTH) && (r_spi_cnt<`SPI_FRAME_WIDTH)) ? r_data_tx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1] : 1'b0;`endif
endmodule // end the spi_slave model

Parametric Structure Design for SPI in FPGA

SPI Master

The SPI master only encapsulates the SPI driver, while the control of writing values and reading them is handled by the upper layer, which is simple and will not be elaborated. Users only need to provide the SPI frame and control enable.

The user only needs to modify parameter parameters: (1) single frame length; (2) instruction length; (3) data length; (4) working clock; (5) SPI clock.

The implementation does not use a state machine but adopts a linear sequence counting method.

//`define SPI_LINE  //Is it three-wire SPI`timescale 1ns/1psmodule spi_master #(parameter p_spi_frame_width = 16, //SPI single frame length  parameter p_spi_ins_width = 8   , //Instruction length  parameter p_spi_data_width = 8    //Data length for reading)(    input                                i_clk             , //System clock    input                                i_rst_n           ,    input                                i_flag            , // Detect rising edge of flag to start a transmission, one clock cycle is sufficient       input     [p_spi_frame_width-1:0]    i_spi_data        ,    output                               o_spi_cs          ,    output                               o_spi_clk         ,
    `ifdef SPI_LINE                 // Conditional compilation    inout                                io_spi_sdio       ,              `else    input                                i_spi_miso        , //SPI miso    output                               o_spi_mosi        , //SPI mosi    `endif         output                               o_transfer_done   , // Single transmission complete    output    [p_spi_data_width-1:0]     o_spi_data          // Read data           );parameter p_clk_fre = 200; //XXM clock frequencyparameter p_spi_clk_fre = 0.5*1000; //SPI clock rate, indicating 1Mparameter p_clk_div = p_clk_fre * 1000/p_spi_clk_fre/2-1;parameter p_spi_cnt_max = p_spi_frame_width*2-1;parameter p_spi_ins_max = p_spi_ins_width*2-1;// Bit width calculation functionfunction integer clogb2 (input integer depth);begin    for (clogb2=0; depth>0; clogb2=clogb2+1)         depth = depth >>1;                          endendfunction// Assign the maximum value to the linear, directly using p_clk_div for simulation has issues, but it works on the boardwire [clogb2(p_clk_div)-1:0] w_clk_div;assign w_clk_div = p_clk_div;//// Clock divisionreg [clogb2(p_clk_div)-1:0] r_cnt_div = 'd0;always @(posedge i_clk)begin    if (r_cnt_div == w_clk_div)        r_cnt_div <= 'd0;    else         r_cnt_div <= r_cnt_div + 'd1;end //alwayswire w_clk_en; // Division clock enableassign w_clk_en = (r_cnt_div == w_clk_div) ? 1'b1 : 1'b0;reg [1:0] r_flag_edge = 2'b00;reg [clogb2(p_spi_cnt_max)-1:0] r_spi_cnt = 'd0;always @(posedge i_clk) // Detect flag edgebegin    r_flag_edge <= {r_flag_edge[0],i_flag};end// Flag signal wide to low-speed clock domainreg r_flag_enlarge = 1'b0;always @(posedge i_clk)begin    if (r_flag_edge == 2'b01) // Raise on rising edge        r_flag_enlarge <= 1'b1;    else if (r_spi_cnt == p_spi_ins_max) // Only pull low when the high level is long enough        r_flag_enlarge <= 1'b0;endreg [1:0] r_flag_enlarge_edge = 2'b00;always @(posedge i_clk)begin    if (w_clk_en)        r_flag_enlarge_edge <= {r_flag_enlarge_edge[0],r_flag_enlarge};endreg r_cs = 1'b1;always @(posedge i_clk)begin    if (w_clk_en)    begin        if (r_flag_enlarge_edge == 2'b01) // Detected need for SPI operation            r_cs <= 1'b0;        else if (r_spi_cnt == p_spi_cnt_max) // Counting to the maximum value indicates one SPI completion            r_cs <= 1'b1;    endendalways @(posedge i_clk)begin    if (w_clk_en)    begin        if(~r_cs) // Count in the operational area            r_spi_cnt <= r_spi_cnt + 'd1;        else             r_spi_cnt <= 'd0;    end    end//// Data transfer segmentreg [p_spi_frame_width-1:0] r_data = 'd0;always @(posedge i_clk)begin    if (w_clk_en)    begin        if (r_flag_enlarge_edge == 2'b01) // Raise on rising edge            r_data <= i_spi_data;        else if (r_spi_cnt[0] == 1'b1) // Data moves            r_data <= {r_data[p_spi_frame_width-2:0],1'b1};    endend//// Data reading segmentreg [p_spi_data_width-1:0] r_data_read = 'd0;always @(posedge i_clk)begin    if (w_clk_en)    begin        if (i_spi_data[p_spi_frame_width-1] && (r_spi_cnt > p_spi_ins_max) && (r_spi_cnt[0] == 1'b0)) // Read        `ifdef SPI_LINE            r_data_read <= {r_data_read[p_spi_data_width-2:0],io_spi_sdio};        `else            r_data_read <= {r_data_read[p_spi_data_width-2:0],i_spi_miso};        `endif     end    end//// SPI output segmentassign o_spi_cs = r_cs;assign o_spi_clk = r_cs ? 1'b0 : r_spi_cnt[0];//// SPI SDIO input/output switching`ifdef SPI_LINEassign io_spi_sdio = (i_spi_data[p_spi_frame_width-1]) ? (((r_spi_cnt >= 'd0) && (r_spi_cnt <= p_spi_ins_max)) ? r_data[p_spi_frame_width-1] : 1'bz ) : r_data[p_spi_frame_width-1];`else assign o_spi_mosi = r_data[p_spi_frame_width-1];`endif assign o_transfer_done = ((~r_cs) && (r_spi_cnt == p_spi_cnt_max)) ? 1'b1:1'b0;assign o_spi_data = r_data_read;
endmodule // end the spi_master model

Simulation is shown below: Write values to four registers and then read them.

The simulation code is as follows:

`define TRANSFER_NUMBER 8 // Number of operations is 4`define DATA 8'ha5//`define SPI_LINEtimeunit 1ns;timeprecision 1ps;module top;parameter p_sim_end_time = 1000000; //nslogic l_clk = 1'b0;always #2.5 l_clk = ~l_clk;//// Reset logic l_rst_n  = 1'b0;initial begin    #100 l_rst_n = 1'b1;end
wire io_sdio;wire o_spi_cs;wire o_spi_clk;wire o_transfer_done;wire [7:0] o_spi_data;//// Multiple data operation modesreg r_flag = 1'b0;reg [1:0] r_first_cnt = 2'b00;always @(posedge l_clk,negedge l_rst_n)begin    if (~l_rst_n)        r_first_cnt <= 2'b00;    else if (r_first_cnt == 2'd3)        r_first_cnt <= r_first_cnt;    else         r_first_cnt <= r_first_cnt + 2'd1;endreg [1:0] r_transfer_done_edge = 2'b00;always @(posedge l_clk)begin    r_transfer_done_edge <<= {r_transfer_done_edge[0],o_transfer_done};endreg [3:0] r_transfer_cnt = 4'd0;always @(posedge l_clk)begin    if ((r_first_cnt == 2'd2) && (r_transfer_cnt < `TRANSFER_NUMBER))        r_flag <= 1'b1;    else if ((r_transfer_done_edge == 2'b10) && (r_transfer_cnt < `TRANSFER_NUMBER-1))        r_flag <= 1'b1;    else         r_flag <= 1'b0;        endalways @(posedge l_clk)begin    if (r_transfer_done_edge == 2'b10)        r_transfer_cnt <= r_transfer_cnt + 'd1;endreg [15:0] r_in_data  = 16'd0;always @(*)begin    if (~l_rst_n) // Simulation does not execute this segment, there will be issues        r_in_data  = 16'h0000;    else     begin     case(r_transfer_cnt)        4'd0:begin  r_in_data  = {8'h00,8'h43}; end         4'd1:begin  r_in_data  = 16'h0132; end         4'd2:begin  r_in_data  = 16'h0245; end         4'd3:begin  r_in_data  = 16'h0367; end         4'd4:begin  r_in_data  = 16'h8000; end         4'd5:begin  r_in_data  = 16'h8100; end         4'd6:begin  r_in_data  = 16'h8200; end         4'd7:begin  r_in_data  = 16'h8300; end 
        default:begin  r_in_data  = 16'h0000; end    endcase    end end
wire w_spi_miso;wire w_spi_mosi;
spi_master inst_spi_master (    .i_clk             (l_clk),    .i_rst_n           (),    .i_flag            (r_flag),    .i_spi_data        (r_in_data),    .o_spi_cs          (o_spi_cs),    .o_spi_clk         (o_spi_clk),    `ifdef SPI_LINE    .io_spi_sdio       (io_sdio),    `else    .i_spi_miso        (w_spi_miso),    .o_spi_mosi        (w_spi_mosi),    `endif    .o_transfer_done   (o_transfer_done),    .o_spi_data        (o_spi_data)    );
spi_slave  inst_spi_slave (    .i_clk                            (l_clk),    .i_rst_n                          (l_rst_n),
    .i_spi_clk                        (o_spi_clk),    .i_spi_cs                         (o_spi_cs),    `ifdef SPI_LINE    .io_spi_sdio                      (io_sdio)    `else    .i_spi_mosi                      (w_spi_mosi),    .o_spi_miso                      (w_spi_miso)    `endif
);
initial begin    #p_sim_end_time $stop;end
    endmodule

Parametric Structure Design for SPI in FPGA

Three-Wire SPI:
Parametric Structure Design for SPI in FPGA

Four-Wire SPI:Parametric Structure Design for SPI in FPGA

You can see that the read and write operations are consistent, and the verification is successful.

– THE END –

šŸ

Selected Past Articles

Parametric Structure Design for SPI in FPGA
Parametric Structure Design for SPI in FPGA

[Free] FPGA Engineer Recruitment Platform

FPGA talent recruitment, HR of enterprises, pay attention!

Selected System Design | Real-time Image Edge Detection System Design Based on FPGA (with code)

Gigabit Ethernet RGMII Interface Design Based on Primitives

Timing Analysis Theory and TimeQuest Usage_ Chinese Edition

Job Interview | Latest Compilation of FPGA or IC Interview Questions

Data Compilation | FPGA Software Installation Packages, Books, Source Code, Technical Documents… (Updated on 2024.01.06)

FPGA Employment Class, starting on 05.04, new course content at no extra charge, high salary employment, online and offline synchronization!

Parametric Structure Design for SPI in FPGA

Parametric Structure Design for SPI in FPGA

FPGA Technology Community Broadly Issues Community Posts

No advertisements in pure mode, providing a pure land for technical exchanges, from beginners to industry elites and big shots, from military to civilian enterprises, covering various directions from communication, image processing to artificial intelligence, QQ WeChat double selection, FPGA technology community builds the purest and most professional technical exchange learning platform.

FPGA Technology Community WeChat Group

Parametric Structure Design for SPI in FPGA

Add the group owner’s WeChat, note your name + school/company + major/position to join the group

FPGA Technology Community QQ Group

Parametric Structure Design for SPI in FPGA

Note: Name + School/Company + Major/Position to join the group

Parametric Structure Design for SPI in FPGA

Leave a Comment