Geometry & shapes

Geometric primitives for ASITIC shapes.

The original C code packs every shape into a 188-byte (0xbc) record with field offsets recovered by Ghidra; here we use plain dataclasses. The conceptual model preserved is:

Shape ──> Polygon ──> Polygon ──> ...    (linked list, +0xec next)
          │
          └── per-polygon: vertices, metal layer, edge data

The numerical kernel doesn’t actually consume polygon vertices directly — it consumes a list of segments (straight conductor runs) that get further discretised into filaments. Each segment carries its endpoints, width, thickness, metal layer and direction unit-vector.

For the closed-form Grover formulas used in the inductance kernels the segment orientation is captured by its endpoints; width and thickness come from the metal-layer descriptor.

class reasitic.geometry.Point[source]

Bases: object

A 3D point in microns. z is the metal-layer center height.

x: float
y: float
z: float = 0.0
distance_to(other)[source]

3D Euclidean distance to other.

Parameters:

other (Point)

Return type:

float

__init__(x, y, z=0.0)
Parameters:
Return type:

None

class reasitic.geometry.Segment[source]

Bases: object

A straight conductor run between two endpoints.

a and b are the endpoints; width and thickness are the cross-section dimensions (microns). metal is the metal layer index (resolved against reasitic.tech.Tech).

a: Point
b: Point
width: float
thickness: float
metal: int
property length: float

Length of the segment in the same units as the endpoints.

property direction: tuple[float, float, float]

Unit vector from a to b. Zero-length segments return (0, 0, 0).

__init__(a, b, width, thickness, metal)
Parameters:
Return type:

None

class reasitic.geometry.Polygon[source]

Bases: object

A closed polyline on a single metal layer.

vertices: list[Point]
metal: int
width: float = 0.0
thickness: float = 0.0
edges()[source]

Return the segments connecting consecutive vertices.

Return type:

list[Segment]

__init__(vertices, metal, width=0.0, thickness=0.0)
Parameters:
Return type:

None

class reasitic.geometry.Shape[source]

Bases: object

A named structure built up from polygons.

Mirrors the original C Shape record (offsets 0x00..0xbc). Per-shape parameters (width, spacing, turns, etc.) are stored verbatim from the build call so downstream code can re-emit the original CLI form.

name: str
polygons: list[Polygon]
width: float = 0.0
length: float = 0.0
spacing: float = 0.0
turns: float = 0.0
sides: int = 4
metal: int = 0
exit_metal: int | None = None
x_origin: float = 0.0
y_origin: float = 0.0
orientation: int = 0
phase: float = 0.0
kind: str = ''
radius: float = 0.0
ilen: float = 0.0
segments()[source]

Flat list of every polygon edge in the shape.

Return type:

list[Segment]

bounding_box()[source]

Return (xmin, ymin, xmax, ymax) over all vertices.

Return type:

tuple[float, float, float, float]

translate(dx, dy)[source]

Return a copy translated by (dx, dy).

Parameters:
Return type:

Shape

flip_horizontal()[source]

Mirror across the y-axis through the shape’s origin (x -x).

Return type:

Shape

flip_vertical()[source]

Mirror across the x-axis through the shape’s origin (y -y).

Return type:

Shape

rotate_xy(angle_rad)[source]

Rotate the shape by angle_rad about its (x_origin, y_origin).

Parameters:

angle_rad (float)

Return type:

Shape

__init__(name, polygons=<factory>, width=0.0, length=0.0, spacing=0.0, turns=0.0, sides=4, metal=0, exit_metal=None, x_origin=0.0, y_origin=0.0, orientation=0, phase=0.0, kind='', radius=0.0, ilen=0.0)
Parameters:
Return type:

None

reasitic.geometry.polygon_edge_vectors(poly, *, direction='forward')[source]

Return the per-edge (dx, dy) vectors for a polygon.

