======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:
{{verilog:spi_master3w_m0m2_half2.png}}
{{verilog:spi_master3w_m1m3_half2.png}}
=======Вариант с удержанием 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 и сможет быть обработан.
{{verilog:spi_master_3wire_cs.png}}
Такой вариант представляется более правильным.