From 8f51da9db528008944c6ec00bf0d531ccbc04759 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Tue, 29 Oct 2019 17:45:59 +0800 Subject: [PATCH 01/19] update .gitignore & setup.py --- .gitignore | 4 ++++ setup.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 610c399..11fffae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ *.pyc /*.egg-info /.eggs + +# tests +*.vcd +*.gtkw diff --git a/setup.py b/setup.py index f83315c..5a30776 100644 --- a/setup.py +++ b/setup.py @@ -23,10 +23,11 @@ def local_scheme(version): #long_description="""TODO""", license="BSD", setup_requires=["setuptools_scm"], - install_requires=["nmigen"], + install_requires=["nmigen~=0.1.rc1"], packages=find_packages(), project_urls={ "Source Code": "https://github.com/m-labs/nmigen-stdio", "Bug Tracker": "https://github.com/m-labs/nmigen-stdio/issues", }, ) + From d665944b2334ba68531a1527cca772a034f28930 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Fri, 22 Nov 2019 17:43:18 +0800 Subject: [PATCH 02/19] spiflash: implement FAST READ protocol, add simple simulation - currently only supporting Dual and Quad protocols --- nmigen_stdio/spiflash.py | 281 +++++++++++++++++++++++++++++ nmigen_stdio/test/test_spiflash.py | 117 ++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 nmigen_stdio/spiflash.py create mode 100644 nmigen_stdio/test/test_spiflash.py diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py new file mode 100644 index 0000000..857d3e6 --- /dev/null +++ b/nmigen_stdio/spiflash.py @@ -0,0 +1,281 @@ +from nmigen import * +from nmigen.lib.io import Pin +from nmigen.build.dsl import Subsignal, Pins +from nmigen.build.plat import Platform +from nmigen.utils import bits_for, log2_int + + +__all__ = ["SPIFlash"] + + + +class SPIFlash(Elaboratable): + + def _check_commands(self, cmd_dict): + cmd_names = [ + "FAST_READ" + # TODO: Add more supported commands + ] + for cmd in cmd_dict: + if cmd not in cmd_names: + raise ValueError("Invalid SPI command {!r}; must be one of {}" + .format(cmd, cmd_names)) + if cmd_dict[cmd] > 2**self.cmd_width: + raise ValueError("Invalid SPI command value {!r}; must not be more than {}-bit wide" + .format(cmd_dict[cmd], 2**self.cmd_width)) + + + def _format_cmd(self, cmd_value): + """ + Returns the values on all the DQ lines that corresponds to the command. + + Since everything is transmitted on all DQ lines (command, address and data), + the input cmd_value is extended/interleaved to full DQ width + even if DQ1-DQ3 are "don't care" during the command phase: + + eg: 1 1 1 0 1 0 1 1 (extended SPI mode) + 11 11 11 10 11 10 11 11 (dual I/O SPI mode) + 1111 1111 1111 1110 1111 1110 1111 1111 (quad I/O SPI mode) + """ + fcmd = 2**(self.cmd_width*self.spi_width) - 1 + for bit in range(self.cmd_width): + if (cmd_value >> bit)%2 == 0: + fcmd &= ~(1 << (bit*self.spi_width)) + return fcmd + + + def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, + dummy_cycles=15, divisor=1, device=None, pins=None): + if protocol not in ["extended", "dual", "quad"]: + raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" + .format(protocol)) + self._protocol = protocol + self.addr_width = addr_width + self.data_width = data_width + self.cmd_width = cmd_width + if not isinstance(cmd_dict, dict): + raise TypeError("Invalid command dict {!r}; must be a dict where each key-value pair is (command name, command value)" + .format(cmd_dict)) + self._check_commands(cmd_dict) + self._cmd_dict = cmd_dict + self.dummy_cycles = dummy_cycles + if divisor < 1: + raise ValueError("Invalid divisor value; must be at least 1" + .format(divisor)) + self.divisor = Signal(bits_for(divisor), reset=divisor) + self._divisor_val = divisor + 1 + + supported_devices = ["lattice_ecp5"] + if device is not None and device not in supported_devices: + raise ValueError("Invalid FPGA device name {!r}; must be one of {}" + .format(device, supported_devices)) + self._device = device + if self._device is not None and pins is None: + raise ValueError("Pins parameter is missing for this FPGA device {}" + .format(self._device)) + self._pins = pins + + if self._protocol == "extended": + self.spi_width = 1 + elif self._protocol == "dual": + self.spi_width = 2 + elif self._protocol == "quad": + self.spi_width = 4 + + self.cs = Signal(reset=0) # Equivalent to ~CS_N + self.clk = Signal() + + if protocol == "extended": + self.mosi = Signal() + self.miso = Signal() + # TODO: Add wp & hold pins + elif protocol == "dual": + self.dq = Pin(2, "io") + elif protocol == "quad": + self.dq = Pin(4, "io") + + self.rdy = Signal() # Internal rdy signal, set at idle state + self.ack = Signal() # External ack signal that begins a read + self.cmd = Signal(cmd_width) # set to the corresponding value of the command + self.addr = Signal(addr_width) + self.r_data = Signal(self.data_width) + self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise + + 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.addr_width, self.data_width)) + self.counter = Signal.like(self.divisor) + + + def _add_clk_primitive(self, module): + """Add a submodule whose instantiation is required by certain devices + when choosing a user clock as SPI clock + """ + # Lattice ECP5: + # "The ECP5 and ECP5-5G devices provide a solution for users + # to choose any user clock as MCLK under this scenario + # by instantiating USRMCLK macro in your Verilog or VHDL." + # (see Section 6.1.2 of FPGA-TN-02039-1.7, + # "ECP5 and ECP5-5G sysCONFIG Usage Guide Technical Note") + if self._device == "lattice_ecp5": + module.submodules += Instance("USRMCLK", + i_USRMCLKI=self._pins.clk, + i_USRMCLKTS=self._pins.cs.o) + + + def elaborate(self, platform): + m = Module() + + shreg = self.shreg + counter = self.counter + + if self._pins is not None: + self._add_clk_primitive(m) + m.d.comb += [ + self._pins.cs.o.eq(self.cs), + self._pins.clk.eq(self.clk) + ] + if self._protocol == "extended": + m.d.comb += [ + MultiReg(self._pins.miso.i, self.miso), + self._pins.mosi.o.eq(self.mosi) + ] + elif self._protocol in ["dual", "quad"]: + dq_oe = Signal() + m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) + # If the user doesn't give pins, create dq Pins + else: + dq_oe = Signal() + if self._protocol == "dual": + dq_pins = Record([ + ("dq", Pin(width=2, dir="io", xdr=0).layout) + ]) + elif self._protocol == "quad": + dq_pins = Record([ + ("dq", Pin(width=4, dir="io", xdr=0).layout) + ]) + tristate_submodule = Module() + tristate_submodule.submodules += Instance("$tribuf", + p_WIDTH=self.dq.width, + i_EN=self.dq.oe, + i_A=Platform._invert_if(False, self.dq.o), + o_Y=dq_pins + ) + m.submodules.dq = tristate_submodule + + # r_data always reads from shreg + m.d.comb += self.r_data.eq(shreg) + + # 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 m.If(counter == self._divisor_val >> 1): + m.d.sync += self.clk.eq(1) + if self._protocol == "extended": + pass # TODO + elif self._protocol in ["dual", "quad"]: + m.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 m.If(counter == 0): + m.d.sync += [ + self.clk.eq(0), + self.counter.eq(self.divisor) + ] + if self._protocol == "extended": + pass # TODO + elif self._protocol in ["dual", "quad"]: + m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) # "pushing" old data out from the left + ## Normal countdown + with m.Else(): + m.d.sync += counter.eq(counter - 1) + + # MOSI logic for Dual and Quad SPI protocols: + # Whenever DQ output should be enabled, + # DQ always output the leftmost `spi_width`-wide bits of shreg + if self._protocol in ["dual", "quad"]: + m.d.comb += [ + self.dq.o.eq(shreg[-self.spi_width:]), + self.dq.oe.eq(dq_oe) + ] + + # Command: FAST READ + if "FAST_READ" in self._cmd_dict: + fcmd = self._format_cmd(self._cmd_dict["FAST_READ"]) + if self._protocol == "extended": + # TODO: implement FAST READ for Extended SPI protocol as well + pass + + elif self._protocol in ["dual", "quad"]: + # addr: convert bus address to byte-sized address + byte_addr = Cat(Repl(0, log2_int(self.data_width//8)), self.addr) + # FSM + with m.FSM() as fsm: + state_durations = { + "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "FASTREAD-ADDR" : self._divisor_val*(self.addr_width//self.spi_width), + "FASTREAD-WAITREAD": self._divisor_val*(self.dummy_cycles+ + self.data_width//self.spi_width), + "FASTREAD-RDYWAIT" : 1+self._divisor_val + } + max_duration = max([dur for state,dur in state_durations.items()]) + # A "count-up" counter for each state of the command + state_counter = Signal(range(max_duration)) + # State: Idling + with m.State("FASTREAD-IDLE"): + m.d.comb += self.rdy.eq(1) + with m.If((self.cmd == self._cmd_dict["FAST_READ"]) & + self.ack & (counter == 0)): + m.d.sync += [ + state_counter.eq(0), + dq_oe.eq(1), + self.cs.eq(1), + shreg[-self.cmd_width:].eq(fcmd) + ] + m.next = "FASTREAD-CMD" + # State: Command, MOSI + with m.State("FASTREAD-CMD"): + with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.addr_width:].eq(byte_addr) + ] + m.next = "FASTREAD-ADDR" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Address, MOSI + with m.State("FASTREAD-ADDR"): + with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): + m.d.sync += [ + state_counter.eq(0), + dq_oe.eq(0) + ] + m.next = "FASTREAD-WAITREAD" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Dummy cycles (waiting), and then Read (MISO) + with m.State("FASTREAD-WAITREAD"): + with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.cs.eq(0), + self.r_rdy.eq(1) + ] + m.next = "FASTREAD-RDYWAIT" + with m.Else(): + 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"): + with m.If(state_counter == 0): + m.d.sync += self.r_rdy.eq(0) + # Early check to skip 1 clock period of doing nothing + with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): + m.d.sync += state_counter.eq(0) + m.next = "FASTREAD-IDLE" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + + return m \ No newline at end of file diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py new file mode 100644 index 0000000..9398ed4 --- /dev/null +++ b/nmigen_stdio/test/test_spiflash.py @@ -0,0 +1,117 @@ +import unittest +from nmigen import * +from nmigen.lib.fifo import SyncFIFO +from nmigen.back.pysim import * + +from ..spiflash import * + + +def simulation_test(dut, process): + with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim: + sim.add_clock(1e-6) + sim.add_sync_process(process) + sim.run() + + +class SPIFlashFastReadTestCase(unittest.TestCase): + def divisor_period(self): + for _ in range((yield self.dut.divisor) + 1): + yield + + class SuperNaiveDualFlash(Elaboratable): + def __init__(self, *, dut, data, data_size): + self.dut = dut + self.data = data + self.data_size = data_size + def elaborate(self, platform): + m = Module() + data_sig = Signal(self.data_size) + recv_data = SyncFIFO(width=self.dut.spi_width, + depth=(self.dut.cmd_width+self.dut.addr_width)//self.dut.spi_width) + stored_data = SyncFIFO(width=self.dut.spi_width, + depth=self.dut.data_width//self.dut.spi_width) + stored_data_num_sent = Signal(range(stored_data.depth+1), + reset=stored_data.depth) + dummy_counter = Signal(range(self.dut.dummy_cycles), + reset=self.dut.dummy_cycles - 1) + m.submodules.recv_data = recv_data + m.submodules.stored_data = stored_data + m.d.comb += recv_data.w_data.eq(self.dut.dq.o) + with m.If(stored_data.r_rdy & stored_data.r_en): + m.d.sync += self.dut.dq.i.eq(stored_data.r_data) + with m.FSM() as fsm: + with m.State("INIT"): + m.d.sync += data_sig.eq(self.data) + with m.If(self.dut.cs): + # Set w_en 1 clock earlier + with m.If(self.dut.counter == 1): + m.d.comb += recv_data.w_en.eq(1) + with m.Else(): + m.d.comb += recv_data.w_en.eq(0) + with m.If(~recv_data.w_rdy & stored_data.w_rdy): + m.next = "PUT-DATA" + with m.State("PUT-DATA"): + with m.If(stored_data_num_sent != 0): + m.d.comb += [ + stored_data.w_en.eq(1), + stored_data.w_data.eq(data_sig[-self.dut.spi_width:]) + ] + m.d.sync += [ + stored_data_num_sent.eq(stored_data_num_sent+1), + data_sig.eq(Cat(Repl(0, self.dut.spi_width), data_sig[:-self.dut.spi_width])) + ] + with m.Else(): + m.d.comb += stored_data.w_en.eq(0) + with m.If((dummy_counter != 1) & (self.dut.counter == 0)): + m.d.sync += dummy_counter.eq(dummy_counter - 1) + with m.Elif((dummy_counter == 1) & + (self.dut.counter == self.dut._divisor_val>>1+1)): + m.d.comb += stored_data.r_en.eq(1) + m.next = "RETURN-DATA" + with m.State("RETURN-DATA"): + with m.If(self.dut.counter == self.dut._divisor_val>>1+1): + m.d.comb += stored_data.r_en.eq(1) + with m.Else(): + m.d.comb += stored_data.r_en.eq(0) + return m + + def test_n25q128a_dual(self): + self.cmd_dict = {"FAST_READ": 0xbb} + self.dut = SPIFlash(protocol="dual", + addr_width=24, + data_width=8, + cmd_width=8, + cmd_dict=self.cmd_dict) + self.flash = (SPIFlashFastReadTestCase. + SuperNaiveDualFlash(dut=self.dut, + data=0xAA, + data_size=8)) + self.simple_test() + + def test_n25q128a_quad(self): + self.cmd_dict = {"FAST_READ": 0xeb} + self.dut = SPIFlash(protocol="quad", + addr_width=24, + data_width=8, + cmd_width=8, + cmd_dict=self.cmd_dict) + self.flash = (SPIFlashFastReadTestCase. + SuperNaiveDualFlash(dut=self.dut, + data=0xAA, + data_size=8)) + self.simple_test() + + def simple_test(self): + m = Module() + m.submodules.master = self.dut + m.submodules.slave = self.flash + def process(): + yield self.dut.ack.eq(1) + yield self.dut.cmd.eq(self.cmd_dict["FAST_READ"]) + while (yield self.dut.rdy): + yield # Wait until it enters CMD state + yield self.dut.ack.eq(0) + while not (yield self.dut.r_rdy): + yield # Wait until it enters RDYWAIT state + self.assertEqual((yield self.dut.r_data), self.flash.data) + simulation_test(m, process) From 756e181df0f622d6af6d5fd8dafb6e2fed978371 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Tue, 3 Dec 2019 14:15:24 +0800 Subject: [PATCH 03/19] spiflash: implement FAST READ for Extended protocol, fix simulation --- nmigen_stdio/spiflash.py | 260 +++++++++++++++++------------ nmigen_stdio/test/test_spiflash.py | 61 +++++-- 2 files changed, 200 insertions(+), 121 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 857d3e6..97ec48d 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -1,4 +1,5 @@ from nmigen import * +from nmigen.lib.cdc import FFSynchronizer from nmigen.lib.io import Pin from nmigen.build.dsl import Subsignal, Pins from nmigen.build.plat import Platform @@ -11,9 +12,12 @@ class SPIFlash(Elaboratable): - def _check_commands(self, cmd_dict): + + def _check_cmd_dict(self, cmd_dict): cmd_names = [ - "FAST_READ" + "FAST_READ", + "WRITE_ENABLE", + "WRITE_ENHANCED_VOLATILE_CONFIG_REG" # TODO: Add more supported commands ] for cmd in cmd_dict: @@ -25,6 +29,21 @@ def _check_commands(self, cmd_dict): .format(cmd_dict[cmd], 2**self.cmd_width)) + def _check_dummy_cycles_dict(self, dummy_cycles_dict): + cmd_names = [ + "FAST_READ" + # TODO: Add more commands that have dummy cycles + ] + for cmd in dummy_cycles_dict: + if cmd not in cmd_names: + raise ValueError("Invalid SPI command {!r}; must be one of {}" + .format(cmd, cmd_names)) + for required_cmd in cmd_names: + if required_cmd not in dummy_cycles_dict and required_cmd in cmd_names: + raise KeyError("Missing dummy cycle value for SPI command {!r}" + .format(required_cmd)) + + def _format_cmd(self, cmd_value): """ Returns the values on all the DQ lines that corresponds to the command. @@ -44,8 +63,8 @@ def _format_cmd(self, cmd_value): return fcmd - def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, - dummy_cycles=15, divisor=1, device=None, pins=None): + def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, dummy_cycles_dict, + divisor=1, device=None, pins=None): if protocol not in ["extended", "dual", "quad"]: raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" .format(protocol)) @@ -53,12 +72,19 @@ def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, self.addr_width = addr_width self.data_width = data_width self.cmd_width = cmd_width + # Dict that pairs an SPI command (specific to the designated protocol) with its name if not isinstance(cmd_dict, dict): raise TypeError("Invalid command dict {!r}; must be a dict where each key-value pair is (command name, command value)" .format(cmd_dict)) - self._check_commands(cmd_dict) + self._check_cmd_dict(cmd_dict) self._cmd_dict = cmd_dict - self.dummy_cycles = dummy_cycles + # Dict that pairs the number of dummy cycles (specific to the designated protocl) with the SPI command name + if not isinstance(dummy_cycles_dict, dict): + raise TypeError("Invalid dummy cycle dict {!r}; must be a dict where each key-value pair is (command name, number of dummy cycles)" + .format(dummy_cycles_dict)) + self._check_dummy_cycles_dict(dummy_cycles_dict) + self._dummy_cycles_dict = dummy_cycles_dict + if divisor < 1: raise ValueError("Invalid divisor value; must be at least 1" .format(divisor)) @@ -137,32 +163,31 @@ def elaborate(self, platform): self._pins.clk.eq(self.clk) ] if self._protocol == "extended": - m.d.comb += [ - MultiReg(self._pins.miso.i, self.miso), - self._pins.mosi.o.eq(self.mosi) - ] + m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) + m.d.comb += self._pins.mosi.o.eq(self.mosi) elif self._protocol in ["dual", "quad"]: dq_oe = Signal() m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) - # If the user doesn't give pins, create dq Pins + # If the user doesn't give pins, create dq Pins for Dual & Quad else: - dq_oe = Signal() - if self._protocol == "dual": - dq_pins = Record([ - ("dq", Pin(width=2, dir="io", xdr=0).layout) - ]) - elif self._protocol == "quad": - dq_pins = Record([ - ("dq", Pin(width=4, dir="io", xdr=0).layout) - ]) - tristate_submodule = Module() - tristate_submodule.submodules += Instance("$tribuf", - p_WIDTH=self.dq.width, - i_EN=self.dq.oe, - i_A=Platform._invert_if(False, self.dq.o), - o_Y=dq_pins - ) - m.submodules.dq = tristate_submodule + if self._protocol in ["dual", "quad"]: + dq_oe = Signal() + if self._protocol == "dual": + dq_pins = Record([ + ("dq", Pin(width=2, dir="io", xdr=0).layout) + ]) + elif self._protocol == "quad": + dq_pins = Record([ + ("dq", Pin(width=4, dir="io", xdr=0).layout) + ]) + tristate_submodule = Module() + tristate_submodule.submodules += Instance("$tribuf", + p_WIDTH=self.dq.width, + i_EN=self.dq.oe, + i_A=Platform._invert_if(False, self.dq.o), + o_Y=dq_pins + ) + m.submodules.dq = tristate_submodule # r_data always reads from shreg m.d.comb += self.r_data.eq(shreg) @@ -175,7 +200,7 @@ def elaborate(self, platform): with m.If(counter == self._divisor_val >> 1): m.d.sync += self.clk.eq(1) if self._protocol == "extended": - pass # TODO + m.d.sync += dq_i.eq(self.miso) elif self._protocol in ["dual", "quad"]: m.d.sync += dq_i.eq(self.dq.i) ## When countdown reaches 0, clock edge goes down (negative) @@ -185,18 +210,19 @@ def elaborate(self, platform): self.clk.eq(0), self.counter.eq(self.divisor) ] - if self._protocol == "extended": - pass # TODO - elif self._protocol in ["dual", "quad"]: - m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) # "pushing" old data out from the left + m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) # "pushing" old data out from the left ## Normal countdown with m.Else(): m.d.sync += counter.eq(counter - 1) + # MOSI logic for Extended SPI protocol: + # MOSI always output the leftmost bit of shreg + if self._protocol == "extended": + m.d.comb += self.mosi.eq(shreg[-1]) # MOSI logic for Dual and Quad SPI protocols: # Whenever DQ output should be enabled, # DQ always output the leftmost `spi_width`-wide bits of shreg - if self._protocol in ["dual", "quad"]: + elif self._protocol in ["dual", "quad"]: m.d.comb += [ self.dq.o.eq(shreg[-self.spi_width:]), self.dq.oe.eq(dq_oe) @@ -204,78 +230,102 @@ def elaborate(self, platform): # Command: FAST READ if "FAST_READ" in self._cmd_dict: + # fcmd: get formatted command based on cmd_dict fcmd = self._format_cmd(self._cmd_dict["FAST_READ"]) - if self._protocol == "extended": - # TODO: implement FAST READ for Extended SPI protocol as well - pass + # dummy_cycles: get from dummy_cycles_dict + dummy_cycles = self._dummy_cycles_dict["FAST_READ"] + # addr: convert bus address to byte-sized address + byte_addr = Cat(Repl(0, log2_int(self.data_width//8)), self.addr) + # FSM + with m.FSM() as fsm: + state_durations = { + "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "FASTREAD-ADDR" : self._divisor_val*(self.addr_width//self.spi_width), + "FASTREAD-WAITREAD": self._divisor_val*(dummy_cycles+ + self.data_width//self.spi_width), + "FASTREAD-RDYWAIT" : 1+self._divisor_val + } + max_duration = max([dur for state,dur in state_durations.items()]) + # A "count-up" counter for each state of the command + state_counter = Signal(range(max_duration)) + # State: Idling + with m.State("FASTREAD-IDLE"): + m.d.comb += self.rdy.eq(1) + with m.If((self.cmd == self._cmd_dict["FAST_READ"]) & + self.ack & (counter == 0)): + m.d.sync += [ + state_counter.eq(0), + self.cs.eq(1), + shreg[-self.cmd_width:].eq(fcmd) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "FASTREAD-CMD" + # State: Command, MOSI + with m.State("FASTREAD-CMD"): + with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.addr_width:].eq(byte_addr) + ] + m.next = "FASTREAD-ADDR" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Address, MOSI + with m.State("FASTREAD-ADDR"): + with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): + m.d.sync += state_counter.eq(0) + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "FASTREAD-WAITREAD" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Dummy cycles (waiting), and then Read (MISO) + with m.State("FASTREAD-WAITREAD"): + with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.cs.eq(0), + self.r_rdy.eq(1) + ] + m.next = "FASTREAD-RDYWAIT" + with m.Else(): + 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"): + with m.If(state_counter == 0): + m.d.sync += self.r_rdy.eq(0) + # Early check to skip 1 clock period of doing nothing + with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): + m.d.sync += state_counter.eq(0) + m.next = "FASTREAD-IDLE" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) - elif self._protocol in ["dual", "quad"]: - # addr: convert bus address to byte-sized address - byte_addr = Cat(Repl(0, log2_int(self.data_width//8)), self.addr) - # FSM - with m.FSM() as fsm: - state_durations = { - "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "FASTREAD-ADDR" : self._divisor_val*(self.addr_width//self.spi_width), - "FASTREAD-WAITREAD": self._divisor_val*(self.dummy_cycles+ - self.data_width//self.spi_width), - "FASTREAD-RDYWAIT" : 1+self._divisor_val - } - max_duration = max([dur for state,dur in state_durations.items()]) - # A "count-up" counter for each state of the command - state_counter = Signal(range(max_duration)) - # State: Idling - with m.State("FASTREAD-IDLE"): - m.d.comb += self.rdy.eq(1) - with m.If((self.cmd == self._cmd_dict["FAST_READ"]) & - self.ack & (counter == 0)): - m.d.sync += [ - state_counter.eq(0), - dq_oe.eq(1), - self.cs.eq(1), - shreg[-self.cmd_width:].eq(fcmd) - ] - m.next = "FASTREAD-CMD" - # State: Command, MOSI - with m.State("FASTREAD-CMD"): - with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.addr_width:].eq(byte_addr) - ] - m.next = "FASTREAD-ADDR" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # State: Address, MOSI - with m.State("FASTREAD-ADDR"): - with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): - m.d.sync += [ - state_counter.eq(0), - dq_oe.eq(0) - ] - m.next = "FASTREAD-WAITREAD" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # State: Dummy cycles (waiting), and then Read (MISO) - with m.State("FASTREAD-WAITREAD"): - with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): - m.d.sync += [ - state_counter.eq(0), - self.cs.eq(0), - self.r_rdy.eq(1) - ] - m.next = "FASTREAD-RDYWAIT" - with m.Else(): - 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"): - with m.If(state_counter == 0): - m.d.sync += self.r_rdy.eq(0) - # Early check to skip 1 clock period of doing nothing - with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): - m.d.sync += state_counter.eq(0) - m.next = "FASTREAD-IDLE" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) + """ + # Command: WRITE ENABLE + if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in self._cmd_dict: + # fcmd: get formatted command based on cmd_dict + fcmd = self._format_cmd(self._cmd_dict["WRITE_ENHANCED_VOLATILE_CONFIG_REG"]) + # FSM + with m.FSM() as fsm: + # + with m.State("WR-EN-CMD"): + m.d.comb += self.rdy.eq(1) + with m.If + """ + + """ + # Command: WRITE ENHANCED VOLATILE CONFIGURATION REGISTER + if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in self._cmd_dict: + # fcmd: get formatted command based on cmd_dict + fcmd = self._format_cmd(self._cmd_dict["WRITE_ENHANCED_VOLATILE_CONFIG_REG"]) + # FSM + with m.FSM() as fsm: + # + with m.State("WR-EN-CMD"): + m.d.comb += self.rdy.eq(1) + with m.If + """ return m \ No newline at end of file diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index 9398ed4..789501e 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -18,11 +18,12 @@ def divisor_period(self): for _ in range((yield self.dut.divisor) + 1): yield - class SuperNaiveDualFlash(Elaboratable): - def __init__(self, *, dut, data, data_size): + class SuperNaiveFlash(Elaboratable): + def __init__(self, *, dut, data, data_size, dummy_cycles): self.dut = dut self.data = data self.data_size = data_size + self.dummy_cycles = dummy_cycles def elaborate(self, platform): m = Module() data_sig = Signal(self.data_size) @@ -32,13 +33,19 @@ def elaborate(self, platform): depth=self.dut.data_width//self.dut.spi_width) stored_data_num_sent = Signal(range(stored_data.depth+1), reset=stored_data.depth) - dummy_counter = Signal(range(self.dut.dummy_cycles), - reset=self.dut.dummy_cycles - 1) + dummy_counter = Signal(range(self.dummy_cycles), + reset=self.dummy_cycles - 1) m.submodules.recv_data = recv_data m.submodules.stored_data = stored_data - m.d.comb += recv_data.w_data.eq(self.dut.dq.o) + if self.dut.spi_width == 1: + m.d.comb += recv_data.w_data.eq(self.dut.mosi) + else: + m.d.comb += recv_data.w_data.eq(self.dut.dq.o) with m.If(stored_data.r_rdy & stored_data.r_en): - m.d.sync += self.dut.dq.i.eq(stored_data.r_data) + if self.dut.spi_width == 1: + m.d.sync += self.dut.miso.eq(stored_data.r_data) + else: + m.d.sync += self.dut.dq.i.eq(stored_data.r_data) with m.FSM() as fsm: with m.State("INIT"): m.d.sync += data_sig.eq(self.data) @@ -62,9 +69,9 @@ def elaborate(self, platform): ] with m.Else(): m.d.comb += stored_data.w_en.eq(0) - with m.If((dummy_counter != 1) & (self.dut.counter == 0)): + with m.If((dummy_counter != 0) & (self.dut.counter == 0)): m.d.sync += dummy_counter.eq(dummy_counter - 1) - with m.Elif((dummy_counter == 1) & + with m.Elif((dummy_counter == 0) & (self.dut.counter == self.dut._divisor_val>>1+1)): m.d.comb += stored_data.r_en.eq(1) m.next = "RETURN-DATA" @@ -75,30 +82,52 @@ def elaborate(self, platform): m.d.comb += stored_data.r_en.eq(0) return m + def test_n25q128a_extended(self): + self.cmd_dict = {"FAST_READ": 0x0b} + self.dummy_cycles_dict = {"FAST_READ": 8} + self.dut = SPIFlash(protocol="extended", + addr_width=24, + data_width=8, + cmd_width=8, + cmd_dict=self.cmd_dict, + dummy_cycles_dict=self.dummy_cycles_dict) + self.flash = (SPIFlashFastReadTestCase. + SuperNaiveFlash(dut=self.dut, + data=0xAA, + data_size=8, + dummy_cycles=8)) + self.simple_test() + def test_n25q128a_dual(self): self.cmd_dict = {"FAST_READ": 0xbb} + self.dummy_cycles_dict = {"FAST_READ": 8} self.dut = SPIFlash(protocol="dual", addr_width=24, data_width=8, cmd_width=8, - cmd_dict=self.cmd_dict) + cmd_dict=self.cmd_dict, + dummy_cycles_dict=self.dummy_cycles_dict) self.flash = (SPIFlashFastReadTestCase. - SuperNaiveDualFlash(dut=self.dut, - data=0xAA, - data_size=8)) + SuperNaiveFlash(dut=self.dut, + data=0xAA, + data_size=8, + dummy_cycles=8)) self.simple_test() def test_n25q128a_quad(self): self.cmd_dict = {"FAST_READ": 0xeb} + self.dummy_cycles_dict = {"FAST_READ": 10} self.dut = SPIFlash(protocol="quad", addr_width=24, data_width=8, cmd_width=8, - cmd_dict=self.cmd_dict) + cmd_dict=self.cmd_dict, + dummy_cycles_dict=self.dummy_cycles_dict) self.flash = (SPIFlashFastReadTestCase. - SuperNaiveDualFlash(dut=self.dut, - data=0xAA, - data_size=8)) + SuperNaiveFlash(dut=self.dut, + data=0xAA, + data_size=8, + dummy_cycles=10)) self.simple_test() def simple_test(self): From 15d2bd7a7e82d1906f3dde5a1af71528173b3239 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Mon, 9 Dec 2019 11:51:41 +0800 Subject: [PATCH 04/19] spiflash: make FAST-READER a standalone module, disallow custom cmd dict --- nmigen_stdio/spiflash.py | 229 ++++++++++++----------------- nmigen_stdio/test/test_spiflash.py | 84 ++++++----- 2 files changed, 139 insertions(+), 174 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 97ec48d..596def4 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -6,42 +6,19 @@ from nmigen.utils import bits_for, log2_int -__all__ = ["SPIFlash"] +__all__ = ["SPIFlashFastReader"] -class SPIFlash(Elaboratable): - - - def _check_cmd_dict(self, cmd_dict): - cmd_names = [ - "FAST_READ", - "WRITE_ENABLE", - "WRITE_ENHANCED_VOLATILE_CONFIG_REG" - # TODO: Add more supported commands - ] - for cmd in cmd_dict: - if cmd not in cmd_names: - raise ValueError("Invalid SPI command {!r}; must be one of {}" - .format(cmd, cmd_names)) - if cmd_dict[cmd] > 2**self.cmd_width: - raise ValueError("Invalid SPI command value {!r}; must not be more than {}-bit wide" - .format(cmd_dict[cmd], 2**self.cmd_width)) - - - def _check_dummy_cycles_dict(self, dummy_cycles_dict): - cmd_names = [ - "FAST_READ" - # TODO: Add more commands that have dummy cycles - ] - for cmd in dummy_cycles_dict: - if cmd not in cmd_names: - raise ValueError("Invalid SPI command {!r}; must be one of {}" - .format(cmd, cmd_names)) - for required_cmd in cmd_names: - if required_cmd not in dummy_cycles_dict and required_cmd in cmd_names: - raise KeyError("Missing dummy cycle value for SPI command {!r}" - .format(required_cmd)) +class SPIFlashFastReader(Elaboratable): + """An SPI flash controller module for fast-reading + """ + CMD_WIDTH = 8 + CMD_DICT = { + "extended": 0x0b, + "dual" : 0x3b, + "quad" : 0x6b + } def _format_cmd(self, cmd_value): @@ -56,14 +33,14 @@ def _format_cmd(self, cmd_value): 11 11 11 10 11 10 11 11 (dual I/O SPI mode) 1111 1111 1111 1110 1111 1110 1111 1111 (quad I/O SPI mode) """ - fcmd = 2**(self.cmd_width*self.spi_width) - 1 - for bit in range(self.cmd_width): + fcmd = 2**(SPIFlashFastReader.CMD_WIDTH*self.spi_width) - 1 + for bit in range(SPIFlashFastReader.CMD_WIDTH): if (cmd_value >> bit)%2 == 0: fcmd &= ~(1 << (bit*self.spi_width)) return fcmd - def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, dummy_cycles_dict, + def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, divisor=1, device=None, pins=None): if protocol not in ["extended", "dual", "quad"]: raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" @@ -71,19 +48,7 @@ def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, dum self._protocol = protocol self.addr_width = addr_width self.data_width = data_width - self.cmd_width = cmd_width - # Dict that pairs an SPI command (specific to the designated protocol) with its name - if not isinstance(cmd_dict, dict): - raise TypeError("Invalid command dict {!r}; must be a dict where each key-value pair is (command name, command value)" - .format(cmd_dict)) - self._check_cmd_dict(cmd_dict) - self._cmd_dict = cmd_dict - # Dict that pairs the number of dummy cycles (specific to the designated protocl) with the SPI command name - if not isinstance(dummy_cycles_dict, dict): - raise TypeError("Invalid dummy cycle dict {!r}; must be a dict where each key-value pair is (command name, number of dummy cycles)" - .format(dummy_cycles_dict)) - self._check_dummy_cycles_dict(dummy_cycles_dict) - self._dummy_cycles_dict = dummy_cycles_dict + self._dummy_cycles = dummy_cycles if divisor < 1: raise ValueError("Invalid divisor value; must be at least 1" @@ -122,11 +87,11 @@ def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, dum self.rdy = Signal() # Internal rdy signal, set at idle state self.ack = Signal() # External ack signal that begins a read - self.cmd = Signal(cmd_width) # set to the corresponding value of the command self.addr = Signal(addr_width) self.r_data = Signal(self.data_width) self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise + self.cmd_width = SPIFlashFastReader.CMD_WIDTH 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) @@ -134,7 +99,7 @@ def __init__(self, *, protocol, addr_width, data_width, cmd_width, cmd_dict, dum self.counter = Signal.like(self.divisor) - def _add_clk_primitive(self, module): + def _add_clk_primitive(self, platform, module): """Add a submodule whose instantiation is required by certain devices when choosing a user clock as SPI clock """ @@ -147,7 +112,7 @@ def _add_clk_primitive(self, module): if self._device == "lattice_ecp5": module.submodules += Instance("USRMCLK", i_USRMCLKI=self._pins.clk, - i_USRMCLKTS=self._pins.cs.o) + i_USRMCLKTS=0) def elaborate(self, platform): @@ -157,7 +122,7 @@ def elaborate(self, platform): counter = self.counter if self._pins is not None: - self._add_clk_primitive(m) + self._add_clk_primitive(platform, m) m.d.comb += [ self._pins.cs.o.eq(self.cs), self._pins.clk.eq(self.clk) @@ -216,9 +181,10 @@ def elaborate(self, platform): m.d.sync += counter.eq(counter - 1) # MOSI logic for Extended SPI protocol: - # MOSI always output the leftmost bit of shreg + # Whenever ACKed, MOSI always output the leftmost bit of shreg if self._protocol == "extended": - m.d.comb += self.mosi.eq(shreg[-1]) + with m.If(self.ack): + m.d.comb += self.mosi.eq(shreg[-1]) # MOSI logic for Dual and Quad SPI protocols: # Whenever DQ output should be enabled, # DQ always output the leftmost `spi_width`-wide bits of shreg @@ -228,98 +194,93 @@ def elaborate(self, platform): self.dq.oe.eq(dq_oe) ] - # Command: FAST READ - if "FAST_READ" in self._cmd_dict: - # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(self._cmd_dict["FAST_READ"]) - # dummy_cycles: get from dummy_cycles_dict - dummy_cycles = self._dummy_cycles_dict["FAST_READ"] - # addr: convert bus address to byte-sized address - byte_addr = Cat(Repl(0, log2_int(self.data_width//8)), self.addr) - # FSM - with m.FSM() as fsm: - state_durations = { - "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "FASTREAD-ADDR" : self._divisor_val*(self.addr_width//self.spi_width), - "FASTREAD-WAITREAD": self._divisor_val*(dummy_cycles+ - self.data_width//self.spi_width), - "FASTREAD-RDYWAIT" : 1+self._divisor_val - } - max_duration = max([dur for state,dur in state_durations.items()]) - # A "count-up" counter for each state of the command - state_counter = Signal(range(max_duration)) - # State: Idling - with m.State("FASTREAD-IDLE"): - m.d.comb += self.rdy.eq(1) - with m.If((self.cmd == self._cmd_dict["FAST_READ"]) & - self.ack & (counter == 0)): - m.d.sync += [ - state_counter.eq(0), - self.cs.eq(1), - shreg[-self.cmd_width:].eq(fcmd) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) - m.next = "FASTREAD-CMD" - # State: Command, MOSI - with m.State("FASTREAD-CMD"): - with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.addr_width:].eq(byte_addr) - ] - m.next = "FASTREAD-ADDR" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # State: Address, MOSI - with m.State("FASTREAD-ADDR"): - with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): - m.d.sync += state_counter.eq(0) - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) - m.next = "FASTREAD-WAITREAD" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # State: Dummy cycles (waiting), and then Read (MISO) - with m.State("FASTREAD-WAITREAD"): - with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): - m.d.sync += [ - state_counter.eq(0), - self.cs.eq(0), - self.r_rdy.eq(1) - ] - m.next = "FASTREAD-RDYWAIT" - with m.Else(): - 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"): - with m.If(state_counter == 0): - m.d.sync += self.r_rdy.eq(0) - # Early check to skip 1 clock period of doing nothing - with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): - m.d.sync += state_counter.eq(0) - m.next = "FASTREAD-IDLE" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) + # fcmd: get formatted command based on cmd_dict + fcmd = self._format_cmd(SPIFlashFastReader.CMD_DICT[self._protocol]) + # addr: convert bus address to byte-sized address + byte_addr = Cat(Repl(0, log2_int(self.data_width//8)), self.addr) + # FSM + with m.FSM() as fsm: + state_durations = { + "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "FASTREAD-ADDR" : self._divisor_val*(self.addr_width//self.spi_width), + "FASTREAD-WAITREAD": self._divisor_val*(self._dummy_cycles+ + self.data_width//self.spi_width), + "FASTREAD-RDYWAIT" : 1+self._divisor_val + } + max_duration = max([dur for state,dur in state_durations.items()]) + # A "count-up" counter for each state of the command + state_counter = Signal(range(max_duration)) + # State: Idling + with m.State("FASTREAD-IDLE"): + m.d.comb += self.rdy.eq(1) + with m.If(self.ack & (counter == 0)): + m.d.sync += [ + state_counter.eq(0), + self.cs.eq(1), + shreg[-self.cmd_width:].eq(fcmd) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "FASTREAD-CMD" + # State: Command, MOSI + with m.State("FASTREAD-CMD"): + with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.addr_width:].eq(byte_addr) + ] + m.next = "FASTREAD-ADDR" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Address, MOSI + with m.State("FASTREAD-ADDR"): + with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): + m.d.sync += state_counter.eq(0) + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "FASTREAD-WAITREAD" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Dummy cycles (waiting), and then Read (MISO) + with m.State("FASTREAD-WAITREAD"): + with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.cs.eq(0), + self.r_rdy.eq(1) + ] + m.next = "FASTREAD-RDYWAIT" + with m.Else(): + 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"): + with m.If(state_counter == 0): + m.d.sync += self.r_rdy.eq(0) + # Early check to skip 1 clock period of doing nothing + with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): + m.d.sync += state_counter.eq(0) + m.next = "FASTREAD-IDLE" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) """ + ### SCRAPPED: other commands should be implemented in a separate core + # Command: WRITE ENABLE - if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in self._cmd_dict: + if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in CMD_DICT: # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(self._cmd_dict["WRITE_ENHANCED_VOLATILE_CONFIG_REG"]) + fcmd = self._format_cmd(CMD_DICT[self._protocol]) # FSM with m.FSM() as fsm: # with m.State("WR-EN-CMD"): m.d.comb += self.rdy.eq(1) with m.If - """ - """ # Command: WRITE ENHANCED VOLATILE CONFIGURATION REGISTER - if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in self._cmd_dict: + if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in CMD_DICT: # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(self._cmd_dict["WRITE_ENHANCED_VOLATILE_CONFIG_REG"]) + fcmd = self._format_cmd(CMD_DICT[self._protocol]) # FSM with m.FSM() as fsm: # diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index 789501e..e5f5911 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -31,7 +31,7 @@ def elaborate(self, platform): depth=(self.dut.cmd_width+self.dut.addr_width)//self.dut.spi_width) stored_data = SyncFIFO(width=self.dut.spi_width, depth=self.dut.data_width//self.dut.spi_width) - stored_data_num_sent = Signal(range(stored_data.depth+1), + stored_data_num_left = Signal(range(stored_data.depth+1), reset=stored_data.depth) dummy_counter = Signal(range(self.dummy_cycles), reset=self.dummy_cycles - 1) @@ -46,6 +46,11 @@ def elaborate(self, platform): m.d.sync += self.dut.miso.eq(stored_data.r_data) else: m.d.sync += self.dut.dq.i.eq(stored_data.r_data) + with m.Else(): + if self.dut.spi_width == 1: + m.d.sync += self.dut.miso.eq(0) + else: + m.d.sync += self.dut.dq.i.eq(0) with m.FSM() as fsm: with m.State("INIT"): m.d.sync += data_sig.eq(self.data) @@ -58,13 +63,13 @@ def elaborate(self, platform): with m.If(~recv_data.w_rdy & stored_data.w_rdy): m.next = "PUT-DATA" with m.State("PUT-DATA"): - with m.If(stored_data_num_sent != 0): + with m.If(stored_data_num_left != 0): m.d.comb += [ stored_data.w_en.eq(1), stored_data.w_data.eq(data_sig[-self.dut.spi_width:]) ] m.d.sync += [ - stored_data_num_sent.eq(stored_data_num_sent+1), + stored_data_num_left.eq(stored_data_num_left-1), data_sig.eq(Cat(Repl(0, self.dut.spi_width), data_sig[:-self.dut.spi_width])) ] with m.Else(): @@ -80,54 +85,54 @@ def elaborate(self, platform): m.d.comb += stored_data.r_en.eq(1) with m.Else(): m.d.comb += stored_data.r_en.eq(0) + # Keep getting data to the FIFO while returning bytes out of it + with m.If(stored_data_num_left != 0): + m.d.comb += [ + stored_data.w_en.eq(1), + stored_data.w_data.eq(data_sig[-self.dut.spi_width:]) + ] + m.d.sync += [ + stored_data_num_left.eq(stored_data_num_left-1), + data_sig.eq(Cat(Repl(0, self.dut.spi_width), data_sig[:-self.dut.spi_width])) + ] + with m.Else(): + m.d.comb += stored_data.w_en.eq(0) return m - def test_n25q128a_extended(self): - self.cmd_dict = {"FAST_READ": 0x0b} - self.dummy_cycles_dict = {"FAST_READ": 8} - self.dut = SPIFlash(protocol="extended", - addr_width=24, - data_width=8, - cmd_width=8, - cmd_dict=self.cmd_dict, - dummy_cycles_dict=self.dummy_cycles_dict) + def test_extended(self): + self.dut = SPIFlashFastReader(protocol="extended", + addr_width=24, + data_width=32, + dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, - data=0xAA, - data_size=8, - dummy_cycles=8)) + data=0xAABBCCDD, + data_size=32, + dummy_cycles=15)) self.simple_test() - def test_n25q128a_dual(self): - self.cmd_dict = {"FAST_READ": 0xbb} - self.dummy_cycles_dict = {"FAST_READ": 8} - self.dut = SPIFlash(protocol="dual", - addr_width=24, - data_width=8, - cmd_width=8, - cmd_dict=self.cmd_dict, - dummy_cycles_dict=self.dummy_cycles_dict) + def test_dual(self): + self.dut = SPIFlashFastReader(protocol="dual", + addr_width=24, + data_width=32, + dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, - data=0xAA, - data_size=8, - dummy_cycles=8)) + data=0xAABBCCDD, + data_size=32, + dummy_cycles=15)) self.simple_test() - def test_n25q128a_quad(self): - self.cmd_dict = {"FAST_READ": 0xeb} - self.dummy_cycles_dict = {"FAST_READ": 10} - self.dut = SPIFlash(protocol="quad", - addr_width=24, - data_width=8, - cmd_width=8, - cmd_dict=self.cmd_dict, - dummy_cycles_dict=self.dummy_cycles_dict) + def test_quad(self): + self.dut = SPIFlashFastReader(protocol="quad", + addr_width=24, + data_width=32, + dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, - data=0xAA, - data_size=8, - dummy_cycles=10)) + data=0xAABBCCDD, + data_size=32, + dummy_cycles=15)) self.simple_test() def simple_test(self): @@ -136,7 +141,6 @@ def simple_test(self): m.submodules.slave = self.flash def process(): yield self.dut.ack.eq(1) - yield self.dut.cmd.eq(self.cmd_dict["FAST_READ"]) while (yield self.dut.rdy): yield # Wait until it enters CMD state yield self.dut.ack.eq(0) From c376830bb2e22ccb7d93c7e8d092eecb0cbaaefd Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Tue, 10 Dec 2019 16:05:32 +0800 Subject: [PATCH 05/19] spiflash: fix CLK, missing WP & HOLD, & MOSI logic for extended SPI --- nmigen_stdio/spiflash.py | 30 +++++++++++++++--------- nmigen_stdio/test/test_spiflash.py | 37 +++++++++++++++++------------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 596def4..bbd059a 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -79,7 +79,6 @@ def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, if protocol == "extended": self.mosi = Signal() self.miso = Signal() - # TODO: Add wp & hold pins elif protocol == "dual": self.dq = Pin(2, "io") elif protocol == "quad": @@ -101,7 +100,10 @@ def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, def _add_clk_primitive(self, platform, module): """Add a submodule whose instantiation is required by certain devices - when choosing a user clock as SPI clock + when choosing a user clock as SPI clock. + + Note that the CLK pin is not connected outside of this function, + as certain devices does not need to request the CLK pin. """ # Lattice ECP5: # "The ECP5 and ECP5-5G devices provide a solution for users @@ -110,8 +112,10 @@ def _add_clk_primitive(self, platform, module): # (see Section 6.1.2 of FPGA-TN-02039-1.7, # "ECP5 and ECP5-5G sysCONFIG Usage Guide Technical Note") if self._device == "lattice_ecp5": + usrmclk = Signal.like(self.clk) + module.d.comb += usrmclk.eq(self.clk) module.submodules += Instance("USRMCLK", - i_USRMCLKI=self._pins.clk, + i_USRMCLKI=usrmclk, i_USRMCLKTS=0) @@ -125,7 +129,8 @@ def elaborate(self, platform): self._add_clk_primitive(platform, m) m.d.comb += [ self._pins.cs.o.eq(self.cs), - self._pins.clk.eq(self.clk) + self._pins.wp.eq(1), + self._pins.hold.eq(1) ] if self._protocol == "extended": m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) @@ -173,18 +178,18 @@ def elaborate(self, platform): with m.If(counter == 0): m.d.sync += [ self.clk.eq(0), - self.counter.eq(self.divisor) + counter.eq(self.divisor) ] - m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) # "pushing" old data out from the left + # MOSI latches from shreg by "pushing" old data out from the left of shreg + m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) ## Normal countdown with m.Else(): m.d.sync += counter.eq(counter - 1) # MOSI logic for Extended SPI protocol: - # Whenever ACKed, MOSI always output the leftmost bit of shreg + # MOSI always output the leftmost bit of shreg if self._protocol == "extended": - with m.If(self.ack): - m.d.comb += self.mosi.eq(shreg[-1]) + m.d.comb += self.mosi.eq(shreg[-1]) # MOSI logic for Dual and Quad SPI protocols: # Whenever DQ output should be enabled, # DQ always output the leftmost `spi_width`-wide bits of shreg @@ -216,7 +221,6 @@ def elaborate(self, platform): with m.If(self.ack & (counter == 0)): m.d.sync += [ state_counter.eq(0), - self.cs.eq(1), shreg[-self.cmd_width:].eq(fcmd) ] if self._protocol in ["dual", "quad"]: @@ -246,7 +250,6 @@ def elaborate(self, platform): with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): m.d.sync += [ state_counter.eq(0), - self.cs.eq(0), self.r_rdy.eq(1) ] m.next = "FASTREAD-RDYWAIT" @@ -262,6 +265,11 @@ def elaborate(self, platform): m.next = "FASTREAD-IDLE" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) + # + with m.If(~fsm.ongoing("FASTREAD-IDLE") & ~fsm.ongoing("FASTREAD-RDYWAIT")): + m.d.comb += self.cs.eq(1) + with m.Else(): + m.d.comb += self.cs.eq(0) """ ### SCRAPPED: other commands should be implemented in a separate core diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index e5f5911..c2e4e82 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -41,26 +41,15 @@ def elaborate(self, platform): m.d.comb += recv_data.w_data.eq(self.dut.mosi) else: m.d.comb += recv_data.w_data.eq(self.dut.dq.o) - with m.If(stored_data.r_rdy & stored_data.r_en): - if self.dut.spi_width == 1: - m.d.sync += self.dut.miso.eq(stored_data.r_data) - else: - m.d.sync += self.dut.dq.i.eq(stored_data.r_data) - with m.Else(): - if self.dut.spi_width == 1: - m.d.sync += self.dut.miso.eq(0) - else: - m.d.sync += self.dut.dq.i.eq(0) with m.FSM() as fsm: with m.State("INIT"): m.d.sync += data_sig.eq(self.data) with m.If(self.dut.cs): - # Set w_en 1 clock earlier - with m.If(self.dut.counter == 1): + with m.If(self.dut.counter == self.dut._divisor_val >> 1): m.d.comb += recv_data.w_en.eq(1) with m.Else(): m.d.comb += recv_data.w_en.eq(0) - with m.If(~recv_data.w_rdy & stored_data.w_rdy): + with m.If(~recv_data.w_rdy & stored_data.w_rdy & (self.dut.counter == 0)): m.next = "PUT-DATA" with m.State("PUT-DATA"): with m.If(stored_data_num_left != 0): @@ -76,12 +65,11 @@ def elaborate(self, platform): m.d.comb += stored_data.w_en.eq(0) with m.If((dummy_counter != 0) & (self.dut.counter == 0)): m.d.sync += dummy_counter.eq(dummy_counter - 1) - with m.Elif((dummy_counter == 0) & - (self.dut.counter == self.dut._divisor_val>>1+1)): + with m.Elif((dummy_counter == 0) & (self.dut.counter == 0)): m.d.comb += stored_data.r_en.eq(1) m.next = "RETURN-DATA" with m.State("RETURN-DATA"): - with m.If(self.dut.counter == self.dut._divisor_val>>1+1): + with m.If(self.dut.counter == 0): m.d.comb += stored_data.r_en.eq(1) with m.Else(): m.d.comb += stored_data.r_en.eq(0) @@ -97,12 +85,23 @@ def elaborate(self, platform): ] with m.Else(): m.d.comb += stored_data.w_en.eq(0) + with m.If(stored_data.r_rdy & stored_data.r_en): + if self.dut.spi_width == 1: + m.d.sync += self.dut.miso.eq(stored_data.r_data) + else: + m.d.sync += self.dut.dq.i.eq(stored_data.r_data) + with m.Elif(~fsm.ongoing("RETURN-DATA")): + if self.dut.spi_width == 1: + m.d.sync += self.dut.miso.eq(0) + else: + m.d.sync += self.dut.dq.i.eq(0) return m def test_extended(self): self.dut = SPIFlashFastReader(protocol="extended", addr_width=24, data_width=32, + divisor=49, dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, @@ -115,6 +114,7 @@ def test_dual(self): self.dut = SPIFlashFastReader(protocol="dual", addr_width=24, data_width=32, + divisor=49, dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, @@ -127,6 +127,7 @@ def test_quad(self): self.dut = SPIFlashFastReader(protocol="quad", addr_width=24, data_width=32, + divisor=49, dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, @@ -147,4 +148,8 @@ def process(): while not (yield self.dut.r_rdy): yield # Wait until it enters RDYWAIT state self.assertEqual((yield self.dut.r_data), self.flash.data) + # simulate continuous reading, informally + yield self.dut.ack.eq(1) + for _ in range(10*self.dut._divisor_val): + yield simulation_test(m, process) From 40ff0b5477d536f4b038e7caaaa6fb18ab44eddd Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Wed, 11 Dec 2019 16:58:10 +0800 Subject: [PATCH 06/19] spiflash: add delay between ACKed and CS, start CLK only when CSed * assuming CPOL=1 & CPHA=1, i.e. CLK goes positive as CS goes positive --- nmigen_stdio/spiflash.py | 49 +++++++++++++++++++----------- nmigen_stdio/test/test_spiflash.py | 4 +-- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index bbd059a..7e5b632 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -46,8 +46,8 @@ def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" .format(protocol)) self._protocol = protocol - self.addr_width = addr_width - self.data_width = data_width + self._addr_width = addr_width + self._data_width = data_width self._dummy_cycles = dummy_cycles if divisor < 1: @@ -87,14 +87,14 @@ def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, self.rdy = Signal() # Internal rdy signal, set at idle state self.ack = Signal() # External ack signal that begins a read self.addr = Signal(addr_width) - self.r_data = Signal(self.data_width) + self.r_data = Signal(self._data_width) self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise self.cmd_width = SPIFlashFastReader.CMD_WIDTH - self.fcmd_width = self.cmd_width * self.spi_width + 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.addr_width, self.data_width)) + self.shreg = Signal(max(self._fcmd_width, self._addr_width, self._data_width, self._dummy_cycles)) self.counter = Signal.like(self.divisor) @@ -167,7 +167,7 @@ def elaborate(self, platform): 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 m.If(counter == self._divisor_val >> 1): + with m.If((counter == self._divisor_val >> 1) & self.cs): m.d.sync += self.clk.eq(1) if self._protocol == "extended": m.d.sync += dq_i.eq(self.miso) @@ -175,7 +175,7 @@ def elaborate(self, platform): m.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 m.If(counter == 0): + with m.If((counter == 0) & self.cs): m.d.sync += [ self.clk.eq(0), counter.eq(self.divisor) @@ -183,7 +183,7 @@ def elaborate(self, platform): # MOSI latches from shreg by "pushing" old data out from the left of shreg m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) ## Normal countdown - with m.Else(): + with m.Elif(self.cs): m.d.sync += counter.eq(counter - 1) # MOSI logic for Extended SPI protocol: @@ -202,14 +202,14 @@ def elaborate(self, platform): # fcmd: get formatted command based on cmd_dict fcmd = self._format_cmd(SPIFlashFastReader.CMD_DICT[self._protocol]) # 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//8)), self.addr) # FSM with m.FSM() as fsm: state_durations = { "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "FASTREAD-ADDR" : self._divisor_val*(self.addr_width//self.spi_width), + "FASTREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), "FASTREAD-WAITREAD": self._divisor_val*(self._dummy_cycles+ - self.data_width//self.spi_width), + self._data_width//self.spi_width), "FASTREAD-RDYWAIT" : 1+self._divisor_val } max_duration = max([dur for state,dur in state_durations.items()]) @@ -218,7 +218,15 @@ def elaborate(self, platform): # State: Idling with m.State("FASTREAD-IDLE"): m.d.comb += self.rdy.eq(1) - with m.If(self.ack & (counter == 0)): + with m.If(self.ack): + m.d.sync += [ + counter.eq((self._divisor_val >> 1) - 1), + self.clk.eq(1) + ] + m.next = "FASTREAD-CS" + # State: Chip select + with m.State("FASTREAD-CS"): + with m.If(counter == 0): m.d.sync += [ state_counter.eq(0), shreg[-self.cmd_width:].eq(fcmd) @@ -231,7 +239,7 @@ def elaborate(self, platform): with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): m.d.sync += [ state_counter.eq(0), - shreg[-self.addr_width:].eq(byte_addr) + shreg[-self._addr_width:].eq(byte_addr) ] m.next = "FASTREAD-ADDR" with m.Else(): @@ -239,9 +247,13 @@ def elaborate(self, platform): # State: Address, MOSI with m.State("FASTREAD-ADDR"): with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): - m.d.sync += state_counter.eq(0) - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) + #m.d.sync += state_counter.eq(0) + #if self._protocol in ["dual", "quad"]: + # m.d.sync += dq_oe.eq(0) + m.d.sync += [ + state_counter.eq(0), + shreg[-self._dummy_cycles:].eq(Repl(0, self._dummy_cycles)) + ] m.next = "FASTREAD-WAITREAD" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) @@ -252,6 +264,8 @@ def elaborate(self, platform): state_counter.eq(0), self.r_rdy.eq(1) ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) m.next = "FASTREAD-RDYWAIT" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) @@ -266,7 +280,8 @@ def elaborate(self, platform): with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) # - with m.If(~fsm.ongoing("FASTREAD-IDLE") & ~fsm.ongoing("FASTREAD-RDYWAIT")): + with m.If(~fsm.ongoing("FASTREAD-IDLE") & + ~fsm.ongoing("FASTREAD-RDYWAIT")): m.d.comb += self.cs.eq(1) with m.Else(): m.d.comb += self.cs.eq(0) diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index c2e4e82..ed94aeb 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -28,9 +28,9 @@ def elaborate(self, platform): m = Module() data_sig = Signal(self.data_size) recv_data = SyncFIFO(width=self.dut.spi_width, - depth=(self.dut.cmd_width+self.dut.addr_width)//self.dut.spi_width) + depth=(self.dut.cmd_width+self.dut._addr_width)//self.dut.spi_width) stored_data = SyncFIFO(width=self.dut.spi_width, - depth=self.dut.data_width//self.dut.spi_width) + depth=self.dut._data_width//self.dut.spi_width) stored_data_num_left = Signal(range(stored_data.depth+1), reset=stored_data.depth) dummy_counter = Signal(range(self.dummy_cycles), From c8e2813960d094853c777ae1ecf2efcc59407e26 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Thu, 12 Dec 2019 10:14:07 +0800 Subject: [PATCH 07/19] spiflash: implement normal READ & READ ID protocol for testing --- nmigen_stdio/spiflash.py | 651 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 650 insertions(+), 1 deletion(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 7e5b632..f180f4d 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -312,4 +312,653 @@ def elaborate(self, platform): with m.If """ - return m \ No newline at end of file + return m + + + +# TODO: Either delete this, or make a base class for all SPI controllers +class SPIFlashSlowReader(Elaboratable): + """An SPI flash controller module for slow-reading (reading 1 byte at a time) + """ + CMD_WIDTH = 8 + CMD_DICT = { + "extended": 0x03, + "dual" : 0x03, + "quad" : 0x03 + } + RESET_EN_CMD = 0x66 + RESET_MEM_CMD = 0x99 + + + def _format_cmd(self, cmd_value): + fcmd = 2**(SPIFlashSlowReader.CMD_WIDTH*self.spi_width) - 1 + for bit in range(SPIFlashSlowReader.CMD_WIDTH): + if (cmd_value >> bit)%2 == 0: + fcmd &= ~(1 << (bit*self.spi_width)) + return fcmd + + + def __init__(self, *, protocol, addr_width, data_width, + divisor=1, device=None, pins=None): + if protocol not in ["extended", "dual", "quad"]: + raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" + .format(protocol)) + self._protocol = protocol + self._addr_width = addr_width + self._data_width = data_width + + if divisor < 1: + raise ValueError("Invalid divisor value; must be at least 1" + .format(divisor)) + self.divisor = Signal(bits_for(divisor), reset=divisor) + self._divisor_val = divisor + 1 + + supported_devices = ["lattice_ecp5"] + if device is not None and device not in supported_devices: + raise ValueError("Invalid FPGA device name {!r}; must be one of {}" + .format(device, supported_devices)) + self._device = device + if self._device is not None and pins is None: + raise ValueError("Pins parameter is missing for this FPGA device {}" + .format(self._device)) + self._pins = pins + + if self._protocol == "extended": + self.spi_width = 1 + elif self._protocol == "dual": + self.spi_width = 2 + elif self._protocol == "quad": + self.spi_width = 4 + + self.cs = Signal(reset=0) # Equivalent to ~CS_N + self.clk = Signal() + + if protocol == "extended": + self.mosi = Signal() + self.miso = Signal() + elif protocol == "dual": + self.dq = Pin(2, "io") + elif protocol == "quad": + self.dq = Pin(4, "io") + + self.rdy = Signal() # Internal rdy signal, set at idle state + self.ack = Signal() # External ack signal that begins a read + self.addr = Signal(addr_width) + self.r_data = Signal(self._data_width) + self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise + + self.cmd_width = SPIFlashFastReader.CMD_WIDTH + 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._addr_width, self._data_width)) + self.counter = Signal.like(self.divisor) + + + def _add_clk_primitive(self, platform, module): + if self._device == "lattice_ecp5": + usrmclk = Signal.like(self.clk) + module.d.comb += usrmclk.eq(self.clk) + module.submodules += Instance("USRMCLK", + i_USRMCLKI=usrmclk, + i_USRMCLKTS=0) + + + def elaborate(self, platform): + m = Module() + + shreg = self.shreg + counter = self.counter + + if self._pins is not None: + self._add_clk_primitive(platform, m) + m.d.comb += [ + self._pins.cs.o.eq(self.cs), + self._pins.wp.eq(0), + self._pins.hold.eq(0) + ] + if self._protocol == "extended": + m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) + m.d.comb += self._pins.mosi.o.eq(self.mosi) + elif self._protocol in ["dual", "quad"]: + dq_oe = Signal() + m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) + # If the user doesn't give pins, create dq Pins for Dual & Quad + else: + if self._protocol in ["dual", "quad"]: + dq_oe = Signal() + if self._protocol == "dual": + dq_pins = Record([ + ("dq", Pin(width=2, dir="io", xdr=0).layout) + ]) + elif self._protocol == "quad": + dq_pins = Record([ + ("dq", Pin(width=4, dir="io", xdr=0).layout) + ]) + tristate_submodule = Module() + tristate_submodule.submodules += Instance("$tribuf", + p_WIDTH=self.dq.width, + i_EN=self.dq.oe, + i_A=Platform._invert_if(False, self.dq.o), + o_Y=dq_pins + ) + m.submodules.dq = tristate_submodule + + # r_data always reads from shreg + m.d.comb += self.r_data.eq(shreg) + + # 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 m.If((counter == self._divisor_val >> 1) & self.cs): + m.d.sync += self.clk.eq(1) + if self._protocol == "extended": + m.d.sync += dq_i.eq(self.miso) + elif self._protocol in ["dual", "quad"]: + m.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 m.If((counter == 0) & self.cs): + m.d.sync += [ + self.clk.eq(0), + counter.eq(self.divisor) + ] + # MOSI latches from shreg by "pushing" old data out from the left of shreg + m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) + ## Normal countdown + with m.Elif(self.cs): + m.d.sync += counter.eq(counter - 1) + + # MOSI logic for Extended SPI protocol: + # MOSI always output the leftmost bit of shreg + if self._protocol == "extended": + m.d.comb += self.mosi.eq(shreg[-1]) + # MOSI logic for Dual and Quad SPI protocols: + # Whenever DQ output should be enabled, + # DQ always output the leftmost `spi_width`-wide bits of shreg + elif self._protocol in ["dual", "quad"]: + m.d.comb += [ + self.dq.o.eq(shreg[-self.spi_width:]), + self.dq.oe.eq(dq_oe) + ] + + # fcmd: get formatted command based on cmd_dict + fcmd = self._format_cmd(SPIFlashSlowReader.CMD_DICT[self._protocol]) + # Get formatted RESET ENABLE & RESET MEMORY commands + fcmd_reset_en = self._format_cmd(SPIFlashSlowReader.RESET_EN_CMD) + fcmd_reset_mem = self._format_cmd(SPIFlashSlowReader.RESET_MEM_CMD) + # addr: convert bus address to byte-sized address + byte_addr = Cat(Repl(0, log2_int(self._data_width//8)), self.addr) + # FSM + with m.FSM() as fsm: + state_durations = { + "RESET-ENCMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "RESET-ENWAIT" : 1+self._divisor_val, + "RESET-MEMCMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "RESET-MEMWAIT" : 1+self._divisor_val, + "SLOWREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "SLOWREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), + "SLOWREAD-READ" : self._divisor_val*(self._data_width//self.spi_width), + "SLOWREAD-RDYWAIT" : 1+self._divisor_val + } + max_duration = max([dur for state,dur in state_durations.items()]) + # A "count-up" counter for each state of the command + state_counter = Signal(range(max_duration)) + # + with m.State("SLOWREAD-INIT"): + m.d.comb += self.rdy.eq(1) + with m.If(self.ack): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "RESET-ENCS" + # + with m.State("RESET-ENCS"): + with m.If(counter == self._divisor_val >> 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.cmd_width:].eq(fcmd_reset_en) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "RESET-ENCMD" + # + with m.State("RESET-ENCMD"): + with m.If(state_counter == state_durations["RESET-ENCMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.clk.eq(0) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "RESET-ENWAIT" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.State("RESET-ENWAIT"): + with m.If(state_counter == state_durations["RESET-ENWAIT"] - 1): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "RESET-MEMCS" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.State("RESET-MEMCS"): + with m.If(counter == self._divisor_val >> 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.cmd_width:].eq(fcmd_reset_mem) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "RESET-MEMCMD" + # + with m.State("RESET-MEMCMD"): + with m.If(state_counter == state_durations["RESET-MEMCMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.clk.eq(0) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "RESET-MEMWAIT" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.State("RESET-MEMWAIT"): + with m.If(state_counter == state_durations["RESET-MEMWAIT"] - 1): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "SLOWREAD-CS" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Chip select + with m.State("SLOWREAD-CS"): + with m.If(counter == self._divisor_val >> 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.cmd_width:].eq(fcmd) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "SLOWREAD-CMD" + # State: Command, MOSI + with m.State("SLOWREAD-CMD"): + with m.If(state_counter == state_durations["SLOWREAD-CMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self._addr_width:].eq(byte_addr) + ] + m.next = "SLOWREAD-ADDR" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Address, MOSI + with m.State("SLOWREAD-ADDR"): + with m.If(state_counter == state_durations["SLOWREAD-ADDR"] - 1): + m.d.sync += state_counter.eq(0) + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "SLOWREAD-READ" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Dummy cycles (waiting), and then Read (MISO) + with m.State("SLOWREAD-READ"): + with m.If(state_counter == state_durations["SLOWREAD-READ"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.r_rdy.eq(1) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "SLOWREAD-RDYWAIT" + with m.Else(): + 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"): + with m.If(state_counter == 0): + m.d.sync += self.r_rdy.eq(0) + # Early check to skip 1 clock period of doing nothing + with m.If(state_counter == state_durations["SLOWREAD-RDYWAIT"] - 2): + m.d.sync += state_counter.eq(0) + m.next = "SLOWREAD-IDLE" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Idling + with m.State("SLOWREAD-IDLE"): + m.d.comb += self.rdy.eq(1) + with m.If(self.ack): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "SLOWREAD-CS" + # + with m.If(~fsm.ongoing("SLOWREAD-INIT") & + ~fsm.ongoing("SLOWREAD-IDLE") & + ~fsm.ongoing("RESET-ENWAIT") & + ~fsm.ongoing("RESET-MEMWAIT") & + ~fsm.ongoing("SLOWREAD-RDYWAIT")): + m.d.comb += self.cs.eq(1) + with m.Else(): + m.d.comb += self.cs.eq(0) + + return m + + + +# TODO: Either delete this, or make a base class for all SPI controllers +class SPIFlashReadID(Elaboratable): + """An SPI flash controller module for issuing the READ ID command + """ + CMD_WIDTH = 8 + CMD_DICT = { + "extended": 0x9E, + "dual" : 0xAF, + "quad" : 0xAF + } + RESET_EN_CMD = 0x66 + RESET_MEM_CMD = 0x99 + + + def _format_cmd(self, cmd_value): + fcmd = 2**(SPIFlashReadID.CMD_WIDTH*self.spi_width) - 1 + for bit in range(SPIFlashReadID.CMD_WIDTH): + if (cmd_value >> bit)%2 == 0: + fcmd &= ~(1 << (bit*self.spi_width)) + return fcmd + + + def __init__(self, *, protocol, data_width, + divisor=1, device=None, pins=None): + if protocol not in ["extended", "dual", "quad"]: + raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" + .format(protocol)) + self._protocol = protocol + self._data_width = data_width + + if divisor < 1: + raise ValueError("Invalid divisor value; must be at least 1" + .format(divisor)) + self.divisor = Signal(bits_for(divisor), reset=divisor) + self._divisor_val = divisor + 1 + + supported_devices = ["lattice_ecp5"] + if device is not None and device not in supported_devices: + raise ValueError("Invalid FPGA device name {!r}; must be one of {}" + .format(device, supported_devices)) + self._device = device + if self._device is not None and pins is None: + raise ValueError("Pins parameter is missing for this FPGA device {}" + .format(self._device)) + self._pins = pins + + if self._protocol == "extended": + self.spi_width = 1 + elif self._protocol == "dual": + self.spi_width = 2 + elif self._protocol == "quad": + self.spi_width = 4 + + self.cs = Signal(reset=0) # Equivalent to ~CS_N + self.clk = Signal() + + if protocol == "extended": + self.mosi = Signal() + self.miso = Signal() + elif protocol == "dual": + self.dq = Pin(2, "io") + elif protocol == "quad": + self.dq = Pin(4, "io") + + self.rdy = Signal() # Internal rdy signal, set at idle state + self.ack = Signal() # External ack signal that begins a read + self.r_data = Signal(self._data_width) + self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise + + self.cmd_width = SPIFlashFastReader.CMD_WIDTH + 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.counter = Signal.like(self.divisor) + + + def _add_clk_primitive(self, platform, module): + if self._device == "lattice_ecp5": + usrmclk = Signal.like(self.clk) + module.d.comb += usrmclk.eq(self.clk) + module.submodules += Instance("USRMCLK", + i_USRMCLKI=usrmclk, + i_USRMCLKTS=0) + + + def elaborate(self, platform): + m = Module() + + shreg = self.shreg + counter = self.counter + + if self._pins is not None: + self._add_clk_primitive(platform, m) + m.d.comb += [ + self._pins.cs.o.eq(self.cs), + self._pins.wp.eq(0), + self._pins.hold.eq(0) + ] + if self._protocol == "extended": + m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) + m.d.comb += self._pins.mosi.o.eq(self.mosi) + elif self._protocol in ["dual", "quad"]: + dq_oe = Signal() + m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) + # If the user doesn't give pins, create dq Pins for Dual & Quad + else: + if self._protocol in ["dual", "quad"]: + dq_oe = Signal() + if self._protocol == "dual": + dq_pins = Record([ + ("dq", Pin(width=2, dir="io", xdr=0).layout) + ]) + elif self._protocol == "quad": + dq_pins = Record([ + ("dq", Pin(width=4, dir="io", xdr=0).layout) + ]) + tristate_submodule = Module() + tristate_submodule.submodules += Instance("$tribuf", + p_WIDTH=self.dq.width, + i_EN=self.dq.oe, + i_A=Platform._invert_if(False, self.dq.o), + o_Y=dq_pins + ) + m.submodules.dq = tristate_submodule + + # r_data always reads from shreg + m.d.comb += self.r_data.eq(shreg) + + # 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 m.If((counter == self._divisor_val >> 1) & self.cs): + m.d.sync += self.clk.eq(1) + if self._protocol == "extended": + m.d.sync += dq_i.eq(self.miso) + elif self._protocol in ["dual", "quad"]: + m.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 m.If((counter == 0) & self.cs): + m.d.sync += [ + self.clk.eq(0), + counter.eq(self.divisor) + ] + # MOSI latches from shreg by "pushing" old data out from the left of shreg + m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) + ## Normal countdown + with m.Elif(self.cs): + m.d.sync += counter.eq(counter - 1) + + # MOSI logic for Extended SPI protocol: + # MOSI always output the leftmost bit of shreg + if self._protocol == "extended": + m.d.comb += self.mosi.eq(shreg[-1]) + # MOSI logic for Dual and Quad SPI protocols: + # Whenever DQ output should be enabled, + # DQ always output the leftmost `spi_width`-wide bits of shreg + elif self._protocol in ["dual", "quad"]: + m.d.comb += [ + self.dq.o.eq(shreg[-self.spi_width:]), + self.dq.oe.eq(dq_oe) + ] + + # fcmd: get formatted command based on cmd_dict + fcmd = self._format_cmd(SPIFlashReadID.CMD_DICT[self._protocol]) + # Get formatted RESET ENABLE & RESET MEMORY commands + fcmd_reset_en = self._format_cmd(SPIFlashReadID.RESET_EN_CMD) + fcmd_reset_mem = self._format_cmd(SPIFlashReadID.RESET_MEM_CMD) + # FSM + with m.FSM() as fsm: + state_durations = { + "RESET-ENCMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "RESET-ENWAIT" : 1+self._divisor_val, + "RESET-MEMCMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "RESET-MEMWAIT" : 1+self._divisor_val, + "READID-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "READID-READ" : self._divisor_val*(self._data_width//self.spi_width), + "READID-RDYWAIT" : 1+self._divisor_val + } + max_duration = max([dur for state,dur in state_durations.items()]) + # A "count-up" counter for each state of the command + state_counter = Signal(range(max_duration)) + # State: Idling + with m.State("READID-IDLE"): + m.d.comb += self.rdy.eq(1) + with m.If(self.ack): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "RESET-ENCS" + # + with m.State("RESET-ENCS"): + with m.If(counter == self._divisor_val >> 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.cmd_width:].eq(fcmd_reset_en) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "RESET-ENCMD" + # + with m.State("RESET-ENCMD"): + with m.If(state_counter == state_durations["RESET-ENCMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.clk.eq(0) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "RESET-ENWAIT" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.State("RESET-ENWAIT"): + with m.If(state_counter == state_durations["RESET-ENWAIT"] - 1): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "RESET-MEMCS" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.State("RESET-MEMCS"): + with m.If(counter == self._divisor_val >> 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.cmd_width:].eq(fcmd_reset_mem) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "RESET-MEMCMD" + # + with m.State("RESET-MEMCMD"): + with m.If(state_counter == state_durations["RESET-MEMCMD"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.clk.eq(0) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "RESET-MEMWAIT" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.State("RESET-MEMWAIT"): + with m.If(state_counter == state_durations["RESET-MEMWAIT"] - 1): + m.d.sync += [ + counter.eq(self._divisor_val), + self.clk.eq(0) + ] + m.next = "READID-CS" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Chip select + with m.State("READID-CS"): + with m.If(counter == self._divisor_val >> 1): + m.d.sync += [ + state_counter.eq(0), + shreg[-self.cmd_width:].eq(fcmd) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(1) + m.next = "READID-CMD" + # State: Command, MOSI + with m.State("READID-CMD"): + with m.If(state_counter == state_durations["READID-CMD"] - 1): + m.d.sync += state_counter.eq(0) + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "READID-READ" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Dummy cycles (waiting), and then Read (MISO) + with m.State("READID-READ"): + with m.If(state_counter == state_durations["READID-READ"] - 1): + m.d.sync += [ + state_counter.eq(0), + self.r_rdy.eq(1) + ] + if self._protocol in ["dual", "quad"]: + m.d.sync += dq_oe.eq(0) + m.next = "READID-RDYWAIT" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # State: Send r_rdy (for 1 clock period), and then Wait + with m.State("READID-RDYWAIT"): + with m.If(state_counter == 0): + m.d.sync += self.r_rdy.eq(0) + # Early check to skip 1 clock period of doing nothing + with m.If(state_counter == state_durations["READID-RDYWAIT"] - 2): + m.d.sync += state_counter.eq(0) + m.next = "READID-IDLE" + with m.Else(): + m.d.sync += state_counter.eq(state_counter + 1) + # + with m.If(~fsm.ongoing("READID-IDLE") & + ~fsm.ongoing("RESET-ENWAIT") & + ~fsm.ongoing("RESET-MEMWAIT") & + ~fsm.ongoing("READID-RDYWAIT")): + m.d.comb += self.cs.eq(1) + with m.Else(): + m.d.comb += self.cs.eq(0) + + return m From c106ef8526ea077689263d98d4d5ef8545ed66a3 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Thu, 12 Dec 2019 17:04:08 +0800 Subject: [PATCH 08/19] spiflash: adopt CPOL=0,CPHA=0, remove READ ID, add Reader base class * Also removed RESET ENABLE / RESET MEMORY commands --- nmigen_stdio/spiflash.py | 745 ++++++--------------------------------- 1 file changed, 103 insertions(+), 642 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index f180f4d..f0a469a 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -6,22 +6,21 @@ from nmigen.utils import bits_for, log2_int -__all__ = ["SPIFlashFastReader"] +__all__ = ["SPIFlashSlowReader", "SPIFlashFastReader"] - -class SPIFlashFastReader(Elaboratable): - """An SPI flash controller module for fast-reading +class _SPIFlashReaderBase: + """A base class for an SPI flash controller that is to perform any READ operations """ CMD_WIDTH = 8 CMD_DICT = { - "extended": 0x0b, - "dual" : 0x3b, - "quad" : 0x6b + "extended": None, + "dual" : None, + "quad" : None } - def _format_cmd(self, cmd_value): + def _format_cmd(self): """ Returns the values on all the DQ lines that corresponds to the command. @@ -33,22 +32,24 @@ def _format_cmd(self, cmd_value): 11 11 11 10 11 10 11 11 (dual I/O SPI mode) 1111 1111 1111 1110 1111 1110 1111 1111 (quad I/O SPI mode) """ - fcmd = 2**(SPIFlashFastReader.CMD_WIDTH*self.spi_width) - 1 - for bit in range(SPIFlashFastReader.CMD_WIDTH): + cmd_value = type(self).CMD_DICT.get(self._protocol) + if cmd_value is None: + raise KeyError("Reader class {} does not support {} SPI protocol" + .format(type(self).__name__, self._protocol)) + fcmd = 2**(type(self).CMD_WIDTH*self.spi_width) - 1 + for bit in range(type(self).CMD_WIDTH): if (cmd_value >> bit)%2 == 0: fcmd &= ~(1 << (bit*self.spi_width)) return fcmd - def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, + def __init__(self, *, protocol, data_width, divisor=1, device=None, pins=None): if protocol not in ["extended", "dual", "quad"]: raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" .format(protocol)) self._protocol = protocol - self._addr_width = addr_width self._data_width = data_width - self._dummy_cycles = dummy_cycles if divisor < 1: raise ValueError("Invalid divisor value; must be at least 1" @@ -86,15 +87,14 @@ def __init__(self, *, protocol, addr_width, data_width, dummy_cycles, self.rdy = Signal() # Internal rdy signal, set at idle state self.ack = Signal() # External ack signal that begins a read - self.addr = Signal(addr_width) self.r_data = Signal(self._data_width) self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise - self.cmd_width = SPIFlashFastReader.CMD_WIDTH + self.cmd_width = type(self).CMD_WIDTH 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._addr_width, self._data_width, self._dummy_cycles)) + self.shreg = Signal(max(self._fcmd_width, self._data_width)) self.counter = Signal.like(self.divisor) @@ -112,32 +112,30 @@ def _add_clk_primitive(self, platform, module): # (see Section 6.1.2 of FPGA-TN-02039-1.7, # "ECP5 and ECP5-5G sysCONFIG Usage Guide Technical Note") if self._device == "lattice_ecp5": - usrmclk = Signal.like(self.clk) - module.d.comb += usrmclk.eq(self.clk) module.submodules += Instance("USRMCLK", - i_USRMCLKI=usrmclk, + i_USRMCLKI=self.clk, i_USRMCLKTS=0) - def elaborate(self, platform): - m = Module() - + def _add_spi_hardware_logic(self, platform, module): + """Add the foundamental hardware logic for controlling all SPI pins to be used + """ shreg = self.shreg counter = self.counter if self._pins is not None: - self._add_clk_primitive(platform, m) - m.d.comb += [ + self._add_clk_primitive(platform, module) + module.d.comb += [ self._pins.cs.o.eq(self.cs), - self._pins.wp.eq(1), - self._pins.hold.eq(1) + self._pins.wp.eq(0), + self._pins.hold.eq(0) ] if self._protocol == "extended": - m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) - m.d.comb += self._pins.mosi.o.eq(self.mosi) + module.submodules += FFSynchronizer(self._pins.miso.i, self.miso) + module.d.comb += self._pins.mosi.o.eq(self.mosi) elif self._protocol in ["dual", "quad"]: dq_oe = Signal() - m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) + module.submodules.dq = platformodule.get_tristate(self.dq, self._pins.dq, None, False) # If the user doesn't give pins, create dq Pins for Dual & Quad else: if self._protocol in ["dual", "quad"]: @@ -154,350 +152,85 @@ def elaborate(self, platform): tristate_submodule.submodules += Instance("$tribuf", p_WIDTH=self.dq.width, i_EN=self.dq.oe, - i_A=Platform._invert_if(False, self.dq.o), + i_A=Platformodule._invert_if(False, self.dq.o), o_Y=dq_pins ) - m.submodules.dq = tristate_submodule + module.submodules.dq = tristate_submodule # r_data always reads from shreg - m.d.comb += self.r_data.eq(shreg) + module.d.comb += self.r_data.eq(shreg) # 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 m.If((counter == self._divisor_val >> 1) & self.cs): - m.d.sync += self.clk.eq(1) + with module.If((counter == self._divisor_val >> 1) & self.cs): + module.d.sync += self.clk.eq(1) if self._protocol == "extended": - m.d.sync += dq_i.eq(self.miso) + module.d.sync += dq_i.eq(self.miso) elif self._protocol in ["dual", "quad"]: - m.d.sync += dq_i.eq(self.dq.i) + 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 m.If((counter == 0) & self.cs): - m.d.sync += [ + with module.If((counter == 0) & self.cs): + module.d.sync += [ self.clk.eq(0), counter.eq(self.divisor) ] # MOSI latches from shreg by "pushing" old data out from the left of shreg - m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) + module.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) ## Normal countdown - with m.Elif(self.cs): - m.d.sync += counter.eq(counter - 1) + with module.Elif(self.cs): + module.d.sync += counter.eq(counter - 1) # MOSI logic for Extended SPI protocol: # MOSI always output the leftmost bit of shreg if self._protocol == "extended": - m.d.comb += self.mosi.eq(shreg[-1]) + module.d.comb += self.mosi.eq(shreg[-1]) # MOSI logic for Dual and Quad SPI protocols: # Whenever DQ output should be enabled, # DQ always output the leftmost `spi_width`-wide bits of shreg elif self._protocol in ["dual", "quad"]: - m.d.comb += [ + module.d.comb += [ self.dq.o.eq(shreg[-self.spi_width:]), self.dq.oe.eq(dq_oe) ] - # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(SPIFlashFastReader.CMD_DICT[self._protocol]) - # addr: convert bus address to byte-sized address - byte_addr = Cat(Repl(0, log2_int(self._data_width//8)), self.addr) - # FSM - with m.FSM() as fsm: - state_durations = { - "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "FASTREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), - "FASTREAD-WAITREAD": self._divisor_val*(self._dummy_cycles+ - self._data_width//self.spi_width), - "FASTREAD-RDYWAIT" : 1+self._divisor_val - } - max_duration = max([dur for state,dur in state_durations.items()]) - # A "count-up" counter for each state of the command - state_counter = Signal(range(max_duration)) - # State: Idling - with m.State("FASTREAD-IDLE"): - m.d.comb += self.rdy.eq(1) - with m.If(self.ack): - m.d.sync += [ - counter.eq((self._divisor_val >> 1) - 1), - self.clk.eq(1) - ] - m.next = "FASTREAD-CS" - # State: Chip select - with m.State("FASTREAD-CS"): - with m.If(counter == 0): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.cmd_width:].eq(fcmd) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) - m.next = "FASTREAD-CMD" - # State: Command, MOSI - with m.State("FASTREAD-CMD"): - with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self._addr_width:].eq(byte_addr) - ] - m.next = "FASTREAD-ADDR" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # State: Address, MOSI - with m.State("FASTREAD-ADDR"): - with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): - #m.d.sync += state_counter.eq(0) - #if self._protocol in ["dual", "quad"]: - # m.d.sync += dq_oe.eq(0) - m.d.sync += [ - state_counter.eq(0), - shreg[-self._dummy_cycles:].eq(Repl(0, self._dummy_cycles)) - ] - m.next = "FASTREAD-WAITREAD" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # State: Dummy cycles (waiting), and then Read (MISO) - with m.State("FASTREAD-WAITREAD"): - with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): - m.d.sync += [ - state_counter.eq(0), - self.r_rdy.eq(1) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) - m.next = "FASTREAD-RDYWAIT" - with m.Else(): - 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"): - with m.If(state_counter == 0): - m.d.sync += self.r_rdy.eq(0) - # Early check to skip 1 clock period of doing nothing - with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): - m.d.sync += state_counter.eq(0) - m.next = "FASTREAD-IDLE" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.If(~fsm.ongoing("FASTREAD-IDLE") & - ~fsm.ongoing("FASTREAD-RDYWAIT")): - m.d.comb += self.cs.eq(1) - with m.Else(): - m.d.comb += self.cs.eq(0) - """ - ### SCRAPPED: other commands should be implemented in a separate core - - # Command: WRITE ENABLE - if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in CMD_DICT: - # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(CMD_DICT[self._protocol]) - # FSM - with m.FSM() as fsm: - # - with m.State("WR-EN-CMD"): - m.d.comb += self.rdy.eq(1) - with m.If - - # Command: WRITE ENHANCED VOLATILE CONFIGURATION REGISTER - if "WRITE_ENHANCED_VOLATILE_CONFIG_REG" in CMD_DICT: - # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(CMD_DICT[self._protocol]) - # FSM - with m.FSM() as fsm: - # - with m.State("WR-EN-CMD"): - m.d.comb += self.rdy.eq(1) - with m.If - """ - - return m - - - -# TODO: Either delete this, or make a base class for all SPI controllers -class SPIFlashSlowReader(Elaboratable): - """An SPI flash controller module for slow-reading (reading 1 byte at a time) +class SPIFlashSlowReader(_SPIFlashReaderBase, Elaboratable): + """An SPI flash controller module for normal reading + (i.e. only available in Extended protocol and a lower frequency, + but no dummy cycles of waiting are required). """ - CMD_WIDTH = 8 CMD_DICT = { "extended": 0x03, - "dual" : 0x03, - "quad" : 0x03 + "dual" : None, + "quad" : None } - RESET_EN_CMD = 0x66 - RESET_MEM_CMD = 0x99 - def _format_cmd(self, cmd_value): - fcmd = 2**(SPIFlashSlowReader.CMD_WIDTH*self.spi_width) - 1 - for bit in range(SPIFlashSlowReader.CMD_WIDTH): - if (cmd_value >> bit)%2 == 0: - fcmd &= ~(1 << (bit*self.spi_width)) - return fcmd - - - def __init__(self, *, protocol, addr_width, data_width, - divisor=1, device=None, pins=None): - if protocol not in ["extended", "dual", "quad"]: - raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" - .format(protocol)) - self._protocol = protocol + def __init__(self, addr_width, *args, **kwargs): + super().__init__(*args, **kwargs) self._addr_width = addr_width - self._data_width = data_width - - if divisor < 1: - raise ValueError("Invalid divisor value; must be at least 1" - .format(divisor)) - self.divisor = Signal(bits_for(divisor), reset=divisor) - self._divisor_val = divisor + 1 - - supported_devices = ["lattice_ecp5"] - if device is not None and device not in supported_devices: - raise ValueError("Invalid FPGA device name {!r}; must be one of {}" - .format(device, supported_devices)) - self._device = device - if self._device is not None and pins is None: - raise ValueError("Pins parameter is missing for this FPGA device {}" - .format(self._device)) - self._pins = pins - - if self._protocol == "extended": - self.spi_width = 1 - elif self._protocol == "dual": - self.spi_width = 2 - elif self._protocol == "quad": - self.spi_width = 4 - - self.cs = Signal(reset=0) # Equivalent to ~CS_N - self.clk = Signal() - - if protocol == "extended": - self.mosi = Signal() - self.miso = Signal() - elif protocol == "dual": - self.dq = Pin(2, "io") - elif protocol == "quad": - self.dq = Pin(4, "io") - - self.rdy = Signal() # Internal rdy signal, set at idle state - self.ack = Signal() # External ack signal that begins a read self.addr = Signal(addr_width) - self.r_data = Signal(self._data_width) - self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise - - self.cmd_width = SPIFlashFastReader.CMD_WIDTH - 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._addr_width, self._data_width)) - self.counter = Signal.like(self.divisor) - - - def _add_clk_primitive(self, platform, module): - if self._device == "lattice_ecp5": - usrmclk = Signal.like(self.clk) - module.d.comb += usrmclk.eq(self.clk) - module.submodules += Instance("USRMCLK", - i_USRMCLKI=usrmclk, - i_USRMCLKTS=0) + self.shreg = Signal(max(self._fcmd_width, self._data_width, self._addr_width)) def elaborate(self, platform): m = Module() + self._add_spi_hardware_logic(platform, m) shreg = self.shreg counter = self.counter - if self._pins is not None: - self._add_clk_primitive(platform, m) - m.d.comb += [ - self._pins.cs.o.eq(self.cs), - self._pins.wp.eq(0), - self._pins.hold.eq(0) - ] - if self._protocol == "extended": - m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) - m.d.comb += self._pins.mosi.o.eq(self.mosi) - elif self._protocol in ["dual", "quad"]: - dq_oe = Signal() - m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) - # If the user doesn't give pins, create dq Pins for Dual & Quad - else: - if self._protocol in ["dual", "quad"]: - dq_oe = Signal() - if self._protocol == "dual": - dq_pins = Record([ - ("dq", Pin(width=2, dir="io", xdr=0).layout) - ]) - elif self._protocol == "quad": - dq_pins = Record([ - ("dq", Pin(width=4, dir="io", xdr=0).layout) - ]) - tristate_submodule = Module() - tristate_submodule.submodules += Instance("$tribuf", - p_WIDTH=self.dq.width, - i_EN=self.dq.oe, - i_A=Platform._invert_if(False, self.dq.o), - o_Y=dq_pins - ) - m.submodules.dq = tristate_submodule - - # r_data always reads from shreg - m.d.comb += self.r_data.eq(shreg) - - # 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 m.If((counter == self._divisor_val >> 1) & self.cs): - m.d.sync += self.clk.eq(1) - if self._protocol == "extended": - m.d.sync += dq_i.eq(self.miso) - elif self._protocol in ["dual", "quad"]: - m.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 m.If((counter == 0) & self.cs): - m.d.sync += [ - self.clk.eq(0), - counter.eq(self.divisor) - ] - # MOSI latches from shreg by "pushing" old data out from the left of shreg - m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) - ## Normal countdown - with m.Elif(self.cs): - m.d.sync += counter.eq(counter - 1) - - # MOSI logic for Extended SPI protocol: - # MOSI always output the leftmost bit of shreg - if self._protocol == "extended": - m.d.comb += self.mosi.eq(shreg[-1]) - # MOSI logic for Dual and Quad SPI protocols: - # Whenever DQ output should be enabled, - # DQ always output the leftmost `spi_width`-wide bits of shreg - elif self._protocol in ["dual", "quad"]: - m.d.comb += [ - self.dq.o.eq(shreg[-self.spi_width:]), - self.dq.oe.eq(dq_oe) - ] - # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(SPIFlashSlowReader.CMD_DICT[self._protocol]) - # Get formatted RESET ENABLE & RESET MEMORY commands - fcmd_reset_en = self._format_cmd(SPIFlashSlowReader.RESET_EN_CMD) - fcmd_reset_mem = self._format_cmd(SPIFlashSlowReader.RESET_MEM_CMD) + 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) # FSM with m.FSM() as fsm: state_durations = { - "RESET-ENCMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "RESET-ENWAIT" : 1+self._divisor_val, - "RESET-MEMCMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "RESET-MEMWAIT" : 1+self._divisor_val, "SLOWREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), "SLOWREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), "SLOWREAD-READ" : self._divisor_val*(self._data_width//self.spi_width), @@ -506,79 +239,15 @@ def elaborate(self, platform): max_duration = max([dur for state,dur in state_durations.items()]) # A "count-up" counter for each state of the command state_counter = Signal(range(max_duration)) - # - with m.State("SLOWREAD-INIT"): + # State: Idling + with m.State("SLOWREAD-IDLE"): m.d.comb += self.rdy.eq(1) with m.If(self.ack): - m.d.sync += [ - counter.eq(self._divisor_val), - self.clk.eq(0) - ] - m.next = "RESET-ENCS" - # - with m.State("RESET-ENCS"): - with m.If(counter == self._divisor_val >> 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.cmd_width:].eq(fcmd_reset_en) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) - m.next = "RESET-ENCMD" - # - with m.State("RESET-ENCMD"): - with m.If(state_counter == state_durations["RESET-ENCMD"] - 1): - m.d.sync += [ - state_counter.eq(0), - self.clk.eq(0) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) - m.next = "RESET-ENWAIT" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.State("RESET-ENWAIT"): - with m.If(state_counter == state_durations["RESET-ENWAIT"] - 1): - m.d.sync += [ - counter.eq(self._divisor_val), - self.clk.eq(0) - ] - m.next = "RESET-MEMCS" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.State("RESET-MEMCS"): - with m.If(counter == self._divisor_val >> 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.cmd_width:].eq(fcmd_reset_mem) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) - m.next = "RESET-MEMCMD" - # - with m.State("RESET-MEMCMD"): - with m.If(state_counter == state_durations["RESET-MEMCMD"] - 1): - m.d.sync += [ - state_counter.eq(0), - self.clk.eq(0) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) - m.next = "RESET-MEMWAIT" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.State("RESET-MEMWAIT"): - with m.If(state_counter == state_durations["RESET-MEMWAIT"] - 1): m.d.sync += [ counter.eq(self._divisor_val), self.clk.eq(0) ] m.next = "SLOWREAD-CS" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) # State: Chip select with m.State("SLOWREAD-CS"): with m.If(counter == self._divisor_val >> 1): @@ -630,20 +299,8 @@ def elaborate(self, platform): m.next = "SLOWREAD-IDLE" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) - # State: Idling - with m.State("SLOWREAD-IDLE"): - m.d.comb += self.rdy.eq(1) - with m.If(self.ack): - m.d.sync += [ - counter.eq(self._divisor_val), - self.clk.eq(0) - ] - m.next = "SLOWREAD-CS" # - with m.If(~fsm.ongoing("SLOWREAD-INIT") & - ~fsm.ongoing("SLOWREAD-IDLE") & - ~fsm.ongoing("RESET-ENWAIT") & - ~fsm.ongoing("RESET-MEMWAIT") & + with m.If(~fsm.ongoing("SLOWREAD-IDLE") & ~fsm.ongoing("SLOWREAD-RDYWAIT")): m.d.comb += self.cs.eq(1) with m.Else(): @@ -652,313 +309,117 @@ def elaborate(self, platform): return m - -# TODO: Either delete this, or make a base class for all SPI controllers -class SPIFlashReadID(Elaboratable): - """An SPI flash controller module for issuing the READ ID command +class SPIFlashFastReader(_SPIFlashReaderBase, Elaboratable): + """An SPI flash controller module for fast-reading + (i.e. available in all SPI protocols and a higher frequency, + but dummy cycles of waiting are required) """ - CMD_WIDTH = 8 CMD_DICT = { - "extended": 0x9E, - "dual" : 0xAF, - "quad" : 0xAF + "extended": 0x0b, + "dual" : 0x3b, + "quad" : 0x6b } - RESET_EN_CMD = 0x66 - RESET_MEM_CMD = 0x99 - - def _format_cmd(self, cmd_value): - fcmd = 2**(SPIFlashReadID.CMD_WIDTH*self.spi_width) - 1 - for bit in range(SPIFlashReadID.CMD_WIDTH): - if (cmd_value >> bit)%2 == 0: - fcmd &= ~(1 << (bit*self.spi_width)) - return fcmd - - def __init__(self, *, protocol, data_width, - divisor=1, device=None, pins=None): - if protocol not in ["extended", "dual", "quad"]: - raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" - .format(protocol)) - self._protocol = protocol - self._data_width = data_width - - if divisor < 1: - raise ValueError("Invalid divisor value; must be at least 1" - .format(divisor)) - self.divisor = Signal(bits_for(divisor), reset=divisor) - self._divisor_val = divisor + 1 - - supported_devices = ["lattice_ecp5"] - if device is not None and device not in supported_devices: - raise ValueError("Invalid FPGA device name {!r}; must be one of {}" - .format(device, supported_devices)) - self._device = device - if self._device is not None and pins is None: - raise ValueError("Pins parameter is missing for this FPGA device {}" - .format(self._device)) - self._pins = pins - - if self._protocol == "extended": - self.spi_width = 1 - elif self._protocol == "dual": - self.spi_width = 2 - elif self._protocol == "quad": - self.spi_width = 4 - - self.cs = Signal(reset=0) # Equivalent to ~CS_N - self.clk = Signal() - - if protocol == "extended": - self.mosi = Signal() - self.miso = Signal() - elif protocol == "dual": - self.dq = Pin(2, "io") - elif protocol == "quad": - self.dq = Pin(4, "io") - - self.rdy = Signal() # Internal rdy signal, set at idle state - self.ack = Signal() # External ack signal that begins a read - self.r_data = Signal(self._data_width) - self.r_rdy = Signal() # 1 if r_data is valid, 0 otherwise - - self.cmd_width = SPIFlashFastReader.CMD_WIDTH - 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.counter = Signal.like(self.divisor) - - - def _add_clk_primitive(self, platform, module): - if self._device == "lattice_ecp5": - usrmclk = Signal.like(self.clk) - module.d.comb += usrmclk.eq(self.clk) - module.submodules += Instance("USRMCLK", - i_USRMCLKI=usrmclk, - i_USRMCLKTS=0) + def __init__(self, addr_width, dummy_cycles, *args, **kwargs): + super().__init__(*args, **kwargs) + self._addr_width = addr_width + self._dummy_cycles = dummy_cycles + self.addr = Signal(addr_width) + self.shreg = Signal(max(self._fcmd_width, self._data_width, self._addr_width)) def elaborate(self, platform): m = Module() + self._add_spi_hardware_logic(platform, m) shreg = self.shreg counter = self.counter - if self._pins is not None: - self._add_clk_primitive(platform, m) - m.d.comb += [ - self._pins.cs.o.eq(self.cs), - self._pins.wp.eq(0), - self._pins.hold.eq(0) - ] - if self._protocol == "extended": - m.submodules += FFSynchronizer(self._pins.miso.i, self.miso) - m.d.comb += self._pins.mosi.o.eq(self.mosi) - elif self._protocol in ["dual", "quad"]: - dq_oe = Signal() - m.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) - # If the user doesn't give pins, create dq Pins for Dual & Quad - else: - if self._protocol in ["dual", "quad"]: - dq_oe = Signal() - if self._protocol == "dual": - dq_pins = Record([ - ("dq", Pin(width=2, dir="io", xdr=0).layout) - ]) - elif self._protocol == "quad": - dq_pins = Record([ - ("dq", Pin(width=4, dir="io", xdr=0).layout) - ]) - tristate_submodule = Module() - tristate_submodule.submodules += Instance("$tribuf", - p_WIDTH=self.dq.width, - i_EN=self.dq.oe, - i_A=Platform._invert_if(False, self.dq.o), - o_Y=dq_pins - ) - m.submodules.dq = tristate_submodule - - # r_data always reads from shreg - m.d.comb += self.r_data.eq(shreg) - - # 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 m.If((counter == self._divisor_val >> 1) & self.cs): - m.d.sync += self.clk.eq(1) - if self._protocol == "extended": - m.d.sync += dq_i.eq(self.miso) - elif self._protocol in ["dual", "quad"]: - m.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 m.If((counter == 0) & self.cs): - m.d.sync += [ - self.clk.eq(0), - counter.eq(self.divisor) - ] - # MOSI latches from shreg by "pushing" old data out from the left of shreg - m.d.sync += shreg.eq(Cat(dq_i, shreg[:-self.spi_width])) - ## Normal countdown - with m.Elif(self.cs): - m.d.sync += counter.eq(counter - 1) - - # MOSI logic for Extended SPI protocol: - # MOSI always output the leftmost bit of shreg - if self._protocol == "extended": - m.d.comb += self.mosi.eq(shreg[-1]) - # MOSI logic for Dual and Quad SPI protocols: - # Whenever DQ output should be enabled, - # DQ always output the leftmost `spi_width`-wide bits of shreg - elif self._protocol in ["dual", "quad"]: - m.d.comb += [ - self.dq.o.eq(shreg[-self.spi_width:]), - self.dq.oe.eq(dq_oe) - ] - # fcmd: get formatted command based on cmd_dict - fcmd = self._format_cmd(SPIFlashReadID.CMD_DICT[self._protocol]) - # Get formatted RESET ENABLE & RESET MEMORY commands - fcmd_reset_en = self._format_cmd(SPIFlashReadID.RESET_EN_CMD) - fcmd_reset_mem = self._format_cmd(SPIFlashReadID.RESET_MEM_CMD) + 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) # FSM with m.FSM() as fsm: state_durations = { - "RESET-ENCMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "RESET-ENWAIT" : 1+self._divisor_val, - "RESET-MEMCMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "RESET-MEMWAIT" : 1+self._divisor_val, - "READID-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "READID-READ" : self._divisor_val*(self._data_width//self.spi_width), - "READID-RDYWAIT" : 1+self._divisor_val + "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "FASTREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), + "FASTREAD-WAITREAD": self._divisor_val*(self._dummy_cycles+ + self._data_width//self.spi_width), + "FASTREAD-RDYWAIT" : 1+self._divisor_val } max_duration = max([dur for state,dur in state_durations.items()]) # A "count-up" counter for each state of the command state_counter = Signal(range(max_duration)) # State: Idling - with m.State("READID-IDLE"): + with m.State("FASTREAD-IDLE"): m.d.comb += self.rdy.eq(1) with m.If(self.ack): m.d.sync += [ counter.eq(self._divisor_val), self.clk.eq(0) ] - m.next = "RESET-ENCS" - # - with m.State("RESET-ENCS"): - with m.If(counter == self._divisor_val >> 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.cmd_width:].eq(fcmd_reset_en) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) - m.next = "RESET-ENCMD" - # - with m.State("RESET-ENCMD"): - with m.If(state_counter == state_durations["RESET-ENCMD"] - 1): - m.d.sync += [ - state_counter.eq(0), - self.clk.eq(0) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) - m.next = "RESET-ENWAIT" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.State("RESET-ENWAIT"): - with m.If(state_counter == state_durations["RESET-ENWAIT"] - 1): - m.d.sync += [ - counter.eq(self._divisor_val), - self.clk.eq(0) - ] - m.next = "RESET-MEMCS" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.State("RESET-MEMCS"): + m.next = "FASTREAD-CS" + # State: Chip select + with m.State("FASTREAD-CS"): with m.If(counter == self._divisor_val >> 1): m.d.sync += [ state_counter.eq(0), - shreg[-self.cmd_width:].eq(fcmd_reset_mem) + shreg[-self.cmd_width:].eq(fcmd) ] if self._protocol in ["dual", "quad"]: m.d.sync += dq_oe.eq(1) - m.next = "RESET-MEMCMD" - # - with m.State("RESET-MEMCMD"): - with m.If(state_counter == state_durations["RESET-MEMCMD"] - 1): + m.next = "FASTREAD-CMD" + # State: Command, MOSI + with m.State("FASTREAD-CMD"): + with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): m.d.sync += [ state_counter.eq(0), - self.clk.eq(0) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) - m.next = "RESET-MEMWAIT" - with m.Else(): - m.d.sync += state_counter.eq(state_counter + 1) - # - with m.State("RESET-MEMWAIT"): - with m.If(state_counter == state_durations["RESET-MEMWAIT"] - 1): - m.d.sync += [ - counter.eq(self._divisor_val), - self.clk.eq(0) + shreg[-self._addr_width:].eq(byte_addr) ] - m.next = "READID-CS" + m.next = "FASTREAD-ADDR" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) - # State: Chip select - with m.State("READID-CS"): - with m.If(counter == self._divisor_val >> 1): - m.d.sync += [ - state_counter.eq(0), - shreg[-self.cmd_width:].eq(fcmd) - ] - if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) - m.next = "READID-CMD" - # State: Command, MOSI - with m.State("READID-CMD"): - with m.If(state_counter == state_durations["READID-CMD"] - 1): + # State: Address, MOSI + with m.State("FASTREAD-ADDR"): + with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): m.d.sync += state_counter.eq(0) if self._protocol in ["dual", "quad"]: m.d.sync += dq_oe.eq(0) - m.next = "READID-READ" + m.next = "FASTREAD-WAITREAD" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) # State: Dummy cycles (waiting), and then Read (MISO) - with m.State("READID-READ"): - with m.If(state_counter == state_durations["READID-READ"] - 1): + with m.State("FASTREAD-WAITREAD"): + with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): m.d.sync += [ state_counter.eq(0), self.r_rdy.eq(1) ] if self._protocol in ["dual", "quad"]: m.d.sync += dq_oe.eq(0) - m.next = "READID-RDYWAIT" + m.next = "FASTREAD-RDYWAIT" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) # State: Send r_rdy (for 1 clock period), and then Wait - with m.State("READID-RDYWAIT"): + with m.State("FASTREAD-RDYWAIT"): with m.If(state_counter == 0): m.d.sync += self.r_rdy.eq(0) # Early check to skip 1 clock period of doing nothing - with m.If(state_counter == state_durations["READID-RDYWAIT"] - 2): + with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): m.d.sync += state_counter.eq(0) - m.next = "READID-IDLE" + m.next = "FASTREAD-IDLE" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) # - with m.If(~fsm.ongoing("READID-IDLE") & - ~fsm.ongoing("RESET-ENWAIT") & - ~fsm.ongoing("RESET-MEMWAIT") & - ~fsm.ongoing("READID-RDYWAIT")): + with m.If(~fsm.ongoing("FASTREAD-IDLE") & + ~fsm.ongoing("FASTREAD-RDYWAIT")): m.d.comb += self.cs.eq(1) with m.Else(): m.d.comb += self.cs.eq(0) return m + + + From f87fa5e1e9614a30ee7b08ebb8e43e253854558e Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Fri, 3 Jan 2020 13:56:44 +0800 Subject: [PATCH 09/19] spiflash: factor out clock primitives, fix styling --- nmigen_stdio/spiflash.py | 81 +++++++++++------------------- nmigen_stdio/test/test_spiflash.py | 22 +++++--- 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index f0a469a..57dbb5b 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -19,7 +19,6 @@ class _SPIFlashReaderBase: "quad" : None } - def _format_cmd(self): """ Returns the values on all the DQ lines that corresponds to the command. @@ -27,7 +26,7 @@ def _format_cmd(self): Since everything is transmitted on all DQ lines (command, address and data), the input cmd_value is extended/interleaved to full DQ width even if DQ1-DQ3 are "don't care" during the command phase: - + eg: 1 1 1 0 1 0 1 1 (extended SPI mode) 11 11 11 10 11 10 11 11 (dual I/O SPI mode) 1111 1111 1111 1110 1111 1110 1111 1111 (quad I/O SPI mode) @@ -38,15 +37,15 @@ def _format_cmd(self): .format(type(self).__name__, self._protocol)) fcmd = 2**(type(self).CMD_WIDTH*self.spi_width) - 1 for bit in range(type(self).CMD_WIDTH): - if (cmd_value >> bit)%2 == 0: + if (cmd_value >> bit) % 2 == 0: fcmd &= ~(1 << (bit*self.spi_width)) return fcmd - - def __init__(self, *, protocol, data_width, + def __init__(self, *, protocol, data_width, divisor=1, device=None, pins=None): if protocol not in ["extended", "dual", "quad"]: - raise ValueError("Invalid SPI protocol {!r}; must be one of \"extended\", \"dual\", or \"quad\"" + raise ValueError("Invalid SPI protocol {!r}; must be one of " + "\"extended\", \"dual\", or \"quad\"" .format(protocol)) self._protocol = protocol self._data_width = data_width @@ -97,26 +96,6 @@ def __init__(self, *, protocol, data_width, self.shreg = Signal(max(self._fcmd_width, self._data_width)) self.counter = Signal.like(self.divisor) - - def _add_clk_primitive(self, platform, module): - """Add a submodule whose instantiation is required by certain devices - when choosing a user clock as SPI clock. - - Note that the CLK pin is not connected outside of this function, - as certain devices does not need to request the CLK pin. - """ - # Lattice ECP5: - # "The ECP5 and ECP5-5G devices provide a solution for users - # to choose any user clock as MCLK under this scenario - # by instantiating USRMCLK macro in your Verilog or VHDL." - # (see Section 6.1.2 of FPGA-TN-02039-1.7, - # "ECP5 and ECP5-5G sysCONFIG Usage Guide Technical Note") - if self._device == "lattice_ecp5": - module.submodules += Instance("USRMCLK", - i_USRMCLKI=self.clk, - i_USRMCLKTS=0) - - def _add_spi_hardware_logic(self, platform, module): """Add the foundamental hardware logic for controlling all SPI pins to be used """ @@ -124,18 +103,21 @@ def _add_spi_hardware_logic(self, platform, module): counter = self.counter if self._pins is not None: - self._add_clk_primitive(platform, module) module.d.comb += [ self._pins.cs.o.eq(self.cs), self._pins.wp.eq(0), self._pins.hold.eq(0) ] + # Platforms that require declaration of a user SPI clock signal + # (e.g. by instantiating a USRMCLK Instance) must NOT pass a CLK on the SPI flash + if hasattr(self._pins, "clk"): + module.d.comb += self._pins.clk.o.eq(self.clk) if self._protocol == "extended": module.submodules += FFSynchronizer(self._pins.miso.i, self.miso) module.d.comb += self._pins.mosi.o.eq(self.mosi) elif self._protocol in ["dual", "quad"]: dq_oe = Signal() - module.submodules.dq = platformodule.get_tristate(self.dq, self._pins.dq, None, False) + module.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) # If the user doesn't give pins, create dq Pins for Dual & Quad else: if self._protocol in ["dual", "quad"]: @@ -163,16 +145,16 @@ 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 + # When countdown is half-way done, clock edge goes up (positive); + # MISO starts latching bit/byte from slave with module.If((counter == self._divisor_val >> 1) & self.cs): module.d.sync += self.clk.eq(1) if self._protocol == "extended": 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 + # 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), @@ -180,7 +162,7 @@ def _add_spi_hardware_logic(self, platform, module): ] # 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 + # Normal countdown with module.Elif(self.cs): module.d.sync += counter.eq(counter - 1) @@ -189,7 +171,7 @@ def _add_spi_hardware_logic(self, platform, module): if self._protocol == "extended": module.d.comb += self.mosi.eq(shreg[-1]) # MOSI logic for Dual and Quad SPI protocols: - # Whenever DQ output should be enabled, + # Whenever DQ output should be enabled, # DQ always output the leftmost `spi_width`-wide bits of shreg elif self._protocol in ["dual", "quad"]: module.d.comb += [ @@ -200,7 +182,7 @@ def _add_spi_hardware_logic(self, platform, module): class SPIFlashSlowReader(_SPIFlashReaderBase, Elaboratable): """An SPI flash controller module for normal reading - (i.e. only available in Extended protocol and a lower frequency, + (i.e. only available in Extended protocol and a lower frequency, but no dummy cycles of waiting are required). """ CMD_DICT = { @@ -209,14 +191,12 @@ class SPIFlashSlowReader(_SPIFlashReaderBase, Elaboratable): "quad" : None } - def __init__(self, addr_width, *args, **kwargs): super().__init__(*args, **kwargs) self._addr_width = addr_width self.addr = Signal(addr_width) self.shreg = Signal(max(self._fcmd_width, self._data_width, self._addr_width)) - def elaborate(self, platform): m = Module() self._add_spi_hardware_logic(platform, m) @@ -231,12 +211,12 @@ def elaborate(self, platform): # FSM with m.FSM() as fsm: state_durations = { - "SLOWREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "SLOWREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), - "SLOWREAD-READ" : self._divisor_val*(self._data_width//self.spi_width), - "SLOWREAD-RDYWAIT" : 1+self._divisor_val + "SLOWREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), + "SLOWREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), + "SLOWREAD-READ" : self._divisor_val*(self._data_width//self.spi_width), + "SLOWREAD-RDYWAIT": 1+self._divisor_val } - max_duration = max([dur for state,dur in state_durations.items()]) + max_duration = max([dur for state, dur in state_durations.items()]) # A "count-up" counter for each state of the command state_counter = Signal(range(max_duration)) # State: Idling @@ -299,8 +279,8 @@ def elaborate(self, platform): m.next = "SLOWREAD-IDLE" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) - # - with m.If(~fsm.ongoing("SLOWREAD-IDLE") & + # Enable/Disable CS + with m.If(~fsm.ongoing("SLOWREAD-IDLE") & ~fsm.ongoing("SLOWREAD-RDYWAIT")): m.d.comb += self.cs.eq(1) with m.Else(): @@ -311,7 +291,7 @@ def elaborate(self, platform): class SPIFlashFastReader(_SPIFlashReaderBase, Elaboratable): """An SPI flash controller module for fast-reading - (i.e. available in all SPI protocols and a higher frequency, + (i.e. available in all SPI protocols and a higher frequency, but dummy cycles of waiting are required) """ CMD_DICT = { @@ -320,7 +300,6 @@ class SPIFlashFastReader(_SPIFlashReaderBase, Elaboratable): "quad" : 0x6b } - def __init__(self, addr_width, dummy_cycles, *args, **kwargs): super().__init__(*args, **kwargs) self._addr_width = addr_width @@ -328,7 +307,6 @@ def __init__(self, addr_width, dummy_cycles, *args, **kwargs): self.addr = Signal(addr_width) self.shreg = Signal(max(self._fcmd_width, self._data_width, self._addr_width)) - def elaborate(self, platform): m = Module() self._add_spi_hardware_logic(platform, m) @@ -349,7 +327,7 @@ def elaborate(self, platform): self._data_width//self.spi_width), "FASTREAD-RDYWAIT" : 1+self._divisor_val } - max_duration = max([dur for state,dur in state_durations.items()]) + max_duration = max([dur for state, dur in state_durations.items()]) # A "count-up" counter for each state of the command state_counter = Signal(range(max_duration)) # State: Idling @@ -412,14 +390,11 @@ def elaborate(self, platform): m.next = "FASTREAD-IDLE" with m.Else(): m.d.sync += state_counter.eq(state_counter + 1) - # - with m.If(~fsm.ongoing("FASTREAD-IDLE") & + # Enable/Disable CS + with m.If(~fsm.ongoing("FASTREAD-IDLE") & ~fsm.ongoing("FASTREAD-RDYWAIT")): m.d.comb += self.cs.eq(1) with m.Else(): m.d.comb += self.cs.eq(0) return m - - - diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index ed94aeb..3dd5082 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -24,13 +24,15 @@ def __init__(self, *, dut, data, data_size, dummy_cycles): self.data = data self.data_size = data_size self.dummy_cycles = dummy_cycles + def elaborate(self, platform): m = Module() data_sig = Signal(self.data_size) recv_data = SyncFIFO(width=self.dut.spi_width, - depth=(self.dut.cmd_width+self.dut._addr_width)//self.dut.spi_width) + depth=(self.dut.cmd_width+self.dut._addr_width) // + self.dut.spi_width) stored_data = SyncFIFO(width=self.dut.spi_width, - depth=self.dut._data_width//self.dut.spi_width) + depth=self.dut._data_width // self.dut.spi_width) stored_data_num_left = Signal(range(stored_data.depth+1), reset=stored_data.depth) dummy_counter = Signal(range(self.dummy_cycles), @@ -51,7 +53,7 @@ def elaborate(self, platform): m.d.comb += recv_data.w_en.eq(0) with m.If(~recv_data.w_rdy & stored_data.w_rdy & (self.dut.counter == 0)): m.next = "PUT-DATA" - with m.State("PUT-DATA"): + with m.State("PUT-DATA"): with m.If(stored_data_num_left != 0): m.d.comb += [ stored_data.w_en.eq(1), @@ -59,7 +61,8 @@ def elaborate(self, platform): ] m.d.sync += [ stored_data_num_left.eq(stored_data_num_left-1), - data_sig.eq(Cat(Repl(0, self.dut.spi_width), data_sig[:-self.dut.spi_width])) + data_sig.eq(Cat(Repl(0, self.dut.spi_width), + data_sig[:-self.dut.spi_width])) ] with m.Else(): m.d.comb += stored_data.w_en.eq(0) @@ -81,7 +84,8 @@ def elaborate(self, platform): ] m.d.sync += [ stored_data_num_left.eq(stored_data_num_left-1), - data_sig.eq(Cat(Repl(0, self.dut.spi_width), data_sig[:-self.dut.spi_width])) + data_sig.eq(Cat(Repl(0, self.dut.spi_width), + data_sig[:-self.dut.spi_width])) ] with m.Else(): m.d.comb += stored_data.w_en.eq(0) @@ -98,7 +102,7 @@ def elaborate(self, platform): return m def test_extended(self): - self.dut = SPIFlashFastReader(protocol="extended", + self.dut = SPIFlashFastReader(protocol="extended", addr_width=24, data_width=32, divisor=49, @@ -111,7 +115,7 @@ def test_extended(self): self.simple_test() def test_dual(self): - self.dut = SPIFlashFastReader(protocol="dual", + self.dut = SPIFlashFastReader(protocol="dual", addr_width=24, data_width=32, divisor=49, @@ -124,7 +128,7 @@ def test_dual(self): self.simple_test() def test_quad(self): - self.dut = SPIFlashFastReader(protocol="quad", + self.dut = SPIFlashFastReader(protocol="quad", addr_width=24, data_width=32, divisor=49, @@ -140,6 +144,7 @@ def simple_test(self): m = Module() m.submodules.master = self.dut m.submodules.slave = self.flash + def process(): yield self.dut.ack.eq(1) while (yield self.dut.rdy): @@ -152,4 +157,5 @@ def process(): yield self.dut.ack.eq(1) for _ in range(10*self.dut._divisor_val): yield + simulation_test(m, process) From 5406337abf07094d821e9a5dd475b6b109839a60 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Mon, 6 Jan 2020 15:48:51 +0800 Subject: [PATCH 10/19] spiflash: remove device parameter --- nmigen_stdio/spiflash.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 57dbb5b..066db38 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -42,7 +42,7 @@ def _format_cmd(self): return fcmd def __init__(self, *, protocol, data_width, - divisor=1, device=None, pins=None): + divisor=1, pins=None): if protocol not in ["extended", "dual", "quad"]: raise ValueError("Invalid SPI protocol {!r}; must be one of " "\"extended\", \"dual\", or \"quad\"" @@ -56,14 +56,6 @@ def __init__(self, *, protocol, data_width, self.divisor = Signal(bits_for(divisor), reset=divisor) self._divisor_val = divisor + 1 - supported_devices = ["lattice_ecp5"] - if device is not None and device not in supported_devices: - raise ValueError("Invalid FPGA device name {!r}; must be one of {}" - .format(device, supported_devices)) - self._device = device - if self._device is not None and pins is None: - raise ValueError("Pins parameter is missing for this FPGA device {}" - .format(self._device)) self._pins = pins if self._protocol == "extended": From 259225cc87df1221c019c730ae62656851124ac7 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Wed, 5 Feb 2020 15:19:01 +0800 Subject: [PATCH 11/19] spi: rename "extended" SPI to "standard" * N28Q125A calls the 1-MISO 1-MOSI protocol "extended" because it supports sending the command on 1 pin while receiving the data on 2 or 4 pins like "dual" or "quad". --- nmigen_stdio/spiflash.py | 26 +++++++++++++------------- nmigen_stdio/test/test_spiflash.py | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 066db38..64c8402 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -14,7 +14,7 @@ class _SPIFlashReaderBase: """ CMD_WIDTH = 8 CMD_DICT = { - "extended": None, + "standard": None, "dual" : None, "quad" : None } @@ -27,7 +27,7 @@ def _format_cmd(self): the input cmd_value is extended/interleaved to full DQ width even if DQ1-DQ3 are "don't care" during the command phase: - eg: 1 1 1 0 1 0 1 1 (extended SPI mode) + eg: 1 1 1 0 1 0 1 1 (standard SPI mode) 11 11 11 10 11 10 11 11 (dual I/O SPI mode) 1111 1111 1111 1110 1111 1110 1111 1111 (quad I/O SPI mode) """ @@ -43,9 +43,9 @@ def _format_cmd(self): def __init__(self, *, protocol, data_width, divisor=1, pins=None): - if protocol not in ["extended", "dual", "quad"]: + if protocol not in ["standard", "dual", "quad"]: raise ValueError("Invalid SPI protocol {!r}; must be one of " - "\"extended\", \"dual\", or \"quad\"" + "\"standard\", \"dual\", or \"quad\"" .format(protocol)) self._protocol = protocol self._data_width = data_width @@ -58,7 +58,7 @@ def __init__(self, *, protocol, data_width, self._pins = pins - if self._protocol == "extended": + if self._protocol == "standard": self.spi_width = 1 elif self._protocol == "dual": self.spi_width = 2 @@ -68,7 +68,7 @@ def __init__(self, *, protocol, data_width, self.cs = Signal(reset=0) # Equivalent to ~CS_N self.clk = Signal() - if protocol == "extended": + if protocol == "standard": self.mosi = Signal() self.miso = Signal() elif protocol == "dual": @@ -104,7 +104,7 @@ def _add_spi_hardware_logic(self, platform, module): # (e.g. by instantiating a USRMCLK Instance) must NOT pass a CLK on the SPI flash if hasattr(self._pins, "clk"): module.d.comb += self._pins.clk.o.eq(self.clk) - if self._protocol == "extended": + if self._protocol == "standard": module.submodules += FFSynchronizer(self._pins.miso.i, self.miso) module.d.comb += self._pins.mosi.o.eq(self.mosi) elif self._protocol in ["dual", "quad"]: @@ -141,7 +141,7 @@ def _add_spi_hardware_logic(self, platform, module): # MISO starts latching bit/byte from slave with module.If((counter == self._divisor_val >> 1) & self.cs): module.d.sync += self.clk.eq(1) - if self._protocol == "extended": + 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) @@ -158,9 +158,9 @@ def _add_spi_hardware_logic(self, platform, module): with module.Elif(self.cs): module.d.sync += counter.eq(counter - 1) - # MOSI logic for Extended SPI protocol: + # MOSI logic for Standard SPI protocol: # MOSI always output the leftmost bit of shreg - if self._protocol == "extended": + if self._protocol == "standard": module.d.comb += self.mosi.eq(shreg[-1]) # MOSI logic for Dual and Quad SPI protocols: # Whenever DQ output should be enabled, @@ -174,11 +174,11 @@ def _add_spi_hardware_logic(self, platform, module): class SPIFlashSlowReader(_SPIFlashReaderBase, Elaboratable): """An SPI flash controller module for normal reading - (i.e. only available in Extended protocol and a lower frequency, + (i.e. only available in Standard protocol and a lower frequency, but no dummy cycles of waiting are required). """ CMD_DICT = { - "extended": 0x03, + "standard": 0x03, "dual" : None, "quad" : None } @@ -287,7 +287,7 @@ class SPIFlashFastReader(_SPIFlashReaderBase, Elaboratable): but dummy cycles of waiting are required) """ CMD_DICT = { - "extended": 0x0b, + "standard": 0x0b, "dual" : 0x3b, "quad" : 0x6b } diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index 3dd5082..f77cae4 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -101,8 +101,8 @@ def elaborate(self, platform): m.d.sync += self.dut.dq.i.eq(0) return m - def test_extended(self): - self.dut = SPIFlashFastReader(protocol="extended", + def test_standard(self): + self.dut = SPIFlashFastReader(protocol="standard", addr_width=24, data_width=32, divisor=49, From e9419973a14dbfd61ddb8032d9f8a25739d26ade Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Thu, 6 Feb 2020 16:10:53 +0800 Subject: [PATCH 12/19] spi: add divisor_bits; fix divisor logic & simulation --- nmigen_stdio/spiflash.py | 123 ++++++++++++++--------------- nmigen_stdio/test/test_spiflash.py | 17 ++-- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 64c8402..c6ef467 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -41,8 +41,7 @@ def _format_cmd(self): fcmd &= ~(1 << (bit*self.spi_width)) return fcmd - def __init__(self, *, protocol, data_width, - divisor=1, pins=None): + def __init__(self, *, protocol, data_width=32, 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\"" @@ -53,8 +52,7 @@ def __init__(self, *, protocol, data_width, if divisor < 1: raise ValueError("Invalid divisor value; must be at least 1" .format(divisor)) - self.divisor = Signal(bits_for(divisor), reset=divisor) - self._divisor_val = divisor + 1 + self.divisor = Signal(divisor_bits or bits_for(divisor), reset=divisor) self._pins = pins @@ -87,6 +85,8 @@ def __init__(self, *, protocol, data_width, # (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.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 @@ -108,12 +108,12 @@ def _add_spi_hardware_logic(self, platform, module): module.submodules += FFSynchronizer(self._pins.miso.i, self.miso) module.d.comb += self._pins.mosi.o.eq(self.mosi) elif self._protocol in ["dual", "quad"]: - dq_oe = Signal() + self.dq_oe = Signal() module.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) # If the user doesn't give pins, create dq Pins for Dual & Quad else: if self._protocol in ["dual", "quad"]: - dq_oe = Signal() + self.dq_oe = Signal() if self._protocol == "dual": dq_pins = Record([ ("dq", Pin(width=2, dir="io", xdr=0).layout) @@ -126,7 +126,7 @@ def _add_spi_hardware_logic(self, platform, module): tristate_submodule.submodules += Instance("$tribuf", p_WIDTH=self.dq.width, i_EN=self.dq.oe, - i_A=Platformodule._invert_if(False, self.dq.o), + i_A=self.dq.o, o_Y=dq_pins ) module.submodules.dq = tristate_submodule @@ -139,8 +139,10 @@ def _add_spi_hardware_logic(self, platform, module): 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_val >> 1) & self.cs): + 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"]: @@ -152,6 +154,8 @@ def _add_spi_hardware_logic(self, platform, module): 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 @@ -168,7 +172,7 @@ def _add_spi_hardware_logic(self, platform, module): elif self._protocol in ["dual", "quad"]: module.d.comb += [ self.dq.o.eq(shreg[-self.spi_width:]), - self.dq.oe.eq(dq_oe) + self.dq.oe.eq(self.dq_oe) ] @@ -203,10 +207,10 @@ def elaborate(self, platform): # FSM with m.FSM() as fsm: state_durations = { - "SLOWREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "SLOWREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), - "SLOWREAD-READ" : self._divisor_val*(self._data_width//self.spi_width), - "SLOWREAD-RDYWAIT": 1+self._divisor_val + "SLOWREAD-CMD" : self.cmd_width//self.spi_width, + "SLOWREAD-ADDR" : self._addr_width//self.spi_width, + "SLOWREAD-READ" : self._data_width//self.spi_width, + "SLOWREAD-RDYWAIT": 1 } max_duration = max([dur for state, dur in state_durations.items()]) # A "count-up" counter for each state of the command @@ -216,67 +220,65 @@ def elaborate(self, platform): m.d.comb += self.rdy.eq(1) with m.If(self.ack): m.d.sync += [ - counter.eq(self._divisor_val), + counter.eq(0), self.clk.eq(0) ] m.next = "SLOWREAD-CS" # State: Chip select with m.State("SLOWREAD-CS"): - with m.If(counter == self._divisor_val >> 1): + m.d.sync += self.cs.eq(1) + with m.If(self.clk_negedge_next): m.d.sync += [ state_counter.eq(0), shreg[-self.cmd_width:].eq(fcmd) ] if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) + m.d.sync += self.dq_oe.eq(1) m.next = "SLOWREAD-CMD" # State: Command, MOSI with m.State("SLOWREAD-CMD"): - with m.If(state_counter == state_durations["SLOWREAD-CMD"] - 1): + with m.If((state_counter == state_durations["SLOWREAD-CMD"] - 1) & + self.clk_negedge_next): m.d.sync += [ state_counter.eq(0), shreg[-self._addr_width:].eq(byte_addr) ] m.next = "SLOWREAD-ADDR" - with m.Else(): + with m.Elif(self.clk_negedge_next): m.d.sync += state_counter.eq(state_counter + 1) # State: Address, MOSI with m.State("SLOWREAD-ADDR"): - with m.If(state_counter == state_durations["SLOWREAD-ADDR"] - 1): + with m.If((state_counter == state_durations["SLOWREAD-ADDR"] - 1) & + self.clk_negedge_next): m.d.sync += state_counter.eq(0) if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) + m.d.sync += self.dq_oe.eq(0) m.next = "SLOWREAD-READ" - with m.Else(): + with m.Elif(self.clk_negedge_next): m.d.sync += state_counter.eq(state_counter + 1) # State: Dummy cycles (waiting), and then Read (MISO) with m.State("SLOWREAD-READ"): - with m.If(state_counter == state_durations["SLOWREAD-READ"] - 1): + 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) ] if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) + m.d.sync += self.dq_oe.eq(0) m.next = "SLOWREAD-RDYWAIT" - with m.Else(): + with m.Elif(self.clk_negedge_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"): - with m.If(state_counter == 0): - m.d.sync += self.r_rdy.eq(0) + m.d.sync += self.r_rdy.eq(0) # Early check to skip 1 clock period of doing nothing - with m.If(state_counter == state_durations["SLOWREAD-RDYWAIT"] - 2): - m.d.sync += state_counter.eq(0) + 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.Else(): + with m.Elif(self.clk_negedge_next): m.d.sync += state_counter.eq(state_counter + 1) - # Enable/Disable CS - with m.If(~fsm.ongoing("SLOWREAD-IDLE") & - ~fsm.ongoing("SLOWREAD-RDYWAIT")): - m.d.comb += self.cs.eq(1) - with m.Else(): - m.d.comb += self.cs.eq(0) return m @@ -313,11 +315,10 @@ def elaborate(self, platform): # FSM with m.FSM() as fsm: state_durations = { - "FASTREAD-CMD" : self._divisor_val*(self.cmd_width//self.spi_width), - "FASTREAD-ADDR" : self._divisor_val*(self._addr_width//self.spi_width), - "FASTREAD-WAITREAD": self._divisor_val*(self._dummy_cycles+ - self._data_width//self.spi_width), - "FASTREAD-RDYWAIT" : 1+self._divisor_val + "FASTREAD-CMD" : self.cmd_width//self.spi_width, + "FASTREAD-ADDR" : self._addr_width//self.spi_width, + "FASTREAD-WAITREAD": self._dummy_cycles+self._data_width//self.spi_width, + "FASTREAD-RDYWAIT" : 1 } max_duration = max([dur for state, dur in state_durations.items()]) # A "count-up" counter for each state of the command @@ -327,66 +328,64 @@ def elaborate(self, platform): m.d.comb += self.rdy.eq(1) with m.If(self.ack): m.d.sync += [ - counter.eq(self._divisor_val), + counter.eq(0), self.clk.eq(0) ] m.next = "FASTREAD-CS" # State: Chip select with m.State("FASTREAD-CS"): - with m.If(counter == self._divisor_val >> 1): + m.d.sync += self.cs.eq(1) + with m.If(self.clk_negedge_next): m.d.sync += [ state_counter.eq(0), shreg[-self.cmd_width:].eq(fcmd) ] if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(1) + m.d.sync += self.dq_oe.eq(1) m.next = "FASTREAD-CMD" # State: Command, MOSI with m.State("FASTREAD-CMD"): - with m.If(state_counter == state_durations["FASTREAD-CMD"] - 1): + with m.If((state_counter == state_durations["FASTREAD-CMD"] - 1) & + self.clk_negedge_next): m.d.sync += [ state_counter.eq(0), shreg[-self._addr_width:].eq(byte_addr) ] m.next = "FASTREAD-ADDR" - with m.Else(): + with m.Elif(self.clk_negedge_next): m.d.sync += state_counter.eq(state_counter + 1) # State: Address, MOSI with m.State("FASTREAD-ADDR"): - with m.If(state_counter == state_durations["FASTREAD-ADDR"] - 1): + with m.If((state_counter == state_durations["FASTREAD-ADDR"] - 1) & + self.clk_negedge_next): m.d.sync += state_counter.eq(0) if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) + m.d.sync += self.dq_oe.eq(0) m.next = "FASTREAD-WAITREAD" - with m.Else(): + with m.Elif(self.clk_negedge_next): m.d.sync += state_counter.eq(state_counter + 1) # State: Dummy cycles (waiting), and then Read (MISO) with m.State("FASTREAD-WAITREAD"): - with m.If(state_counter == state_durations["FASTREAD-WAITREAD"] - 1): + 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) ] if self._protocol in ["dual", "quad"]: - m.d.sync += dq_oe.eq(0) + m.d.sync += self.dq_oe.eq(0) m.next = "FASTREAD-RDYWAIT" - with m.Else(): + with m.Elif(self.clk_negedge_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"): - with m.If(state_counter == 0): - m.d.sync += self.r_rdy.eq(0) + m.d.sync += self.r_rdy.eq(0) # Early check to skip 1 clock period of doing nothing - with m.If(state_counter == state_durations["FASTREAD-RDYWAIT"] - 2): - m.d.sync += state_counter.eq(0) + 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.Else(): + with m.Elif(self.clk_negedge_next): m.d.sync += state_counter.eq(state_counter + 1) - # Enable/Disable CS - with m.If(~fsm.ongoing("FASTREAD-IDLE") & - ~fsm.ongoing("FASTREAD-RDYWAIT")): - m.d.comb += self.cs.eq(1) - with m.Else(): - m.d.comb += self.cs.eq(0) return m diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index f77cae4..eb7ad53 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -15,7 +15,7 @@ def simulation_test(dut, process): class SPIFlashFastReadTestCase(unittest.TestCase): def divisor_period(self): - for _ in range((yield self.dut.divisor) + 1): + for _ in range((yield self.divisor) + 1): yield class SuperNaiveFlash(Elaboratable): @@ -47,7 +47,7 @@ def elaborate(self, platform): with m.State("INIT"): m.d.sync += data_sig.eq(self.data) with m.If(self.dut.cs): - with m.If(self.dut.counter == self.dut._divisor_val >> 1): + with m.If(self.dut.counter == self.dut.divisor >> 1): m.d.comb += recv_data.w_en.eq(1) with m.Else(): m.d.comb += recv_data.w_en.eq(0) @@ -102,10 +102,11 @@ def elaborate(self, platform): return m def test_standard(self): + self.divisor = 9 self.dut = SPIFlashFastReader(protocol="standard", addr_width=24, data_width=32, - divisor=49, + divisor=self.divisor, dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, @@ -115,10 +116,11 @@ def test_standard(self): self.simple_test() def test_dual(self): + self.divisor = 9 self.dut = SPIFlashFastReader(protocol="dual", addr_width=24, data_width=32, - divisor=49, + divisor=self.divisor, dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, @@ -128,10 +130,11 @@ def test_dual(self): self.simple_test() def test_quad(self): + self.divisor = 9 self.dut = SPIFlashFastReader(protocol="quad", addr_width=24, data_width=32, - divisor=49, + divisor=self.divisor, dummy_cycles=15) self.flash = (SPIFlashFastReadTestCase. SuperNaiveFlash(dut=self.dut, @@ -154,8 +157,10 @@ def process(): yield # Wait until it enters RDYWAIT state self.assertEqual((yield self.dut.r_data), self.flash.data) # simulate continuous reading, informally + while not (yield self.dut.rdy): + yield yield self.dut.ack.eq(1) - for _ in range(10*self.dut._divisor_val): + for _ in range(10*self.divisor): yield simulation_test(m, process) From 99e51ec59f8093bdf6623fac60cf2adc89611aaa Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Fri, 7 Feb 2020 22:29:30 +0800 Subject: [PATCH 13/19] spi: improve logic for waiting response --- nmigen_stdio/spiflash.py | 113 +++++++++++++++++------------ nmigen_stdio/test/test_spiflash.py | 4 + 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index c6ef467..31f2d4a 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -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" @@ -83,7 +97,7 @@ 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() @@ -91,6 +105,8 @@ def __init__(self, *, protocol, data_width=32, divisor=1, divisor_bits=None, pin 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 @@ -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 @@ -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 @@ -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 = { @@ -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 @@ -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 = { @@ -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) @@ -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 diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index eb7ad53..e2404d3 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -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. @@ -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. @@ -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. @@ -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 From fc4fac104856788dfb57afe744bcd56af8b81a7d Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Sat, 8 Feb 2020 02:19:32 +0800 Subject: [PATCH 14/19] spi: make counter independent of CS; fix simulation --- nmigen_stdio/spiflash.py | 65 +++++++++++++++++------------- nmigen_stdio/test/test_spiflash.py | 8 ++-- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 31f2d4a..8b89438 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -99,6 +99,7 @@ def __init__(self, *, protocol, data_width=32, granularity=8, # (Note: DQ pins are both input/output only for Dual or Quad SPI protocols) self.shreg = None self.counter = Signal.like(self.divisor) + self.counter_en = Signal() self.clk_posedge_next = Signal() self.clk_negedge_next = Signal() @@ -109,6 +110,7 @@ def _add_spi_hardware_logic(self, platform, module): raise NotImplementedError("Shift register shreg has not been defined!") shreg = self.shreg counter = self.counter + counter_en = self.counter_en if self._pins is not None: module.d.comb += [ @@ -153,13 +155,15 @@ 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) - # Enable SCLK only when CS: - with module.If(self.cs): + # When counter is enabled: + with module.If(counter_en): # 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) + # Enable SCLK only when CS: + with module.If(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": @@ -170,16 +174,19 @@ def _add_spi_hardware_logic(self, platform, module): # - 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) - ] + # Enable SCLK only when CS: + with module.If(self.cs): + module.d.sync += self.clk.eq(0) + module.d.sync += 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) + # When counter is disabled, reset: + with module.Else(): + module.d.sync += counter.eq(self.divisor) # MOSI logic for Standard SPI protocol: # MOSI always output the leftmost bit of shreg @@ -222,6 +229,7 @@ def elaborate(self, platform): shreg = self.shreg counter = self.counter + counter_en = self.counter_en # fcmd: get formatted command based on cmd_dict fcmd = self._format_cmd() @@ -230,10 +238,9 @@ def elaborate(self, platform): # FSM with m.FSM() as fsm: state_durations = { - "SLOWREAD-CMD" : self.cmd_width//self.spi_width, - "SLOWREAD-ADDR" : self._addr_width//self.spi_width, - "SLOWREAD-READ" : self._data_width//self.spi_width, - "SLOWREAD-RDYWAIT": 1 + "SLOWREAD-CMD" : self.cmd_width//self.spi_width, + "SLOWREAD-ADDR": self._addr_width//self.spi_width, + "SLOWREAD-READ": self._data_width//self.spi_width } max_duration = max([dur for state, dur in state_durations.items()]) # A "count-up" counter for each state of the command @@ -244,6 +251,7 @@ def elaborate(self, platform): with m.If(self.ack): m.d.sync += [ counter.eq(0), + counter_en.eq(1), self.clk.eq(0) ] m.next = "SLOWREAD-CS" @@ -283,7 +291,6 @@ def elaborate(self, platform): with m.State("SLOWREAD-READ"): with m.If((state_counter == state_durations["SLOWREAD-READ"] - 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" @@ -291,15 +298,15 @@ def elaborate(self, platform): 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"): - 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): + with m.If(self.clk_negedge_next): m.d.sync += self.cs.eq(0) + with m.If(self.clk_posedge_next): + m.d.sync += [ + self.r_rdy.eq(1), + counter_en.eq(0) + ] + with m.If(self.r_rdy): m.next = "SLOWREAD-IDLE" - with m.Elif(self.clk_posedge_next): - m.d.sync += state_counter.eq(state_counter + 1) return m @@ -328,6 +335,7 @@ def elaborate(self, platform): shreg = self.shreg counter = self.counter + counter_en = self.counter_en # fcmd: get formatted command based on cmd_dict fcmd = self._format_cmd() @@ -338,8 +346,7 @@ def elaborate(self, platform): state_durations = { "FASTREAD-CMD" : self.cmd_width//self.spi_width, "FASTREAD-ADDR" : self._addr_width//self.spi_width, - "FASTREAD-WAITREAD": self._dummy_cycles+self._data_width//self.spi_width, - "FASTREAD-RDYWAIT" : 1 + "FASTREAD-WAITREAD": self._dummy_cycles+self._data_width//self.spi_width } max_duration = max([dur for state, dur in state_durations.items()]) # A "count-up" counter for each state of the command @@ -350,6 +357,7 @@ def elaborate(self, platform): with m.If(self.ack): m.d.sync += [ counter.eq(0), + counter_en.eq(1), self.clk.eq(0) ] m.next = "FASTREAD-CS" @@ -389,7 +397,6 @@ def elaborate(self, platform): with m.State("FASTREAD-WAITREAD"): with m.If((state_counter == state_durations["FASTREAD-WAITREAD"] - 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" @@ -397,14 +404,14 @@ def elaborate(self, platform): 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"): - 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): + with m.If(self.clk_negedge_next): m.d.sync += self.cs.eq(0) + with m.If(self.clk_posedge_next): + m.d.sync += [ + self.r_rdy.eq(1), + counter_en.eq(0) + ] + with m.If(self.r_rdy): m.next = "FASTREAD-IDLE" - with m.Elif(self.clk_posedge_next): - m.d.sync += state_counter.eq(state_counter + 1) return m diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index e2404d3..89b5887 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -47,11 +47,11 @@ def elaborate(self, platform): with m.State("INIT"): m.d.sync += data_sig.eq(self.data) with m.If(self.dut.cs): - with m.If(self.dut.counter == self.dut.divisor >> 1): + with m.If(self.dut.clk_posedge_next): m.d.comb += recv_data.w_en.eq(1) with m.Else(): m.d.comb += recv_data.w_en.eq(0) - with m.If(~recv_data.w_rdy & stored_data.w_rdy & (self.dut.counter == 0)): + with m.If(~recv_data.w_rdy & stored_data.w_rdy & self.dut.clk_negedge_next): m.next = "PUT-DATA" with m.State("PUT-DATA"): with m.If(stored_data_num_left != 0): @@ -66,9 +66,9 @@ def elaborate(self, platform): ] with m.Else(): m.d.comb += stored_data.w_en.eq(0) - with m.If((dummy_counter != 0) & (self.dut.counter == 0)): + with m.If((dummy_counter != 0) & self.dut.clk_negedge_next): m.d.sync += dummy_counter.eq(dummy_counter - 1) - with m.Elif((dummy_counter == 0) & (self.dut.counter == 0)): + with m.Elif((dummy_counter == 0) & self.dut.clk_negedge_next): m.d.comb += stored_data.r_en.eq(1) m.next = "RETURN-DATA" with m.State("RETURN-DATA"): From 5fd478de3865fda28972b57eb8c8761563d629b2 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Tue, 25 Feb 2020 16:20:58 +0800 Subject: [PATCH 15/19] spi: fix presence of WP & HOLD not checked --- nmigen_stdio/spiflash.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 8b89438..5dc655d 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -113,11 +113,11 @@ def _add_spi_hardware_logic(self, platform, module): counter_en = self.counter_en if self._pins is not None: - module.d.comb += [ - self._pins.cs.o.eq(self.cs), - self._pins.wp.eq(0), - self._pins.hold.eq(0) - ] + module.d.comb += self._pins.cs.o.eq(self.cs) + if hasattr(self._pins, "wp"): + module.d.comb += self._pins.wp.eq(0) + if hasattr(self._pins, "hold"): + module.d.comb += self._pins.hold.eq(0) # Platforms that require declaration of a user SPI clock signal # (e.g. by instantiating a USRMCLK Instance) must NOT pass a CLK on the SPI flash if hasattr(self._pins, "clk"): From 72c02900d3cc982ace9574adcc42f170a834a1e5 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Tue, 25 Feb 2020 19:03:39 +0800 Subject: [PATCH 16/19] spi: fix DQ pin not working --- nmigen_stdio/spiflash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 5dc655d..6758a7a 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -127,7 +127,7 @@ def _add_spi_hardware_logic(self, platform, module): module.d.comb += self._pins.mosi.o.eq(self.mosi) elif self._protocol in ["dual", "quad"]: self.dq_oe = Signal() - module.submodules.dq = platform.get_tristate(self.dq, self._pins.dq, None, False) + self.dq = self._pins.dq # If the user doesn't give pins, create dq Pins for Dual & Quad else: if self._protocol in ["dual", "quad"]: From b3d3c38eab3b12690169956b5397cb997a87f773 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Thu, 27 Feb 2020 21:13:34 +0800 Subject: [PATCH 17/19] spi: better fast read tests --- nmigen_stdio/test/test_spiflash.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index 89b5887..24fe56a 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -54,18 +54,6 @@ def elaborate(self, platform): with m.If(~recv_data.w_rdy & stored_data.w_rdy & self.dut.clk_negedge_next): m.next = "PUT-DATA" with m.State("PUT-DATA"): - with m.If(stored_data_num_left != 0): - m.d.comb += [ - stored_data.w_en.eq(1), - stored_data.w_data.eq(data_sig[-self.dut.spi_width:]) - ] - m.d.sync += [ - stored_data_num_left.eq(stored_data_num_left-1), - data_sig.eq(Cat(Repl(0, self.dut.spi_width), - data_sig[:-self.dut.spi_width])) - ] - with m.Else(): - m.d.comb += stored_data.w_en.eq(0) with m.If((dummy_counter != 0) & self.dut.clk_negedge_next): m.d.sync += dummy_counter.eq(dummy_counter - 1) with m.Elif((dummy_counter == 0) & self.dut.clk_negedge_next): @@ -89,6 +77,16 @@ def elaborate(self, platform): ] with m.Else(): m.d.comb += stored_data.w_en.eq(0) + with m.If((stored_data_num_left != 0) & ~fsm.ongoing("INIT")): + m.d.comb += [ + stored_data.w_en.eq(1), + stored_data.w_data.eq(data_sig[-self.dut.spi_width:]) + ] + m.d.sync += [ + stored_data_num_left.eq(stored_data_num_left-1), + data_sig.eq(Cat(Repl(0, self.dut.spi_width), + data_sig[:-self.dut.spi_width])) + ] with m.If(stored_data.r_rdy & stored_data.r_en): if self.dut.spi_width == 1: m.d.sync += self.dut.miso.eq(stored_data.r_data) @@ -164,7 +162,7 @@ def process(): while not (yield self.dut.rdy): yield yield self.dut.ack.eq(1) - for _ in range(10*self.divisor): - yield + while not (yield self.dut.cs): + yield # Test fails if simulation got stuck here simulation_test(m, process) From f56c9f12b9f257efc3f52d0656832ffa12399e01 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Sun, 1 Mar 2020 11:00:42 +0800 Subject: [PATCH 18/19] spi: remove granularity option --- nmigen_stdio/spiflash.py | 21 +++++++-------------- nmigen_stdio/test/test_spiflash.py | 3 --- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 6758a7a..856db9c 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -41,7 +41,7 @@ def _format_cmd(self): fcmd &= ~(1 << (bit*self.spi_width)) return fcmd - def __init__(self, *, protocol, data_width=32, granularity=8, + def __init__(self, *, protocol, data_width=32, divisor=1, divisor_bits=None, pins=None): if protocol not in ["standard", "dual", "quad"]: raise ValueError("Invalid SPI protocol {!r}; must be one of " @@ -49,19 +49,12 @@ def __init__(self, *, protocol, data_width=32, granularity=8, .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 {}" + raise ValueError("Bus 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)) + if data_width < 8 or data_width % 8: + raise ValueError("Data width must be divisible by 8 ({} % 8 != 0)" + .format(data_width)) self._data_width = data_width - self._granularity = granularity if divisor < 1: raise ValueError("Invalid divisor value; must be at least 1" @@ -234,7 +227,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 // self._granularity)), self.addr) + byte_addr = Cat(Repl(0, log2_int(self._data_width // 8)), self.addr) # FSM with m.FSM() as fsm: state_durations = { @@ -340,7 +333,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 // self._granularity)), self.addr) + byte_addr = Cat(Repl(0, log2_int(self._data_width // 8)), self.addr) # FSM with m.FSM() as fsm: state_durations = { diff --git a/nmigen_stdio/test/test_spiflash.py b/nmigen_stdio/test/test_spiflash.py index 24fe56a..2bac691 100644 --- a/nmigen_stdio/test/test_spiflash.py +++ b/nmigen_stdio/test/test_spiflash.py @@ -104,7 +104,6 @@ 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. @@ -119,7 +118,6 @@ 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. @@ -134,7 +132,6 @@ 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. From 6a1a23cc2d29f444a6b668c5c470dc5f5e60de29 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Wed, 22 Apr 2020 13:02:09 +0800 Subject: [PATCH 19/19] spi: fix typo --- nmigen_stdio/spiflash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nmigen_stdio/spiflash.py b/nmigen_stdio/spiflash.py index 856db9c..c2c737b 100644 --- a/nmigen_stdio/spiflash.py +++ b/nmigen_stdio/spiflash.py @@ -97,7 +97,7 @@ def __init__(self, *, protocol, data_width=32, 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 + """Add the fundamental hardware logic for controlling all SPI pins to be used """ if self.shreg is None: raise NotImplementedError("Shift register shreg has not been defined!")