Mirrors the binary’s forward_diff_2d_inplace (decomp address 0x08056198) and backward_diff_2d_inplace (0x08056148) in-place differencing helpers.

  • direction="forward" returns vertices[i+1] - vertices[i] for i in 0..N-2; matches forward_diff_2d_inplace after - sign flip (the binary stores arr[i] -= arr[i+1], i.e. -(next - curr); we return the geometric forward edge).

  • direction="backward" returns vertices[i] - vertices[i-1] for i in 1..N-1; matches backward_diff_2d_inplace.

Parameters:
Return type:

list[tuple[float, float]]

reasitic.geometry.shapes_bounding_box(shapes, tech=None)[source]

Return the union bbox (x_min, y_min, x_max, y_max) of shapes.

Mirrors the binary’s compute_overall_bounding_box (decomp address 0x08081ed4). If the input is empty and tech is provided, falls back to the chip outline (0, 0, chipx, chipy); if neither shapes nor tech is supplied, returns the all-zero bbox.

The world-frame translation (x_origin, y_origin) of each Shape is folded into the bounding-box result, matching the binary which adds the cell offset to each shape’s local bbox.

Parameters:
Return type:

tuple[float, float, float, float]

reasitic.geometry.extend_terminal_segment(shape, *, dx_um=0.0)[source]

Extend the tail of shape’s last polygon along its own axis.

Mirrors shape_terminal_segment_extend_unit (decomp 0x0805b348). Walks to the last polygon, normalises the last edge’s direction vector, then re-projects its endpoint to length/2 + dx_um along that direction.

The binary uses this when extending a winding terminal so the last segment leaves the chip with a fixed unit length plus a small dx offset. Returns a copy; the original is untouched.

Parameters:
Return type:

Shape

reasitic.geometry.emit_vias_at_layer_transitions(shape, tech)[source]

Insert via polygons between adjacent polygons on different metals.

Mirrors shape_emit_vias_at_layer_transitions (decomp 0x0805ba2c). Walks the polygon list pair-wise; whenever two adjacent polygons are on different metal layers, looks up the via that bridges them and inserts a single-vertex (zero-extent) via polygon at the midpoint of the metal-to-metal transition.

The via is placed on the via index of the matching Via record in the tech file (matching top/bottom to the adjacent metal indices). If no via record matches, no insertion is made for that transition.

Returns a copy; the original is untouched.

Parameters:
Return type:

Shape

reasitic.geometry.extend_last_segment_to_chip_edge(shape, tech)[source]

Push the last segment of shape out to the nearest chip boundary.

Mirrors shape_extend_last_to_chip_edge (decomp 0x0805b154). The binary uses this on the export path so a winding’s terminal segment sticks out of the chip outline by enough to become a port.

The decision tree:

  • If the last segment runs in +Y → snap its tail to chipy.

  • If it runs in -Y → snap its tail to 0.

  • If it runs in +X → snap its tail to chipx.

  • If it runs in -X → snap its tail to 0.

A copy of the shape is returned; the original is untouched.

Parameters:
Return type:

Shape

reasitic.geometry.layout_polygons(shape, tech)[source]

Return filled layout polygons matching ASITIC’s CIF/GDS geometry.

Parameters:
Return type:

list[Polygon]

reasitic.geometry.square_spiral(name, *, length, width, spacing, turns, tech, metal=0, x_origin=0.0, y_origin=0.0, phase=0.0)[source]

Build a square (4-sided) spiral.

Mirrors the binary’s cmd_square_build_geometry for the simple case (no exit metal, no ILEN inner-bound, no 3D mirroring): each turn is one closed square loop, with the spiral collapsing inward by width + spacing per turn. The spiral occupies the metal layer metal (index or tech-file name).

Parameters are in microns. turns may be fractional; integer turns each emit four sides, and the fractional remainder contributes round(4*frac) additional sides on a partial turn.

