forked from HEL/circuiteria
Compare commits
1
Commits
port-shape
...
breakout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6070ce7fe3 |
No files matched your search
@@ -81,6 +81,81 @@ 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")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,117 @@
|
||||
// Demonstrates the wire.breakout element used to split off slices of a
|
||||
// bus that connects blocks. The main bus wires are always laid out
|
||||
// orthogonally; breakouts diagonally tap intermediate points on those
|
||||
// wires and route the sub-bus to another block via an L-shaped zigzag.
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// ---- Source block on the left, main sink on the right ----
|
||||
element.block(x: 0, y: 0, w: 2, h: 2, id: "src", fill: util.colors.blue, ports: (
|
||||
east: ((id: "out"),),
|
||||
))
|
||||
|
||||
element.block(x: 15, y: 0, w: 2, h: 2, id: "dstMain", fill: util.colors.green, ports: (
|
||||
west: ((id: "in"),),
|
||||
))
|
||||
|
||||
// Full bus from src to dstMain, labelled [15:0] at the start.
|
||||
wire.wire("wbus", ("src-port-out", "dstMain-port-in"), name: "[15:0]", name-pos: "start")
|
||||
|
||||
// ---- Upper-left sink: taps bits [11:8] via NW breakout ----
|
||||
element.block(x: 0, y: 4, w: 2, h: 2, id: "dstNW", fill: util.colors.yellow, ports: (
|
||||
east: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapNE",
|
||||
x: (rel: 3, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "north-east",
|
||||
port-side: "west",
|
||||
name: "[15:12]",
|
||||
name-pos: "north-east",
|
||||
)
|
||||
|
||||
// L-shape: up first, then left into dstNW.
|
||||
wire.wire(
|
||||
"wNW",
|
||||
("tapNE-port-tapNE", "dstNW-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
|
||||
// ---- Upper-right sink: taps bits [15:12] via NE breakout ----
|
||||
element.block(x: 15, y: 4, w: 2, h: 2, id: "dstNE", fill: util.colors.orange, ports: (
|
||||
west: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapNW",
|
||||
x: (rel: 7, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "north-west",
|
||||
port-side: "east",
|
||||
name: "[11:8]",
|
||||
name-pos: "north-east",
|
||||
)
|
||||
|
||||
// L-shape: up first, then right into dstNE.
|
||||
wire.wire(
|
||||
"wNE",
|
||||
("tapNW-port-tapNW", "dstNE-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
|
||||
// ---- Lower-left sink: taps bits [3:0] via SW breakout ----
|
||||
element.block(x: 0, y: -4, w: 2, h: 2, id: "dstSW", fill: util.colors.purple, ports: (
|
||||
east: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapSE",
|
||||
x: (rel: 5, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "south-east",
|
||||
port-side: "west",
|
||||
name: "[7:4]",
|
||||
name-pos: "south-east",
|
||||
)
|
||||
|
||||
// L-shape: down first, then left into dstSW.
|
||||
wire.wire(
|
||||
"wSW",
|
||||
("tapSE-port-tapSE", "dstSW-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
|
||||
// ---- Lower-right sink: taps bits [7:4] via SE breakout ----
|
||||
element.block(x: 15, y: -4, w: 2, h: 2, id: "dstSE", fill: util.colors.pink, ports: (
|
||||
west: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapSW",
|
||||
x: (rel: 11, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "south-west",
|
||||
port-side: "east",
|
||||
name: "[3:0]",
|
||||
name-pos: "south-west",
|
||||
)
|
||||
|
||||
// L-shape: down first, then right into dstSE.
|
||||
wire.wire(
|
||||
"wSE",
|
||||
("tapSW-port-tapSW", "dstSE-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
})
|
||||
+8506
-7516
File diff suppressed because it is too large.
Load diff
+139
-1
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "util.typ": opposite-anchor, normalize-coord
|
||||
#import "util.typ": opposite-anchor, normalize-coord, resolve-rel-coord
|
||||
|
||||
/// List of valid wire styles
|
||||
/// #examples.wires
|
||||
@@ -511,3 +511,141 @@
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a wire breakout: a 45° diagonal line starting at (x, y) and
|
||||
/// ending 1 unit away in one of the four diagonal directions. The end
|
||||
/// point exposes a port anchor named `<id>-port-<id>` so wires can attach
|
||||
/// to it as if it were a block port. A synthesized `<id>.center` anchor
|
||||
/// is placed opposite `port-side` so #doc-ref("wire.wire") with
|
||||
/// `style: "u-shape"` can auto-detect the port side.
|
||||
///
|
||||
/// #examples.breakout
|
||||
///
|
||||
/// Label positions are anchored so that the label sits neatly next to the
|
||||
/// end point of the breakout. When the label direction matches the
|
||||
/// breakout direction (which would overlap the line) the label is
|
||||
/// automatically placed at the start point instead so the text does not
|
||||
/// cover the line.
|
||||
///
|
||||
/// #examples.breakout-label
|
||||
///
|
||||
/// The port anchor makes it easy to fan out a bus:
|
||||
///
|
||||
/// #examples.breakout-wire
|
||||
///
|
||||
/// - id (str): The breakout id (also used as the port id)
|
||||
/// - x (number, dictionary): The starting x position (same forms as `element.block`)
|
||||
/// - y (number, dictionary): The starting y position (same forms as `element.block`)
|
||||
/// - direction (str): One of `"north-east"`, `"north-west"`, `"south-east"`, `"south-west"`
|
||||
/// - length (length, number): The diagonal length (default `1em`, matches port size)
|
||||
/// - port-side (str): Cardinal side used for wire-attach behavior (`"north"`, `"east"`, `"south"`, `"west"`)
|
||||
/// - stroke (stroke): Line stroke (paint + thickness)
|
||||
/// - name (none, str): Optional label displayed near the breakout
|
||||
/// - name-pos (none, str): Label position, one of `"north-west"`, `"north-east"`, `"south-west"`, `"south-east"`
|
||||
/// - debug (bool): If true, shows a red dot at the port anchor
|
||||
#let breakout(
|
||||
id,
|
||||
x: none, y: none,
|
||||
direction: "north-east",
|
||||
length: 1em,
|
||||
port-side: "east",
|
||||
stroke: black + 1pt,
|
||||
name: none,
|
||||
name-pos: none,
|
||||
debug: false,
|
||||
) = draw.get-ctx(ctx => {
|
||||
if x == none { panic("breakout: parameter x must be set") }
|
||||
if y == none { panic("breakout: parameter y must be set") }
|
||||
|
||||
let valid-directions = ("north-east", "north-west", "south-east", "south-west")
|
||||
if not direction in valid-directions {
|
||||
panic("breakout: invalid direction '" + repr(direction)
|
||||
+ "'; must be one of " + repr(valid-directions))
|
||||
}
|
||||
let valid-sides = ("north", "east", "south", "west")
|
||||
if not port-side in valid-sides {
|
||||
panic("breakout: invalid port-side '" + repr(port-side)
|
||||
+ "'; must be one of " + repr(valid-sides))
|
||||
}
|
||||
|
||||
// Diagonal component: length / sqrt(2). Resolve to a canvas-space
|
||||
// number so we can do arithmetic with anchor coordinates.
|
||||
let d-raw = length / calc.sqrt(2)
|
||||
let (_, d-vec) = coordinate.resolve(ctx, (d-raw, 0, 0))
|
||||
let d = d-vec.at(0)
|
||||
|
||||
// Direction sign vector for (dx, dy).
|
||||
let sx = if direction.ends-with("east") { 1 } else { -1 }
|
||||
let sy = if direction.starts-with("north") { 1 } else { -1 }
|
||||
|
||||
// Resolve x.
|
||||
let sx0 = x
|
||||
if type(x) == dictionary {
|
||||
if "rel" in x {
|
||||
sx0 = resolve-rel-coord(ctx, x, 0)
|
||||
} else if "from" in x {
|
||||
let (_, from-pos) = coordinate.resolve(ctx, x.from)
|
||||
sx0 = from-pos.at(0)
|
||||
} else {
|
||||
panic("breakout: unsupported x dictionary: " + repr(x))
|
||||
}
|
||||
}
|
||||
// Resolve y.
|
||||
let sy0 = y
|
||||
if type(y) == dictionary {
|
||||
if "rel" in y {
|
||||
sy0 = resolve-rel-coord(ctx, y, 1)
|
||||
} else if "from" in y {
|
||||
let (_, from-pos) = coordinate.resolve(ctx, y.from)
|
||||
sy0 = from-pos.at(1)
|
||||
} else {
|
||||
panic("breakout: unsupported y dictionary: " + repr(y))
|
||||
}
|
||||
}
|
||||
|
||||
let start = (sx0, sy0)
|
||||
let end = (rel: (sx * d, sy * d), to: start)
|
||||
|
||||
// Offset used for the synthesized center anchor: opposite of port-side.
|
||||
let center-offset = (
|
||||
"north": (0, -1em),
|
||||
"east": (-1em, 0),
|
||||
"south": (0, 1em),
|
||||
"west": (1em, 0),
|
||||
).at(port-side)
|
||||
|
||||
let port-anchor-name = id + "-port-" + id
|
||||
|
||||
draw.group(name: id, {
|
||||
draw.line(start, end, stroke: stroke)
|
||||
draw.anchor("start", start)
|
||||
draw.anchor("end", end)
|
||||
draw.anchor("center", (rel: center-offset, to: end))
|
||||
|
||||
if name != none and name-pos != none {
|
||||
let label-anchor = (
|
||||
"north-west": "south-east",
|
||||
"north-east": "south-west",
|
||||
"south-west": "north-east",
|
||||
"south-east": "north-west",
|
||||
).at(name-pos)
|
||||
|
||||
// Overlap case: label direction is opposite to breakout direction,
|
||||
// so the label at the end point would extend back over the line.
|
||||
// Anchor it at the start point instead.
|
||||
let label-pos = if name-pos == opposite-anchor(direction) { start } else { end }
|
||||
|
||||
draw.content(label-pos, anchor: label-anchor, padding: 3pt, name)
|
||||
}
|
||||
})
|
||||
|
||||
// Port anchor emitted at top level so it can be referenced by its
|
||||
// bare name (mirrors add-port's approach in ports.typ).
|
||||
if debug {
|
||||
draw.circle(end, name: port-anchor-name,
|
||||
radius: .1, stroke: none, fill: red)
|
||||
} else {
|
||||
draw.hide(draw.circle(end, name: port-anchor-name,
|
||||
radius: 0, stroke: none))
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user