diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..cffc922b0 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake . --impure diff --git a/.gitignore b/.gitignore index ccf975800..c2661557b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ /*.bit /*.json /*.csv + +.direnv + +# Nix build outputs +/result +/result-* diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..891b3ffc9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,76 @@ +{ + "nodes": { + "flake-compat": { + "locked": { + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1737469691, + "narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..63a59566e --- /dev/null +++ b/flake.nix @@ -0,0 +1,60 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + flake-compat.url = "github:edolstra/flake-compat"; + }; + outputs = + { nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + lib = pkgs.lib; + in + { + packages = rec { + glasgow = pkgs.glasgow.overrideAttrs { + version = "0+unstable-memory-24x-emu"; + src = ./.; + doInstallCheck = false; + }; + default = glasgow; + }; + + devShell = pkgs.mkShell { + packages = with pkgs; [ + # Must come before yosys in this array, so it has precedence + # over the propagatedBuildInput python3 from yosys. + (python3.withPackages ( + pypkgs: with pypkgs; [ + typing-extensions + amaranth + packaging + platformdirs + fx2 + libusb1 + pyvcd + aiohttp + + unittestCheckHook + ] + )) + + yosys + icestorm + nextpnr + + sdcc + + ]; + + YOSYS = "${lib.getBin pkgs.yosys}/bin/yosys"; + ICEPACK = "${lib.getBin pkgs.icestorm}/bin/icepack"; + NEXTPNR_ICE40 = "${lib.getBin pkgs.nextpnr}/bin/nextpnr-ice40"; + }; + + formatter = pkgs.nixfmt-rfc-style; + } + ); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..942ce016a --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).shellNix diff --git a/software/glasgow/applet/memory/_24x_emu/README.md b/software/glasgow/applet/memory/_24x_emu/README.md new file mode 100644 index 000000000..47ad05cfe --- /dev/null +++ b/software/glasgow/applet/memory/_24x_emu/README.md @@ -0,0 +1,55 @@ +# 24-series I²C EEPROM emulation applet + +Emulates the read and write operations of a generic 24-series I²C EEPROM. The memory size, address width, and initial contents can be chosen at build time. + +Operations besides read and write operations no further operations are implemented. Reads and writes are not paged and may be performed in arbitrarily sized batches (reads only or writes only). Paging reliant behavior like writes/reads wrapping around to the start of a page when the end of a page is reached in an operation is not supported. All reads and writes will always access the current address, wrapping around only once the end of the memory is reached. + + + +## Random Address Read +During a random address read the I²C initiator sends a start strobe initiating a write and writes as many bytes as the chosen address width. This is followed by a restart strobe initiating a read. This read will return the byte from the selected address. + +Deviating from I²C devices, the initiator may follow up with further reads. Each read will increment the address being read from. See [Sequential Read](#sequential-read) + +```mermaid +--- +title: Random Address Read +--- +packet-beta + 0: "STA" + 1-7: "I²C device address" + 8: "R/W" + 9: "ACK" + 10-17: "memory address (= n)" + 18: "ACK" + 19: "STA" + 20-26: "I²C device address" + 27: "R/w" + 28: "ACK" + 29-36: "data (n)" + 37: "NAK" + 38: "STO" +``` + +## Sequential Read +During a sequential read, the initiator sends a start strobe initiating a read. The read will return the byte in memory stored at the current address. This may be followed by as many further reads as desired. Each read increments the current address, i.e. consecutive reads will read bytes sequentially from memory. + +```mermaid +--- +title: Sequential Read +--- +packet-beta + 0: "STA" + 1-7: "I²C device address" + 8: "R/w" + 9: "ACK" + 10-17: "data (n)" + 18: "ACK" + 19-26: "data (n + 1)" + 27: "NAK" + 28: "STO" +``` diff --git a/software/glasgow/applet/memory/_24x_emu/__init__.py b/software/glasgow/applet/memory/_24x_emu/__init__.py new file mode 100644 index 000000000..d2341f43c --- /dev/null +++ b/software/glasgow/applet/memory/_24x_emu/__init__.py @@ -0,0 +1,452 @@ +import argparse +import logging +import enum +from typing import Any, Optional +from amaranth import * +from amaranth.lib.memory import Memory + +from ....access import ( + AccessArguments, + AccessDemultiplexerInterface, + AccessMultiplexer, +) +from ....applet import GlasgowApplet +from ....device.hardware import GlasgowHardwareDevice +from ....gateware.i2c import I2CTarget +from ....gateware.ports import PortGroup +from ....target.analyzer import GlasgowAnalyzer +from ....target.hardware import GlasgowHardwareTarget + + +class Event(enum.IntEnum): + READ = 0x10 + "Memory read performed. Followed by the current address and the data byte returned in the read" + WRITE = 0x20 + "Memory write performed. Followed by the current address and the data byte written to our memory" + + +class Memory24xEmuSubtarget(Elaboratable): + def __init__( + self, + ports: PortGroup, + in_fifo, + i2c_address: int, + address_width: int, + initial_data: bytearray, + analyzer: GlasgowAnalyzer = None, + ): + self.ports = ports + self.in_fifo = in_fifo + self.i2c_address = i2c_address + + self.address_width = address_width + self.initial_data = initial_data + + self.current_address = Signal(8 * self.address_width) + self.incoming_write_data = Signal(8) + self.incoming_address_byte_index = Signal(range(self.address_width)) + self.incoming_address = Signal(8 * self.address_width) + + self.i2c_target = I2CTarget(self.ports, analyzer=analyzer) + + def elaborate(self, platform): + m = Module() + + m.submodules.memory = memory = Memory( + shape=unsigned(8), depth=len(self.initial_data), init=self.initial_data + ) + wr_port = memory.write_port() + rd_port = memory.read_port(domain="comb") + m.d.comb += [ + wr_port.addr.eq(self.current_address), + rd_port.addr.eq(self.current_address), + ] + + m.submodules.i2c_target = i2c_target = self.i2c_target + m.d.comb += [ + i2c_target.address.eq(self.i2c_address), + i2c_target.data_o.eq(rd_port.data), + ] + + # TODO: handle invalid command ordering + with m.FSM(): + m.d.comb += i2c_target.busy.eq(1) + + with m.State("IDLE"): + m.d.comb += i2c_target.busy.eq(0) + + with m.If(i2c_target.start): + m.next = "TRANSACTION-STARTED" + + with m.State("TRANSACTION-STARTED"): + m.d.comb += i2c_target.busy.eq(0) + + with m.If(i2c_target.write): + m.d.sync += [ + self.incoming_address.eq(i2c_target.data_i), + self.incoming_address_byte_index.eq(0), + ] + m.d.comb += i2c_target.ack_o.eq(1) + m.next = "RECEIVING-ADDRESS" + + with m.Elif(i2c_target.read): + m.next = "BEING-READ-SEND-EVENT" + + with m.Elif(i2c_target.stop): + m.next = "IDLE" + + with m.State("RECEIVING-ADDRESS"): + m.d.comb += i2c_target.busy.eq(0) + + with m.If( + (self.incoming_address_byte_index < self.address_width - 1) + & i2c_target.write + ): + m.d.sync += [ + self.incoming_address.eq( + self.incoming_address << 8 | i2c_target.data_i + ), + self.incoming_address_byte_index.eq( + self.incoming_address_byte_index + 1 + ), + ] + m.d.comb += i2c_target.ack_o.eq(1) + m.next = "RECEIVING-ADDRESS" + + with m.Elif(i2c_target.write): + m.d.sync += [ + self.incoming_write_data.eq(i2c_target.data_i), + self.current_address.eq( + self.incoming_address % len(self.initial_data) + ), + ] + m.d.comb += i2c_target.ack_o.eq(1) + m.next = "BEING-WRITTEN-SEND-EVENT" + + with m.Elif(i2c_target.read): + m.d.sync += self.current_address.eq( + self.incoming_address % len(self.initial_data) + ) + m.next = "BEING-READ-SEND-EVENT" + + with m.Elif(i2c_target.stop): + m.d.sync += self.current_address.eq( + self.incoming_address % len(self.initial_data) + ) + m.next = "IDLE" + + # --- Reading --- + + with m.State("BEING-READ-SEND-EVENT"): + m.d.comb += [ + self.in_fifo.w_data.eq(Event.READ), + self.in_fifo.w_en.eq(1), + ] + m.next = "BEING-READ-SEND-ADDRESS" + + with m.State("BEING-READ-SEND-ADDRESS"): + m.d.comb += [ + self.in_fifo.w_data.eq(self.current_address), + self.in_fifo.w_en.eq(1), + ] + m.next = "BEING-READ-SEND-DATA" + + with m.State("BEING-READ-SEND-DATA"): + m.d.comb += [ + self.in_fifo.w_data.eq(rd_port.data), + self.in_fifo.w_en.eq(1), + ] + m.d.sync += self.current_address.eq( + (self.current_address + 1) % len(self.initial_data) + ) + m.next = "BEING-READ" + + with m.State("BEING-READ"): + m.d.comb += i2c_target.busy.eq(0) + + with m.If(i2c_target.read): + m.next = "BEING-READ-SEND-EVENT" + + with m.If(i2c_target.stop): + m.next = "IDLE" + + # --- Writing --- + + with m.State("BEING-WRITTEN-SEND-EVENT"): + m.d.comb += [ + self.in_fifo.w_data.eq(Event.WRITE), + self.in_fifo.w_en.eq(1), + wr_port.en.eq(1), + ] + + m.d.sync += wr_port.data.eq(self.incoming_write_data) + + m.next = "BEING-WRITTEN-SEND-ADDRESS" + + with m.State("BEING-WRITTEN-SEND-ADDRESS"): + m.d.comb += [ + self.in_fifo.w_data.eq(self.current_address), + self.in_fifo.w_en.eq(1), + ] + m.next = "BEING-WRITTEN-SEND-DATA" + + with m.State("BEING-WRITTEN-SEND-DATA"): + m.d.comb += [ + self.in_fifo.w_data.eq(self.incoming_write_data), + self.in_fifo.w_en.eq(1), + ] + m.d.sync += self.current_address.eq( + (self.current_address + 1) % len(self.initial_data) + ) + m.next = "BEING-WRITTEN" + + with m.State("BEING-WRITTEN"): + m.d.comb += i2c_target.busy.eq(0) + + with m.If(i2c_target.write): + m.d.sync += self.incoming_write_data.eq(i2c_target.data_i) + m.d.comb += i2c_target.ack_o.eq(1) + m.next = "BEING-WRITTEN-SEND-EVENT" + + with m.Elif(i2c_target.stop): + m.next = "IDLE" + + return m + + +class Memory24xEmuInterface: + + def __init__(self, interface: AccessDemultiplexerInterface, logger: logging.Logger): + self.lower = interface + self._logger = logger + self._level: int = ( + logging.DEBUG if self._logger.name == __name__ else logging.TRACE + ) + + def _log(self, message: str): + self._logger.log(self._level, "I²C: %s", message) + + async def read_event(self): + event: int = (await self.lower.read(1))[0] + if event == Event.WRITE: + address, data_byte = await self.lower.read(2) + + self._log( + f"Written to at <0x{address:02x}>: 0x{data_byte:02x} ({bytes([data_byte])!r})" + ) + + elif event == Event.READ: + address, data_byte = await self.lower.read(2) + + self._log( + f"Read at <0x{address:02x}>: 0x{data_byte:02x} ({bytes([data_byte])!r})" + ) + + +class Memory24xEmuApplet(GlasgowApplet): + logger = logging.getLogger(__name__) + help = "emulate a 24-series I²C EEPROM" + description = """ + FIXME: description + """ + required_revision = "C0" + + mux_interface: AccessMultiplexer + + @classmethod + def add_build_arguments( + cls, parser: argparse.ArgumentParser, access: AccessArguments + ): + super().add_build_arguments(parser, access) + + access.add_pin_argument(parser, "scl", default=True) + access.add_pin_argument(parser, "sda", default=True) + + def i2c_address(arg: str) -> int: + return int(arg, base=0) + + def data_str(arg: str) -> bytes: + # `unicode_escape` decodes as latin-1, resolving python backslash-escapes + # + # The round-trip through latin-1 de-/encoding with resolution of + # backslash escape sequences preserves byte values of utf-8 encoded chars. + # The intermediary python unicode string is however not sensible, + # only the end result. + return arg.encode("utf_8").decode("unicode_escape").encode("latin_1") + + def filler_byte(arg: str): + val = int(arg, base=0) + assert val >= -128 and val <= 255, "filler value must fit into a byte" + return val + + parser.add_argument( + "-A", + "--i2c-address", + metavar="I2C-ADDR", + help="I²C address of the target", + type=i2c_address, + required=True, + ) + parser.add_argument( + "-w", + "--address-width", + metavar="ADDR-WIDTH", + help="Width of the memory addresses in bytes", + type=int, + default=1, + ) + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-d", + "--init-data", + metavar="INIT-DATA", + help="Data the memory is initialized with", + type=data_str, + ) + group.add_argument( + "-f", + "--init-data-file", + metavar="INIT-DATA-FILE", + help="File to read data bytes from with which the memory is initialized", + type=argparse.FileType("rb"), + ) + + parser.add_argument( + "-s", + "--memory-size", + metavar="MEM-SIZE", + help="Size of the memory in bytes (len(init_data) + init_data_offset by default)", + type=int, + ) + parser.add_argument( + "--init-data-offset", + metavar="INIT-DATA-OFFSET", + help="Offset the initial data within the memory by this amount of bytes", + type=int, + ) + parser.add_argument( + "--filler-byte", + metavar="FILLER-BYTE", + help="Byte with which to fill the remaining memory not covered by the provided initial data", + type=filler_byte, + default=0, + ) + + def build(self, target: GlasgowHardwareTarget, args: argparse.Namespace): + self.mux_interface = iface = target.multiplexer.claim_interface(self, args) + + init_data_offset = ( + args.init_data_offset if args.init_data_offset is not None else 0 + ) + + init_data = bytes([]) + init_data_not_provided = False + if args.init_data is not None: + init_data = args.init_data + elif args.init_data_file is not None: + init_data = args.init_data_file.read() + args.init_data_file.close() + else: + init_data_not_provided = True + + if init_data_not_provided and args.init_data_offset is not None: + raise argparse.ArgumentError( + None, + "--init-data-offset requires one of -d/--init-data or -f/--init-data-file to be specified", + ) + + memory_size = ( + args.memory_size + if args.memory_size is not None + else len(init_data) + init_data_offset + ) + + if args.memory_size is None and init_data_not_provided: + memory_size = 4000 + + # Pad start according to offset + init_data = bytes([args.filler_byte]) * init_data_offset + init_data + # Pad end according to size + init_data += bytes([args.filler_byte]) * max(0, memory_size - len(init_data)) + + if args.memory_size is not None and len(init_data) > args.memory_size: + self.logger.log( + "Initial data exceeds specified memory size after applying the offset. Extending size to make it fit" + ) + + ports = iface.get_port_group(scl=args.pin_scl, sda=args.pin_sda) + + subtarget = Memory24xEmuSubtarget( + ports=ports, + in_fifo=iface.get_in_fifo(), + i2c_address=args.i2c_address, + address_width=args.address_width, + initial_data=init_data, + analyzer=target.analyzer, + ) + + if target.analyzer: + analyzer = target.analyzer + + analyzer.add_generic_event( + self, + "memory_24x_emu-current_address", + subtarget.current_address, + ) + analyzer.add_generic_event( + self, + "memory_24x_emu-incoming_incoming_write_data", + subtarget.incoming_write_data, + ) + if args.address_width > 1: + # With an address_width <= 1 the incoming_address_byte_index is a 0-width signal, + # because it only requires one state (last and only address byte being received) + # The analyzer doesn't like zero width signals ^^ + analyzer.add_generic_event( + self, + "memory_24x_emu-incoming_address_byte_index", + subtarget.incoming_address_byte_index, + ) + analyzer.add_generic_event( + self, "memory_24x_emu-incoming_address", subtarget.incoming_address + ) + + iface.add_subtarget(subtarget) + + @classmethod + def add_run_arguments( + cls, parser: argparse._ArgumentGroup, access: AccessArguments + ): + super().add_run_arguments(parser, access) + + parser.add_argument( + "--pulls", + default=False, + action="store_true", + help="enable integrated pull-ups", + ) + + async def run(self, device: GlasgowHardwareDevice, args: argparse.Namespace): + pulls = set() + if args.pulls: + pulls = {args.pin_scl, args.pin_sda} + iface = await device.demultiplexer.claim_interface( + self, self.mux_interface, args, pull_high=pulls + ) + return Memory24xEmuInterface(iface, self.logger) + + async def interact( + self, + device: GlasgowHardwareDevice, + args: argparse.Namespace, + iface: Memory24xEmuInterface, + ): + while True: + await iface.read_event() + + @classmethod + def tests(cls): + from . import test + + return test.Memory24xEmuAppletTestCase diff --git a/software/glasgow/applet/memory/_24x_emu/test.py b/software/glasgow/applet/memory/_24x_emu/test.py new file mode 100644 index 000000000..fc8d9d455 --- /dev/null +++ b/software/glasgow/applet/memory/_24x_emu/test.py @@ -0,0 +1,8 @@ +from ... import * +from . import Memory24xEmuApplet + + +class Memory24xEmuAppletTestCase(GlasgowAppletTestCase, applet=Memory24xEmuApplet): + @synthesis_test + def test_build(self): + self.assertBuilds(args=["-A", "0b1010000"]) diff --git a/software/glasgow/gateware/i2c.py b/software/glasgow/gateware/i2c.py index 7c941c29c..4455c95c3 100644 --- a/software/glasgow/gateware/i2c.py +++ b/software/glasgow/gateware/i2c.py @@ -14,7 +14,7 @@ class I2CBus(Elaboratable): Decodes bus conditions (start, stop, sample and setup) and provides synchronization. """ - def __init__(self, pads): + def __init__(self, pads, analyzer=None): self.pads = pads self.scl_i = Signal() @@ -27,11 +27,28 @@ def __init__(self, pads): self.start = Signal(name="bus_start") self.stop = Signal(name="bus_stop") + self.scl_t = io.Buffer("io", self.pads.scl) + self.sda_t = io.Buffer("io", self.pads.sda) + + if analyzer: + analyzer.add_generic_event(None, "i2c-scl_i", self.scl_i) + analyzer.add_generic_event(None, "i2c-scl_o", self.scl_o) + analyzer.add_generic_event(None, "i2c-sda_i", self.sda_i) + analyzer.add_generic_event(None, "i2c-sda_o", self.sda_o) + + analyzer.add_generic_event(None, "i2c-sample", self.sample) + analyzer.add_generic_event(None, "i2c-setup", self.setup) + analyzer.add_generic_event(None, "i2c-start", self.start) + analyzer.add_generic_event(None, "i2c-stop", self.stop) + + analyzer.add_pin_event(None, "i2c-scl_t", self.scl_t) + analyzer.add_pin_event(None, "i2c-sda_t", self.sda_t) + def elaborate(self, platform): m = Module() - m.submodules.io_scl = scl_t = io.Buffer("io", self.pads.scl) - m.submodules.io_sda = sda_t = io.Buffer("io", self.pads.sda) + m.submodules.io_scl = scl_t = self.scl_t + m.submodules.io_sda = sda_t = self.sda_t scl_r = Signal(init=1) sda_r = Signal(init=1) @@ -99,7 +116,7 @@ class I2CInitiator(Elaboratable): :attr ack_i: Acknowledge bit to be transmitted. Latched immediately after ``read`` is asserted. """ - def __init__(self, pads, period_cyc, clk_stretch=True): + def __init__(self, pads, period_cyc, clk_stretch=True, analyzer=None): self.period_cyc = int(period_cyc) self.clk_stretch = clk_stretch @@ -113,7 +130,18 @@ def __init__(self, pads, period_cyc, clk_stretch=True): self.data_o = Signal(8) self.ack_i = Signal() - self.bus = I2CBus(pads) + self.bus = I2CBus(pads, analyzer=analyzer) + + if analyzer: + analyzer.add_generic_event(None, "i2c-i-busy", self.busy) + analyzer.add_generic_event(None, "i2c-i-start", self.start) + analyzer.add_generic_event(None, "i2c-i-stop", self.stop) + analyzer.add_generic_event(None, "i2c-i-read", self.read) + analyzer.add_generic_event(None, "i2c-i-data_i", self.data_i) + analyzer.add_generic_event(None, "i2c-i-ack_o", self.ack_o) + analyzer.add_generic_event(None, "i2c-i-write", self.write) + analyzer.add_generic_event(None, "i2c-i-data_o", self.data_o) + analyzer.add_generic_event(None, "i2c-i-ack_i", self.ack_i) def elaborate(self, platform): m = Module() @@ -265,6 +293,14 @@ class I2CTarget(Elaboratable): :attr address: The 7-bit address the target will respond to. + :attr address_mask: + Address bits to mask when checking if a command is meant for this target. + For regular I2C targets, this should be all ones. + For e.g. a 24x-series EEPROM that uses the last 3 address bit for its own purposes, + set this to 0b111_1000. This way only the first 4 address bits are matched against. + :attr address_i: + The 7-bit address received from the initiator after the last start strobe. + Valid as soon as ``start`` becomes high, until the next stop/restart strobe. :attr start: Start strobe. Active for one cycle immediately after acknowledging address. :attr stop: @@ -287,8 +323,10 @@ class I2CTarget(Elaboratable): Data octet to be transmitted to the initiator. Latched immediately after receiving a read command. """ - def __init__(self, pads): + def __init__(self, pads, analyzer=None): self.address = Signal(7) + self.address_mask = Signal(7, init=0b111_1111) + self.address_i = Signal(7) self.busy = Signal() # clock stretching request (experimental, undocumented) self.start = Signal() self.stop = Signal() @@ -300,7 +338,22 @@ def __init__(self, pads): self.data_o = Signal(8) self.ack_i = Signal() - self.bus = I2CBus(pads) + self.bus = I2CBus(pads, analyzer=analyzer) + + if analyzer: + analyzer.add_generic_event(None, "i2c-t-address", self.address) + analyzer.add_generic_event(None, "i2c-t-address_mask", self.address_mask) + analyzer.add_generic_event(None, "i2c-t-address_i", self.address_i) + analyzer.add_generic_event(None, "i2c-t-busy", self.busy) + analyzer.add_generic_event(None, "i2c-t-start", self.start) + analyzer.add_generic_event(None, "i2c-t-stop", self.stop) + analyzer.add_generic_event(None, "i2c-t-restart", self.restart) + analyzer.add_generic_event(None, "i2c-t-write", self.write) + analyzer.add_generic_event(None, "i2c-t-data_i", self.data_i) + analyzer.add_generic_event(None, "i2c-t-ack_o", self.ack_o) + analyzer.add_generic_event(None, "i2c-t-read", self.read) + analyzer.add_generic_event(None, "i2c-t-data_o", self.data_o) + analyzer.add_generic_event(None, "i2c-t-ack_i", self.ack_i) def elaborate(self, platform): m = Module() @@ -334,13 +387,20 @@ def elaborate(self, platform): with m.Elif(self.bus.setup): m.d.sync += bitno.eq(bitno + 1) with m.If(bitno == 7): - with m.If(shreg_i[1:] == self.address): - m.d.comb += self.start.eq(1) - m.d.sync += self.bus.sda_o.eq(0) + with m.If( + (shreg_i[1:] & self.address_mask) + == (self.address & self.address_mask) + ): + m.d.sync += [ + self.address_i.eq(shreg_i[1:]), + self.start.eq(1), + self.bus.sda_o.eq(0), + ] m.next = "ADDR-ACK" with m.Else(): m.next = "IDLE" with m.State("ADDR-ACK"): + m.d.sync += self.start.eq(0) with m.If(self.bus.stop): m.d.comb += self.stop.eq(1) m.next = "IDLE" diff --git a/software/pyproject.toml b/software/pyproject.toml index edcad145e..434faab1d 100644 --- a/software/pyproject.toml +++ b/software/pyproject.toml @@ -95,6 +95,7 @@ sbw-probe = "glasgow.applet.interface.sbw_probe:SpyBiWireProbeApplet" swd-openocd = "glasgow.applet.interface.swd_openocd:SWDOpenOCDApplet" memory-24x = "glasgow.applet.memory._24x:Memory24xApplet" +memory-24x-emu = "glasgow.applet.memory._24x_emu:Memory24xEmuApplet" memory-25x = "glasgow.applet.memory._25x:Memory25xApplet" memory-onfi = "glasgow.applet.memory.onfi:MemoryONFIApplet" memory-prom = "glasgow.applet.memory.prom:MemoryPROMApplet"