The trace is generated as a single connected polyline matching ASITIC’s cmd_square_build_geometry (decompiled at 0x08056670):

  • centerlines are inset by W/2 from the outer length × length bounding box, so the outer metal edge sits exactly at ±L/2;

  • the entry lead extends the outermost top-side centerline all the way to the left edge (x = -L/2) so the spiral can be probed at the chip boundary;

  • each successive turn shrinks inward by pitch = W + S and the bottom-left corner of one turn connects to the top-left corner of the next via the outer-left side, producing a true Archimedean spiral instead of nested closed loops;

  • the very last segment is trimmed by 1.5 × W to leave clearance for the exit-via attachment, matching the binary’s reference output.

The Python signature follows ASITIC’s documented convention: (x_origin, y_origin) is the lower-left corner of the spiral’s outer bounding box, so the spiral metal occupies [x_origin, x_origin + length] × [y_origin, y_origin + length] in world coords (with phase = 0).

Verified vertex-for-vertex against the 1999 ASITIC binary’s LISTSEGS output (BiCMOS tech, captured under qemu-i386-static).

Parameters:
Return type:

Shape

reasitic.geometry.polygon_spiral(name, *, radius, width, spacing, turns, tech, sides=8, metal=0, x_origin=0.0, y_origin=0.0, phase=0.0)[source]

Build an n-sided polygon spiral inscribed in radius.

Mirrors ASITIC’s cmd_spiral_build_geometry (decompiled at 0x08057248): the spiral is generated as one connected polyline that turns by 2π/sides each step while the radius decreases by pitch_radial / sides per side, where pitch_radial = (W + S) / cos(π/sides) is the turn-to-turn radial pitch measured along the polygon’s perpendicular bisector.

The bbox is then centered on (x_origin, y_origin) (per ASITIC’s shape_translate_inplace_xy post-build pass) so the user’s origin parameter ends up at the spiral centre — matching the documented behaviour for Spiral (NAME:RADIUS:SIDES:…:XORG:YORG).

Parameters:
Return type:

Shape

reasitic.geometry.wire(name, *, length, width, tech, metal=0, x_origin=0.0, y_origin=0.0, phase=0.0)[source]

Build a single straight wire of length length on metal.

Matches ASITIC’s W NAME=…:LEN=…:WID=…:METAL=…:XORG=…:YORG=… convention: (x_origin, y_origin) is the lower-left corner of the wire’s bounding box, so the metal occupies [x_origin, x_origin + length] × [y_origin, y_origin + width] when phase=0. The centerline runs along y = y_origin + W/2.

Parameters:
Return type:

Shape

reasitic.geometry.via(name, *, tech, via_index=0, nx=1, ny=1, x_origin=0.0, y_origin=0.0)[source]

Build a via cluster of size nx × ny at (x, y).

Mirrors cmd_via_build_geometry (decomp 0x08057b78): emits one polygon record at the shape’s origin with X-extent nx · via_w + (nx-1) · via_s and Y-extent ny · via_w + (ny-1) · via_s. The polygon is tagged with both metal-layer colours (top via table[cc] and bottom via table[d0]) so the CIF/GDS emitter expands it to:

  • an M2 box pad covering the array

  • an M3 box pad at the same position

  • nx × ny VIA squares of size via_w × via_w spaced by via_s + via_w in a regular grid

The Python form returns a Shape carrying all three layer polygons directly (the C’s “polygon record references both metals + emits a grid” is decoded into individual records).

Parameters:
Return type:

Shape

reasitic.geometry.ring(name, *, radius, width, gap=0.0, sides=32, tech, metal=0, x_origin=0.0, y_origin=0.0, phase=0.0)[source]

Build a single closed-ring loop (Ring, command id 22).

A ring is a polygon spiral with exactly one turn — implemented as a thin wrapper for clarity, since the binary’s REPL exposes Ring as a separate command.

Parameters:
Return type:

Shape

reasitic.geometry.transformer(name, *, length=None, width=None, spacing=None, turns=None, primary_length=None, primary_width=None, primary_spacing=None, primary_turns=None, secondary_length=None, secondary_width=None, secondary_spacing=None, secondary_turns=None, tech, metal=None, exit_metal=None, metal_primary=None, metal_secondary=None, x_origin=0.0, y_origin=0.0, which='primary')[source]

