# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a **Saleae Logic 2 High-Level Analyzer (HLA)** extension for I3C protocol analysis. It receives frames from an I3C Low-Level Analyzer (LLA) and groups them into meaningful transactions. Current version: `2.4.0` (see `extension.json`).

The LLA source is included as a git submodule at `i3c-analyzer/`. Its `CLAUDE.md` documents the LLA architecture, build system, and state machines in detail — read it for context when working on frame handling logic.

## Protocol Reference

The MIPI I3C v1.2 specification is available at `i3c-analyzer/docs/i3c-spec/v1.2/mipi_I3C_specification_v1-2.pdf`. SDR is in §5 (~pp. 38–56), HDR-DDR in §6.2 (~pp. 243–340).

The MCTP over I3C binding specification (DSP0233 v1.0.1) is available at `SecondLayerHLAs/docs/mctp-spec/mctp_i3c_transport_binding_v1.0.1.pdf`.

## No Build Step

This is a pure-Python Saleae extension. There is no build, compile, or install step. Development workflow:
1. Edit `.py` files directly.
2. In Saleae Logic 2, use **Extensions → Reload Extension** (or re-open the capture) to pick up changes.
3. Log output from `logging` calls appears in the Logic 2 extension console.

There are no automated tests and no test runner.

## Architecture

### Entry Point and Mode Routing

`HighLevelAnalyzer.py` — `Hla` class — is the Saleae entry point. It owns two decoders and a `mode` flag:

```
Saleae Logic 2
  → Hla.decode(frame)
      → SDRDecoder.processFrame(frame)   [when mode == SDR]
      → DDRDecoder.processFrame(frame)   [when mode == DDR]
```

Each `processFrame()` returns `(AnalyzerFrame | None, DecodingModes)`. The returned mode is stored back into `Hla.mode`, which is how SDR↔DDR switching is driven: `SDRDecoder` returns `DecodingModes.DDR` when it sees `BC ENTHDR0 (CCC 0x20)`, and `DDRDecoder` returns `DecodingModes.SDR` on `DDR STOP` or `DDR HDR EXIT`.

### Decoder State Machines

Both `SDRDecoder` and `DDRDecoder` extend `GenericDecoder` and implement the same pattern:
- `self.temp_frame` accumulates an in-progress transaction across multiple LLA frames.
- Delimiter frames (`START`, `STOP`, `R-START`, `DDR CRC`, etc.) either open, close, or commit `temp_frame`.
- On commit, the frame is passed to `self.second_layer_analyzer.instruction_decoder()` before being returned to Saleae.

### Second Layer HLA (SLA) System

The decoders don't emit frames directly — they delegate through a second-layer analyzer:

```
SecondLayerAnalyzerHandler.get(option, mode)
    → DefaultHla / DDRDefaultHla               (pass-through)
    → SPDHla / DDRSPDHla                       (SPD5118 decoding)
    → MCTPPacketHla / DDRMCTPPacketHla         (MCTP packet-level view, DSP0233)
    → MCTPMessageHla / DDRMCTPMessageHla       (MCTP message-level view, DSP0233)
```

`SecondLayerHLA` (in `SecondLayerHLAs/SecondLayerAnalyzer.py`) is the abstract base with four methods every SLA must implement:

| Method | Purpose |
|--------|---------|
| `update_ccc(ccc_dict)` | Inject vendor-specific CCC overrides into the CCC name dictionary |
| `update_result_types(result_types)` | Add SLA-specific transaction types to the display format dict |
| `instruction_decoder(frame)` | Optionally transform or replace a completed I3C transaction frame |
| `handle_abort_message(temp_frame, end_time)` | Return the appropriate aborted-transaction frame |

**To add a new second layer HLA:**
1. Create a class inheriting `SecondLayerHLA` in `SecondLayerHLAs/<name>/`.
2. Add `DDR<name>Hla` and `<name>Hla` variants (one for each mode).
3. Add both to `SecondLayerAnalyzerHandler.get()`.
4. Add the option string to the `ChoicesSetting` in `HighLevelAnalyzer.py`.

### CCC Dictionaries

`I3CDictionaries` holds four cumulative CCC name dicts (`ccc_text_v1_0` through `ccc_text_v1_2`) and a version map that merges them. `getCCCDict(i3c_version_option)` returns the merged dict for the selected version. CCC codes `< 0x80` are broadcast; `≥ 0x80` are direct. `0x20` (`BC ENTHDR0`) is the SDR→DDR mode switch trigger.

### Display Format (`result_types`)

`I3CDictionaries.result_types` maps each HLA transaction type name to `format` (Saleae bubble label) and `console` (log line) templates. All field references use Python `.format(**frame.data)` syntax. SLAs extend this dict via `update_result_types()`.

## LLA Interface Contract

The HLA receives `AnalyzerFrame` objects whose `.type` strings are defined by the LLA. The authoritative source is:
- **SDR frames**: `i3c-analyzer/src/I3cDefinitions.cpp` — `I3C::cTextFrameType` map
- **DDR frames**: `i3c-analyzer/src/HDR-DDR/I3cDDRDefinitions.cpp` — `I3cDDR::cTextDDRFrameType` map

### SDR Frame Types (from LLA)

| `frame.type` | `frame.data` fields used by HLA |
|---|---|
| `START` | — |
| `STOP` | — |
| `R-START` | — |
| `ADDRESS` | `Data` (bytes), `W/R` |
| `DATA` | `Data` (bytes) |
| `CCC CMD` | `Data` (bytes) |
| `IBI ADDR` | `Data` (bytes), `W/R` |
| `I3C BC ADDR` | — (presence triggers I3C Transaction type) |
| `HDR EXIT` | `Info` |
| `HOT-JOIN` | — |
| `ABORT` | — |
| `TARGET RESET` | — |
| `UNSUPPORTED` | — |

### DDR Frame Types (from LLA)

| `frame.type` | `frame.data` fields used by HLA |
|---|---|
| `DDR CMD` | `DDR Address`, `DDR Command`, `DDR W/R`, `DDR Error` |
| `DDR DATA` | `DDR Data`, `DDR Error` |
| `DDR CRC` | `DDR Error` |
| `DDR ABORT` | — |
| `DDR ERROR` | — |
| `DDR CCC IND` | — |
| `DDR CCC CMD` | `DDR Data` |
| `DDR CCC DATA` | `DDR Data` |
| `DDR CCC SEL` | `DDR Address`, `DDR Command` |
| `DDR HDR R-START` | `DDR Info` |
| `DDR HDR EXIT` | `DDR Info` |
| `DDR STOP` | — |
| `UNSUPPORTED` | — |

Any new HLA built on top of this transaction HLA (e.g. MCTP) will receive the **HLA output transaction types** listed in `I3CDictionaries.result_types`, not raw LLA frame types.
