diff --git a/gallery/target_api.pdf b/gallery/target_api.pdf new file mode 100644 index 0000000..e56479d Binary files /dev/null and b/gallery/target_api.pdf differ 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 {