Пример модуля 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
При непрерывной передаче данных в предыдущем примере сигнал 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 и сможет быть обработан.
Такой вариант представляется более правильным.