Build a planar two-coil transformer (Trans).

Mirrors cmd_trans_build_geometry (decomp 0x080576d4): two square spirals on the same metal (METAL), interleaved by a double-pitch + a half-pitch offset so the secondary’s tracks fit between the primary’s. The secondary is built with a flip in both axes via cmd_flipv_apply + cmd_fliph_apply.

For the canonical TRANS PNAME=TP:SNAME=TS:LEN=L:W=W:S=S:N=N case both coils have identical dimensions. The primary’s internal lower-left corner sits at:

(XORG + (W + S), YORG + (2W + S))

and each coil uses an effective spacing of W + 2·S so the inter-turn pitch is 2·(W + S) — leaving room for the secondary’s interleaved turns.

The C builder leaves both coils linked and CIFSAVE addresses each by name. Use which="primary" (default) or which="secondary" to pick the coil to materialise.

Both legacy positional kwargs (length, width, spacing, turns) and the test-harness primary/secondary pair are accepted; missing fields default to the corresponding primary_* value or vice versa.

Parameters:
  • name (str)

  • length (float | None)

  • width (float | None)

  • spacing (float | None)

  • turns (float | None)

  • primary_length (float | None)

  • primary_width (float | None)

  • primary_spacing (float | None)

  • primary_turns (float | None)

  • secondary_length (float | None)

  • secondary_width (float | None)

  • secondary_spacing (float | None)

  • secondary_turns (float | None)

  • tech (Tech)

  • metal (int | str | None)

  • exit_metal (int | str | None)

  • metal_primary (int | str | None)

  • metal_secondary (int | str | None)

  • x_origin (float)

  • y_origin (float)

  • which (str)

Return type:

Shape

reasitic.geometry.symmetric_square(name, *, length, width, spacing, turns, tech, metal=0, primary_metal=None, exit_metal=None, bridge_metal=None, ilen=0.0, x_origin=0.0, y_origin=0.0)[source]

Build a symmetric centre-tapped square spiral (SymSq).

Mirrors cmd_symsq_build_geometry (decomp 0x08059854).

The geometry is decoded by piece in _symsq_layout_polygons(); this builder just wires the arguments through. Currently full CIF parity is reached for turns=2 (the simplest case); turns≥3 is a follow-up.

Parameters:
  • length (float) – outer side length L (μm).

  • width (float) – metal trace width W (μm).

  • spacing (float) – edge-to-edge gap S between turns (μm).

  • turns (float) – integer turn count N (only N=2 fully supported).

  • ilen (float) – centre-tap span (ASITIC’s ILEN parameter).

  • primary_metal (int | str | None) – trace metal layer.

  • exit_metal (int | str | None) – layer used for the centre-tap M2 trace + via cluster connection.

  • y_origin (float) – lower-left of the LxL bbox in μm.

  • name (str)

  • tech (Tech)

  • metal (int | str)

  • primary_metal

  • bridge_metal (int | str | None)

  • x_origin (float)

  • y_origin

Return type:

Shape

reasitic.geometry.capacitor(name, *, length, width, metal_top, metal_bottom, tech, x_origin=0.0, y_origin=0.0)[source]

Build a metal-insulator-metal (MIM) capacitor (Capacitor).

Two stacked rectangles on different metal layers. The geometric overlap × dielectric thickness gives the MIM capacitance; we emit both rectangles as separate polygons so the caller can run geometry-only analyses (area, footprint).

Mirrors cmd_capacitor_build_geometry in the original.

Parameters:
Return type:

Shape

reasitic.geometry.symmetric_polygon(name, *, radius, width, spacing, turns, ilen=0.0, sides=8, tech, metal=0, primary_metal=None, exit_metal=None, x_origin=0.0, y_origin=0.0)[source]

