Files
circuiteria/doc/examples.typ
T
David Stadelmann fff5b2ecd5 feat: Support cross-element port alignment via align-with
Extend the port `align-with` field to accept references to ports of
other elements, in addition to the existing local-port form:

  - "<local-id>"                    → same-element port (unchanged)
  - (element: "<id>", port: "<id>") → cross-element reference
  - "<raw-anchor>"                  → arbitrary CeTZ anchor name

Cross-element alignment is implemented via CeTZ perpendicular
projection (`|-`) so the port sits on this element's side line while
sharing the remote port's y (east/west) or x (north/south) coordinate.
Local resolution still validates matching axis and detects cycles;
external refs skip axis validation (remote side unknown at eval time)
and require the target element to be drawn first.

Also enrich port-related panic messages with the element id and port
context to make offending figures easier to locate, and add a manual
example (`examples.align-with-ports`) demonstrating both local and
cross-element alignment.
2026-09-14 17:12:57 +02:00

450 lines
14 KiB
Typst
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "example.typ": example
#let alu = example(```
element.alu(x: 0, y: 0, w: 1, h: 2, id: "alu")
wire.stub("alu-port-in1", "west")
wire.stub("alu-port-in2", "west")
wire.stub("alu-port-out", "east")
```)
#let block = example(```
element.block(
x: 0, y: 0, w: 2, h: 2, id: "block",
ports: (
north: ((id: "clk", clock: true),),
west: ((id: "in1", name: "A"),
(id: "in2", name: "B")),
east: ((id: "out", name: "C"),)
)
)
wire.stub("block-port-clk", "north")
wire.stub("block-port-in1", "west")
wire.stub("block-port-in2", "west")
wire.stub("block-port-out", "east")
```)
#let align-with-ports = example(```
// `align-with` reuses another port's coord
// (E/W: y, N/S: x). Forms:
// 1. "id" — local
// 2. (element:"e", port:"p") — cross-elmt
// 3. "raw-anchor" — CeTZ anchor
element.block(x: 0, y: 0, w: 2, h: 2, id: "b1",
ports: (
west: ((id: "a", name: "A", y: 30%),),
east: ((id: "oa", name: "A'",
"align-with": "a"),),
))
element.block(x: 4, y: -.3, w: 2, h: 2.6, id: "b2",
ports: (
west: ((id: "ia", name: "A",
"align-with":
(element: "b1", port: "oa")),),
east: ((id: "o", name: "C"),),
))
wire.stub("b1-port-a", "west")
wire.wire("w", ("b1-port-oa", "b2-port-ia"),
style: "direct")
wire.stub("b2-port-o", "east")
```, vertical: true)
#let extender = example(```
element.extender(
x: 0, y: 0, w: 3, h: 1,
id: "extender"
)
wire.stub("extender-port-in", "west")
wire.stub("extender-port-out", "east")
```)
#let multiplexer = example(```
element.multiplexer(
x: 0, y: 0, w: 1, h: 3,
id: "multiplexer",
entries: 3
)
wire.stub("multiplexer.north", "north")
wire.stub("multiplexer-port-out", "east")
element.multiplexer(
x: 0, y: -4, w: 1, h: 3,
id: "multiplexer2",
entries: ("A", "B", "C")
)
wire.stub("multiplexer2.south", "south")
wire.stub("multiplexer2-port-out", "east")
for i in range(3) {
wire.stub("multiplexer-port-in" + str(i), "west")
wire.stub("multiplexer2-port-in" + str(i), "west")
}
```)
#let wires = example(```
for i in range(5) {
draw.circle((i * 3, 0), radius: .1, name: "p" + str(i * 2))
draw.circle((i * 3 + 2, 1), radius: .1, name: "p" + str(i * 2 + 1))
draw.content((i * 3 + 1, -1), raw(wire.wire-styles.at(i)))
}
wire.wire("w1", ("p0", "p1"), style: "direct")
wire.wire("w2", ("p2", "p3"), style: "zigzag")
wire.wire("w3", ("p4", "p5"), style: "dodge",
dodge-y: -0.5, dodge-margins: (0.5, 0.5))
wire.wire("w4", ("p6","p7"), style: "guided",
guided-center:(20%, 40%), guided-margins: (90%,87%),
guided-sides: ("north","south"))
wire.wire("w5", ("p8", "p9"), style: "u-shape",
u-side: "east", u-depth: 0.5)
```, vertical: true)
#let stub = example(```
draw.circle((0, 0), radius: .1, name: "p")
wire.stub("p", "north", name: "north", length: 1)
wire.stub("p", "east", name: "east", vertical: true)
wire.stub("p", "south", name: "south", length: 15pt)
wire.stub("p", "west", name: "west", length: 3em)
```)
#let breakout = example(```
// 16 breakouts: 4 directions × 4 label positions, laid out
// two per row so the labels have room to breathe.
let dirs = ("north-east", "north-west", "south-east", "south-west")
let poss = ("north-east", "north-west", "south-east", "south-west")
let short = (
"north-east": "NE", "north-west": "NW",
"south-east": "SE", "south-west": "SW",
)
let cols = 2
let dx = 5
let dy = 2
for (i, dir) in dirs.enumerate() {
for (j, pos) in poss.enumerate() {
let n = i * poss.len() + j
let r = calc.floor(n / cols)
let c = calc.rem(n, cols)
draw.circle((c * dx, -r * dy), radius: .05)
wire.breakout("b" + str(n),
x: c * dx, y: -r * dy,
direction: dir,
port-side: if dir.ends-with("east") { "west" } else { "east" },
name: short.at(dir) + "/" + short.at(pos),
name-pos: pos)
}
}
```)
#let breakout-label = example(```
// name-pos matches direction → label sits at the end point.
wire.breakout("b1", x: 0, y: 0, direction: "north-east",
port-side: "east",
name: "at end", name-pos: "north-east")
// name-pos opposite to direction → label would cover the line, so
// it falls back to the start point.
wire.breakout("b2", x: 4, y: 0, direction: "north-east",
port-side: "east",
name: "at start", name-pos: "south-west")
```, vertical: true)
#let breakout-wire = example(```
// Bus fan-out: a horizontal wire from the left block to the lower
// right block carries the full 10-bit bus. A breakout in the middle
// of that wire taps bits [9:5] and routes them to the upper right
// block via an L-shaped zigzag (up, then right).
element.block(x: 0, y: 0, w: 2, h: 2, id: "src",
fill: util.colors.blue,
ports: (east: ((id: "out"),)))
element.block(x: 6, y: 0, w: 2, h: 2, id: "dstL",
fill: util.colors.green,
ports: (west: ((id: "in"),)))
element.block(x: 6, y: 4, w: 2, h: 2, id: "dstU",
fill: util.colors.orange,
ports: (west: ((id: "in"),)))
// Full bus wire, labelled "[9:0]" on the left.
wire.wire("wbus", ("src-port-out", "dstL-port-in"),
name: "[9:0]", name-pos: "start")
// Breakout tapped from the midpoint of the bus.
wire.breakout("tap",
x: (rel: 2, to: "src-port-out"),
y: (from: "src-port-out"),
direction: "north-east",
port-side: "west",
name: "[9:5]", name-pos: "north-east")
// L-shaped wire from the breakout up and then right to the upper
// block's input.
wire.wire("wtap", ("tap-port-tap", "dstU-port-in"),
style: "zigzag", zigzag-dir: "horizontal",
zigzag-ratio: 100%)
```)
#let gate-and = example(```
gates.gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-nand = example(```
gates.gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-buf = example(```
gates.gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-not = example(```
gates.gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-or = example(```
gates.gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-nor = example(```
gates.gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-xor = example(```
gates.gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let gate-xnor = example(```
gates.gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-and = example(```
gates.iec-gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-nand = example(```
gates.iec-gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-or = example(```
gates.iec-gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-nor = example(```
gates.iec-gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-xor = example(```
gates.iec-gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-xnor = example(```
gates.iec-gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-buf = example(```
gates.iec-gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-not = example(```
gates.iec-gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let group = example(```
element.group(
id: "g1", name: "Group 1", stroke: (dash: "dashed"),
{
element.block(id: "b1", w: 2, h: 2,
x: 0, y: 1.5,
ports: (east: ((id: "out"),)),
fill: util.colors.green
)
element.block(id: "b2", w: 2, h: 1,
x: 0, y: 0,
ports: (east: ((id: "out"),)),
fill: util.colors.orange
)
}
)
element.block(id: "b3", w: 2, h: 3,
x: (rel: 1, to: "g1.east"),
y: (from: "b1-port-out", to: "in1"),
ports: (west: ((id: "in1"), (id: "in2"))),
fill: util.colors.blue
)
wire.wire("w1", ("b1-port-out", "b3-port-in1"))
wire.wire("w2", ("b2-port-out", "b3-port-in2"),
style: "zigzag")
```)
#let group-ports = example(```
// outside-port-names: true (default) — labels outside the border
element.group(
id: "g1", name: "outside (default)",
stroke: (dash: "dashed"),
fill: util.colors.blue.lighten(60%),
padding: 2em,
outside-port-names: true,
port-name-padding: 4pt,
ports: (
west: ((id: "in", name: "D"),),
east: ((id: "out", name: "Q"),),
),
{ element.block(id: "b1", w: 2, h: 1.5, x: 0, y: 0,
fill: util.colors.green,
ports: (west: ((id: "d", name: "d"),),
east: ((id: "q", name: "q"),))) }
)
wire.wire("wG1in", ("g1-port-in", "b1-port-d"), directed: true)
wire.wire("wG1out", ("b1-port-q", "g1-port-out"), directed: true)
// outside-port-names: false — labels inside the border (block style)
// with extra padding via port-name-padding
element.group(
id: "g2", name: "inside",
stroke: (dash: "dashed"),
fill: util.colors.orange.lighten(60%),
padding: 2em,
outside-port-names: false,
port-name-padding: 0.4em,
ports: (
west: ((id: "in", name: "D"),),
east: ((id: "out", name: "Q"),),
),
{ element.block(id: "b2", w: 2, h: 1.5, x: 6, y: 0,
fill: util.colors.green,
ports: (west: ((id: "d"),), east: ((id: "q"),))) }
)
wire.stub("g2-port-in", "west")
wire.stub("g2-port-out", "east")
```, vertical: true)
#let port-names-outside = example(```
element.block(
x: 0, y: 0, w: 2, h: 2, id: "block",
port-names-outside: true,
port-name-padding: 4pt,
ports: (
north: ((id: "clk", name: "CLK", clock: true),),
west: ((id: "in1", name: "A"),
(id: "in2", name: "B")),
east: ((id: "out", name: "Y"),)
)
)
```)
#let port-shape = example(```
// Block with pentagon-shaped ports.
// - port-shape / port-kind / port-size are element-level defaults.
// - Any port field (shape, kind, size, stroke, fill) overrides per port.
// - kind: "in" → tip points into the block (default)
// kind: "out" → tip points away from the block
// - clock ports with shape: "pentagon" drop their triangle and default
// to a gray fill.
element.block(
id: "b", w: 3, h: 3, x: 0, y: 0,
fill: util.colors.yellow.lighten(60%),
port-shape: "pentagon",
port-size: 1em,
ports: (
// west: default input
west: ((id: "in", name: "D"),),
// east: output — tip points away
east: ((id: "out", name: "Q", kind: "out"),),
// north: clock port — pentagon replaces the triangle, gray fill
north: ((id: "clk", name: "CLK", clock: true),),
// south: styled output with per-port fill and stroke
south: ((id: "rdy", name: "RDY", kind: "out",
fill: util.colors.blue, stroke: red + 1.5pt),),
),
)
// Wires connect at the outer end of each pentagon.
wire.stub("b-port-in", "west")
wire.stub("b-port-out", "east")
wire.stub("b-port-clk", "north")
wire.stub("b-port-rdy", "south")
```)
#let shell = example(```
// A shell block acts as a container: port name labels are drawn
// outside by default, and wires attach at the *inside* tip of
// pentagon-shaped ports so child elements can sit within.
element.block(
id: "sh", w: 5, h: 3, x: 0, y: 0,
shell: true,
port-shape: "pentagon",
fill: util.colors.blue.lighten(80%),
ports: (
west: ((id: "in", name: "IN"),),
east: ((id: "out", name: "OUT", kind: "out"),),
),
{
element.block(id: "inner", w: 2, h: 1.5, x: 1.5, y: 0.75,
fill: util.colors.yellow,
ports: (west: ((id: "a"),), east: ((id: "b"),)))
}
)
wire.wire("w-in", ("sh-port-in", "inner-port-a"))
wire.wire("w-out", ("inner-port-b", "sh-port-out"))
```)
#let intersection = example(```
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
wire.wire("w2", ((0, 0), (1, -.5)),
style: "zigzag", zigzag-ratio: 80%)
wire.intersection("w1.zig")
```)
#let u-shape-wire = example(```
// east <-> east (u-side auto-detected)
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "eA",
fill: util.colors.orange,
ports: (east: ((id: "out"),)))
element.block(x: 0.5, y: -2.5, w: 1.5, h: 1.5, id: "eB",
fill: util.colors.orange,
ports: (east: ((id: "out"),)))
wire.wire("uEE", ("eA-port-out", "eB-port-out"),
style: "u-shape", u-depth: 0.5)
// west <-> west (auto-detected)
element.block(x: 6, y: 0, w: 1.5, h: 1.5, id: "wA",
fill: util.colors.yellow,
ports: (west: ((id: "in"),)))
element.block(x: 6.5, y: -2.5, w: 1.5, h: 1.5, id: "wB",
fill: util.colors.yellow,
ports: (west: ((id: "in"),)))
wire.wire("uWW", ("wA-port-in", "wB-port-in"),
style: "u-shape", u-depth: 0.5)
// north <-> north (auto-detected)
element.block(x: 10, y: -3, w: 1.5, h: 1.5, id: "nA",
fill: util.colors.green,
ports: (north: ((id: "out"),)))
element.block(x: 12.5, y: -2.5, w: 1.5, h: 1.5, id: "nB",
fill: util.colors.green,
ports: (north: ((id: "out"),)))
wire.wire("uNN", ("nA-port-out", "nB-port-out"),
style: "u-shape", u-depth: 0.5)
// south <-> south with explicit u-side override
element.block(x: 16, y: -3, w: 1.5, h: 1.5, id: "sA",
fill: util.colors.pink,
ports: (south: ((id: "out"),)))
element.block(x: 18.5, y: -3.5, w: 1.5, h: 1.5, id: "sB",
fill: util.colors.pink,
ports: (south: ((id: "out"),)))
wire.wire("uSS", ("sA-port-out", "sB-port-out"),
style: "u-shape", u-side: "south", u-depth: 0.5)
```, vertical: true)