soc/cores: add new spi master, remove obsolete one
This commit is contained in:
parent
f73eb5fe71
commit
1acca39397
|
@ -0,0 +1,373 @@
|
|||
from itertools import product
|
||||
|
||||
from litex.gen import *
|
||||
from litex.soc.interconnect.csr import *
|
||||
|
||||
|
||||
class SPIClockGen(Module):
|
||||
def __init__(self, width):
|
||||
self.load = Signal(width)
|
||||
self.bias = Signal() # bias this clock phase to longer times
|
||||
self.edge = Signal()
|
||||
self.clk = Signal(reset=1)
|
||||
|
||||
cnt = Signal.like(self.load)
|
||||
bias = Signal()
|
||||
zero = Signal()
|
||||
self.comb += [
|
||||
zero.eq(cnt == 0),
|
||||
self.edge.eq(zero & ~bias),
|
||||
]
|
||||
self.sync += [
|
||||
If(zero,
|
||||
bias.eq(0),
|
||||
).Else(
|
||||
cnt.eq(cnt - 1),
|
||||
),
|
||||
If(self.edge,
|
||||
cnt.eq(self.load[1:]),
|
||||
bias.eq(self.load[0] & (self.clk ^ self.bias)),
|
||||
self.clk.eq(~self.clk),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class SPIRegister(Module):
|
||||
def __init__(self, width):
|
||||
self.data = Signal(width)
|
||||
self.o = Signal()
|
||||
self.i = Signal()
|
||||
self.lsb = Signal()
|
||||
self.shift = Signal()
|
||||
self.sample = Signal()
|
||||
|
||||
self.comb += [
|
||||
self.o.eq(Mux(self.lsb, self.data[0], self.data[-1])),
|
||||
]
|
||||
self.sync += [
|
||||
If(self.shift,
|
||||
If(self.lsb,
|
||||
self.data[:-1].eq(self.data[1:]),
|
||||
).Else(
|
||||
self.data[1:].eq(self.data[:-1]),
|
||||
)
|
||||
),
|
||||
If(self.sample,
|
||||
If(self.lsb,
|
||||
self.data[-1].eq(self.i),
|
||||
).Else(
|
||||
self.data[0].eq(self.i),
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class SPIBitCounter(Module):
|
||||
def __init__(self, width):
|
||||
self.n_read = Signal(width)
|
||||
self.n_write = Signal(width)
|
||||
self.read = Signal()
|
||||
self.write = Signal()
|
||||
self.done = Signal()
|
||||
|
||||
self.comb += [
|
||||
self.write.eq(self.n_write != 0),
|
||||
self.read.eq(self.n_read != 0),
|
||||
self.done.eq(~(self.write | self.read)),
|
||||
]
|
||||
self.sync += [
|
||||
If(self.write,
|
||||
self.n_write.eq(self.n_write - 1),
|
||||
).Elif(self.read,
|
||||
self.n_read.eq(self.n_read - 1),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class SPIMachine(Module):
|
||||
def __init__(self, data_width, clock_width, bits_width):
|
||||
ce = CEInserter()
|
||||
self.submodules.cg = ce(SPIClockGen(clock_width))
|
||||
self.submodules.reg = ce(SPIRegister(data_width))
|
||||
self.submodules.bits = ce(SPIBitCounter(bits_width))
|
||||
self.div_write = Signal.like(self.cg.load)
|
||||
self.div_read = Signal.like(self.cg.load)
|
||||
self.clk_phase = Signal()
|
||||
self.start = Signal()
|
||||
self.cs = Signal()
|
||||
self.oe = Signal()
|
||||
self.done = Signal()
|
||||
|
||||
# # #
|
||||
|
||||
fsm = CEInserter()(FSM("IDLE"))
|
||||
self.submodules += fsm
|
||||
|
||||
fsm.act("IDLE",
|
||||
If(self.start,
|
||||
If(self.clk_phase,
|
||||
NextState("WAIT"),
|
||||
).Else(
|
||||
NextState("SETUP"),
|
||||
)
|
||||
)
|
||||
)
|
||||
fsm.act("SETUP",
|
||||
self.reg.sample.eq(1),
|
||||
NextState("HOLD"),
|
||||
)
|
||||
fsm.act("HOLD",
|
||||
If(self.bits.done & ~self.start,
|
||||
If(self.clk_phase,
|
||||
NextState("IDLE"),
|
||||
).Else(
|
||||
NextState("WAIT"),
|
||||
)
|
||||
).Else(
|
||||
self.reg.shift.eq(~self.start),
|
||||
NextState("SETUP"),
|
||||
)
|
||||
)
|
||||
fsm.act("WAIT",
|
||||
If(self.bits.done,
|
||||
NextState("IDLE"),
|
||||
).Else(
|
||||
NextState("SETUP"),
|
||||
)
|
||||
)
|
||||
|
||||
write0 = Signal()
|
||||
self.sync += [
|
||||
If(self.cg.edge & self.reg.shift,
|
||||
write0.eq(self.bits.write),
|
||||
)
|
||||
]
|
||||
self.comb += [
|
||||
self.cg.ce.eq(self.start | self.cs | ~self.cg.edge),
|
||||
If(self.bits.write | ~self.bits.read,
|
||||
self.cg.load.eq(self.div_write),
|
||||
).Else(
|
||||
self.cg.load.eq(self.div_read),
|
||||
),
|
||||
self.cg.bias.eq(self.clk_phase),
|
||||
fsm.ce.eq(self.cg.edge),
|
||||
self.cs.eq(~fsm.ongoing("IDLE")),
|
||||
self.reg.ce.eq(self.cg.edge),
|
||||
self.bits.ce.eq(self.cg.edge & self.reg.sample),
|
||||
self.done.eq(self.cg.edge & self.bits.done & fsm.ongoing("HOLD")),
|
||||
self.oe.eq(write0 | self.bits.write),
|
||||
]
|
||||
|
||||
|
||||
class SPIMasterCore(Module):
|
||||
"""SPI Master Core.
|
||||
|
||||
Notes:
|
||||
* M = 32 is the data width (maximum write bits, maximum read bits)
|
||||
* Every transfer consists of a write_length 0-M bit write followed
|
||||
by a read_length 0-M bit read.
|
||||
* cs_n is asserted at the beginning and deasserted at the end of the
|
||||
transfer if there is no other transfer pending.
|
||||
* cs_n handling is agnostic to whether it is one-hot or decoded
|
||||
somewhere downstream. If it is decoded, "cs_n all deasserted"
|
||||
should be handled accordingly (no slave selected).
|
||||
If it is one-hot, asserting multiple slaves should only be attempted
|
||||
if miso is either not connected between slaves, or open collector,
|
||||
or correctly multiplexed externally.
|
||||
* If config.cs_polarity == 0 (cs active low, the default),
|
||||
"cs_n all deasserted" means "all cs_n bits high".
|
||||
* cs is not mandatory in pads. Framing and chip selection can also
|
||||
be handled independently through other means.
|
||||
* If there is a miso wire in pads, the input and output can be done
|
||||
with two signals (a.k.a. 4-wire SPI), else mosi must be used for
|
||||
both output and input (a.k.a. 3-wire SPI) and config.half_duplex
|
||||
must to be set when reading data is desired.
|
||||
* For 4-wire SPI only the sum of read_length and write_length matters.
|
||||
The behavior is the same no matter how the total transfer length is
|
||||
divided between the two. For 3-wire SPI, the direction of mosi/miso
|
||||
is switched from output to input after write_len cycles, at the
|
||||
"shift_out" clk edge corresponding to bit write_length + 1 of the
|
||||
transfer.
|
||||
* The first bit output on mosi is always the MSB/LSB (depending on
|
||||
config.lsb_first) of the miso_data signal, independent of
|
||||
xfer.write_len. The last bit input from miso always ends up in
|
||||
the LSB/MSB (respectively) of the misoc_data signal, independent of
|
||||
read_len.
|
||||
* Data output on mosi in 4-wire SPI during the read cycles is what
|
||||
is found in the data register at the time.
|
||||
Data in the data register outside the least/most (depending
|
||||
on config.lsb_first) significant read_length bits is what is
|
||||
seen on miso during the write cycles.
|
||||
* The SPI data register is double-buffered: Once a transfer has
|
||||
started, new write data can be written, queuing a new transfer.
|
||||
Transfers submitted this way are chained and executed without
|
||||
deasserting cs. Once a transfer completes, the previous transfer's
|
||||
read data is available in the data register.
|
||||
* Changes to config signal take effect immediately. Changes
|
||||
to xfer_* signals are synchronized to the start of a transfer.
|
||||
|
||||
Transaction Sequence:
|
||||
* If desired, set the config signal to set up the core.
|
||||
* If designed, set the xfer signal to set up lengths and cs_n.
|
||||
* Set the miso_data signal (not required for zero-length writes),
|
||||
* Set start signal to 1
|
||||
* Wait for active and pending signals to be 0.
|
||||
* If desired, use the misoc_data signal corresponding to the last
|
||||
completed transfer.
|
||||
|
||||
Core IOs:
|
||||
|
||||
config signal:
|
||||
1 offline: all pins high-z (reset=1)
|
||||
1 active: cs/transfer active (read-only)
|
||||
1 pending: transfer pending in intermediate buffer (read-only)
|
||||
1 cs_polarity: active level of chip select (reset=0)
|
||||
1 clk_polarity: idle level of clk (reset=0)
|
||||
1 clk_phase: first edge after cs assertion to sample data on (reset=0)
|
||||
(clk_polarity, clk_phase) == (CPOL, CPHA) in Freescale language.
|
||||
(0, 0): idle low, output on falling, input on rising
|
||||
(0, 1): idle low, output on rising, input on falling
|
||||
(1, 0): idle high, output on rising, input on falling
|
||||
(1, 1): idle high, output on falling, input on rising
|
||||
There is never a clk edge during a cs edge.
|
||||
1 lsb_first: LSB is the first bit on the wire (reset=0)
|
||||
1 half_duplex: 3-wire SPI, in/out on mosi (reset=0)
|
||||
8 undefined
|
||||
8 div_write: counter load value to divide this module's clock
|
||||
to generate the SPI write clk (reset=0)
|
||||
f_clk/f_spi_write == div_write + 2
|
||||
8 div_read: ditto for the read clock
|
||||
|
||||
xfer_config signal:
|
||||
16 cs: active high bit mask of chip selects to assert (reset=0)
|
||||
6 write_len: 0-M bits (reset=0)
|
||||
2 undefined
|
||||
6 read_len: 0-M bits (reset=0)
|
||||
2 undefined
|
||||
|
||||
xfer_mosi/miso_data signal:
|
||||
M write/read data (reset=0)
|
||||
"""
|
||||
def __init__(self, pads):
|
||||
self.config = Record([
|
||||
("offline", 1),
|
||||
("padding0", 2),
|
||||
("cs_polarity", 1),
|
||||
("clk_polarity", 1),
|
||||
("clk_phase", 1),
|
||||
("lsb_first", 1),
|
||||
("half_duplex", 1),
|
||||
("padding1", 8),
|
||||
("div_write", 8),
|
||||
("div_read", 8),
|
||||
])
|
||||
self.config.offline.reset = 1
|
||||
|
||||
self.xfer = Record([
|
||||
("cs", 16),
|
||||
("write_length", 6),
|
||||
("padding0", 2),
|
||||
("read_length", 6),
|
||||
("padding1", 2),
|
||||
])
|
||||
|
||||
self.start = Signal()
|
||||
self.active = Signal()
|
||||
self.pending = Signal()
|
||||
self.mosi_data = Signal(32)
|
||||
self.miso_data = Signal(32)
|
||||
|
||||
# # #
|
||||
|
||||
self.submodules.machine = machine = SPIMachine(
|
||||
data_width=32,
|
||||
clock_width=len(self.config.div_read),
|
||||
bits_width=len(self.xfer.read_length))
|
||||
|
||||
pending = Signal()
|
||||
cs = Signal.like(self.xfer.cs)
|
||||
data_read = Signal.like(machine.reg.data)
|
||||
data_write = Signal.like(machine.reg.data)
|
||||
|
||||
self.comb += [
|
||||
self.miso_data.eq(data_read),
|
||||
machine.start.eq(pending & (~machine.cs | machine.done)),
|
||||
machine.clk_phase.eq(self.config.clk_phase),
|
||||
machine.reg.lsb.eq(self.config.lsb_first),
|
||||
machine.div_write.eq(self.config.div_write),
|
||||
machine.div_read.eq(self.config.div_read),
|
||||
]
|
||||
self.sync += [
|
||||
If(machine.done,
|
||||
data_read.eq(machine.reg.data),
|
||||
),
|
||||
If(machine.start,
|
||||
cs.eq(self.xfer.cs),
|
||||
machine.bits.n_write.eq(self.xfer.write_length),
|
||||
machine.bits.n_read.eq(self.xfer.read_length),
|
||||
machine.reg.data.eq(data_write),
|
||||
pending.eq(0),
|
||||
),
|
||||
If(self.start,
|
||||
data_write.eq(self.mosi_data),
|
||||
pending.eq(1)
|
||||
),
|
||||
self.active.eq(machine.cs),
|
||||
self.pending.eq(pending),
|
||||
]
|
||||
|
||||
# I/O
|
||||
if hasattr(pads, "cs_n"):
|
||||
cs_n_t = TSTriple(len(pads.cs_n))
|
||||
self.specials += cs_n_t.get_tristate(pads.cs_n)
|
||||
self.comb += [
|
||||
cs_n_t.oe.eq(~self.config.offline),
|
||||
cs_n_t.o.eq((cs & Replicate(machine.cs, len(cs))) ^
|
||||
Replicate(~self.config.cs_polarity, len(cs))),
|
||||
]
|
||||
|
||||
clk_t = TSTriple()
|
||||
self.specials += clk_t.get_tristate(pads.clk)
|
||||
self.comb += [
|
||||
clk_t.oe.eq(~self.config.offline),
|
||||
clk_t.o.eq((machine.cg.clk & machine.cs) ^ self.config.clk_polarity),
|
||||
]
|
||||
|
||||
mosi_t = TSTriple()
|
||||
self.specials += mosi_t.get_tristate(pads.mosi)
|
||||
self.comb += [
|
||||
mosi_t.oe.eq(~self.config.offline & machine.cs &
|
||||
(machine.oe | ~self.config.half_duplex)),
|
||||
mosi_t.o.eq(machine.reg.o),
|
||||
machine.reg.i.eq(Mux(self.config.half_duplex, mosi_t.i,
|
||||
getattr(pads, "miso", mosi_t.i))),
|
||||
]
|
||||
|
||||
|
||||
class SPIMaster(Module, AutoCSR):
|
||||
"""SPI Master."""
|
||||
def __init__(self, pads, interface="csr"):
|
||||
self.submodules.core = core = SPIMasterCore(pads)
|
||||
|
||||
# # #
|
||||
|
||||
if interface == "csr":
|
||||
self.config = CSRStorage(32)
|
||||
self.xfer = CSRStorage(32)
|
||||
self.start = CSR()
|
||||
self.active = CSRStatus()
|
||||
self.pending = CSRStatus()
|
||||
self.mosi_data = CSRStorage(32)
|
||||
self.miso_data = CSRStatus(32)
|
||||
|
||||
self.comb += [
|
||||
core.config.raw_bits().eq(self.config.storage),
|
||||
core.xfer.raw_bits().eq(self.xfer.storage),
|
||||
core.start.eq(self.start.re & self.start.r),
|
||||
self.active.status.eq(core.active),
|
||||
self.pending.status.eq(core.pending),
|
||||
core.mosi_data.eq(self.mosi_data.storage),
|
||||
self.miso_data.status.eq(core.miso_data)
|
||||
]
|
||||
else:
|
||||
raise NotImplementedError
|
|
@ -1 +0,0 @@
|
|||
from litex.soc.cores.spi.core import SPIMaster
|
|
@ -1,151 +0,0 @@
|
|||
from litex.gen import *
|
||||
from litex.soc.interconnect.csr import *
|
||||
|
||||
|
||||
class SPIMaster(Module, AutoCSR):
|
||||
def __init__(self, pads, width=24, div=2, cpha=1):
|
||||
self.pads = pads
|
||||
|
||||
self._ctrl = CSR()
|
||||
self._length = CSRStorage(8)
|
||||
self._status = CSRStatus()
|
||||
if hasattr(pads, "mosi"):
|
||||
self._mosi = CSRStorage(width)
|
||||
if hasattr(pads, "miso"):
|
||||
self._miso = CSRStatus(width)
|
||||
|
||||
self.irq = Signal()
|
||||
|
||||
###
|
||||
|
||||
# ctrl
|
||||
start = Signal()
|
||||
length = self._length.storage
|
||||
enable_cs = Signal()
|
||||
enable_shift = Signal()
|
||||
done = Signal()
|
||||
|
||||
self.comb += [
|
||||
start.eq(self._ctrl.re & self._ctrl.r[0]),
|
||||
self._status.status.eq(done)
|
||||
]
|
||||
|
||||
# clk
|
||||
i = Signal(max=div)
|
||||
set_clk = Signal()
|
||||
clr_clk = Signal()
|
||||
self.sync += [
|
||||
If(set_clk,
|
||||
pads.clk.eq(enable_cs)
|
||||
),
|
||||
If(clr_clk,
|
||||
pads.clk.eq(0),
|
||||
i.eq(0)
|
||||
).Else(
|
||||
i.eq(i + 1),
|
||||
)
|
||||
]
|
||||
|
||||
self.comb += [
|
||||
set_clk.eq(i == (div//2-1)),
|
||||
clr_clk.eq(i == (div-1))
|
||||
]
|
||||
|
||||
# fsm
|
||||
cnt = Signal(8)
|
||||
clr_cnt = Signal()
|
||||
inc_cnt = Signal()
|
||||
self.sync += \
|
||||
If(clr_cnt,
|
||||
cnt.eq(0)
|
||||
).Elif(inc_cnt,
|
||||
cnt.eq(cnt+1)
|
||||
)
|
||||
|
||||
fsm = FSM(reset_state="IDLE")
|
||||
self.submodules += fsm
|
||||
fsm.act("IDLE",
|
||||
If(start,
|
||||
NextState("WAIT_CLK")
|
||||
),
|
||||
done.eq(1),
|
||||
clr_cnt.eq(1)
|
||||
)
|
||||
fsm.act("WAIT_CLK",
|
||||
If(clr_clk,
|
||||
NextState("SHIFT")
|
||||
),
|
||||
)
|
||||
fsm.act("SHIFT",
|
||||
If(cnt == length,
|
||||
NextState("END")
|
||||
).Else(
|
||||
inc_cnt.eq(clr_clk),
|
||||
),
|
||||
enable_cs.eq(1),
|
||||
enable_shift.eq(1),
|
||||
)
|
||||
fsm.act("END",
|
||||
If(set_clk,
|
||||
NextState("IDLE")
|
||||
),
|
||||
enable_shift.eq(1),
|
||||
self.irq.eq(1)
|
||||
)
|
||||
|
||||
# miso
|
||||
if hasattr(pads, "miso"):
|
||||
miso = Signal()
|
||||
sr_miso = Signal(width)
|
||||
|
||||
# (cpha = 1: capture on clk falling edge)
|
||||
if cpha:
|
||||
self.sync += \
|
||||
If(enable_shift,
|
||||
If(clr_clk,
|
||||
miso.eq(pads.miso),
|
||||
).Elif(set_clk,
|
||||
sr_miso.eq(Cat(miso, sr_miso[:-1]))
|
||||
)
|
||||
)
|
||||
# (cpha = 0: capture on clk rising edge)
|
||||
else:
|
||||
self.sync += \
|
||||
If(enable_shift,
|
||||
If(set_clk,
|
||||
miso.eq(pads.miso),
|
||||
).Elif(clr_clk,
|
||||
sr_miso.eq(Cat(miso, sr_miso[:-1]))
|
||||
)
|
||||
)
|
||||
self.comb += self._miso.status.eq(sr_miso)
|
||||
|
||||
# mosi
|
||||
if hasattr(pads, "mosi"):
|
||||
sr_mosi = Signal(width)
|
||||
|
||||
# (cpha = 1: propagated on clk rising edge)
|
||||
if cpha:
|
||||
self.sync += \
|
||||
If(start,
|
||||
sr_mosi.eq(self._mosi.storage)
|
||||
).Elif(clr_clk & enable_shift,
|
||||
sr_mosi.eq(Cat(Signal(), sr_mosi[:-1]))
|
||||
).Elif(set_clk,
|
||||
pads.mosi.eq(sr_mosi[-1])
|
||||
)
|
||||
|
||||
# (cpha = 0: propagated on clk falling edge)
|
||||
else:
|
||||
self.sync += [
|
||||
If(start,
|
||||
sr_mosi.eq(self._mosi.storage)
|
||||
).Elif(set_clk & enable_shift,
|
||||
sr_mosi.eq(Cat(Signal(), sr_mosi[:-1]))
|
||||
).Elif(clr_clk,
|
||||
pads.mosi.eq(sr_mosi[-1])
|
||||
)
|
||||
]
|
||||
|
||||
# cs_n
|
||||
self.comb += pads.cs_n.eq(~enable_cs)
|
Loading…
Reference in New Issue