/* spi.v
KISS ("keep it simple & stupid") SPI master engine. Configured for
2 slaves, occupies just 38 LCs in an Intel MAX10 or 34 in a Xilinx
Spatan6.
Parameter
DIVPWR: SPI sck is derived from input clk divided by 2^DIVPWR.
SELECTS: Number of outgoing nSS signals, i.e. number of slaves hooked to
the bus.
Interface Registers
Write Read
Address 1: ctrl nSS SPIready
Address 0: data byte to xmit received byte
You may have slaves at the bus that are routed to different FPGA pins. While
MOSI and SCK can simply be made to drive multiple pins, MISO lines have to
be combined. Since only the selected slave will drive MISO low, we can simply
'and' them togehther.
SW actions (or how to use it)
Write 0xFF to ctrl to DESELECT all slaves. To select a slave, write a 0 to
the appropriate bit and 1s to all other bits. The data register may be
written/read when SPIready is true. - Hint: your SW may spend enough time
between successive SPI actions anyhow to not have to check SPIready every
time.
(C) 2021 binär industrie-informatik Bernd Kohler
bk 25Mar21
*/
module spi #(parameter DIVPWR = 3, parameter SELECTS = 1) (
// Global
input reset,
input clk,
// uP
input wr,
input rd,
input addr,
input [7:0] din, output [7:0] dout,
// SPI
output reg [SELECTS-1:0] nss,
output sck,
input [SELECTS-1:0] miso, output mosi
);
reg [DIVPWR-1:0] clks;
wire spiclk;
reg prev_spiclk;
wire rise_spiclk = !prev_spiclk & spiclk; // Time to sample miso line(s)
wire trail_spiclk = prev_spiclk & !spiclk; // Time to shift in/out
reg state;
reg [7:0] shift;
reg [2:0] bits = 3'b0;
wire [7:0] rddat = addr ? {8{!state}} : shift;
reg int_miso; // 'And' of incoming miso lines.
assign spiclk = clks[DIVPWR-1];
assign sck = state ? spiclk : 1'b0;
assign mosi = shift[7];
assign dout = rd ? rddat : 8'hAA; // Shouldn't see the hex AA!
always @(posedge clk) begin
if (state) begin
clks <= clks + 1;
prev_spiclk <= spiclk;
end
end
always @(posedge clk or posedge reset) begin
if (reset) begin
state <= 0;
nss <= {SELECTS{1'b1}};
shift <= 8'b0;
end // if (reset)
else begin // !reset
if (wr) begin
if (addr) begin
nss <= din[SELECTS-1:0];
end // if (address)
else begin
state <= 1;
shift <= din;
bits <= 3'b000;
end // if (!address)
end // if (wr)
if (state) begin
if (rise_spiclk)
int_miso <= &miso; // Sample incoming data
if (trail_spiclk) begin
shift <= {shift[6:0], int_miso}; // Shift out
if (bits == 3'h7) begin
state <= 0;
end // if (bits == 7)
else
bits <= bits + 1;
end // if trail_spiclk
end // if (state)
end // !reset
end // @(posedge clk or posedge reset)
endmodule