From 358de4825d2ef27c779e1f55e4fdfeb2bf35249f Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Fri, 18 Apr 2025 17:25:38 +0200 Subject: [PATCH] initial rewrite of drawing pipeline + positioning --- gallery/target_api.pdf | Bin 0 -> 6072 bytes gallery/target_api.typ | 297 +++++++++++++++++++++++++++++++++++++++ src/circuit.typ | 36 ++++- src/elements/block.typ | 39 +---- src/elements/element.typ | 272 ++++++++++++++++++++++++----------- src/elements/ports.typ | 16 +-- 6 files changed, 536 insertions(+), 124 deletions(-) create mode 100644 gallery/target_api.pdf create mode 100644 gallery/target_api.typ diff --git a/gallery/target_api.pdf b/gallery/target_api.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e56479de9be760ab82fe19b546b7c6458bb4f9c7 GIT binary patch literal 6072 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 yE0uyK72PaYr-|JZVX$h02ov literal 0 HcmV?d00001 diff --git a/gallery/target_api.typ b/gallery/target_api.typ new file mode 100644 index 0000000..991caf9 --- /dev/null +++ b/gallery/target_api.typ @@ -0,0 +1,297 @@ +#import "../src/lib.typ": * +#import "@preview/cetz:0.3.4": draw +#circuit({ + element.block( + size: (1.5, 2.2), + id: "PCBuf", + fill: util.colors.orange, + ports: ( + west: "PCNext", + north: (id: "CLK", clock: true), + east: "PC", + south: (("EN", "EN"),) + ), + debug: (ports: true) + ) + + element.block( + size: (1, 2), + ports: ( + west: (("a", "A"), "e"), + north: "b", + east: "c", + south: "d" + ), + pos: ( + (offset: -1, from: "PCBuf.south"), + 2,//(align: "e", with: "PCBuf.EN"), + ) + ) + + /* + wire.stub("PCBuf.CLK", name: "CLK") + wire.stub("PCBuf.EN", name: "PCWrite") + + element.multiplexer( + pos: ( + 3, (align: "in0", with: "PCBuf.PC") + ), + size: (1, 2), + id: "AdrSrc-MP", + fill: util.colors.orange, + entries: 2 + ) + wire.wire( + "PCBuf.PC", + "AdrSrc-MP.in0", + id: "wPCBuf-InstDataMgr", + name: "PC", + bus: true + ) + wire.stub("AdrSrc-MP.north", name: "AdrSrc") + + element.block( + pos: ( + 6, (align: "A", with: "AdrSrc-MP.out") + ), + size: (3, 4), + id: "InstDataMgr", + fill: util.colors.yellow, + ports: ( + west: ( + ("A", "A"), + ("WD", "WD") + ), + north: ( + (id: "CLK", clock: true), + (id: "WE", name: "WE", vertical: true), + (id: "IRWrite", name: "IRWrite", vertical: true) + ), + east: ( + ("Instr", "Instr."), + ("RD", "RD") + ) + ), + ports-margins: ( + west: (30%, 0%), + east: (40%, 0%) + ) + ) + wire.wire( + "AdrSrc-MP.out", + "InstDataMgr.A", + id: "wAdrSrcMP-InstDataMgr", + name: (none, "Adr"), + bus: true + ) + + wire.stub("InstDataMgr.CLK", name: "CLK") + wire.stub("InstDataMgr.WE") + wire.stub("InstDataMgr.IRWrite") + wire.stub("InstDataMgr.WD") + + element.block( + pos: ( + 15, (align: "WD3", with: "InstDataMgr.RD") + ), + size: (3, 4), + id: "RegFile", + fill: util.colors.pink, + ports: ( + west: ( + ("A1", "A1"), + ("A2", "A2"), + ("A3", "A3"), + ("WD3", "WD3"), + ), + north: ( + (id: "CLK", clock: true), + (id: "WE3", name: "WE3", vertical: true) + ), + east: ( + ("RD1", "RD1"), + ("RD2", "RD2"), + ) + ), + ports-margins: ( + east: (20%, 20%) + ) + ) + wire.stub("RegFile.CLK", name: "CLK") + wire.stub("RegFile.WE3", name: "Regwrite", name-offset: 0.6) + wire.stub("RegFile.A2") + wire.stub("RegFile.RD2") + + element.extender( + pos: (15, -3.5), + size: (3, 1), + id: "Extender", + fill: util.colors.green + ) + wire.wire( + "Extender.north", + (18, -2), + id: "wExtender-ImmSrc", + style: "zigzag", + zigzag-ratio: 0%, + name: (none, "ImmSrc"), + bus: true + ) + + let mid = ("InstDataMgr.east", 50%, "RegFile.west") + wire.wire( + "InstDataMgr.Instr", + (vertical: (), horizontal: mid), + id: "wInstDataMgr-Bus" + name: ("Instr", none), + bus: true + ) + wire.wire( + (v => (v.at(0), -3.5), mid), + (horizontal: (), vertical: (0, 3.5)), + id: "wBus", + bus: true + ) + wire.wire( + "RegFile.A1", + (horizontal: mid, vertical: ()), + id: "wBus-RegFile-A1" + name: (none, "RS1"), + slice: (19, 15), + reverse: true, + bus: true + ) + wire.wire( + "RegFile.A3", + (horizontal: mid, vertical: ()), + id: "wBus-RegFile-A3" + name: (none, "RD"), + slice: (11, 7), + reverse: true, + bus: true + ) + wire.wire( + "Extender.in", + (horizontal: mid, vertical: ()), + id: "wBus-Extender" + slice: (31, 7), + reverse: true, + bus: true + ) + + element.alu( + pos: ( + 22, (align: "in1", with: "RegFile.RD1") + ), + size: (1, 2), + id: "ALU", + fill: util.colors.purple + ) + wire.wire( + "RegFile.RD1", + "ALU.in1", + id: "wRegFile-ALU" + name: ("A", "SrcA"), + bus: true + ) + + element.block( + pos: ( + 26, (align: "in", with: "ALU.out") + ), + size: (1.5, 2), + id: "OutBuf", + fill: util.colors.orange, + ports: ( + west: "in", + north: (id: "CLK", clock: true), + east: "out" + ) + ) + wire.stub("OutBuf.CLK", name: "CLK") + wire.wire( + "ALU.out", + "OutBuf.in", + id: "wALU-OutBuf" + name: "ALUResult", + bus: true + ) + + element.multiplexer( + pos: ( + 30, (align: "in0", with: "OutBuf.out") + ), + size: (1, 2.5), + id: "Res-MP", + fill: util.colors.orange, + entries: 3 + ) + wire.stub("Res-MP.north", name: "ResultSrc") + wire.stub("Res-MP.in2") + wire.wire( + "OutBuf.out", + "Res-MP.in0", + id: "wOutBuf-ResMP" + name: "ALUOut", + bus: true + ) + + wire.wire( + "Extender.out", + "ALU.in2", + id: "wExt-ALU" + name: ("ImmExt", "SrcB"), + bus: true, + style: "zigzag", + zigzag-ratio: 60% + ) + + wire.wire( + "InstDataMgr.RD", + "Res-MP.in1", + id: "wInstDataMgr-ResMP" + style: "dodge", + dodge-y: -4, + dodge-sides: ("east", "west"), + name: ("Data", none), + bus: true + ) + + wire.wire( + "Res-MP.out", + "AdrSrc-MP.in1", + id: "wResMP-AdrSrc" + style: "dodge", + dodge-y: -5, + dodge-sides: ("east", "west"), + dodge-margins: (0.5, 1), + bus: true + ) + + wire.wire( + "Res-MP.out", + "RegFile.WD3", + id: "wResMP-RegFile" + style: "dodge", + dodge-y: -5, + dodge-sides: ("east", "west"), + dodge-margins: (0.5, 1), + bus: true + ) + + wire.wire( + "Res-MP.out", + "PCBuf.PCNext", + id: "wResMP-PCBuf" + style: "dodge", + dodge-y: -5, + dodge-sides: ("east", "west"), + dodge-margins: (0.5, 1.5), + name: (none, "PCNext"), + bus: true + ) + + wire.intersection("wResMP-RegFile.dodge-end", radius: .2) + wire.intersection("wResMP-AdrSrc.dodge-end", radius: .2) + */ +}) \ No newline at end of file diff --git a/src/circuit.typ b/src/circuit.typ index 7a701c7..bdf6184 100644 --- a/src/circuit.typ +++ b/src/circuit.typ @@ -9,6 +9,38 @@ /// - length (length, ratio): Optional base unit /// -> none #let circuit(body, length: 2em) = { - set text(font: "Source Sans 3") - canvas(length: length, body) + let next-id = 0 + let elements = (:) + + for element in body { + let internal = type(element) == dictionary and "id" in element + let eid = if internal {element.id} else {none} + if eid == none { + while str(next-id) in elements { + next-id += 1 + } + eid = str(next-id) + if internal { + element.id = eid + } + next-id += 1 + } + elements.insert(eid, element) + } + + for element in elements.values() { + if type(element) == dictionary and "pre-process" in element { + elements = (element.pre-process)(elements, element) + } + } + + canvas(length: length, { + for element in elements.values() { + if type(element) == dictionary and "draw" in element { + (element.draw)(element) + } else { + (element,) + } + } + }) } \ No newline at end of file diff --git a/src/elements/block.typ b/src/elements/block.typ index 84623fe..58a1b6f 100644 --- a/src/elements/block.typ +++ b/src/elements/block.typ @@ -1,16 +1,15 @@ #import "@preview/cetz:0.3.2": draw #import "element.typ" -#let draw-shape(id, tl, tr, br, bl, fill, stroke) = { +#let draw-shape(elmt, bounds) = { let f = draw.rect( radius: 0.5em, inset: 0.5em, - fill: fill, - stroke: stroke, - name: id, - bl, tr + fill: elmt.fill, + stroke: elmt.stroke, + bounds.bl, bounds.tr ) - return (f, tl, tr, br, bl) + return (f, bounds) } /// Draws a block element @@ -18,32 +17,8 @@ /// #examples.block /// For parameters description, see #doc-ref("element.elmt") #let block( - x: none, - y: none, - w: none, - h: none, - name: none, - name-anchor: "center", - ports: (), - ports-margins: (), - fill: none, - stroke: black + 1pt, - id: "", - debug: ( - ports: false - ) + ..args ) = element.elmt( draw-shape: draw-shape, - x: x, - y: y, - w: w, - h: h, - name: name, - name-anchor: name-anchor, - ports: ports, - ports-margins: ports-margins, - fill: fill, - stroke: stroke, - id: id, - debug: debug + ..args ) \ No newline at end of file diff --git a/src/elements/element.typ b/src/elements/element.typ index 635fc0b..2dc0bb6 100644 --- a/src/elements/element.typ +++ b/src/elements/element.typ @@ -1,4 +1,4 @@ -#import "@preview/cetz:0.3.2": draw, coordinate +#import "@preview/cetz:0.3.2": draw, coordinate, matrix, vector #import "ports.typ": add-ports, add-port #import "../util.typ" @@ -13,10 +13,163 @@ panic("Could not find port with id " + str(id)) } -#let default-draw-shape(id, tl, tr, br, bl, fill, stroke) = { - return ({}, tl, tr, br, bl) +#let local-to-global(origin, u, v, points) = { + return points-real = points.map(p => { + let (pu, pv) = p + return vector.add( + origin, + vector.add( + vector.scale(u, pu), + vector.scale(v, pv) + ) + ) + }) } +#let default-draw-shape(elmt, bounds) = { + return ({}, bounds) +} + +#let default-pre-process(elements, element) = { + return elements +} + +#let resolve-offset(ctx, offset, from, axis) = { + let (ctx, pos) = coordinate.resolve( + ctx, + (rel: offset, to: from) + ) + return pos.at(axis) +} + +#let resolve-align(ctx, elmt, align, with, axis) = { + let (align-side, i) = find-port(elmt.ports, align) + let margins = (0%, 0%) + if align-side in elmt.ports-margins { + margins = elmt.ports-margins.at(align-side) + } + + let parallel-sides = ( + ("north", "south"), + ("west", "east") + ).at(axis) + + let ortho-sides = ( + ("west", "east"), + ("north", "south") + ).at(axis) + + let dl + let start-margin + let len = elmt.size.at(axis) + if align-side in parallel-sides { + let used-pct = 100% - margins.at(0) - margins.at(1) + let used-len = len * used-pct / 100% + start-margin = len * margins.at(0) / 100% + + dl = used-len * (i + 1) / (elmt.ports.at(align-side).len() + 1) + + if not elmt.auto-ports { + start-margin = 0 + dl = elmt.ports-pos.at(with)(len) + } + } else if align-side == ortho-sides.first() { + dl = 0 + start-margin = 0 + } else { + dl = len + start-margin = 0 + } + + if axis == 1 { + dl = len - dl + } + + let (ctx, with-pos) = coordinate.resolve(ctx, with) + return with-pos.at(axis) - dl + start-margin +} + +#let resolve-coordinate(ctx, elmt, coord, axis) = { + if type(coord) == dictionary { + let offset = coord.at("offset", default: none) + let from = coord.at("from", default: none) + let align = coord.at("align", default: none) + let with = coord.at("with", default: none) + + if none not in (offset, from) { + if type(offset) != array { + let a = (0, 0) + a.at(axis) = offset + offset = a + } + return resolve-offset(ctx, offset, from, axis) + + } else if none not in (align, with) { + return resolve-align(ctx, elmt, align, with, axis) + } else { + panic("Dictionnary must either provide both 'offset' and 'from', or 'align' and 'with'") + } + } + if type(coord) not in (int, float, length) { + panic("Invalid " + "xy".at(axis) + " coordinate: " + repr(coord)) + } + return coord +} + +#let make-bounds(x, y, w, h) = { + let w2 = w / 2 + let h2 = h / 2 + + return ( + bl: (x, y), + tl: (x, y + h), + tr: (x + w, y + h), + br: (x + w, y), + center: (x + w2, y + h2), + b: (x + w2, y), + t: (x + w2, y + h), + l: (x, y + h2), + r: (x + w, y + h2), + ) +} + +#let render(draw-shape, elmt) = draw.group(name: elmt.id, ctx => { + let width = elmt.size.first() + let height = elmt.size.last() + + let x = elmt.pos.first() + let y = elmt.pos.last() + + x = resolve-coordinate(ctx, elmt, x, 0) + y = resolve-coordinate(ctx, elmt, y, 1) + + let bounds = make-bounds(x, y, width, height) + + // Workaround because CeTZ needs to have all draw functions in the body + let func = {} + (func, bounds) = draw-shape(elmt, bounds) + func + + if elmt.name != none { + draw.content( + (name: elmt.id, anchor: elmt.name-anchor), + anchor: if elmt.name-anchor in util.valid-anchors {elmt.name-anchor} else {"center"}, + padding: 0.5em, + align(center)[*#elmt.name*] + ) + } + + if elmt.auto-ports { + add-ports( + elmt.id, + bounds, + elmt.ports, + elmt.ports-margins, + debug: elmt.debug.ports + ) + } +}) + /// Draws an element /// - draw-shape (function): Draw function /// - x (number, dictionary): The x position (bottom-left corner). @@ -46,10 +199,9 @@ /// - `ports`: if true, shows dots on all ports of the element #let elmt( draw-shape: default-draw-shape, - x: none, - y: none, - w: none, - h: none, + pre-process: default-pre-process, + pos: (0, 0), + size: (1, 1), name: none, name-anchor: "center", ports: (:), @@ -62,84 +214,42 @@ debug: ( ports: false ) -) = draw.get-ctx(ctx => { - let width = w - let height = h - - let x = x - let y = y - if x == none { panic("Parameter x must be set") } - if y == none { panic("Parameter y must be set") } - if w == none { panic("Parameter w must be set") } - if h == none { panic("Parameter h must be set") } - - if (type(x) == dictionary) { - let offset = x.rel - let to = x.to - let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to)) - x = to-pos.at(0) - } - - if (type(y) == dictionary) { - let from = y.from - let to = y.to - let (to-side, i) = find-port(ports, to) - let margins = (0%, 0%) - if to-side in ports-margins { - margins = ports-margins.at(to-side) +) = { + for (key, side-ports) in ports.pairs() { + if type(side-ports) == str { + side-ports = ((id: side-ports),) + } else if type(side-ports) == dictionary { + side-ports = (side-ports,) } - let dy - let top-margin - if to-side in ("east", "west") { - let used-pct = 100% - margins.at(0) - margins.at(1) - let used-height = height * used-pct / 100% - top-margin = height * margins.at(0) / 100% - - dy = used-height * (i + 1) / (ports.at(to-side).len() + 1) - - if not auto-ports { - top-margin = 0 - dy = ports-y.at(to)(height) + for (i, port) in side-ports.enumerate() { + if type(port) == array { + side-ports.at(i) = ( + id: port.at(0, default: ""), + name: port.at(1, default: "") + ) + } else if type(port) == str { + side-ports.at(i) = (id: port) } - } else if to-side == "north" { - dy = 0 - top-margin = 0 - } else if to-side == "south" { - dy = height - top-margin = 0 } - - let (ctx, from-pos) = coordinate.resolve(ctx, from) - y = from-pos.at(1) + dy - height + top-margin + ports.at(key) = side-ports } - let tl = (x, y + height) - let tr = (x + width, y + height) - let br = (x + width, y) - let bl = (x, y) - // Workaround because CeTZ needs to have all draw functions in the body - let func = {} - (func, tl, tr, br, bl) = draw-shape(id, tl, tr, br, bl, fill, stroke) - func - - if (name != none) { - draw.content( - (name: id, anchor: name-anchor), - anchor: if name-anchor in util.valid-anchors {name-anchor} else {"center"}, - padding: 0.5em, - align(center)[*#name*] - ) - } - - if auto-ports { - add-ports( - id, - tl, tr, br, bl, - ports, - ports-margins, - debug: debug.ports - ) - } -}) \ No newline at end of file + return (( + id: id, + draw: render.with(draw-shape), + pre-process: pre-process, + pos: pos, + size: size, + name: name, + name-anchor: name-anchor, + ports: ports, + ports-margins: ports-margins, + fill: fill, + stroke: stroke, + auto-ports: auto-ports, + ports-y: ports-y, + debug: debug + ),) +} diff --git a/src/elements/ports.typ b/src/elements/ports.typ index 951fa4a..66d1f0c 100644 --- a/src/elements/ports.typ +++ b/src/elements/ports.typ @@ -34,12 +34,10 @@ angle: if name-rotate {90deg} else {0deg}, name ) - let id = elmt-id + "-port-" + port.at("id") if debug { draw.circle( pos, - name: id, radius: .1, stroke: none, fill: red @@ -49,24 +47,24 @@ draw.hide(draw.circle( pos, radius: 0, - stroke: none, - name: id + stroke: none )) } + draw.anchor(port.id, pos) } #let add-ports( elmt-id, - tl, tr, br, bl, + bounds, ports, ports-margins, debug: false ) = { let sides = ( - "north": (tl, tr), - "east": (tr, br), - "south": (bl, br), - "west": (tl, bl) + "north": (bounds.tl, bounds.tr), + "east": (bounds.tr, bounds.br), + "south": (bounds.bl, bounds.br), + "west": (bounds.tl, bounds.bl) ) if type(ports) != dictionary {