cores/spi_flash: add back old SpiFlashDualQuad and rename new one as SpiFlashQuadReadWrite.

This commit is contained in:
Florent Kermarrec 2020-05-12 16:51:47 +02:00
parent 2a5a7536b8
commit 3fb99b7d33
1 changed files with 273 additions and 127 deletions

View File

@ -19,13 +19,16 @@ from litex.soc.cores.spi import SPIMaster
# 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
"""
@ -78,8 +81,272 @@ class SpiFlashCommon(Module):
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.
@ -137,7 +404,7 @@ class SpiFlashDualQuad(SpiFlashCommon, AutoCSR):
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:
@ -284,7 +551,7 @@ class SpiFlashDualQuad(SpiFlashCommon, AutoCSR):
# 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))
@ -309,127 +576,6 @@ class SpiFlashDualQuad(SpiFlashCommon, AutoCSR):
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):