SPI_Master_3Wire

Пример модуля SPI для вывода в ЦАП по трем линиям CS, SDI, SCK.

module SPI_Master_3Wire_SC #( 
  parameter SCK_CLK_HALF_PER = 4,
  parameter BIT_COUNT        = 8,
  parameter SPI_MODE	     = 0
)
(
    input                   clk,
    input                   rst_n,
    // TX data
    input                   i_tx_dv,
    input  [BIT_COUNT-1:0]  i_tx_data,	 
    output                  o_tx_ready,
    // SPI pins
    output                  o_spi_cs,
    output                  o_spi_sck,
    output                  o_spi_mosi
);


// ===========  SPI Clock ==========
reg                       tx_busy;

localparam CLK_CNT_BITS = $clog2(SCK_CLK_HALF_PER + 1);
localparam CLK_CNT_BITS_n1 = CLK_CNT_BITS - 1;

reg  [CLK_CNT_BITS-1:0]   clk_cnt;
reg                       spi_clk_dv;

always @(posedge clk or negedge rst_n)
begin
  if (~rst_n)
  begin
    clk_cnt <= {CLK_CNT_BITS{1'b0}};
    spi_clk_dv <= 1'b0;
  end  
  else if (tx_busy)
  begin
    spi_clk_dv <= clk_cnt == (SCK_CLK_HALF_PER - 1);
    clk_cnt <= clk_cnt + 1'b1;

    // Предыдущий вариант:
    // if (clk_cnt == (SCK_CLK_HALF_PER - 1)) | 
    // begin
    //   clk_cnt <= {CLK_CNT_BITS{1'b0}};
    //   spi_clk_dv <= 1'b1;
    // end
    // else
    // begin
    //   clk_cnt <= clk_cnt + 1'b1;
    //   spi_clk_dv <= 1'b0;
    // end
  end
  else 
  begin
      clk_cnt <= 1'b1;    // Недостающие биты дополнятся нулями
      spi_clk_dv <= 1'b0;
  end
end  



// ===========  Settings  ==========
// CPOL: Clock Polarity
// CPOL=0 means clock idles at 0, leading edge is rising edge.
// CPOL=1 means clock idles at 1, leading edge is falling edge.
wire w_CPOL  = (SPI_MODE == 2) | (SPI_MODE == 3);

// CPHA: Clock Phase
// CPHA=0 means the "out" side changes the data on trailing edge of clock
//              the "in" side captures data on leading edge of clock
// CPHA=1 means the "out" side changes the data on leading edge of clock
//              the "in" side captures data on the trailing edge of clock
wire w_CPHA  = (SPI_MODE == 1) | (SPI_MODE == 3);

// Rise + Fall CLK count
// BIT_COUNT = 8 (4'b1000), EVENT_CNT = 16 (5'b1_0000), $clog2(BIT_COUNT) = 3
localparam EVENT_CNT_BITS = $clog2(BIT_COUNT) + 2;
wire [EVENT_CNT_BITS-1:0] w_EVENT_COUNT = BIT_COUNT[EVENT_CNT_BITS-1:0] << 1;

// MOSI default level
wire w_MOSI_DEF =  1'b0;
//wire w_MOSI_DEF =  1'bZ;


// ===========  SPI  ==========
reg  [BIT_COUNT-1:0]      tx_data;
reg  [EVENT_CNT_BITS-1:0] tx_event_cnt;
reg                       is_set_phase;

wire tx_start = ~tx_busy & i_tx_dv;
wire tx_event =  tx_busy & spi_clk_dv; // Half Period

reg  r_spi_sck, r_spi_mosi;

assign o_spi_sck  = r_spi_sck;
assign o_spi_mosi = r_spi_mosi;
assign o_tx_ready = ~tx_busy;
assign o_spi_cs   = ~tx_busy;

always @(posedge clk or negedge rst_n)
begin
  if (~rst_n)
  begin
    tx_data <= {BIT_COUNT{1'b0}};
    tx_event_cnt <= {EVENT_CNT_BITS{1'b0}};
    tx_busy <= 1'b0;

    is_set_phase <= 1'b0;

    r_spi_sck  <= w_CPOL;
    r_spi_mosi <= w_MOSI_DEF;
  end  
  else
  begin
    if (tx_start)
    begin
        tx_data <= i_tx_data;
 	      tx_event_cnt  <= w_EVENT_COUNT;
        tx_busy <= 1'b1;
        is_set_phase <= w_CPHA;

        r_spi_sck  <= w_CPOL;
        if (~w_CPHA)
          r_spi_mosi <= i_tx_data[BIT_COUNT - 1];
    end
    else if (tx_event)
    begin
      if (tx_event_cnt == 0)
      begin
        r_spi_sck  <= w_CPOL;
        // r_spi_mosi <= w_MOSI_DEF;
        tx_busy <= 1'b0;
      end
      else
      begin
        r_spi_sck  <= ~r_spi_sck;
        is_set_phase <= ~is_set_phase;
        tx_event_cnt <= tx_event_cnt - 1'b1;

        if (is_set_phase)
        begin
          r_spi_mosi <= tx_data[BIT_COUNT - 1];
        end
        else
        begin
          tx_data <= tx_data << 1;
          // Add sampling MISO here for 4-wire version
        end
      end
    end
  end
end 

endmodule

Симуляция в разных режимах SPI:

Вариант с удержанием CS

При непрерывной передаче данных в предыдущем примере сигнал CS возвращается в высокий уровень всего на один такт системной частоты CLK. Принимающее устройство может не заметить такой короткий импульс CS, поэтому лучше сделать CS длительностью соизмеримой с частотой SCK.

module SPI_Master_3Wire_CS #( 
  parameter SCK_CLK_HALF_PER = 4,
  parameter BIT_COUNT        = 8,
  parameter SPI_MODE	     = 0
)
(
    input                   clk,
    input                   rst_n,
    // TX data
    input                   i_tx_dv,	 
    input  [BIT_COUNT-1:0]  i_tx_data,	 
    output                  o_tx_ready,
    // SPI pins
    output                  o_spi_cs,
    output                  o_spi_sck,
    output                  o_spi_mosi
);


// ===========  SPI Clock ==========
reg  r_spi_rdy;

localparam CLK_CNT_BITS = $clog2(SCK_CLK_HALF_PER + 1);
localparam CLK_CNT_BITS_n1 = CLK_CNT_BITS - 1;

reg  [CLK_CNT_BITS-1:0]  	clk_cnt;
reg                       spi_clk_dv;

always @(posedge clk or negedge rst_n)
begin
  if (~rst_n)
  begin
    clk_cnt <= {CLK_CNT_BITS{1'b0}};
    spi_clk_dv <= 1'b0;
  end  
  else if (~r_spi_rdy)
  begin
    spi_clk_dv <= clk_cnt == (SCK_CLK_HALF_PER - 1);
    clk_cnt <= clk_cnt + 1'b1;

    // if (clk_cnt == (SCK_CLK_HALF_PER - 1)) | 
    // begin
    //   clk_cnt <= {CLK_CNT_BITS{1'b0}};
    //   spi_clk_dv <= 1'b1;
    // end
    // else
    // begin
    //   clk_cnt <= clk_cnt + 1'b1;
    //   spi_clk_dv <= 1'b0;
    // end
  end
  else 
  begin
      clk_cnt <= 1'b1;
      spi_clk_dv <= 1'b0;
  end
end  



// ===========  Settings  ==========
// CPOL: Clock Polarity
// CPOL=0 means clock idles at 0, leading edge is rising edge.
// CPOL=1 means clock idles at 1, leading edge is falling edge.
wire w_CPOL  = (SPI_MODE == 2) | (SPI_MODE == 3);

// CPHA: Clock Phase
// CPHA=0 means the "out" side changes the data on trailing edge of clock
//              the "in" side captures data on leading edge of clock
// CPHA=1 means the "out" side changes the data on leading edge of clock
//              the "in" side captures data on the trailing edge of clock
wire w_CPHA  = (SPI_MODE == 1) | (SPI_MODE == 3);

// Riase + Falls CLK count
// BIT_COUNT = 8 (4'b1000), EVENT_CNT = 16 (5'b1_0000), $clog2(BIT_COUNT) = 3
localparam EVENT_CNT_BITS = $clog2(BIT_COUNT + 1) + 2;
wire [EVENT_CNT_BITS-1:0] w_EVENT_COUNT = BIT_COUNT[EVENT_CNT_BITS-1:0] << 1;

// MOSI default level
wire w_MOSI_DEF =  1'b0;
//wire w_MOSI_DEF =  1'bZ;


// ===========  SPI  ==========
reg                       tx_busy;
reg  [BIT_COUNT-1:0]      tx_data;
reg  [EVENT_CNT_BITS-1:0] tx_event_cnt;
reg                       is_set_phase;

wire tx_start =   r_spi_rdy & i_tx_dv;
wire tx_event =  ~r_spi_rdy & spi_clk_dv; // Half Period

reg  r_spi_sck, r_spi_mosi;

assign o_spi_sck  = r_spi_sck;
assign o_spi_mosi = r_spi_mosi;
assign o_spi_cs   = ~tx_busy;
assign o_tx_ready = r_spi_rdy;

always @(posedge clk or negedge rst_n)
begin
  if (~rst_n)
  begin
    tx_data <= {BIT_COUNT{1'b0}};
    tx_event_cnt <= {EVENT_CNT_BITS{1'b0}};
    tx_busy <= 1'b0;
    r_spi_rdy <= 1'b1;

    is_set_phase <= 1'b0;

    r_spi_sck  <= w_CPOL;
    r_spi_mosi <= w_MOSI_DEF;
  end  
  else
  begin
    if (tx_start)
    begin
        tx_data <= i_tx_data;
 	      tx_event_cnt  <= w_EVENT_COUNT + 1'b1;
        tx_busy <= 1'b1;
        r_spi_rdy <= 1'b0;
        is_set_phase <= w_CPHA;

        r_spi_sck  <= w_CPOL;
        if (~w_CPHA)
          r_spi_mosi <= i_tx_data[BIT_COUNT - 1];
    end
    else if (tx_event)
    begin
      if (tx_event_cnt == 1)
      begin
        tx_event_cnt <= tx_event_cnt - 1'b1;
        r_spi_sck  <= w_CPOL;
        // r_spi_mosi <= w_MOSI_DEF;
        tx_busy <= 1'b0;
      end
      else if (tx_event_cnt == 0)
      begin
        r_spi_rdy <= 1'b1;
      end      
      else
      begin
        r_spi_sck  <= ~r_spi_sck;
        is_set_phase <= ~is_set_phase;
        tx_event_cnt <= tx_event_cnt - 1'b1;

        if (is_set_phase)
        begin
          r_spi_mosi <= tx_data[BIT_COUNT - 1];
        end
        else
        begin
          tx_data <= tx_data << 1;
        end
      end
    end
  end
end 

endmodule

На сравнительных диаграммах видно, что теперь сигнал o_tx_ready выставляется позже выставления o_spi_cs=1. Этот o_tx_ready дает посылающему блоку информацию что spi еще не готов в посылке следующих данных. За это время сигнал o_spi_cs дойдет до абонента SPI и сможет быть обработан.

Такой вариант представляется более правильным.