Skip to content

Commit

Permalink
spi: improve logic for waiting response
Browse files Browse the repository at this point in the history
  • Loading branch information
HarryMakes committed Feb 7, 2020
1 parent e941997 commit 99e51ec
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 47 deletions.
113 changes: 66 additions & 47 deletions nmigen_stdio/spiflash.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,27 @@ def _format_cmd(self):
fcmd &= ~(1 << (bit*self.spi_width))
return fcmd

def __init__(self, *, protocol, data_width=32, divisor=1, divisor_bits=None, pins=None):
def __init__(self, *, protocol, data_width=32, granularity=8,
divisor=1, divisor_bits=None, pins=None):
if protocol not in ["standard", "dual", "quad"]:
raise ValueError("Invalid SPI protocol {!r}; must be one of "
"\"standard\", \"dual\", or \"quad\""
.format(protocol))
self._protocol = protocol
if not isinstance(data_width, int) or data_width < 1:
raise ValueError("Data width must be a positive integer, not {}"
.format(repr(data_width)))
if not isinstance(granularity, int) or granularity < 8 or (
granularity != 1 << (granularity.bit_length()-1)
):
raise ValueError("Granularity must be a positive integer, "
"at least 8 and a power of 2, not {}"
.format(repr(granularity)))
if data_width < granularity or data_width % granularity:
raise ValueError("Data width must be divisible by granularity ({} % {} != 0)"
.format(data_width, granularity))
self._data_width = data_width
self._granularity = granularity

if divisor < 1:
raise ValueError("Invalid divisor value; must be at least 1"
Expand Down Expand Up @@ -83,14 +97,16 @@ def __init__(self, *, protocol, data_width=32, divisor=1, divisor_bits=None, pin
self._fcmd_width = self.cmd_width * self.spi_width
# A two-way register storing the current value on the DQ I/O pins
# (Note: DQ pins are both input/output only for Dual or Quad SPI protocols)
self.shreg = Signal(max(self._fcmd_width, self._data_width))
self.shreg = None
self.counter = Signal.like(self.divisor)
self.clk_posedge_next = Signal()
self.clk_negedge_next = Signal()

def _add_spi_hardware_logic(self, platform, module):
"""Add the foundamental hardware logic for controlling all SPI pins to be used
"""
if self.shreg is None:
raise NotImplementedError("Shift register shreg has not been defined!")
shreg = self.shreg
counter = self.counter

Expand Down Expand Up @@ -137,30 +153,33 @@ def _add_spi_hardware_logic(self, platform, module):
# Countdown logic for counter based on divisor
# Also implements MISO logic
dq_i = Signal(self.spi_width)
# When countdown is half-way done, clock edge goes up (positive);
# MISO starts latching bit/byte from slave
with module.If((counter == (self.divisor+1) >> 1) & self.cs):
module.d.sync += self.clk.eq(1)
# Indicate imminent posedge
module.d.comb += self.clk_posedge_next.eq(1)
if self._protocol == "standard":
module.d.sync += dq_i.eq(self.miso)
elif self._protocol in ["dual", "quad"]:
module.d.sync += dq_i.eq(self.dq.i)
# When countdown reaches 0, clock edge goes down (negative)
# shreg latches from MISO for r_data to read
with module.If((counter == 0) & self.cs):
module.d.sync += [
self.clk.eq(0),
counter.eq(self.divisor)
]
# Indicate imminent negedge
module.d.comb += self.clk_negedge_next.eq(1)
# MOSI latches from shreg by "pushing" old data out from the left of shreg
module.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width]))
# Normal countdown
with module.Elif(self.cs):
module.d.sync += counter.eq(counter - 1)
# Enable SCLK only when CS:
with module.If(self.cs):
# When countdown is half-way done, clock edge goes up (positive):
# - MISO latches bit/byte from slave
# - slave has been latching from MOSI mid-way (since previous negedge)
with module.If(counter == (self.divisor+1) >> 1):
module.d.sync += self.clk.eq(1)
# Indicate imminent posedge
module.d.comb += self.clk_posedge_next.eq(1)
if self._protocol == "standard":
module.d.sync += dq_i.eq(self.miso)
elif self._protocol in ["dual", "quad"]:
module.d.sync += dq_i.eq(self.dq.i)
# When countdown reaches 0, clock edge goes down (negative):
# - MOSI latches from shreg by "pushing" old data out from the left of shreg;
# - slave has been outputting MISO mid-way (since previous posedge)
with module.If(counter == 0):
module.d.sync += [
self.clk.eq(0),
counter.eq(self.divisor)
]
# Indicate imminent negedge
module.d.comb += self.clk_negedge_next.eq(1)
module.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width]))
# Normal countdown
with module.Else():
module.d.sync += counter.eq(counter - 1)

