1
0
mirror of https://github.com/pConst/basic_verilog.git synced 2025-01-14 06:42:54 +08:00
basic_verilog/spi_master.sv

334 lines
12 KiB
Systemverilog
Raw Normal View History

2019-02-17 22:13:26 +03:00
//------------------------------------------------------------------------------
// spi_master.sv
// Konstantin Pavlov, pavlovconst@gmail.com
//------------------------------------------------------------------------------
// INFO ------------------------------------------------------------------------
// Universal spi master
//
// * Supports all SPI bus modes
// mode 0 (CPOL = 0, CPHA = 0)
// mode 1 (CPOL = 0, CPHA = 1)
// mode 2 (CPOL = 1, CPHA = 0)
// mode 3 (CPOL = 1, CPHA = 1)
// * Moreover, universal spi master features separate parameters to set
// clock edge to update data by spi master and
// clock edge to latch data by spi master
// * Spi clock can be made free-running (some slaves require that)
// * OE pin for bidirectional buffer connection, in case DO and DI pins are combined
//
// * Universal spi master successfully synthesize at clk speeds up to 200MHz
// * That means, that SPI clocks up to 50MHz are supported
//
/* --- INSTANTIATION TEMPLATE BEGIN ---
spi_master #(
.WRITE_WIDTH( 8 ),
.WRITE_MSB_FIRST( 1 ),
.WRITE_DATA_EDGE( 1 ),
.READ_WIDTH( 8 ),
.READ_MSB_FIRST( 1 ),
.READ_DATA_EDGE( 1 ),
.FREE_RUNNING_SPI_CLK( 0 )
) SM1 (
.clk( ),
.nrst( ),
.spi_clk( ),
.spi_wr_cmd( ),
.spi_rd_cmd( ),
.spi_busy( ),
.mosi_data( ),
.miso_data( ),
.clk_pin( ),
.ncs_pin( ),
.d_out_pin( ),
.d_oe( ),
.d_in_pin( )
);
--- INSTANTIATION TEMPLATE END ---*/
module spi_master #( parameter
bit [4:0] WRITE_WIDTH = 8, // data word width in bits
bit WRITE_MSB_FIRST = 1, // 0 - LSB first
// 1 - MSB first
bit WRITE_DATA_EDGE = 0, // 0 - master updates on rising edge
// slave reads data on falling edge
// 1 - master updates on falling edge
// slave reads data on rising edge
bit [4:0] READ_WIDTH = 8, // data word width in bits
bit READ_MSB_FIRST = 1, // 0 - LSB first
// 1 - MSB first
bit READ_DATA_EDGE = 1, // 0 - slave updates on rising edge
// master reads data on falling edge
// 1 - slave updates on falling edge
// master reads data on rising edge
bit FREE_RUNNING_SPI_CLK = 0 // 0 - clk_pin is active only when ncs_pin = 0
// 1 - clk pin is always active
)(
input clk, // system clock
input nrst, // reset (inversed)
input spi_clk, // prescaler clock
// spi_clk must be >= 4 clk cycles
// must be synchronous multiple of clk cycles
input spi_wr_cmd, // spi write command, shifting begins on rising edge
input spi_rd_cmd, // spi read command, shifting begins on rising edge
output logic spi_busy, // shifting is active
input [WRITE_WIDTH-1:0] mosi_data, // data for shifting out from master
output logic [READ_WIDTH-1:0] miso_data = 0, // shifted in data from slave
output logic clk_pin = 0, // spi master's clock pin
output logic ncs_pin = 1, // spi master's chip select (inversed)
output logic d_out_pin = 0, // spi master's data in
output logic d_oe = 1, // spi master's output enable
// in case of slave has only one SDIO pin
input d_in_pin // spi master's data out
);
// sequence_cntr[7:0]==0 - waiting for spi_wr_cmd or spi_wr_cmd
// WRITE_SEQ_START - switching mode or transaction end
// WRITE_SEQ_END - waiting for right edge to set first data
localparam WRITE_SEQ_START = 1;
localparam WRITE_SEQ_END = WRITE_SEQ_START+2*WRITE_WIDTH;
localparam READ_SEQ_START = WRITE_SEQ_END+1;
localparam READ_SEQ_END = READ_SEQ_START+2*READ_WIDTH;
logic spi_clk_rise;
logic spi_clk_fall;
edge_detect ed_spi_clk (
.clk( clk ),
.nrst( nrst ),
.in( spi_clk ),
.rising( spi_clk_rise ),
.falling( spi_clk_fall ),
.both( )
);
logic spi_wr_cmd_rise;
logic spi_rd_cmd_rise;
edge_detect ed_cmds [1:0] (
.clk( {2{clk}} ),
.nrst( {2{nrst}} ),
.in( {spi_wr_cmd,spi_rd_cmd} ),
.rising( {spi_wr_cmd_rise,spi_rd_cmd_rise} ),
.falling( ),
.both( )
);
// input synchronizer for d_in_pin in clk domain, 2 cycles delay
// making similar delay for clk edges for read operation timing
logic d_in_pin_d2;
logic spi_clk_rise_d2;
logic spi_clk_fall_d2;
delay #(
.LENGTH( 2 )
) d_in_pin_synch [2:0] (
.clk( {3{clk}} ),
.nrst( {3{nrst}} ),
.ena( {3{1'b1}} ),
.in( {d_in_pin,spi_clk_rise,spi_clk_fall} ),
.out( {d_in_pin_d2,spi_clk_rise_d2,spi_clk_fall_d2} )
);
logic [7:0] sequence_cntr = 0;
logic rd_nwr = 0; // buffering data direction
logic [WRITE_WIDTH-1:0] mosi_data_buf = 0; // buffering mosi_data
always_ff @(posedge clk) begin
if( ~nrst ) begin
miso_data[READ_WIDTH-1:0] <= 0;
clk_pin <= ~WRITE_DATA_EDGE;
ncs_pin <= 1'b1;
d_out_pin <= 1'b0;
d_oe <= 1'b1;
sequence_cntr[7:0] <= 0;
rd_nwr <= 0;
mosi_data_buf[WRITE_WIDTH-1:0] <= 0;
end else begin
if( FREE_RUNNING_SPI_CLK ) begin
if ( spi_clk_rise ) begin
clk_pin <= 1'b1;
end
if( spi_clk_fall ) begin
clk_pin <= 1'b0;
end
end else begin // FREE_RUNNING_SPI_CLK = 0
if ( ~ncs_pin &&
// fixing extra clock glitch in the end of read transaction
~((sequence_cntr[7:0] == READ_SEQ_END) &&
(WRITE_DATA_EDGE != READ_DATA_EDGE)) ) begin
if ( spi_clk_rise ) begin
clk_pin <= 1'b1;
end
if( spi_clk_fall ) begin
clk_pin <= 1'b0;
end
end else begin // ncs_pin = 1
clk_pin <= ~WRITE_DATA_EDGE;
end
end
// WRITE =======================================================================
// sequence start condition
if( sequence_cntr[7:0]==0 && (spi_wr_cmd_rise || spi_rd_cmd_rise) ) begin
// outputs should NOT be updated here
if( spi_rd_cmd_rise ) begin
rd_nwr <= 1'b1;
end else begin
rd_nwr <= 1'b0;
end
sequence_cntr[7:0] <= 1;
// buffering mosi_data to avoid data change after shift_cmd issued
mosi_data_buf[WRITE_WIDTH-1:0] <= mosi_data[WRITE_WIDTH-1:0];
end
// clocking out data
if( sequence_cntr[7:0]>=WRITE_SEQ_START && sequence_cntr[7:0]<WRITE_SEQ_END ) begin
if( WRITE_DATA_EDGE ) begin
// we should omit this edge when sequence_cntr[7:0]==WRITE_SEQ_START
if ( spi_clk_rise && (sequence_cntr[7:0]!=WRITE_SEQ_START) ) begin
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
if( spi_clk_fall ) begin
// changing d_out_pin
d_out_pin <= mosi_data_buf[WRITE_WIDTH-1];
// shifting out data
if( WRITE_MSB_FIRST ) begin
mosi_data_buf[WRITE_WIDTH-1:0] <= {mosi_data_buf[WRITE_WIDTH-2:0],1'b0};
end else begin
mosi_data_buf[WRITE_WIDTH-1:0] <= {1'b0,mosi_data_buf[WRITE_WIDTH-1:1]};
end
// spi output starts on WRITE_DATA_EDGE edge, to set first data
if( sequence_cntr[7:0]==WRITE_SEQ_START ) begin
ncs_pin <= 1'b0;
d_oe <= 1'b1;
end
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
end else begin // WRITE_DATA_EDGE == 0
if ( spi_clk_rise ) begin
// changing d_out_pin
d_out_pin <= mosi_data_buf[WRITE_WIDTH-1];
// shifting out data
if( WRITE_MSB_FIRST ) begin
mosi_data_buf[WRITE_WIDTH-1:0] <= {mosi_data_buf[WRITE_WIDTH-2:0],1'b0};
end else begin
mosi_data_buf[WRITE_WIDTH-1:0] <= {1'b0,mosi_data_buf[WRITE_WIDTH-1:1]};
end
// spi output starts on WRITE_DATA_EDGE edge, to set first data
if( sequence_cntr[7:0]==WRITE_SEQ_START ) begin
ncs_pin <= 1'b0;
d_oe <= 1'b1;
end
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
// we should omit this edge when sequence_cntr[7:0]==WRITE_SEQ_START
if( spi_clk_fall && (sequence_cntr[7:0]!=WRITE_SEQ_START) ) begin
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
end // if( WRITE_DATA_EDGE )
end
// waiting for valid edge to switch direction
if( ~rd_nwr ) begin
// end of write transaction
// resetting shifter to default state
if( sequence_cntr[7:0]==WRITE_SEQ_END &&
( (~WRITE_DATA_EDGE && spi_clk_rise) ||
(WRITE_DATA_EDGE && spi_clk_fall)) ) begin
ncs_pin <= 1'b1;
d_out_pin <= 1'b0;
d_oe <= 1'b1;
sequence_cntr[7:0] <= 0;
end
end else begin
if( sequence_cntr[7:0]==WRITE_SEQ_END &&
( (~WRITE_DATA_EDGE && spi_clk_rise) ||
(WRITE_DATA_EDGE && spi_clk_fall)) ) begin
//ncs_pin <= 1'b0;
d_out_pin <= 1'b0;
d_oe <= 1'b0;
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
// READ ========================================================================
// In some combinations of WRITE_DATA_EDGE and READ_DATA_EDGE and slave timing -
// we will get false first bit(s) when reading data. That is not a bug. To get valid
// read data - increment READ_WIDTH and ommit first received bit(s)
// clocking in data
// spi_clk edges and d_in_pin are 2 cycles delayed
if( sequence_cntr[7:0]>=READ_SEQ_START && sequence_cntr[7:0]<READ_SEQ_END ) begin
if( READ_DATA_EDGE ) begin
if ( spi_clk_rise_d2 && (sequence_cntr[7:0]!=READ_SEQ_START) ) begin
// shifting in delayed data
if( READ_MSB_FIRST ) begin
miso_data[READ_WIDTH-1:0] <= {miso_data[READ_WIDTH-2:0],d_in_pin_d2};
end else begin
miso_data[READ_WIDTH-1:0] <= {d_in_pin_d2,miso_data[WRITE_WIDTH-1:1]};
end
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
// we should omit this edge when sequence_cntr[7:0]==READ_SEQ_START
if( spi_clk_fall_d2 ) begin
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
end else begin // READ_DATA_EDGE = 0
// we should omit this edge when sequence_cntr[7:0]==READ_SEQ_START
if( spi_clk_rise_d2 ) begin
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
if ( spi_clk_fall_d2 && (sequence_cntr[7:0]!=READ_SEQ_START) ) begin
// shifting in delayed data
if( READ_MSB_FIRST ) begin
miso_data[READ_WIDTH-1:0] <= {miso_data[READ_WIDTH-2:0],d_in_pin_d2};
end else begin
miso_data[READ_WIDTH-1:0] <= {d_in_pin_d2,miso_data[WRITE_WIDTH-1:1]};
end
sequence_cntr[7:0] <= sequence_cntr[7:0] + 1'b1;
end
end // if( READ_DATA_EDGE )
end
// waiting for valid edge to end read transaction
if( sequence_cntr[7:0]==READ_SEQ_END &&
( (~READ_DATA_EDGE && spi_clk_rise_d2) ||
(READ_DATA_EDGE && spi_clk_fall_d2)) ) begin
ncs_pin <= 1'b1;
d_out_pin <= 1'b0;
d_oe <= 1'b1;
sequence_cntr[7:0] <= 0;
end
end // if( ~rd_nwr )
end // if( nrst )
end // always
always_comb begin
spi_busy = (sequence_cntr[7:0] != 0);
end
endmodule