188 lines
6.0 KiB
Systemverilog
188 lines
6.0 KiB
Systemverilog
///////////////////////////////////////////////////////////////////////////////
|
|
//Source: https://github.com/nandland/spi-master/tree/master/Verilog/source
|
|
//Description: SPI (Serial Peripheral Interface) Master
|
|
// With single chip-select (AKA Slave Select) capability
|
|
//
|
|
// Supports arbitrary length byte transfers.
|
|
//
|
|
// Instantiates a SPI Master and adds single CS.
|
|
// If multiple CS signals are needed, will need to use different
|
|
// module, OR multiplex the CS from this at a higher level.
|
|
//
|
|
// Note: i_Clk must be at least 2x faster than i_SPI_Clk
|
|
//
|
|
// Parameters: SPI_MODE, can be 0, 1, 2, or 3. See above.
|
|
// Can be configured in one of 4 modes:
|
|
// Mode | Clock Polarity (CPOL/CKP) | Clock Phase (CPHA)
|
|
// 0 | 0 | 0
|
|
// 1 | 0 | 1
|
|
// 2 | 1 | 0
|
|
// 3 | 1 | 1
|
|
//
|
|
// CLKS_PER_HALF_BIT - Sets frequency of o_SPI_Clk. o_SPI_Clk is
|
|
// derived from i_Clk. Set to integer number of clocks for each
|
|
// half-bit of SPI data. E.g. 100 MHz i_Clk, CLKS_PER_HALF_BIT = 2
|
|
// would create o_SPI_CLK of 25 MHz. Must be >= 2
|
|
//
|
|
// MAX_BYTES_PER_CS - Set to the maximum number of bytes that
|
|
// will be sent during a single CS-low pulse.
|
|
//
|
|
// CS_INACTIVE_CLKS - Sets the amount of time in clock cycles to
|
|
// hold the state of Chip-Selct high (inactive) before next
|
|
// command is allowed on the line. Useful if chip requires some
|
|
// time when CS is high between trasnfers.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
`include "iSPI.sv"
|
|
|
|
|
|
module SPI_Master_With_Single_CS
|
|
#(parameter SPI_MODE = 0,
|
|
parameter CLKS_PER_HALF_BIT = 2,
|
|
parameter MAX_BYTES_PER_CS = 1,
|
|
parameter CS_INACTIVE_CLKS = 1)
|
|
(
|
|
// Control/Data Signals,
|
|
input i_Rst_L, // FPGA Reset
|
|
input i_Clk, // FPGA Clock
|
|
|
|
// TX (MOSI) Signals
|
|
input [$clog2(MAX_BYTES_PER_CS+1)-1:0] i_TX_Count, // # bytes per CS low
|
|
input [7:0] i_TX_Byte, // Byte to transmit on MOSI
|
|
input i_TX_DV, // Data Valid Pulse with i_TX_Byte
|
|
output o_TX_Ready, // Transmit Ready for next byte
|
|
|
|
// RX (MISO) Signals
|
|
output reg [$clog2(MAX_BYTES_PER_CS+1)-1:0] o_RX_Count, // Index RX byte
|
|
output o_RX_DV, // Data Valid pulse (1 clock cycle)
|
|
output [7:0] o_RX_Byte, // Byte received on MISO
|
|
|
|
// SPI Interface
|
|
output o_SPI_Clk,
|
|
input i_SPI_MISO,
|
|
output o_SPI_MOSI,
|
|
output o_SPI_CS_n
|
|
);
|
|
|
|
localparam IDLE = 2'b00;
|
|
localparam TRANSFER = 2'b01;
|
|
localparam CS_INACTIVE = 2'b10;
|
|
|
|
reg [1:0] r_SM_CS;
|
|
reg r_CS_n;
|
|
reg [$clog2(CS_INACTIVE_CLKS)-1:0] r_CS_Inactive_Count;
|
|
reg [$clog2(MAX_BYTES_PER_CS+1)-1:0] r_TX_Count;
|
|
wire w_Master_Ready;
|
|
|
|
// Instantiate Master
|
|
SPI_Master
|
|
#(.SPI_MODE(SPI_MODE),
|
|
.CLKS_PER_HALF_BIT(CLKS_PER_HALF_BIT)
|
|
) SPI_Master_Inst
|
|
(
|
|
// Control/Data Signals,
|
|
.i_Rst_L(i_Rst_L), // FPGA Reset
|
|
.i_Clk(i_Clk), // FPGA Clock
|
|
|
|
// TX (MOSI) Signals
|
|
.i_TX_Byte(i_TX_Byte), // Byte to transmit
|
|
.i_TX_DV(i_TX_DV), // Data Valid Pulse
|
|
.o_TX_Ready(w_Master_Ready), // Transmit Ready for Byte
|
|
|
|
// RX (MISO) Signals
|
|
.o_RX_DV(o_RX_DV), // Data Valid pulse (1 clock cycle)
|
|
.o_RX_Byte(o_RX_Byte), // Byte received on MISO
|
|
|
|
// SPI Interface
|
|
.o_SPI_Clk(o_SPI_Clk),
|
|
.i_SPI_MISO(i_SPI_MISO),
|
|
.o_SPI_MOSI(o_SPI_MOSI)
|
|
);
|
|
|
|
|
|
// Purpose: Control CS line using State Machine
|
|
always @(posedge i_Clk or negedge i_Rst_L)
|
|
begin
|
|
if (~i_Rst_L)
|
|
begin
|
|
r_SM_CS <= IDLE;
|
|
r_CS_n <= 1'b1; // Resets to high
|
|
r_TX_Count <= 0;
|
|
r_CS_Inactive_Count <= CS_INACTIVE_CLKS;
|
|
end
|
|
else
|
|
begin
|
|
|
|
case (r_SM_CS)
|
|
IDLE:
|
|
begin
|
|
if (r_CS_n & i_TX_DV) // Start of transmission
|
|
begin
|
|
r_TX_Count <= i_TX_Count - 1; // Register TX Count
|
|
r_CS_n <= 1'b0; // Drive CS low
|
|
r_SM_CS <= TRANSFER; // Transfer bytes
|
|
end
|
|
end
|
|
|
|
TRANSFER:
|
|
begin
|
|
// Wait until SPI is done transferring do next thing
|
|
if (w_Master_Ready)
|
|
begin
|
|
if (r_TX_Count > 0)
|
|
begin
|
|
if (i_TX_DV)
|
|
begin
|
|
r_TX_Count <= r_TX_Count - 1;
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
r_CS_n <= 1'b1; // we done, so set CS high
|
|
r_CS_Inactive_Count <= CS_INACTIVE_CLKS;
|
|
r_SM_CS <= CS_INACTIVE;
|
|
end // else: !if(r_TX_Count > 0)
|
|
end // if (w_Master_Ready)
|
|
end // case: TRANSFER
|
|
|
|
CS_INACTIVE:
|
|
begin
|
|
if (r_CS_Inactive_Count > 0)
|
|
begin
|
|
r_CS_Inactive_Count <= r_CS_Inactive_Count - 1'b1;
|
|
end
|
|
else
|
|
begin
|
|
r_SM_CS <= IDLE;
|
|
end
|
|
end
|
|
|
|
default:
|
|
begin
|
|
r_CS_n <= 1'b1; // we done, so set CS high
|
|
r_SM_CS <= IDLE;
|
|
end
|
|
endcase // case (r_SM_CS)
|
|
end
|
|
end // always @ (posedge i_Clk or negedge i_Rst_L)
|
|
|
|
|
|
// Purpose: Keep track of RX_Count
|
|
always @(posedge i_Clk)
|
|
begin
|
|
begin
|
|
if (r_CS_n)
|
|
begin
|
|
o_RX_Count <= 0;
|
|
end
|
|
else if (o_RX_DV)
|
|
begin
|
|
o_RX_Count <= o_RX_Count + 1'b1;
|
|
end
|
|
end
|
|
end
|
|
|
|
assign o_SPI_CS_n = r_CS_n;
|
|
|
|
assign o_TX_Ready = ((r_SM_CS == IDLE) | (r_SM_CS == TRANSFER && w_Master_Ready == 1'b1 && r_TX_Count > 0)) & ~i_TX_DV;
|
|
|
|
endmodule // SPI_Master_With_Single_CS
|