diff --git a/litex/soc/cores/spi.py b/litex/soc/cores/spi.py new file mode 100644 index 000000000..d5f927899 --- /dev/null +++ b/litex/soc/cores/spi.py @@ -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 diff --git a/litex/soc/cores/spi/__init__.py b/litex/soc/cores/spi/__init__.py deleted file mode 100644 index 05e31b21e..000000000 --- a/litex/soc/cores/spi/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from litex.soc.cores.spi.core import SPIMaster diff --git a/litex/soc/cores/spi/core.py b/litex/soc/cores/spi/core.py deleted file mode 100644 index 3bc515fde..000000000 --- a/litex/soc/cores/spi/core.py +++ /dev/null @@ -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)