# MOSI logic for Standard SPI protocol:
# MOSI always output the leftmost bit of shreg
Expand All @@ -175,6 +194,10 @@ def _add_spi_hardware_logic(self, platform, module):
self.dq.oe.eq(self.dq_oe)
]

# r_rdy is asserted for only 1 clock cycle
with module.If(self.r_rdy):
module.d.sync += self.r_rdy.eq(0)


class SPIFlashSlowReader(_SPIFlashReaderBase, Elaboratable):
"""An SPI flash controller module for normal reading
Expand Down Expand Up @@ -203,7 +226,7 @@ def elaborate(self, platform):
# fcmd: get formatted command based on cmd_dict
fcmd = self._format_cmd()
# addr: convert bus address to byte-sized address
byte_addr = Cat(Repl(0, log2_int(self._data_width//8)), self.addr)
byte_addr = Cat(Repl(0, log2_int(self._data_width // self._granularity)), self.addr)
# FSM
with m.FSM() as fsm:
state_durations = {
Expand Down Expand Up @@ -249,35 +272,33 @@ def elaborate(self, platform):
# State: Address, MOSI
with m.State("SLOWREAD-ADDR"):
with m.If((state_counter == state_durations["SLOWREAD-ADDR"] - 1) &
self.clk_negedge_next):
self.clk_posedge_next):
m.d.sync += state_counter.eq(0)
if self._protocol in ["dual", "quad"]:
m.d.sync += self.dq_oe.eq(0)
m.next = "SLOWREAD-READ"
with m.Elif(self.clk_negedge_next):
m.d.sync += state_counter.eq(state_counter + 1)
# State: Dummy cycles (waiting), and then Read (MISO)
# State: Read, MISO
with m.State("SLOWREAD-READ"):
with m.If((state_counter == state_durations["SLOWREAD-READ"] - 1) &
self.clk_negedge_next):
m.d.sync += [
state_counter.eq(0),
self.r_rdy.eq(1)
]
self.clk_posedge_next):
m.d.sync += state_counter.eq(0)
if self._protocol in ["dual", "quad"]:
m.d.sync += self.dq_oe.eq(0)
m.next = "SLOWREAD-RDYWAIT"
with m.Elif(self.clk_negedge_next):
with m.Elif(self.clk_posedge_next):
m.d.sync += state_counter.eq(state_counter + 1)
# State: Send r_rdy (for 1 clock period), and then Wait
with m.State("SLOWREAD-RDYWAIT"):
m.d.sync += self.r_rdy.eq(0)
with m.If((state_counter == 0) & self.clk_negedge_next):
m.d.sync += self.r_rdy.eq(1)
# Early check to skip 1 clock period of doing nothing
with m.If((state_counter == state_durations["SLOWREAD-RDYWAIT"] - 1) &
self.clk_posedge_next):
m.d.sync += self.cs.eq(0)
m.next = "SLOWREAD-IDLE"
with m.Elif(self.clk_negedge_next):
with m.Elif(self.clk_posedge_next):
m.d.sync += state_counter.eq(state_counter + 1)

return m
Expand Down Expand Up @@ -311,7 +332,7 @@ def elaborate(self, platform):
# fcmd: get formatted command based on cmd_dict
fcmd = self._format_cmd()
# addr: convert bus address to byte-sized address
byte_addr = Cat(Repl(0, log2_int(self._data_width//8)), self.addr)
byte_addr = Cat(Repl(0, log2_int(self._data_width // self._granularity)), self.addr)
# FSM
with m.FSM() as fsm:
state_durations = {
Expand Down Expand Up @@ -357,7 +378,7 @@ def elaborate(self, platform):
# State: Address, MOSI
with m.State("FASTREAD-ADDR"):
with m.If((state_counter == state_durations["FASTREAD-ADDR"] - 1) &
self.clk_negedge_next):
self.clk_posedge_next):
m.d.sync += state_counter.eq(0)
if self._protocol in ["dual", "quad"]:
m.d.sync += self.dq_oe.eq(0)
Expand All @@ -367,25 +388,23 @@ def elaborate(self, platform):
# State: Dummy cycles (waiting), and then Read (MISO)
with m.State("FASTREAD-WAITREAD"):
with m.If((state_counter == state_durations["FASTREAD-WAITREAD"] - 1) &
self.clk_negedge_next):
m.d.sync += [
state_counter.eq(0),
self.r_rdy.eq(1)
]
self.clk_posedge_next):
m.d.sync += state_counter.eq(0)
if self._protocol in ["dual", "quad"]:
m.d.sync += self.dq_oe.eq(0)
m.next = "FASTREAD-RDYWAIT"
with m.Elif(self.clk_negedge_next):
with m.Elif(self.clk_posedge_next):
m.d.sync += state_counter.eq(state_counter + 1)
# State: Send r_rdy (for 1 clock period), and then Wait
with m.State("FASTREAD-RDYWAIT"):
m.d.sync += self.r_rdy.eq(0)
with m.If((state_counter == 0) & self.clk_negedge_next):
m.d.sync += self.r_rdy.eq(1)
# Early check to skip 1 clock period of doing nothing
with m.If((state_counter == state_durations["FASTREAD-RDYWAIT"] - 1) &
self.clk_posedge_next):
m.d.sync += self.cs.eq(0)
m.next = "FASTREAD-IDLE"
with m.Elif(self.clk_negedge_next):
with m.Elif(self.clk_posedge_next):
m.d.sync += state_counter.eq(state_counter + 1)

return m
4 changes: 4 additions & 0 deletions nmigen_stdio/test/test_spiflash.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def test_standard(self):
self.dut = SPIFlashFastReader(protocol="standard",
addr_width=24,
data_width=32,
granularity=8,
divisor=self.divisor,
dummy_cycles=15)
self.flash = (SPIFlashFastReadTestCase.
Expand All @@ -120,6 +121,7 @@ def test_dual(self):
self.dut = SPIFlashFastReader(protocol="dual",
addr_width=24,
data_width=32,
granularity=8,
divisor=self.divisor,
dummy_cycles=15)
self.flash = (SPIFlashFastReadTestCase.
Expand All @@ -134,6 +136,7 @@ def test_quad(self):
self.dut = SPIFlashFastReader(protocol="quad",
addr_width=24,
data_width=32,
granularity=8,
divisor=self.divisor,
dummy_cycles=15)
self.flash = (SPIFlashFastReadTestCase.
Expand All @@ -149,6 +152,7 @@ def simple_test(self):
m.submodules.slave = self.flash

def process():
yield self.dut.addr.eq(0x123456 >> 2)
yield self.dut.ack.eq(1)
while (yield self.dut.rdy):
yield # Wait until it enters CMD state
Expand Down

0 comments on commit 99e51ec

Please sign in to comment.