GitHub - ryanm101/revsch: KiCad DSL for reverse engineering

4 min read Original article ↗

revsch is a Python tool for turning a small reverse-engineering DSL into a KiCad schematic.

The DSL supports two closely related workflows:

  • schematic-style capture: declare parts, pins, and named nets explicitly
  • probe-first mapping: capture continuity quickly with X -> Y, Z traces, batch-generated parts, and sparse pin metadata

The output is a valid .kicad_sch file that opens in KiCad and can be refined there by swapping symbols, improving layout, and adding drawing detail.

For standard generic parts, the emitter now prefers official KiCad library symbols. For custom and unknown parts, it falls back to embedded generated symbols so the schematic remains self-contained.

Install and Run

This project uses uv.

uv run revsch lint examples/basic_minimal.revsch
uv run revsch compile examples/basic_minimal.revsch -o ~/tmp/basic_minimal.kicad_sch

Useful commands:

uv run revsch lint input.revsch
uv run revsch lint --mapping-mode input.revsch
uv run revsch compile input.revsch -o output.kicad_sch
uv run revsch compile --mapping-mode input.revsch -o output.kicad_sch
uv run revsch compile input.revsch -o output.json --output-format json
uv run revsch dump-ir input.revsch
uv run revsch dump-json input.revsch
uv run revsch format input.revsch

DSL Overview

Supported statement types:

  • part REFDES ...
  • pin REFDES.PIN ...
  • net NAME = REFDES.PIN, REFDES.PIN
  • alias ALT = NET
  • property TARGET KEY = VALUE
  • note TARGET = VALUE
  • PREFIX key=value ... Used for batch generation, for example IC name=PLA pincount=16 count=6
  • A -> B, C, D Used for fast probe mapping

Comments start with #. Quoted strings are supported.

<X> Batch Part Syntax

part REFDES<X> ... count=N [start=S] generates multiple identical parts. <X> is replaced with an index for each generated part. start sets the first index (default 0).

For example, part ENC<X> count=2 start=1 creates ENC1 and ENC2. Subsequent pin, net, property, note, and trace statements using <X> in the reference designator expand across all generated parts.

Schematic-Style Example

part U1 kind=IC value="STM32F103C8" package=LQFP48
pin U1.24 name=VDD type=power_in
pin U1.32 name=PA11 type=bidirectional
pin U1.33 name=PA12 type=bidirectional

part J1 kind=CONN value="USB-C" package=USB_C_16
pin J1.A6 name=D+
pin J1.A7 name=D-

net VCC_3V3 = U1.24
net USB_D+ = U1.33, J1.A6
net USB_D- = U1.32, J1.A7
alias +3V3 = VCC_3V3

Probe-First Mapping Example

This is the fast capture workflow for tracing a board with a continuity probe.

IC name=PLA pincount=16 count=6
C count=10
R count=5
J name=EDGE pincount=44 count=1

J1.1 -> +5V
J1.2 -> C1, R3, IC1.3
C1 -> GND
R3 -> GND

Behavior:

  • IC name=PLA pincount=16 count=6 creates IC1 through IC6, each with 16 pins
  • C count=10 creates C1 through C10 with two pins each
  • J1.2 -> C1, R3, IC1.3 creates one net containing J1.2, one implicit pin from C1, one implicit pin from R3, and explicit pin IC1.3
  • bare net names such as +5V or GND become canonical net names
  • if no net name appears in a trace statement, an automatic net name is created

Sparse Capture

For reverse engineering, most pin metadata can be omitted:

part U1
pin U1.6
pin U1.7
net ROW0 = U1.6
net COL0 = U1.7

If a pin has no explicit name, the emitter falls back to the pin ID so the generated symbol is still usable.

Mapping Mode

--mapping-mode is intended for probe capture, where many pins are still untraced.

It suppresses warnings that are usually noise during early reverse engineering:

  • missing value
  • missing package
  • unused pins
  • single-member nets

It does not suppress structural errors such as undeclared pins, duplicate parts, or conflicting net assignments.

No-Connect Handling

Pins explicitly marked as no-connect emit real KiCad no_connect markers:

Pins marked this way do not produce unused-pin warnings.

Symbol Strategy

The emitter uses two symbol strategies.

Official KiCad library symbols are used for common standard parts:

  • resistor -> Device:R_Small
  • capacitor -> Device:C_Small
  • inductor / coil -> Device:L_Small
  • diode -> Device:D
  • LED -> Device:LED
  • crystal -> Device:Crystal
  • 2-pin switch -> Switch:SW_SPST
  • 3-pin switch -> Switch:SW_SPDT

Embedded generated symbols are used for:

  • ICs
  • connectors that do not map cleanly to a stock symbol
  • unknown parts
  • anything else without a supported stock mapping

This keeps standard parts recognizable in KiCad while preserving a self-contained fallback for reverse-engineered or custom devices.

JSON Output

There are two JSON export paths:

  • dump-json Exports the parsed DSL statement structure
  • dump-ir Exports the normalized internal representation used for validation and emission
  • compile --output-format json Writes the parsed DSL statement structure to a file

Example:

uv run revsch dump-json examples/mapping_probe.revsch
uv run revsch dump-ir examples/mapping_probe.revsch
uv run revsch compile examples/mapping_probe.revsch -o ~/tmp/mapping_probe.json --output-format json

dump-json and compile --output-format json are useful if another tool wants to consume the DSL without reimplementing the parser.

Examples

See examples/README.md for the full example index.

Included examples:

  • examples/basic_minimal.revsch
  • examples/metadata_and_aliases.revsch
  • examples/sparse_reverse_capture.revsch
  • examples/mapping_probe.revsch
  • examples/mapping_probe_named_nets.revsch
  • examples/esp_keyboard.revsch
  • examples/ch579m_key12_v02.revsch

Status

Current implementation includes:

  • parser and semantic validation
  • KiCad .kicad_sch emission
  • deterministic UUID and symbol generation
  • mapping-mode warning profile
  • source-level and IR-level JSON exports
  • pytest coverage for parser, semantics, emitter, and CLI