soc/cores: add new spi master, remove obsolete one

This commit is contained in:
Florent Kermarrec 2017-04-19 10:16:10 +02:00
parent f73eb5fe71
commit 1acca39397
3 changed files with 373 additions and 152 deletions

373
litex/soc/cores/spi.py Normal file
View File

@ -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

View File

@ -1 +0,0 @@
from litex.soc.cores.spi.core import SPIMaster

View File

@ -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)