Metadata-Version: 2.4
Name: elvis-lvs
Version: 0.2.1
License-File: LICENSE
Summary: A simple LVS (Layout vs. Schematic) tool for GDSFactory
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# Elvis 0.2.1

Layout vs. Schematic (LVS) for GDSFactory.

User documentation: [doplaydo.github.io/elvis](https://doplaydo.github.io/elvis/)

## Development setup

```bash
# Build the Rust crates
cargo build --release

# Build and install the Python package locally
maturin develop
```

## Crates

- **gf-netlist** - GDSFactory netlist schema (input .pic.yml format)
- **elvis-netlist** - Extracted netlist schema (simplified subset for LVS)
- **elvis-rdb** - KLayout Report Database (lyrdb) format
- **elvis-core** - Core extraction and LVS functionality
- **elvis-cli** - Command-line interface
- **elvis-python** - Python bindings via PyO3

## How it works

### Netlist Extraction

Elvis extracts connectivity information from GDS files through a multi-step process:

#### 1. Parsing GDS Structure

The GDS file contains a hierarchy of cells (structures). Elvis identifies the **top
cell** - the cell that is not instantiated by any other cell. This is the design being
verified.

```
GDS File
├── top_cell (not referenced by others → this is extracted)
│   ├── instance: mmi (references mmi1x2 cell)
│   ├── instance: wg_in (references straight cell)
│   └── instance: wg_out1 (references straight cell)
├── mmi1x2 (component cell)
├── straight (component cell)
└── bend_euler (component cell)
```

#### 2. Extracting Port Metadata

kfactory/gdsfactory stores port information in GDS PROPATTR records with a specific
format:

```
kfactory:ports:0')={'name'=>'o1','port_type'=>'optical','trans'=>[trans:r180 -35500,625]}
```

Elvis parses these properties to extract:

- **name**: Port identifier (e.g., `o1`, `o2`)
- **position**: X, Y coordinates in database units
- **orientation**: Rotation angle (0, 90, 180, 270)
- **port_type**: Optional type (e.g., `optical`, `electrical`)

#### 3. Instance Name Resolution

Each cell reference (SREF) in the GDS becomes an instance. The instance name is
determined by:

1. **Explicit name**: From GDS property with `attr=0` (if present)
2. **Generated name**: `{cell_name}_{x}_{y}[_r{rotation}][_m]` as fallback

The generated name includes rotation and mirror suffixes to ensure uniqueness when
multiple instances of the same cell exist at the same position with different
transforms.

#### 4. Port Transformation

Each instance's ports are transformed from local (cell) coordinates to global (layout)
coordinates:

```
Global Position = Instance Position + Rotate(Local Position, Instance Rotation)
Global Orientation = Local Orientation + Instance Rotation (± mirror)
```

#### 5. Connection Detection

Two ports are considered **connected** when ALL of the following conditions are met:

| Condition       | Requirement                                  |
| --------------- | -------------------------------------------- |
| **Position**    | Within tolerance (default: 1nm)              |
| **Orientation** | Facing opposite directions (180 +/- 1)       |
| **Port type**   | Same type, or either type is unknown         |
| **Instance**    | On different instances (no self-connections) |

```
        ┌─────────┐           ┌───────┐
        │  mmi    │  o2 ←→ o1 │  wg   │
        │         │─────●─────│       │
        └─────────┘  180°  0° └───────┘
                   Connected: same position, opposite orientation
```

The **port type check** ensures that only compatible ports connect:

- `optical` ↔ `optical`: Connected
- `electrical` ↔ `electrical`: Connected
- `optical` ↔ `electrical`: Not connected (will appear as open)
- `unknown` ↔ `anything`: Connected (permissive fallback)

### LVS Algorithm

Elvis uses a **graph-based connectivity comparison** where 2-port routing instances are
removed from the net-comparisons.

#### The Problem with Naive Comparison

A direct comparison would fail on any routed design:

```
Schematic (what the designer specified):
┌──────────┐      ┌──────────┐
│ splitter │──────│ arm_top  │
└──────────┘      └──────────┘
     2 instances, 1 net

Layout (after auto-routing):
┌──────────┐   ┌──────┐   ┌────────┐   ┌──────┐   ┌──────────┐
│ splitter │───│ bend │───│straight│───│ bend │───│ arm_top  │
└──────────┘   └──────┘   └────────┘   └──────┘   └──────────┘
     5 instances, 4 nets

Naive LVS: FAILED - 3 extra instances, 3 extra nets
```

#### The Solution: Graph-Based Tracing

Elvis treats the layout as a connectivity graph and traces through **2-port components**
(which act as "wires") to find connections between **reference instances** (components
from the schematic).

**Key insight:** A 2-port component (like a waveguide or bend) doesn't change
connectivity - it just extends a path. Only components with 3+ ports (like splitters,
couplers) are true circuit elements that define the topology.

#### Algorithm Steps

**Step 1: Identify Reference Instances**

Reference instances are those that appear in the schematic. These are the "real"
components we care about - they define the circuit topology.

```python
reference_instances = {name for name in schematic.instances}
# e.g., {"splitter", "arm_top", "arm_bottom", "combiner"}
```

**Step 2: Build Connectivity Graph**

Create a graph from the layout where:

- Nodes are `(instance, port)` pairs
- Edges connect ports that are physically connected

```
Layout connectivity graph:
(splitter,o2) ─── (bend_1,o1)
(bend_1,o2) ─── (straight_1,o1)
(straight_1,o2) ─── (bend_2,o1)
(bend_2,o2) ─── (arm_top,o1)
```

**Step 3: Classify Traversable Instances**

An instance is **traversable** if:

1. It has exactly 2 ports (it's a "wire-like" component)
2. It is NOT a reference instance (not in the schematic)

```python
def is_traversable(instance):
    return port_count[instance] == 2 and instance not in reference_instances
```

Reference instances are **never traversable** - they are endpoints where tracing stops.

**Step 4: Trace Through 2-Port Intermediates**

For each connection in the schematic, verify it exists in the layout by tracing through
traversable instances:

```python
def trace_to_endpoint(start_instance, start_port):
    current = get_connected_port(start_instance, start_port)

    while is_traversable(current.instance):
        # Find the other port on this 2-port instance
        other_port = get_other_port(current.instance, current.port)
        # Follow the connection from that port
        current = get_connected_port(current.instance, other_port)

    return current  # Returns the endpoint (a reference instance)
```

Example trace:

```
Start: (splitter, o2)
  → connected to (bend_1, o1)
  → bend_1 is traversable, other port is o2
  → (bend_1, o2) connected to (straight_1, o1)
  → straight_1 is traversable, other port is o2
  → (straight_1, o2) connected to (bend_2, o1)
  → bend_2 is traversable, other port is o2
  → (bend_2, o2) connected to (arm_top, o1)
  → arm_top is NOT traversable (it's a reference instance)
End: (arm_top, o1) ✓
```

**Step 5: Mark Valid Intermediates**

All 2-port instances encountered on valid paths are marked as **valid intermediates**.
These won't trigger "missing in schematic" errors.

```
Valid intermediates: {bend_1, straight_1, bend_2}
These exist in layout but not schematic - that's OK, they're routing.
```

**Step 6: Check for Extra Connections**

Also verify that the layout doesn't have extra connections between reference instances
that aren't in the schematic (topological shorts).

#### Array Instance Support

GDS array references (`AREF`) are expanded into individual instances with `<col.row>`
naming (e.g., `pads<0.0>`, `pads<1.0>`). Schematic array instances are likewise
expanded during netlist conversion, so both sides use the same naming convention.

#### Visual Example

```
SCHEMATIC:
                    ┌─────────┐
              ┌─────┤ arm_top ├─────┐
              │     └─────────┘     │
        ┌─────┴─────┐         ┌─────┴─────┐
   ───○─┤ splitter  │         │ combiner  ├─○───
        └─────┬─────┘         └─────┬─────┘
              │     ┌─────────┐     │
              └─────┤ arm_bot ├─────┘
                    └─────────┘

   4 reference instances, 4 nets

LAYOUT (with routing):
                         ╭───────────────────╮
                    ┌────┤ arm_top           ├────┐
                    │    └───────────────────┘    │
              ╭─────╯                             ╰─────╮
              │  (bends and straights)                  │
        ┌─────┴─────┐                            ┌──────┴────┐
   ───○─┤ splitter  │                            │ combiner  ├─○───
        └─────┬─────┘                            └─────┬─────┘
              │  (bends and straights)                 │
              ╰─────╮                             ╭────╯
                    │    ┌───────────────────┐    │
                    └────┤ arm_bot           ├────┘
                         └───────────────────┘

   64 instances total (4 reference + 60 routing), 64 nets

LVS RESULT: PASSED
   - All 4 schematic connections verified through routing
   - 60 routing instances are valid intermediates
```