Symmetric centre-tapped polygon spiral (SymPoly, case 17).

Mirrors cmd_sympoly_build_geometry (decomp 0x0805a45c): one continuous polygon spiral that runs 2N half-turns from outer-to-inner-to-outer with a centre-tap stub at the apex and cross-ring slants at every other transition. The structure is decoded by _sympoly_layout_polygons(); this builder just wires the parameters through.

Parameters:
  • radius (float) – outer-corner polygon radius R (μm).

  • width (float) – metal trace width W (μm).

  • spacing (float) – edge-to-edge gap S between turns (μm).

  • turns (float) – integer turn count N.

  • ilen (float) – centre-tap span (ASITIC’s ILEN parameter; defaults to W + S per the C edit_args fallback).

  • sides (int) – polygon side count (must be even, ≥ 4).

  • primary_metal (int | str | None) – trace metal layer.

  • exit_metal (int | str | None) – alternating slant + via-cluster metal (typically one layer below metal).

  • name (str)

  • tech (Tech)

  • metal (int | str)

  • primary_metal

  • x_origin (float)

  • y_origin (float)

Return type:

Shape

reasitic.geometry.multi_metal_square(name, *, length, width, spacing, turns, tech, metals=None, metal=None, exit_metal=None, x_origin=0.0, y_origin=0.0)[source]

Multi-metal series square inductor (MMSquare).

Mirrors cmd_mmsquare_build_geometry (decomp 0x0805af5c): builds a square spiral on the top metal, then a Y-mirrored, list-reversed copy on each lower metal layer down to and including exit_metal. Adjacent layers connect via implicit vias at the inner-end of one and the outer-start of the next, boosting L for a given footprint by re-using area.

Two equivalent calling conventions are supported:

  • metal="m3", exit_metal="m2" — matches the C ASITIC MMSQ NAME=...:METAL=m3:EXIT=m2 form. The Python uses every metal layer from metal down to exit_metal inclusive.

  • metals=[m3, m2] — backward-compatible explicit list.

The basic square-spiral on the top metal is built with cmd_square_build_geometry’s exit-routing branch suppressed (the C sets shape.exit_metal = -1 before calling cmd_square_build_geometry). The full per-layer flip cascade is then applied by _mmsquare_layout_polygons().

Parameters:
Return type:

Shape

reasitic.geometry.transformer_3d(name, *, length, width, spacing, turns, tech, metal_top, metal_bottom, via_index=0, x_origin=0.0, y_origin=0.0)[source]

3-D non-planar mirror-stacked transformer (3DTrans).

Two co-axial square spirals on different metal layers, vertically aligned and connected by a via at the centre. The two coils share the same chip footprint, so there’s no horizontal separation as in transformer().

Mirrors the simple-case path of cmd_3dtrans_build_geometry (asitic_repl.c:0x08057d40). The full binary version supports centre-tapped variants and multi-via stacks; this implementation is the planar-mirror form.

Parameters:
Return type:

Shape

reasitic.geometry.balun(name, *, length, width, spacing, turns, tech, metal=None, metal2=None, primary_metal=None, secondary_metal=None, exit_metal=None, x_origin=0.0, y_origin=0.0, which='primary')[source]

Build one coil of a 3D / planar balun (Balun, decomp 0x0805bc74).

The C builder is a 72-byte wrapper that calls cmd_symsq_build_geometry twice and applies cmd_flipv_apply to the secondary. Each coil is a partial SYMSQ — only alternating rings are emitted, so the two coils together interleave nicely in 3D:

Primary   coil:  rings 0, 2, 4, … (even k)
Secondary coil:  rings 1, 3, 5, … (odd  k)

The internal ILEN is derived from the build args (no explicit ILEN parameter for BALUN) — decoded from gold balun_200x8x3x3_m3_m2: ILEN = 2 · (W + S) = 2 · pitch.

Use which="primary" (default) or which="secondary" to select the coil to materialise.

Parameters:
Return type:

Shape