forked from HEL/circuiteria
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.
450 lines
14 KiB
Typst
450 lines
14 KiB
Typst
#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)
|