From c64c89c6526b54a4401929a1e47f1f67dfe93697 Mon Sep 17 00:00:00 2001 From: Jeremy Herbert Date: Sat, 11 Mar 2023 13:41:10 +1000 Subject: [PATCH 1/2] add docstrings to bitbang --- litex/soc/cores/bitbang.py | 129 +++++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 35 deletions(-) diff --git a/litex/soc/cores/bitbang.py b/litex/soc/cores/bitbang.py index 8453c90f7..4ac78bb47 100644 --- a/litex/soc/cores/bitbang.py +++ b/litex/soc/cores/bitbang.py @@ -12,29 +12,41 @@ from litex.soc.interconnect.csr import * # I2C Master Bit-Banging --------------------------------------------------------------------------- class I2CMaster(Module, AutoCSR): - """I2C Master Bit-Banging + """I2C bus master (bit-banged). - Provides the minimal hardware to do software I2C Master bit banging. + This core provides minimal hardware for use as a software controlled bit-banged I2C bus master. I2C uses a + tristate/open-drain output driver configuration with pull-up resistors, and this core expects that the pull-ups will + be provided externally. - On the same write CSRStorage (_w), software can control: - - SCL (I2C_SCL). - - SDA direction and value (I2C_OE, I2C_W). + Further information about the I2C bus can be found in the I2C standard document from NXP, `UM10204`_. - Software get back SDA value with the read CSRStatus (_r). + .. _UM10204: https://www.pololu.com/file/0J435/UM10204.pdf """ pads_layout = [("scl", 1), ("sda", 1)] + def __init__(self, pads=None, default_dev=False): + """ + Class constructor. + + :param pads: (optional) A ``Record`` object containing the pads ``scl`` and ``sda``. + :param default_dev: (optional) A `bool` indicating whether this I2C master should be used as the default I2C + interface (default is ``False``) + """ self.init = [] if pads is None: pads = Record(self.pads_layout) self.pads = pads self._w = CSRStorage(fields=[ - CSRField("scl", size=1, offset=0, reset=1), - CSRField("oe", size=1, offset=1), - CSRField("sda", size=1, offset=2, reset=1)], + CSRField("scl", size=1, offset=0, reset=1, access=CSRAccess.WriteOnly, + description="Drives the state of the SCL pad."), + CSRField("oe", size=1, offset=1, access=CSRAccess.WriteOnly, + description="Output Enable - if 0, both the SCL and SDA output drivers are disconnected."), + CSRField("sda", size=1, offset=2, reset=1, access=CSRAccess.WriteOnly, + description="Drives the state of the SDA pad.")], name="w") self._r = CSRStatus(fields=[ - CSRField("sda", size=1, offset=0)], + CSRField("sda", size=1, offset=0, access=CSRAccess.ReadOnly, + description="Contains the current state of the SDA pad.")], name="r") self.default_dev = default_dev @@ -42,25 +54,48 @@ class I2CMaster(Module, AutoCSR): self.connect(pads) def connect(self, pads): + """ + Attaches the signals from inside the core to the input/output pads. This function is normally only called from + inside the class constructor. + + :param pads: A ``Record`` object containing the pads ``scl`` and ``sda``. + """ # SCL self.specials += Tristate(pads.scl, - o = 0, # I2C uses Pull-ups, only drive low. - oe = ~self._w.fields.scl # Drive when scl is low. + o = 0, # I2C uses Pull-ups, only drive low. + oe = ~self._w.fields.scl # Drive when scl is low. ) # SDA self.specials += Tristate(pads.sda, - o = 0, # I2C uses Pull-ups, only drive low. - oe = self._w.fields.oe & ~self._w.fields.sda, # Drive when oe and sda is low. + o = 0, # I2C uses Pull-ups, only drive low. + oe = self._w.fields.oe & ~self._w.fields.sda, # Drive when oe and sda is low. i = self._r.fields.sda ) def add_init(self, addr, init, init_addr_len=1): + """ + Adds an I2C write transaction that will be executed on startup. This method can be called multiple times to add + multiple transactions that will be executed in order for this core instance. + + :param addr: The I2C slave address to write to + :param init: The bytes to write to the slave. + :param init_addr_len: (optional) The init address length in bytes (default is 1) + """ + if init_addr_len not in (1, 2): + raise ValueError("I2C slave addresses can only have a length of one or two bytes") + + if init_addr_len == 1 and not 0 <= addr <= 127: + raise ValueError("I2C slave address must be between 0 and 127 (inclusive)") + elif init_addr_len == 2 and not 0 <= addr <= 1023: + raise ValueError("I2C slave address must be between 0 and 1023 (inclusive)") + self.init.append((addr, init, init_addr_len)) -class I2CMasterSim(I2CMaster): - """I2C Master Bit-Banging for Verilator simulation - Uses separate pads for SDA IN/OUT as Verilator does not support tristate pins well. +class I2CMasterSim(I2CMaster): + """I2C bus master (bit-banged) for Verilator simulation + + This core uses separate pads for SDA IN/OUT as Verilator does not support tristate pins well. """ pads_layout = [("scl", 1), ("sda_in", 1), ("sda_out", 1)] @@ -88,44 +123,68 @@ class I2CMasterSim(I2CMaster): # TODO: Find a more generic way to do it that would also apply to other peripherals? def collect_i2c_info(soc): + """ + Collects all the I2C write transactions that have been added to run on startup for all ``I2CMaster`` instances + into a single list. This information is used to generate C header files in ``litex.soc.integration.export``. + + See ``I2CMaster.add_init`` for more information. + + :param soc: ``SoCBase`` instance to scan for ``I2CMaster`` instances. + :return: ``i2c_devs, i2c_init`` where ``i2c_devs`` is a list of all ``I2CMaster`` instances, and ``i2c_init`` is + a list of tuples, where each tuple is (core instance name, slave address, bytes to write, slave address + length in bytes) + """ i2c_init = [] i2c_devs = [] for name, obj in xdir(soc, True): if isinstance(obj, I2CMaster): soc.add_config("HAS_I2C", check_duplicate=False) - i2c_devs.append((name, getattr(obj, "default_dev"))) - if hasattr(obj, "init"): - for addr, init, init_addr_len in obj.init: - i2c_init.append((name, addr, init, init_addr_len)) + i2c_devs.append((name, obj.default_dev)) + for addr, init, init_addr_len in obj.init: + i2c_init.append((name, addr, init, init_addr_len)) return i2c_devs, i2c_init # SPI Master Bit-Banging --------------------------------------------------------------------------- class SPIMaster(Module, AutoCSR): - """3/4-wire SPI Master Bit-Banging + """3/4-wire SPI bus master (bit-banged). - Provides the minimal hardware to do software 3/4-wire SPI Master bit banging. + This core provides minimal hardware for use as a software controlled bit-banged SPI bus master. - On the same write CSRStorage (_w), software can control CLK (SPI_CLK), MOSI (SPI_MOSI), MOSI - direction (SPI_OE) in the case 3-wire SPI and up to 4 Chip Selects (SPI_CS). Software get back - MISO (SPI_MISO) with the read CSRStatus (_r). + This core supports the typical SPI pads (MOSI, MISO, CLK) and a maximum of 4 CS outputs. If pull-up resistors are + needed for 3 wire operation, they must be added externally. """ pads_layout = [("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)] + def __init__(self, pads=None): + """ + Class constructor. + + :param pads: (optional) A ``Record`` object containing: ``clk``, ``cs_n``, ``mosi`` and ``miso``. + """ if pads is None: pads = Record(self.pads_layout) self.pads = pads - assert len(pads.cs_n) <= 4 + + if len(pads.cs_n) > 4: + raise ValueError("This core only supports a maximum of 4 CS outputs") + self._w = CSRStorage(fields=[ - CSRField("clk", size=1, offset=0), - CSRField("mosi", size=1, offset=1), - CSRField("oe", size=1, offset=2), - CSRField("cs", size=1, offset=4)], - name="w") + CSRField("clk", size=1, offset=0, access=CSRAccess.WriteOnly, + description="Drives the state of the CLK pad."), + CSRField("mosi", size=1, offset=1, access=CSRAccess.WriteOnly, + description="Drives the state of the MOSI pad."), + CSRField("oe", size=1, offset=2, access=CSRAccess.WriteOnly, + description="Output Enable for MOSI - if 0, the MOSI output driver is disconnected."), + CSRField("cs", size=4, offset=4, access=CSRAccess.WriteOnly, + description="Drives the state of the CS pads (up to 4, active high).")], + name="w", description="SPI master output pad controls.") self._r = CSRStatus(fields=[ - CSRField("miso", size=1, offset=0), - CSRField("mosi", size=1, offset=1)], - name="r") + CSRField("miso", size=1, offset=0, access=CSRAccess.ReadOnly, + description="Contains the current state of the MISO pad."), + CSRField("mosi", size=1, offset=1, access=CSRAccess.ReadOnly, + description="Contains the current state of the MOSI pad.")], + name="r", description="SPI master input pad states.") # # # From 1256ca3767d0e26e7efc8090612b067ea59fdf22 Mon Sep 17 00:00:00 2001 From: Jeremy Herbert Date: Tue, 14 Mar 2023 11:38:24 +1000 Subject: [PATCH 2/2] small doc fixes, add type hints and PEP8 whitespace --- litex/soc/cores/bitbang.py | 129 ++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 59 deletions(-) diff --git a/litex/soc/cores/bitbang.py b/litex/soc/cores/bitbang.py index 4ac78bb47..1e293734c 100644 --- a/litex/soc/cores/bitbang.py +++ b/litex/soc/cores/bitbang.py @@ -4,19 +4,22 @@ # Copyright (c) 2019 Florent Kermarrec # SPDX-License-Identifier: BSD-2-Clause +from typing import Optional, Tuple, List + from migen import * from migen.fhdl.specials import Tristate from litex.soc.interconnect.csr import * + # I2C Master Bit-Banging --------------------------------------------------------------------------- class I2CMaster(Module, AutoCSR): """I2C bus master (bit-banged). - This core provides minimal hardware for use as a software controlled bit-banged I2C bus master. I2C uses a - tristate/open-drain output driver configuration with pull-up resistors, and this core expects that the pull-ups will - be provided externally. + This core provides minimal hardware for use as a software controlled bit-banged I2C bus master via a memory-mapped + interface. I2C uses a tristate/open-drain output driver configuration with pull-up resistors, and this core expects + that the pull-ups willbe provided externally. Further information about the I2C bus can be found in the I2C standard document from NXP, `UM10204`_. @@ -24,36 +27,38 @@ class I2CMaster(Module, AutoCSR): """ pads_layout = [("scl", 1), ("sda", 1)] - def __init__(self, pads=None, default_dev=False): + def __init__(self, pads: Optional[Record] = None, default_dev: bool = False): """ Class constructor. :param pads: (optional) A ``Record`` object containing the pads ``scl`` and ``sda``. - :param default_dev: (optional) A `bool` indicating whether this I2C master should be used as the default I2C + :param default_dev: (optional) A ``bool`` indicating whether this I2C master should be used as the default I2C interface (default is ``False``) """ self.init = [] + if pads is None: pads = Record(self.pads_layout) self.pads = pads + self._w = CSRStorage(fields=[ - CSRField("scl", size=1, offset=0, reset=1, access=CSRAccess.WriteOnly, - description="Drives the state of the SCL pad."), - CSRField("oe", size=1, offset=1, access=CSRAccess.WriteOnly, - description="Output Enable - if 0, both the SCL and SDA output drivers are disconnected."), - CSRField("sda", size=1, offset=2, reset=1, access=CSRAccess.WriteOnly, - description="Drives the state of the SDA pad.")], - name="w") + CSRField("scl", size=1, offset=0, reset=1, description="Drives the state of the SCL pad."), + CSRField("oe", size=1, offset=1, + description="Output Enable - if 0, both the SCL and SDA output drivers are disconnected."), + CSRField("sda", size=1, offset=2, reset=1, description="Drives the state of the SDA pad.") + ], + name="w", description="I2C master output pad controls.") + self._r = CSRStatus(fields=[ - CSRField("sda", size=1, offset=0, access=CSRAccess.ReadOnly, - description="Contains the current state of the SDA pad.")], - name="r") + CSRField("sda", size=1, offset=0, description="Contains the current state of the SDA pad.") + ], + name="r", description="SPI master input pad states.") self.default_dev = default_dev - self.connect(pads) + self._connect(pads) - def connect(self, pads): + def _connect(self, pads: Record): """ Attaches the signals from inside the core to the input/output pads. This function is normally only called from inside the class constructor. @@ -61,18 +66,20 @@ class I2CMaster(Module, AutoCSR): :param pads: A ``Record`` object containing the pads ``scl`` and ``sda``. """ # SCL - self.specials += Tristate(pads.scl, - o = 0, # I2C uses Pull-ups, only drive low. - oe = ~self._w.fields.scl # Drive when scl is low. + self.specials += Tristate( + pads.scl, + o=0, # I2C uses Pull-ups, only drive low. + oe=~self._w.fields.scl # Drive when scl is low. ) # SDA - self.specials += Tristate(pads.sda, - o = 0, # I2C uses Pull-ups, only drive low. - oe = self._w.fields.oe & ~self._w.fields.sda, # Drive when oe and sda is low. - i = self._r.fields.sda + self.specials += Tristate( + pads.sda, + o=0, # I2C uses Pull-ups, only drive low. + oe=self._w.fields.oe & ~self._w.fields.sda, # Drive when oe and sda is low. + i=self._r.fields.sda ) - def add_init(self, addr, init, init_addr_len=1): + def add_init(self, addr: int, init: bytes, init_addr_len: int = 1): """ Adds an I2C write transaction that will be executed on startup. This method can be called multiple times to add multiple transactions that will be executed in order for this core instance. @@ -99,36 +106,37 @@ class I2CMasterSim(I2CMaster): """ pads_layout = [("scl", 1), ("sda_in", 1), ("sda_out", 1)] - def connect(self, pads): - _sda_w = Signal() + def _connect(self, pads: Record): + _sda_w = Signal() _sda_oe = Signal() - _sda_r = Signal() + _sda_r = Signal() _sda_in = Signal() self.comb += [ pads.scl.eq(self._w.fields.scl), - _sda_oe.eq( self._w.fields.oe), - _sda_w.eq( self._w.fields.sda), + _sda_oe.eq(self._w.fields.oe), + _sda_w.eq(self._w.fields.sda), If(_sda_oe, - pads.sda_out.eq(_sda_w), - self._r.fields.sda.eq(_sda_w), - ).Else( + pads.sda_out.eq(_sda_w), + self._r.fields.sda.eq(_sda_w), + ).Else( pads.sda_out.eq(1), self._r.fields.sda.eq(pads.sda_in), ) ] + # I2C Master Info Collection ---------------------------------------------------------------------- -# TODO: Find a more generic way to do it that would also apply to other peripherals? - -def collect_i2c_info(soc): +def collect_i2c_info(soc) -> Tuple[List[Tuple[str, bool]], List[Tuple[str, int, bytes, int]]]: """ Collects all the I2C write transactions that have been added to run on startup for all ``I2CMaster`` instances into a single list. This information is used to generate C header files in ``litex.soc.integration.export``. See ``I2CMaster.add_init`` for more information. + TODO: Find a more generic way to do it that would also apply to other peripherals? + :param soc: ``SoCBase`` instance to scan for ``I2CMaster`` instances. :return: ``i2c_devs, i2c_init`` where ``i2c_devs`` is a list of all ``I2CMaster`` instances, and ``i2c_init`` is a list of tuples, where each tuple is (core instance name, slave address, bytes to write, slave address @@ -144,23 +152,26 @@ def collect_i2c_info(soc): i2c_init.append((name, addr, init, init_addr_len)) return i2c_devs, i2c_init + # SPI Master Bit-Banging --------------------------------------------------------------------------- class SPIMaster(Module, AutoCSR): """3/4-wire SPI bus master (bit-banged). - This core provides minimal hardware for use as a software controlled bit-banged SPI bus master. + This core provides minimal hardware for use as a software controlled bit-banged SPI bus master via a memory-mapped + interface. This core supports the typical SPI pads (MOSI, MISO, CLK) and a maximum of 4 CS outputs. If pull-up resistors are needed for 3 wire operation, they must be added externally. """ pads_layout = [("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)] - def __init__(self, pads=None): + def __init__(self, pads: Optional[Record] = None): """ Class constructor. - :param pads: (optional) A ``Record`` object containing: ``clk``, ``cs_n``, ``mosi`` and ``miso``. + :param pads: (optional) A ``Record`` object containing: ``clk``, ``cs_n``, ``mosi`` and ``miso``. ``cs_n`` may + have a length of less than or equal to 4. """ if pads is None: pads = Record(self.pads_layout) @@ -170,35 +181,35 @@ class SPIMaster(Module, AutoCSR): raise ValueError("This core only supports a maximum of 4 CS outputs") self._w = CSRStorage(fields=[ - CSRField("clk", size=1, offset=0, access=CSRAccess.WriteOnly, - description="Drives the state of the CLK pad."), - CSRField("mosi", size=1, offset=1, access=CSRAccess.WriteOnly, - description="Drives the state of the MOSI pad."), - CSRField("oe", size=1, offset=2, access=CSRAccess.WriteOnly, - description="Output Enable for MOSI - if 0, the MOSI output driver is disconnected."), - CSRField("cs", size=4, offset=4, access=CSRAccess.WriteOnly, - description="Drives the state of the CS pads (up to 4, active high).")], + CSRField("clk", size=1, offset=0, description="Drives the state of the CLK pad."), + CSRField("mosi", size=1, offset=1, description="Drives the state of the MOSI pad."), + CSRField("oe", size=1, offset=2, + description="Output Enable for MOSI - if 0, the MOSI output driver is disconnected."), + CSRField("cs", size=4, offset=4, + description="Drives the state of the CS pads (up to 4, active high).") + ], name="w", description="SPI master output pad controls.") + self._r = CSRStatus(fields=[ - CSRField("miso", size=1, offset=0, access=CSRAccess.ReadOnly, - description="Contains the current state of the MISO pad."), - CSRField("mosi", size=1, offset=1, access=CSRAccess.ReadOnly, - description="Contains the current state of the MOSI pad.")], + CSRField("miso", size=1, offset=0, description="Contains the current state of the MISO pad."), + CSRField("mosi", size=1, offset=1, description="Contains the current state of the MOSI pad.") + ], name="r", description="SPI master input pad states.") - # # # - - _mosi_w = Signal() + _mosi_w = Signal() _mosi_oe = Signal() - _mosi_r = Signal() - _cs = Signal(4) + _mosi_r = Signal() + _cs = Signal(4) + self.comb += [ - pads.clk.eq( self._w.fields.clk), - _mosi_w.eq( self._w.fields.mosi), - _mosi_oe.eq( self._w.fields.oe), + pads.clk.eq(self._w.fields.clk), + _mosi_w.eq(self._w.fields.mosi), + _mosi_oe.eq(self._w.fields.oe), pads.cs_n.eq(~self._w.fields.cs), self._r.fields.mosi.eq(_mosi_r), ] + if hasattr(pads, "miso"): self.comb += self._r.fields.miso.eq(pads.miso) + self.specials += Tristate(pads.mosi, _mosi_w, _mosi_oe, _mosi_r)