# SpiFlash Quad/Dual/Single (memory-mapped) --------------------------------------------------------
-_QIOFR = 0xeb
-_QIOPP = 0x12
+_FAST_READ = 0x0b
+_DIOFR = 0xbb
+_QIOFR = 0xeb
+_QIOPP = 0x12
def _format_cmd(cmd, spi_width):
"""
`cmd` is the read instruction. Since everything is transmitted on all
- dq lines (cmd, adr and data), we need to reformat cmd in single spi mode.
+ dq lines (cmd, adr and data), extend/interleave cmd to full pads.dq
+ width even if dq1-dq3 are don't care during the command phase:
For example, for N25Q128, 0xeb is the quad i/o fast read, and
extended to 4 bits (dq1,dq2,dq3 high) is: 0xfffefeff
"""
if hasattr(self, "clk_primitive_needed"):
assert self.clk_primitive_registered == True
-
class SpiFlashDualQuad(SpiFlashCommon, AutoCSR):
+ def __init__(self, pads, dummy=15, div=2, with_bitbang=True, endianness="big"):
+ """
+ Simple SPI flash.
+ Supports multi-bit pseudo-parallel reads (aka Dual or Quad I/O Fast
+ Read). Only supports mode3 (cpol=1, cpha=1).
+ """
+ SpiFlashCommon.__init__(self, pads)
+ self.bus = bus = wishbone.Interface()
+ spi_width = len(pads.dq)
+ assert spi_width >= 2
+
+ if with_bitbang:
+ self.bitbang = CSRStorage(4, reset_less=True, fields=[
+ CSRField("mosi", description="Output value for MOSI pin, valid whenever ``dir`` is ``0``."),
+ CSRField("clk", description="Output value for SPI CLK pin."),
+ CSRField("cs_n", description="Output value for SPI CSn pin."),
+ CSRField("dir", description="Sets the direction for *ALL* SPI data pins except CLK and CSn.", values=[
+ ("0", "OUT", "SPI pins are all output"),
+ ("1", "IN", "SPI pins are all input"),
+ ])
+ ], description="""
+ Bitbang controls for SPI output. Only standard 1x SPI is supported, and as
+ a result all four wires are ganged together. This means that it is only possible
+ to perform half-duplex operations, using this SPI core.
+ """)
+ self.miso = CSRStatus(description="Incoming value of MISO signal.")
+ self.bitbang_en = CSRStorage(description="Write a ``1`` here to disable memory-mapped mode and enable bitbang mode.")
+
+ # # #
+
+ cs_n = Signal(reset=1)
+ clk = Signal()
+ dq_oe = Signal()
+ wbone_width = len(bus.dat_r)
+
+
+ read_cmd_params = {
+ 4: (_format_cmd(_QIOFR, 4), 4*8),
+ 2: (_format_cmd(_DIOFR, 2), 2*8),
+ 1: (_format_cmd(_FAST_READ, 1), 1*8)
+ }
+ read_cmd, cmd_width = read_cmd_params[spi_width]
+ addr_width = 24
+
+ dq = TSTriple(spi_width)
+ # Keep DQ2,DQ3 as outputs during bitbang, this ensures they activate ~WP or ~HOLD functions
+ self.specials.dq0 = Tristate(pads.dq[0], o=dq.o[0], i=dq.i[0], oe=dq.oe)
+ self.specials.dq1 = Tristate(pads.dq[1], o=dq.o[1], i=dq.i[1], oe=dq.oe)
+ self.specials.dq2 = Tristate(pads.dq[2], o=dq.o[2], i=dq.i[2], oe=(dq.oe | self.bitbang_en.storage))
+ self.specials.dq3 = Tristate(pads.dq[3], o=dq.o[3], i=dq.i[3], oe=(dq.oe | self.bitbang_en.storage))
+
+ sr = Signal(max(cmd_width, addr_width, wbone_width))
+ if endianness == "big":
+ self.comb += bus.dat_r.eq(sr)
+ else:
+ self.comb += bus.dat_r.eq(reverse_bytes(sr))
+
+ hw_read_logic = [
+ pads.clk.eq(clk),
+ pads.cs_n.eq(cs_n),
+ dq.o.eq(sr[-spi_width:]),
+ dq.oe.eq(dq_oe)
+ ]
+
+ if with_bitbang:
+ bitbang_logic = [
+ pads.clk.eq(self.bitbang.storage[1]),
+ pads.cs_n.eq(self.bitbang.storage[2]),
+
+ # In Dual/Quad mode, no single data pin is consistently
+ # an input or output thanks to dual/quad reads, so we need a bit
+ # to swap direction of the pins. Aside from this additional bit,
+ # bitbang mode is identical for Single/Dual/Quad; dq[0] is mosi
+ # and dq[1] is miso, meaning remaining data pin values don't
+ # appear in CSR registers.
+ If(self.bitbang.storage[3],
+ dq.oe.eq(0)
+ ).Else(
+ dq.oe.eq(1)
+ ),
+ If(self.bitbang.storage[1], # CPOL=0/CPHA=0 or CPOL=1/CPHA=1 only.
+ self.miso.status.eq(dq.i[1])
+ ),
+ dq.o.eq(Cat(self.bitbang.storage[0], Replicate(1, spi_width-1)))
+ ]
+
+ self.comb += [
+ If(self.bitbang_en.storage,
+ bitbang_logic
+ ).Else(
+ hw_read_logic
+ )
+ ]
+
+ else:
+ self.comb += hw_read_logic
+
+ if div < 2:
+ raise ValueError("Unsupported value \'{}\' for div parameter for SpiFlash core".format(div))
+ else:
+ i = Signal(max=div)
+ dqi = Signal(spi_width)
+ self.sync += [
+ If(i == div//2 - 1,
+ clk.eq(1),
+ dqi.eq(dq.i),
+ ),
+ If(i == div - 1,
+ i.eq(0),
+ clk.eq(0),
+ sr.eq(Cat(dqi, sr[:-spi_width]))
+ ).Else(
+ i.eq(i + 1),
+ ),
+ ]
+
+ # spi is byte-addressed, prefix by zeros
+ z = Replicate(0, log2_int(wbone_width//8))
+
+ seq = [
+ (cmd_width//spi_width*div,
+ [dq_oe.eq(1), cs_n.eq(0), sr[-cmd_width:].eq(read_cmd)]),
+ (addr_width//spi_width*div,
+ [sr[-addr_width:].eq(Cat(z, bus.adr))]),
+ ((dummy + wbone_width//spi_width)*div,
+ [dq_oe.eq(0)]),
+ (1,
+ [bus.ack.eq(1), cs_n.eq(1)]),
+ (div, # tSHSL!
+ [bus.ack.eq(0)]),
+ (0,
+ []),
+ ]
+
+ # accumulate timeline deltas
+ t, tseq = 0, []
+ for dt, a in seq:
+ tseq.append((t, a))
+ t += dt
+
+ self.sync += timeline(bus.cyc & bus.stb & (i == div - 1), tseq)
+
+class SpiFlashSingle(SpiFlashCommon, AutoCSR):
+ def __init__(self, pads, dummy=15, div=2, with_bitbang=True, endianness="big"):
+ """
+ Simple memory-mapped SPI flash.
+ Supports 1-bit reads. Only supports mode3 (cpol=1, cpha=1).
+ """
+ SpiFlashCommon.__init__(self, pads)
+ self.bus = bus = wishbone.Interface()
+
+ if with_bitbang:
+ self.bitbang = CSRStorage(4, reset_less=True, fields=[
+ CSRField("mosi", description="Output value for SPI MOSI pin."),
+ CSRField("clk", description="Output value for SPI CLK pin."),
+ CSRField("cs_n", description="Output value for SPI CSn pin."),
+ CSRField("dir", description="Unused in this design.")
+ ], description="""Bitbang controls for SPI output.""")
+ self.miso = CSRStatus(description="Incoming value of MISO pin.")
+ self.bitbang_en = CSRStorage(description="Write a ``1`` here to disable memory-mapped mode and enable bitbang mode.")
+
+ # # #
+
+ if hasattr(pads, "wp"):
+ self.comb += pads.wp.eq(1)
+
+ if hasattr(pads, "hold"):
+ self.comb += pads.hold.eq(1)
+
+ cs_n = Signal(reset=1)
+ clk = Signal()
+ wbone_width = len(bus.dat_r)
+
+ read_cmd = _FAST_READ
+ cmd_width = 8
+ addr_width = 24
+
+ sr = Signal(max(cmd_width, addr_width, wbone_width))
+ if endianness == "big":
+ self.comb += bus.dat_r.eq(sr)
+ else:
+ self.comb += bus.dat_r.eq(reverse_bytes(sr))
+
+ hw_read_logic = [
+ pads.clk.eq(clk),
+ pads.cs_n.eq(cs_n),
+ pads.mosi.eq(sr[-1:])
+ ]
+
+ if with_bitbang:
+ bitbang_logic = [
+ pads.clk.eq(self.bitbang.storage[1]),
+ pads.cs_n.eq(self.bitbang.storage[2]),
+ If(self.bitbang.storage[1], # CPOL=0/CPHA=0 or CPOL=1/CPHA=1 only.
+ self.miso.status.eq(pads.miso)
+ ),
+ pads.mosi.eq(self.bitbang.storage[0])
+ ]
+
+ self.comb += [
+ If(self.bitbang_en.storage,
+ bitbang_logic
+ ).Else(
+ hw_read_logic
+ )
+ ]
+
+ else:
+ self.comb += hw_read_logic
+
+ if div < 2:
+ raise ValueError("Unsupported value \'{}\' for div parameter for SpiFlash core".format(div))
+ else:
+ i = Signal(max=div)
+ miso = Signal()
+ self.sync += [
+ If(i == div//2 - 1,
+ clk.eq(1),
+ miso.eq(pads.miso),
+ ),
+ If(i == div - 1,
+ i.eq(0),
+ clk.eq(0),
+ sr.eq(Cat(miso, sr[:-1]))
+ ).Else(
+ i.eq(i + 1),
+ ),
+ ]
+
+ # spi is byte-addressed, prefix by zeros
+ z = Replicate(0, log2_int(wbone_width//8))
+
+ seq = [
+ (cmd_width*div,
+ [cs_n.eq(0), sr[-cmd_width:].eq(read_cmd)]),
+ (addr_width*div,
+ [sr[-addr_width:].eq(Cat(z, bus.adr))]),
+ ((dummy + wbone_width)*div,
+ []),
+ (1,
+ [bus.ack.eq(1), cs_n.eq(1)]),
+ (div, # tSHSL!
+ [bus.ack.eq(0)]),
+ (0,
+ []),
+ ]
+
+ # accumulate timeline deltas
+ t, tseq = 0, []
+ for dt, a in seq:
+ tseq.append((t, a))
+ t += dt
+
+ self.sync += timeline(bus.cyc & bus.stb & (i == div - 1), tseq)
+
+
+def SpiFlash(pads, *args, **kwargs):
+ if hasattr(pads, "mosi"):
+ return SpiFlashSingle(pads, *args, **kwargs)
+ else:
+ return SpiFlashDualQuad(pads, *args, **kwargs)
+
+# SpiFlash Quad Read/Write (memory-mapped) ---------------------------------------------------------
+
+class SpiFlashQuadReadWrite(SpiFlashCommon, AutoCSR):
def __init__(self, pads, dummy=15, div=2, with_bitbang=True, endianness="big"):
"""
Simple SPI flash.
self.specials.dq0 = Tristate(pads.dq[0], o=dq.o[0], i=dq.i[0], oe=dq.oe)
self.specials.dq1 = Tristate(pads.dq[1], o=dq.o[1], i=dq.i[1], oe=dq.oe)
if with_bitbang:
- # Keep DQ2,DQ3 as outputs during bitbang, this ensures they activate ~WP or ~HOLD functions
+ # Keep DQ2,DQ3 as outputs during bitbang, this ensures they activate ~WP or ~HOLD functions
self.specials.dq2 = Tristate(pads.dq[2], o=dq.o[2], i=dq.i[2], oe=(dq.oe | self.bitbang_en.storage))
self.specials.dq3 = Tristate(pads.dq[3], o=dq.o[3], i=dq.i[3], oe=(dq.oe | self.bitbang_en.storage))
else:
# write data to sr
self.sync += If(queue.status[2] & (i == div - 1) & ~self.en_quad.storage[0],
- sr[-max_transfer_size:].eq(self.spi_in.storage), queue.status[2].eq(0), queue.status[3].eq(1), cs_n.eq(0), dq_oe.eq(1))
+ sr[-max_transfer_size:].eq(self.spi_in.storage), queue.status[2].eq(0), queue.status[3].eq(1), cs_n.eq(0), dq_oe.eq(1))
# count spi to slave transfer cycles
self.sync += If(queue.status[3] & (self.in_left > 0) & (i == div - 1), self.in_left.eq(self.in_left - 1), dq_oe.eq(1))
self.sync += timeline(queue.status[0] & ~self.en_quad.storage[0] & (i == div - 1), accumulate_timeline_deltas(read_seq))
self.sync += timeline(queue.status[1] & ~self.en_quad.storage[0] & (i == div - 1), accumulate_timeline_deltas(write_seq))
-
-class SpiFlashSingle(SpiFlashCommon, AutoCSR):
- def __init__(self, pads, dummy=15, div=2, with_bitbang=True, endianness="big"):
- """
- Simple memory-mapped SPI flash.
- Supports 1-bit reads. Only supports mode3 (cpol=1, cpha=1).
- """
- SpiFlashCommon.__init__(self, pads)
- self.bus = bus = wishbone.Interface()
-
- if with_bitbang:
- self.bitbang = CSRStorage(4, reset_less=True, fields=[
- CSRField("mosi", description="Output value for SPI MOSI pin."),
- CSRField("clk", description="Output value for SPI CLK pin."),
- CSRField("cs_n", description="Output value for SPI CSn pin."),
- CSRField("dir", description="Unused in this design.")
- ], description="""Bitbang controls for SPI output.""")
- self.miso = CSRStatus(description="Incoming value of MISO pin.")
- self.bitbang_en = CSRStorage(description="Write a ``1`` here to disable memory-mapped mode and enable bitbang mode.")
-
- # # #
-
- if hasattr(pads, "wp"):
- self.comb += pads.wp.eq(1)
-
- if hasattr(pads, "hold"):
- self.comb += pads.hold.eq(1)
-
- cs_n = Signal(reset=1)
- clk = Signal()
- wbone_width = len(bus.dat_r)
-
- read_cmd = _FAST_READ
- cmd_width = 8
- addr_width = 24
-
- sr = Signal(max(cmd_width, addr_width, wbone_width))
- if endianness == "big":
- self.comb += bus.dat_r.eq(sr)
- else:
- self.comb += bus.dat_r.eq(reverse_bytes(sr))
-
- hw_read_logic = [
- pads.clk.eq(clk),
- pads.cs_n.eq(cs_n),
- pads.mosi.eq(sr[-1:])
- ]
-
- if with_bitbang:
- bitbang_logic = [
- pads.clk.eq(self.bitbang.storage[1]),
- pads.cs_n.eq(self.bitbang.storage[2]),
- If(self.bitbang.storage[1], # CPOL=0/CPHA=0 or CPOL=1/CPHA=1 only.
- self.miso.status.eq(pads.miso)
- ),
- pads.mosi.eq(self.bitbang.storage[0])
- ]
-
- self.comb += [
- If(self.bitbang_en.storage,
- bitbang_logic
- ).Else(
- hw_read_logic
- )
- ]
-
- else:
- self.comb += hw_read_logic
-
- if div < 2:
- raise ValueError("Unsupported value \'{}\' for div parameter for SpiFlash core".format(div))
- else:
- i = Signal(max=div)
- miso = Signal()
- self.sync += [
- If(i == div//2 - 1,
- clk.eq(1),
- miso.eq(pads.miso),
- ),
- If(i == div - 1,
- i.eq(0),
- clk.eq(0),
- sr.eq(Cat(miso, sr[:-1]))
- ).Else(
- i.eq(i + 1),
- ),
- ]
-
- # spi is byte-addressed, prefix by zeros
- z = Replicate(0, log2_int(wbone_width//8))
-
- seq = [
- (cmd_width*div,
- [cs_n.eq(0), sr[-cmd_width:].eq(read_cmd)]),
- (addr_width*div,
- [sr[-addr_width:].eq(Cat(z, bus.adr))]),
- ((dummy + wbone_width)*div,
- []),
- (1,
- [bus.ack.eq(1), cs_n.eq(1)]),
- (div, # tSHSL!
- [bus.ack.eq(0)]),
- (0,
- []),
- ]
-
- # accumulate timeline deltas
- t, tseq = 0, []
- for dt, a in seq:
- tseq.append((t, a))
- t += dt
-
- self.sync += timeline(bus.cyc & bus.stb & (i == div - 1), tseq)
-
-
-def SpiFlash(pads, *args, **kwargs):
- if hasattr(pads, "mosi"):
- return SpiFlashSingle(pads, *args, **kwargs)
- else:
- return SpiFlashDualQuad(pads, *args, **kwargs)
-
# Xilinx 7-Series FPGAs SPI Flash (non-memory-mapped) ----------------------------------------------
class S7SPIFlash(Module, AutoCSR):