Compare commits
	
		
			38 Commits
		
	
	
		
			1e6c6cfc43
			...
			v0.1.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9a1fda087c | |||
| c5a0cf7ecc | |||
| 9a6973fb89 | |||
| d8094384ad | |||
| 86ca91920f | |||
| 64d6635d5f | |||
| 6a6ae96a96 | |||
| a856b7f634 | |||
| 011802ffbe | |||
| 4fae4fe19a | |||
| d6248865b3 | |||
| 2317fec71d | |||
| ff9ad9e94a | |||
| 4dc69a92c5 | |||
| ba7630c03c | |||
| f9916e5856 | |||
| 7731159dd0 | |||
| 699b3c0189 | |||
| 08f931bbde | |||
| 7da2bb2b64 | |||
| e4194d0925 | |||
| 86d9122740 | |||
| 5bd3dd8111 | |||
| 66ac91af7b | |||
| 047c3b8893 | |||
| 64a6146f32 | |||
| 8ec245f36e | |||
| a9014e03e9 | |||
| c01e33cab4 | |||
| 9e4767663e | |||
| 4d97062d30 | |||
| ccc38c3e1d | |||
| 21b5d1cbfe | |||
| 7e0209b712 | |||
| ef4461bd18 | |||
| 6174baf650 | |||
| 6d6c357534 | |||
| c9155a0b99 | 
							
								
								
									
										63
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,3 +1,64 @@ | ||||
| # circuiteria | ||||
|  | ||||
| Drawing block circuits with Typst made easy, using CeTZ | ||||
| Circuiteria is a [Typst](https://typst.app) package for drawing block circuit diagrams using the [CeTZ](https://typst.app/universe/package/cetz) package. | ||||
|  | ||||
| <p align="center"> | ||||
|   <img src="./gallery/platypus.png" alt="Perry the platypus"> | ||||
| </p> | ||||
|  | ||||
| ## Examples | ||||
| <table> | ||||
|   <tr> | ||||
|     <td colspan="2"> | ||||
|       <a href="./gallery/test.typ"> | ||||
|         <img src="./gallery/test.png" width="500px"> | ||||
|       </a> | ||||
|     </td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td colspan="2">A bit of eveything</td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td colspan="2"> | ||||
|       <a href="./gallery/test5.typ"> | ||||
|         <img src="./gallery/test5.png" width="500px"> | ||||
|       </a> | ||||
|     </td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td colspan="2">Wires everywhere</td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td> | ||||
|       <a href="./gallery/test4.typ"> | ||||
|         <img src="./gallery/test4.png" width="250px"> | ||||
|       </a> | ||||
|     </td> | ||||
|     <td> | ||||
|       <a href="./gallery/test6.typ"> | ||||
|         <img src="./gallery/test6.png" width="250px"> | ||||
|       </a> | ||||
|     </td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td>Groups</td> | ||||
|     <td>Rotated</td> | ||||
|   </tr> | ||||
| </table> | ||||
|  | ||||
| > **Note**\ | ||||
| > These circuit layouts were copied from a digital design course given by prof. S. Zahno and recreated using this package | ||||
|  | ||||
| *Click on the example image to jump to the code.* | ||||
|  | ||||
| ## Usage | ||||
| For more information, see the [manual](manual.pdf) | ||||
|  | ||||
| To use this package, simply import [circuiteria](https://typst.app/universe/package/circuiteria) and call the `circuit` function: | ||||
| ```typ | ||||
| #import "@preview/circuiteria:0.1.0" | ||||
| #circuiteria.circuit({ | ||||
|   import circuiteria: * | ||||
|   ... | ||||
| }) | ||||
| ``` | ||||
							
								
								
									
										41
									
								
								doc/example.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "../src/circuit.typ": circuit | ||||
| #import "../src/util.typ" | ||||
|  | ||||
| #let example-preamble = "import \"../src/lib.typ\": *;" | ||||
| #let example-scope = ( | ||||
|   draw: draw | ||||
| ) | ||||
|  | ||||
| #let example(src, show-src: true, vertical: false, fill: true) = { | ||||
|   src = src.text | ||||
|   let full-src = example-preamble + src | ||||
|   let body = eval(full-src, scope: example-scope) | ||||
|   let img = circuit(length: 2em, body) | ||||
|  | ||||
|   block(width: 100%, | ||||
|     align(center, | ||||
|       box( | ||||
|         stroke: black + 1pt, | ||||
|         radius: .5em, | ||||
|         fill: if fill {util.colors.yellow.lighten(80%)} else {none}, | ||||
|         if show-src { | ||||
|           let src-block = align(left, raw(src, lang: "typc")) | ||||
|           table( | ||||
|             columns: if vertical {1} else {2}, | ||||
|             inset: 1em, | ||||
|             align: horizon + center, | ||||
|             stroke: none, | ||||
|             img, | ||||
|             if vertical {table.hline()} else {table.vline()}, src-block | ||||
|           ) | ||||
|         } else { | ||||
|           table( | ||||
|             inset: 1em, | ||||
|             img | ||||
|           ) | ||||
|         } | ||||
|       ) | ||||
|     ) | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										150
									
								
								doc/examples.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,150 @@ | ||||
| #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 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(3) { | ||||
|   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)) | ||||
| ```, 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 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 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 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") | ||||
| ```) | ||||
							
								
								
									
										40
									
								
								gallery.bash
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| PDFS=false | ||||
|  | ||||
| while getopts "p" flag | ||||
| do | ||||
|     case "${flag}" in | ||||
|         p) PDFS=true;; | ||||
|     esac | ||||
| done | ||||
|  | ||||
| echo "Generating gallery images" | ||||
|  | ||||
| set -- ./gallery/*.typ | ||||
| cnt="$#" | ||||
| i=1 | ||||
| for f | ||||
| do | ||||
|     f2="${f/typ/png}" | ||||
|     echo "($i/$cnt) $f -> $f2" | ||||
|     typst c --root ./ "$f" "$f2" | ||||
|     i=$((i+1)) | ||||
| done | ||||
|  | ||||
| if [ "$PDFS" = true ] | ||||
| then | ||||
|     echo | ||||
|     echo "Generating gallery PDFs" | ||||
|  | ||||
|     set -- ./gallery/*.typ | ||||
|     cnt="$#" | ||||
|     i=1 | ||||
|     for f | ||||
|     do | ||||
|         f2="${f/typ/pdf}" | ||||
|         echo "($i/$cnt) $f -> $f2" | ||||
|         typst c --root ./ "$f" "$f2" | ||||
|         i=$((i+1)) | ||||
|     done | ||||
| fi | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/platypus.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/platypus.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 45 KiB | 
							
								
								
									
										77
									
								
								gallery/platypus.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,77 @@ | ||||
| #import "../src/lib.typ": * | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #let teal = rgb(37, 155, 166) | ||||
| #let orange = rgb(254, 160, 93) | ||||
| #let brown = rgb(97, 54, 60) | ||||
|  | ||||
| #circuit({ | ||||
|   element.group(id: "platypus", name: "A platypus", { | ||||
|     element.block( | ||||
|       x: 0, y: 0, w: 2, h: 3, id: "body", | ||||
|       fill: teal, | ||||
|       ports: ( | ||||
|         east: ( | ||||
|           (id: "out"), | ||||
|         ) | ||||
|       ), | ||||
|       ports-margins: ( | ||||
|         east: (50%, 10%) | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     element.block( | ||||
|       x: 2.5, y: 1.5, w: 1.5, h: 1, id: "beak", | ||||
|       fill: orange, | ||||
|       ports: ( | ||||
|         south: ( | ||||
|           (id: "in"), | ||||
|         ) | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     wire.wire("w1", ("body-port-out", "beak-port-in"), style: "zigzag", zigzag-ratio: 100%) | ||||
|   }) | ||||
|  | ||||
|   let O = (rel: (2, 0), to: "platypus.south-east") | ||||
|    | ||||
|   element.group(id: "perry", name: "Perry the platypus", { | ||||
|     element.block( | ||||
|       x: (rel: 0, to: O), y: 0, w: 2, h: 3, id: "body", | ||||
|       fill: teal, | ||||
|       ports: ( | ||||
|         east: ( | ||||
|           (id: "out"), | ||||
|         ) | ||||
|       ), | ||||
|       ports-margins: ( | ||||
|         east: (50%, 10%) | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     element.block( | ||||
|       x: (rel: 2.5, to: O), y: 1.5, w: 1.5, h: 1, id: "beak", | ||||
|       fill: orange, | ||||
|       ports: ( | ||||
|         south: ( | ||||
|           (id: "in"), | ||||
|         ) | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|     element.block( | ||||
|       x: (rel: 0.25, to: O), y: 3.2, w: 1.5, h: 0.5, id: "hat", | ||||
|       fill: brown | ||||
|     ) | ||||
|  | ||||
|     wire.wire("w2", ("body-port-out", "beak-port-in"), style: "zigzag", zigzag-ratio: 100%) | ||||
|   }) | ||||
|  | ||||
|   wire.wire( | ||||
|     "w3", | ||||
|     ("platypus.east", (horizontal: "perry.west", vertical: ())), | ||||
|     directed: true, | ||||
|     bus: true | ||||
|   ) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/test.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/test.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 142 KiB | 
							
								
								
									
										299
									
								
								gallery/test.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,299 @@ | ||||
| #import "../src/lib.typ": circuit, element, util, wire | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #circuit({ | ||||
|   element.block( | ||||
|     x: 0, y: 0, w: 1.5, h: 2.2, | ||||
|     id: "PCBuf", | ||||
|     fill: util.colors.orange, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "PCNext"), | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "PC"), | ||||
|       ), | ||||
|       south: ( | ||||
|         (id: "EN", name: "EN"), | ||||
|       ) | ||||
|     ) | ||||
|   ) | ||||
|   wire.stub("PCBuf-port-CLK", "north", name: "CLK") | ||||
|   wire.stub("PCBuf-port-EN", "south", name: "PCWrite") | ||||
|    | ||||
|   element.multiplexer( | ||||
|     x: 3, y: (from: "PCBuf-port-PC", to: "in0"), w: 1, h: 2, | ||||
|     id: "AdrSrc-MP", | ||||
|     fill: util.colors.orange, | ||||
|     entries: 2 | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wPCBuf-InstDataMgr", ( | ||||
|       "PCBuf-port-PC", | ||||
|       "AdrSrc-MP-port-in0" | ||||
|     ), | ||||
|     name: "PC", | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.stub("AdrSrc-MP.north", "north", name: "AdrSrc") | ||||
|    | ||||
|   element.block( | ||||
|     x: 6, y: (from: "AdrSrc-MP-port-out", to: "A"), w: 3, h: 4, | ||||
|     id: "InstDataMgr", | ||||
|     fill: util.colors.yellow, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "A", name: "A"), | ||||
|         (id: "WD", name: "WD") | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|         (id: "WE", name: "WE", vertical: true), | ||||
|         (id: "IRWrite", name: "IRWrite", vertical: true) | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "Instr", name: "Instr."), | ||||
|         (id: "RD", name: "RD") | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       west: (30%, 0%), | ||||
|       east: (40%, 0%) | ||||
|     ) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wAdrSrcMP-InstDataMgr", ( | ||||
|       "AdrSrc-MP-port-out", | ||||
|       "InstDataMgr-port-A" | ||||
|     ), | ||||
|     name: "Adr", | ||||
|     name-pos: "end", | ||||
|     bus: true | ||||
|   ) | ||||
|    | ||||
|   wire.stub("InstDataMgr-port-CLK", "north", name: "CLK") | ||||
|   wire.stub("InstDataMgr-port-WE", "north") | ||||
|   wire.stub("InstDataMgr-port-IRWrite", "north") | ||||
|   wire.stub("InstDataMgr-port-WD", "west") | ||||
|  | ||||
|   element.block( | ||||
|     x: 15, y: (from: "InstDataMgr-port-RD", to: "WD3"), w: 3, h: 4, | ||||
|     id: "RegFile", | ||||
|     fill: util.colors.pink, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "A1", name: "A1"), | ||||
|         (id: "A2", name: "A2"), | ||||
|         (id: "A3", name: "A3"), | ||||
|         (id: "WD3", name: "WD3"), | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|         (id: "WE3", name: "WE3", vertical: true) | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "RD1", name: "RD1"), | ||||
|         (id: "RD2", name: "RD2"), | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       east: (20%, 20%) | ||||
|     ) | ||||
|   ) | ||||
|   wire.stub("RegFile-port-CLK", "north", name: "CLK") | ||||
|   wire.stub("RegFile-port-WE3", "north", name: "Regwrite", name-offset: 0.6) | ||||
|   wire.stub("RegFile-port-A2", "west") | ||||
|   wire.stub("RegFile-port-RD2", "east") | ||||
|  | ||||
|   element.extender( | ||||
|     x: 15, y: -3.5, w: 3, h: 1, | ||||
|     id: "Extender", | ||||
|     fill: util.colors.green | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wExtender-ImmSrc", ( | ||||
|       "Extender.north", | ||||
|       (18, -2) | ||||
|     ), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 0%, | ||||
|     name: "ImmSrc", | ||||
|     name-pos: "end", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   let mid = ("InstDataMgr.east", 50%, "RegFile.west") | ||||
|   wire.wire( | ||||
|     "wInstDataMgr-Bus", ( | ||||
|       "InstDataMgr-port-Instr", | ||||
|       (vertical: (), horizontal: mid) | ||||
|     ), | ||||
|     name: "Instr", | ||||
|     name-pos: "start", | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus", ( | ||||
|       (v => (v.at(0), -3.5), mid), | ||||
|       (horizontal: (), vertical: (0, 3.5)), | ||||
|     ), | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus-RegFile-A1", ( | ||||
|       "RegFile-port-A1", | ||||
|       (horizontal: mid, vertical: ()), | ||||
|     ), | ||||
|     name: "RS1", | ||||
|     name-pos: "end", | ||||
|     slice: (19, 15), | ||||
|     reverse: true, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus-RegFile-A3", ( | ||||
|       "RegFile-port-A3", | ||||
|       (horizontal: mid, vertical: ()), | ||||
|     ), | ||||
|     name: "RD", | ||||
|     name-pos: "end", | ||||
|     slice: (11, 7), | ||||
|     reverse: true, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus-Extender", ( | ||||
|       "Extender-port-in", | ||||
|       (horizontal: mid, vertical: ()), | ||||
|     ), | ||||
|     slice: (31, 7), | ||||
|     reverse: true, | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   element.alu( | ||||
|     x: 22, y: (from: "RegFile-port-RD1", to: "in1"), w: 1, h: 2, | ||||
|     id: "ALU", | ||||
|     fill: util.colors.purple | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wRegFile-ALU", ( | ||||
|       "RegFile-port-RD1", | ||||
|       "ALU-port-in1" | ||||
|     ), | ||||
|     name: ("A", "SrcA"), | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   element.block( | ||||
|     x: 26, y: (from: "ALU-port-out", to: "in"), w: 1.5, h: 2, | ||||
|     id: "OutBuf", | ||||
|     fill: util.colors.orange, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "in"), | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "out"), | ||||
|       ) | ||||
|     ) | ||||
|   ) | ||||
|   wire.stub("OutBuf-port-CLK", "north", name: "CLK") | ||||
|   wire.wire( | ||||
|     "wALU-OutBuf", ( | ||||
|       "ALU-port-out", | ||||
|       "OutBuf-port-in" | ||||
|     ), | ||||
|     name: "ALUResult", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   element.multiplexer( | ||||
|     x: 30, y: (from: "OutBuf-port-out", to: "in0"), w: 1, h: 2.5, | ||||
|     id: "Res-MP", | ||||
|     fill: util.colors.orange, | ||||
|     entries: 3 | ||||
|   ) | ||||
|   wire.stub("Res-MP.north", "north", name: "ResultSrc") | ||||
|   wire.stub("Res-MP-port-in2", "west") | ||||
|   wire.wire( | ||||
|     "wOutBuf-ResMP", ( | ||||
|       "OutBuf-port-out", | ||||
|       "Res-MP-port-in0" | ||||
|     ), | ||||
|     name: "ALUOut", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wExt-ALU", ( | ||||
|       "Extender-port-out", | ||||
|       "ALU-port-in2", | ||||
|     ), | ||||
|     name: ("ImmExt", "SrcB"), | ||||
|     bus: true, | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 60% | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wInstDataMgr-ResMP", ( | ||||
|       "InstDataMgr-port-RD", | ||||
|       "Res-MP-port-in1" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -4, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     name: "Data", | ||||
|     name-pos: "start", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wResMP-AdrSrc", ( | ||||
|       "Res-MP-port-out", | ||||
|       "AdrSrc-MP-port-in1" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -5, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-margins: (0.5, 1), | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wResMP-RegFile", ( | ||||
|       "Res-MP-port-out", | ||||
|       "RegFile-port-WD3" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -5, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-margins: (0.5, 1), | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wResMP-PCBuf", ( | ||||
|       "Res-MP-port-out", | ||||
|       "PCBuf-port-PCNext" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -5, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-margins: (0.5, 1.5), | ||||
|     name: "PCNext", | ||||
|     name-pos: "end", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.intersection("wResMP-RegFile.dodge-end", radius: .2) | ||||
|   wire.intersection("wResMP-AdrSrc.dodge-end", radius: .2) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/test2.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/test2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 142 KiB | 
							
								
								
									
										312
									
								
								gallery/test2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,312 @@ | ||||
| #import "../src/lib.typ": circuit, element, util, wire | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #circuit({ | ||||
|   element.block( | ||||
|     x: 0, y: 0, w: 1.5, h: 2.2, | ||||
|     id: "PCBuf", | ||||
|     fill: util.colors.orange, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "PCNext"), | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "PC"), | ||||
|       ), | ||||
|       south: ( | ||||
|         (id: "EN", name: "EN"), | ||||
|       ) | ||||
|     ) | ||||
|   ) | ||||
|   wire.stub("PCBuf-port-CLK", "north", name: "CLK") | ||||
|   wire.stub("PCBuf-port-EN", "south", name: "PCWrite") | ||||
|    | ||||
|   element.multiplexer( | ||||
|     x: (rel: 1.5, to: "PCBuf.east"), | ||||
|     y: (from: "PCBuf-port-PC", to: "in0"), | ||||
|     w: 1, h: 2, | ||||
|     id: "AdrSrc-MP", | ||||
|     fill: util.colors.orange, | ||||
|     entries: 2 | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wPCBuf-InstDataMgr", ( | ||||
|       "PCBuf-port-PC", | ||||
|       "AdrSrc-MP-port-in0" | ||||
|     ), | ||||
|     name: "PC", | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.stub("AdrSrc-MP.north", "north", name: "AdrSrc") | ||||
|    | ||||
|   element.block( | ||||
|     x: (rel: 2, to: "AdrSrc-MP.east"), | ||||
|     y: (from: "AdrSrc-MP-port-out", to: "A"), | ||||
|     w: 3, h: 4, | ||||
|     id: "InstDataMgr", | ||||
|     fill: util.colors.yellow, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "A", name: "A"), | ||||
|         (id: "WD", name: "WD") | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|         (id: "WE", name: "WE", vertical: true), | ||||
|         (id: "IRWrite", name: "IRWrite", vertical: true) | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "Instr", name: "Instr."), | ||||
|         (id: "RD", name: "RD") | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       west: (30%, 0%), | ||||
|       east: (40%, 0%) | ||||
|     ) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wAdrSrcMP-InstDataMgr", ( | ||||
|       "AdrSrc-MP-port-out", | ||||
|       "InstDataMgr-port-A" | ||||
|     ), | ||||
|     name: "Adr", | ||||
|     name-pos: "end", | ||||
|     bus: true | ||||
|   ) | ||||
|    | ||||
|   wire.stub("InstDataMgr-port-CLK", "north", name: "CLK") | ||||
|   wire.stub("InstDataMgr-port-WE", "north") | ||||
|   wire.stub("InstDataMgr-port-IRWrite", "north") | ||||
|   wire.stub("InstDataMgr-port-WD", "west") | ||||
|  | ||||
|   element.block( | ||||
|     x: (rel: 6, to: "InstDataMgr.east"), | ||||
|     y: (from: "InstDataMgr-port-RD", to: "WD3"), w: 3, h: 4, | ||||
|     id: "RegFile", | ||||
|     fill: util.colors.pink, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "A1", name: "A1"), | ||||
|         (id: "A2", name: "A2"), | ||||
|         (id: "A3", name: "A3"), | ||||
|         (id: "WD3", name: "WD3"), | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|         (id: "WE3", name: "WE3", vertical: true) | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "RD1", name: "RD1"), | ||||
|         (id: "RD2", name: "RD2"), | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       east: (20%, 20%) | ||||
|     ) | ||||
|   ) | ||||
|   wire.stub("RegFile-port-CLK", "north", name: "CLK") | ||||
|   wire.stub("RegFile-port-WE3", "north", name: "Regwrite", name-offset: 0.6) | ||||
|   wire.stub("RegFile-port-A2", "west") | ||||
|   wire.stub("RegFile-port-RD2", "east") | ||||
|  | ||||
|   element.extender( | ||||
|     x: (rel: 0, to: "RegFile.west"), | ||||
|     y: -3.5, | ||||
|     w: 3, h: 1, | ||||
|     id: "Extender", | ||||
|     fill: util.colors.green | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wExtender-ImmSrc", ( | ||||
|       "Extender.north", | ||||
|       (18, -2) | ||||
|     ), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 0%, | ||||
|     name: "ImmSrc", | ||||
|     name-pos: "end", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   let mid = ("InstDataMgr.east", 50%, "RegFile.west") | ||||
|   wire.wire( | ||||
|     "wInstDataMgr-Bus", ( | ||||
|       "InstDataMgr-port-Instr", | ||||
|       (vertical: (), horizontal: mid) | ||||
|     ), | ||||
|     name: "Instr", | ||||
|     name-pos: "start", | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus", ( | ||||
|       (v => (v.at(0), -3.5), mid), | ||||
|       (horizontal: (), vertical: (0, 3.5)), | ||||
|     ), | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus-RegFile-A1", ( | ||||
|       "RegFile-port-A1", | ||||
|       (horizontal: mid, vertical: ()), | ||||
|     ), | ||||
|     name: "RS1", | ||||
|     name-pos: "end", | ||||
|     slice: (19, 15), | ||||
|     reverse: true, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus-RegFile-A3", ( | ||||
|       "RegFile-port-A3", | ||||
|       (horizontal: mid, vertical: ()), | ||||
|     ), | ||||
|     name: "RD", | ||||
|     name-pos: "end", | ||||
|     slice: (11, 7), | ||||
|     reverse: true, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wBus-Extender", ( | ||||
|       "Extender-port-in", | ||||
|       (horizontal: mid, vertical: ()), | ||||
|     ), | ||||
|     slice: (31, 7), | ||||
|     reverse: true, | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   element.alu( | ||||
|     x: (rel: 4, to: "RegFile.east"), | ||||
|     y: (from: "RegFile-port-RD1", to: "in1"), | ||||
|     w: 1, h: 2, | ||||
|     id: "ALU", | ||||
|     fill: util.colors.purple | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wRegFile-ALU", ( | ||||
|       "RegFile-port-RD1", | ||||
|       "ALU-port-in1" | ||||
|     ), | ||||
|     name: ("A", "SrcA"), | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   element.block( | ||||
|     x: (rel: 3, to: "ALU.east"), | ||||
|     y: (from: "ALU-port-out", to: "in"), | ||||
|     w: 1.5, h: 2, | ||||
|     id: "OutBuf", | ||||
|     fill: util.colors.orange, | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "in"), | ||||
|       ), | ||||
|       north: ( | ||||
|         (id: "CLK", clock: true), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "out"), | ||||
|       ) | ||||
|     ) | ||||
|   ) | ||||
|   wire.stub("OutBuf-port-CLK", "north", name: "CLK") | ||||
|   wire.wire( | ||||
|     "wALU-OutBuf", ( | ||||
|       "ALU-port-out", | ||||
|       "OutBuf-port-in" | ||||
|     ), | ||||
|     name: "ALUResult", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   element.multiplexer( | ||||
|     x: (rel: 2.5, to: "OutBuf.east"), | ||||
|     y: (from: "OutBuf-port-out", to: "in0"), | ||||
|     w: 1, h: 2.5, | ||||
|     id: "Res-MP", | ||||
|     fill: util.colors.orange, | ||||
|     entries: 3 | ||||
|   ) | ||||
|   wire.stub("Res-MP.north", "north", name: "ResultSrc") | ||||
|   wire.stub("Res-MP-port-in2", "west") | ||||
|   wire.wire( | ||||
|     "wOutBuf-ResMP", ( | ||||
|       "OutBuf-port-out", | ||||
|       "Res-MP-port-in0" | ||||
|     ), | ||||
|     name: "ALUOut", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wExt-ALU", ( | ||||
|       "Extender-port-out", | ||||
|       "ALU-port-in2", | ||||
|     ), | ||||
|     name: ("ImmExt", "SrcB"), | ||||
|     bus: true, | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 60% | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wInstDataMgr-ResMP", ( | ||||
|       "InstDataMgr-port-RD", | ||||
|       "Res-MP-port-in1" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -4, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     name: "Data", | ||||
|     name-pos: "start", | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wResMP-AdrSrc", ( | ||||
|       "Res-MP-port-out", | ||||
|       "AdrSrc-MP-port-in1" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -5, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-margins: (0.5, 1), | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wResMP-RegFile", ( | ||||
|       "Res-MP-port-out", | ||||
|       "RegFile-port-WD3" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -5, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-margins: (0.5, 1), | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wResMP-PCBuf", ( | ||||
|       "Res-MP-port-out", | ||||
|       "PCBuf-port-PCNext" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-y: -5, | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-margins: (0.5, 1.5), | ||||
|     name: "PCNext", | ||||
|     name-pos: "end", | ||||
|     bus: true | ||||
|   ) | ||||
|    | ||||
|   wire.intersection("wResMP-RegFile.dodge-end", radius: .2) | ||||
|   wire.intersection("wResMP-AdrSrc.dodge-end", radius: .2) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/test3.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/test3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 66 KiB | 
							
								
								
									
										84
									
								
								gallery/test3.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,84 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "../src/lib.typ": circuit, element, util, wire | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #circuit({ | ||||
|   element.block( | ||||
|     x: 0, y: 0, w: 2, h: 3, id: "block", | ||||
|     name: "Test", | ||||
|     ports: ( | ||||
|       east: ( | ||||
|         (id: "out0"), | ||||
|         (id: "out1"), | ||||
|         (id: "out2"), | ||||
|       ) | ||||
|     ) | ||||
|   ) | ||||
|   element.gate-and( | ||||
|     x: 4, y: 0, w: 2, h: 2, id: "and1", | ||||
|     inverted: ("in1") | ||||
|   ) | ||||
|   element.gate-or( | ||||
|     x: 7, y: 0, w: 2, h: 2, id: "or1", | ||||
|     inverted: ("in0", "out") | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "w1", | ||||
|     ("block-port-out0", "and1-port-in0"), | ||||
|     style: "dodge", | ||||
|     dodge-y: 3, | ||||
|     dodge-margins: (20%, 20%) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "w2", | ||||
|     ("block-port-out1", "and1-port-in1"), | ||||
|     style: "zigzag" | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "w3", | ||||
|     ("and1-port-out", "or1-port-in0") | ||||
|   ) | ||||
|  | ||||
|   element.gate-and( | ||||
|     x: 11, y: 0, w: 2, h: 2, id: "and2", inputs: 3, | ||||
|     inverted: ("in0", "in2") | ||||
|   ) | ||||
|   for i in range(3) { | ||||
|     wire.stub("and2-port-in"+str(i), "west") | ||||
|   } | ||||
|  | ||||
|   element.gate-xor( | ||||
|     x: 14, y: 0, w: 2, h: 2, id: "xor", | ||||
|     inverted: ("in1") | ||||
|   ) | ||||
|    | ||||
|   element.gate-buf( | ||||
|     x: 0, y: -3, w: 2, h: 2, id: "buf" | ||||
|   ) | ||||
|   element.gate-not( | ||||
|     x: 0, y: -6, w: 2, h: 2, id: "not" | ||||
|   ) | ||||
|    | ||||
|   element.gate-and( | ||||
|     x: 3, y: -3, w: 2, h: 2, id: "and" | ||||
|   ) | ||||
|   element.gate-nand( | ||||
|     x: 3, y: -6, w: 2, h: 2, id: "nand" | ||||
|   ) | ||||
|    | ||||
|   element.gate-or( | ||||
|     x: 6, y: -3, w: 2, h: 2, id: "or" | ||||
|   ) | ||||
|   element.gate-nor( | ||||
|     x: 6, y: -6, w: 2, h: 2, id: "nor" | ||||
|   ) | ||||
|    | ||||
|   element.gate-xor( | ||||
|     x: 9, y: -3, w: 2, h: 2, id: "xor" | ||||
|   ) | ||||
|   element.gate-xnor( | ||||
|     x: 9, y: -6, w: 2, h: 2, id: "xnor" | ||||
|   ) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/test4.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/test4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 159 KiB | 
							
								
								
									
										221
									
								
								gallery/test4.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,221 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "../src/lib.typ": * | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #circuit({ | ||||
|   element.group(id: "toplvl", name: "Toplevel", { | ||||
|     element.group( | ||||
|       id: "proc", | ||||
|       name: "Processor", | ||||
|       padding: 1.5em, | ||||
|       stroke: (dash: "dashed"), | ||||
|       { | ||||
|       element.block( | ||||
|         x: 0, y: 0, w: 8, h: 4, | ||||
|         id: "dp", | ||||
|         fill: util.colors.pink, | ||||
|         name: "Datapath", | ||||
|         ports: ( | ||||
|           north: ( | ||||
|             (id: "clk", clock: true, small: true), | ||||
|             (id: "Zero"), | ||||
|             (id: "Regsrc"), | ||||
|             (id: "PCSrc"), | ||||
|             (id: "ResultSrc"), | ||||
|             (id: "ALUControl"), | ||||
|             (id: "ImmSrc"), | ||||
|             (id: "RegWrite"), | ||||
|             (id: "dummy") | ||||
|           ), | ||||
|           east: ( | ||||
|             (id: "PC", name: "PC"), | ||||
|             (id: "Instr", name: "Instr"), | ||||
|             (id: "ALUResult", name: "ALUResult"), | ||||
|             (id: "dummy"), | ||||
|             (id: "WriteData", name: "WriteData"), | ||||
|             (id: "ReadData", name: "ReadData"), | ||||
|           ), | ||||
|           west: ( | ||||
|             (id: "rst"), | ||||
|           ) | ||||
|         ), | ||||
|         ports-margins: ( | ||||
|           north: (0%, 0%), | ||||
|           west: (0%, 70%) | ||||
|         ) | ||||
|       ) | ||||
|        | ||||
|       element.block( | ||||
|         x: 0, y: 7, w: 8, h: 3, | ||||
|         id: "ctrl", | ||||
|         fill: util.colors.orange, | ||||
|         name: "Controller", | ||||
|         ports: ( | ||||
|           east: ( | ||||
|             (id: "Instr", name: "Instr"), | ||||
|           ), | ||||
|           south: ( | ||||
|             (id: "dummy"), | ||||
|             (id: "Zero"), | ||||
|             (id: "Regsrc"), | ||||
|             (id: "PCSrc"), | ||||
|             (id: "ResultSrc"), | ||||
|             (id: "ALUControl"), | ||||
|             (id: "ImmSrc"), | ||||
|             (id: "RegWrite"), | ||||
|             (id: "MemWrite") | ||||
|           ) | ||||
|         ), | ||||
|         ports-margins: ( | ||||
|           south: (0%, 0%) | ||||
|         ) | ||||
|       ) | ||||
|       wire.wire( | ||||
|         "w-Zero", | ||||
|         ("dp-port-Zero", "ctrl-port-Zero"), | ||||
|         name: "Zero", | ||||
|         name-pos: "start", | ||||
|         directed: true | ||||
|       ) | ||||
|       for p in ("Regsrc", "PCSrc", "ResultSrc", "ALUControl", "ImmSrc", "RegWrite") { | ||||
|         wire.wire( | ||||
|           "w-" + p, | ||||
|           ("ctrl-port-"+p, "dp-port-"+p), | ||||
|           name: p, | ||||
|           name-pos: "start", | ||||
|           directed: true | ||||
|         ) | ||||
|       } | ||||
|  | ||||
|       draw.content( | ||||
|         (rel: (0, 1em), to: "ctrl.north"), | ||||
|         [*RISCV single*], | ||||
|         anchor: "south" | ||||
|       ) | ||||
|     }) | ||||
|      | ||||
|     element.block( | ||||
|       x: (rel: 3.5, to: "dp.east"), | ||||
|       y: (from: "dp-port-ReadData", to: "RD"), | ||||
|       w: 3, h: 4, | ||||
|       id: "dmem", | ||||
|       fill: util.colors.green, | ||||
|       name: "Data\n Memory", | ||||
|       ports: ( | ||||
|         north: ( | ||||
|           (id: "clk", clock: true, small: true), | ||||
|           (id: "WE", name: "WE") | ||||
|         ), | ||||
|         west: ( | ||||
|           (id: "dummy"), | ||||
|           (id: "dummy"), | ||||
|           (id: "A", name: "A"), | ||||
|           (id: "dummy"), | ||||
|           (id: "WD", name: "WD"), | ||||
|           (id: "RD", name: "RD"), | ||||
|         ) | ||||
|       ), | ||||
|       ports-margins: ( | ||||
|         north: (0%, 10%) | ||||
|       ) | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-DataAddr", | ||||
|       ("dp-port-ALUResult", "dmem-port-A"), | ||||
|       name: "DataAddr", | ||||
|       name-pos: "end", | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-WriteData", | ||||
|       ("dp-port-WriteData", "dmem-port-WD"), | ||||
|       name: "WriteData", | ||||
|       name-pos: "end", | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-ReadData", | ||||
|       ("dmem-port-RD", "dp-port-ReadData"), | ||||
|       name: "ReadData", | ||||
|       name-pos: "end", | ||||
|       reverse: true, | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-MemWrite", | ||||
|       ("ctrl-port-MemWrite", "dmem-port-WE"), | ||||
|       style: "zigzag", | ||||
|       name: "MemWrite", | ||||
|       name-pos: "start", | ||||
|       zigzag-dir: "horizontal", | ||||
|       zigzag-ratio: 80%, | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.stub( | ||||
|       "dmem-port-clk", "north", | ||||
|       name: "clk", length: 3pt | ||||
|     ) | ||||
|  | ||||
|     element.block( | ||||
|       x: (rel: 3.5, to: "dp.east"), | ||||
|       y: (from: "ctrl-port-Instr", to: "dummy"), | ||||
|       w: 3, h: 4, | ||||
|       id: "imem", | ||||
|       fill: util.colors.green, | ||||
|       name: "Instruction\n Memory", | ||||
|       ports: ( | ||||
|         west: ( | ||||
|           (id: "A", name: "A"), | ||||
|           (id: "dummy"), | ||||
|           (id: "dummy2"), | ||||
|           (id: "RD", name: "RD"), | ||||
|         ) | ||||
|       ) | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-PC", | ||||
|       ("dp-port-PC", "imem-port-A"), | ||||
|       style: "zigzag", | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-Instr1", | ||||
|       ("imem-port-RD", "dp-port-Instr"), | ||||
|       style: "zigzag", | ||||
|       zigzag-ratio: 30%, | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.wire( | ||||
|       "w-Instr2", | ||||
|       ("imem-port-RD", "ctrl-port-Instr"), | ||||
|       style: "zigzag", | ||||
|       zigzag-ratio: 30%, | ||||
|       directed: true | ||||
|     ) | ||||
|     wire.intersection("w-Instr1.zig", radius: 2pt) | ||||
|     draw.content("w-Instr1.zig", "Instr", anchor: "south", padding: 4pt) | ||||
|     draw.content("w-PC.zig", "PC", anchor: "south-east", padding: 2pt) | ||||
|  | ||||
|     draw.content("dmem.south-west", [*External Memories*], anchor: "north", padding: 10pt) | ||||
|   }) | ||||
|  | ||||
|   draw.line(name: "w-dp-clk", | ||||
|     "dp-port-clk", | ||||
|     (rel: (0, .5), to: ()), | ||||
|     ( | ||||
|       rel: (-.5, 0), | ||||
|       to: (horizontal: "toplvl.west", vertical: ()) | ||||
|     ) | ||||
|   ) | ||||
|   draw.content("w-dp-clk.end", "clk", anchor: "east", padding: 3pt) | ||||
|    | ||||
|   draw.line(name: "w-dp-rst", | ||||
|     "dp-port-rst", | ||||
|     ( | ||||
|       rel: (-.5, 0), | ||||
|       to: (horizontal: "toplvl.west", vertical: ()) | ||||
|     ) | ||||
|   ) | ||||
|   draw.content("w-dp-rst.end", "rst", anchor: "east", padding: 3pt) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/test5.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/test5.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 276 KiB | 
							
								
								
									
										435
									
								
								gallery/test5.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,435 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "../src/lib.typ": * | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #circuit({ | ||||
|   element.multiplexer( | ||||
|     x: 0, y: 0, w: .5, h: 1.5, id: "PCMux", | ||||
|     entries: 2, | ||||
|     fill: util.colors.blue, | ||||
|     h-ratio: 80% | ||||
|   ) | ||||
|   element.block( | ||||
|     x: (rel: 2, to: "PCMux.east"), | ||||
|     y: (from: "PCMux-port-out", to: "in"), | ||||
|     w: 1, h: 1.5, id: "PCBuf", | ||||
|     ports: ( | ||||
|       north: ((id: "clk", clock: true),), | ||||
|       west: ((id: "in"),), | ||||
|       east: ((id: "out"),) | ||||
|     ), | ||||
|     fill: util.colors.green | ||||
|   ) | ||||
|  | ||||
|   element.block( | ||||
|     x: (rel: 2, to: "PCBuf.east"), | ||||
|     y: (from: "PCBuf-port-out", to: "A"), | ||||
|     w: 3, h: 4, id: "IMem", | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "A", name: "A"), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "RD", name: "RD"), | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       west: (0%, 50%), | ||||
|       east: (0%, 50%) | ||||
|     ), | ||||
|     fill: util.colors.green, | ||||
|     name: "Instruction\nMemory" | ||||
|   ) | ||||
|   element.block( | ||||
|     x: (rel: 3, to: "IMem.east"), | ||||
|     y: (from: "IMem-port-RD", to: "A1"), | ||||
|     w: 4.5, h: 4, id: "RegFile", | ||||
|     ports: ( | ||||
|       north: ( | ||||
|         (id: "clk", clock: true, small: true), | ||||
|         (id: "WE3", name: "WE3"), | ||||
|         (id: "dummy1") | ||||
|       ), | ||||
|       west: ( | ||||
|         (id: "dummy2"), | ||||
|         (id: "A1", name: "A1"), | ||||
|         (id: "dummy3"), | ||||
|         (id: "A2", name: "A2"), | ||||
|         (id: "A3", name: "A3"), | ||||
|         (id: "dummy4"), | ||||
|         (id: "WD3", name: "WD3"), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "RD1", name: "RD1"), | ||||
|         (id: "RD2", name: "RD2"), | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       north: (-20%, -20%), | ||||
|       east: (0%, 10%) | ||||
|     ), | ||||
|     fill: util.colors.green, | ||||
|     name: "Register\nFile" | ||||
|   ) | ||||
|  | ||||
|   element.alu( | ||||
|     x: (rel: -.7, to: "IMem.center"), | ||||
|     y: -7, | ||||
|     w: 1.4, h: 2.8, id: "PCAdd", | ||||
|     name: text("+", size: 1.5em), | ||||
|     name-anchor: "name", | ||||
|     fill: util.colors.pink | ||||
|   ) | ||||
|   element.extender( | ||||
|     x: (rel: 0, to: "RegFile.west"), | ||||
|     y: (from: "PCAdd-port-out", to: "in"), | ||||
|     w: 4, h: 1.5, id: "Ext", | ||||
|     h-ratio: 50%, | ||||
|     name: "Extend", | ||||
|     name-anchor: "south", | ||||
|     align-out: false, | ||||
|     fill: util.colors.green | ||||
|   ) | ||||
|  | ||||
|   element.multiplexer( | ||||
|     x: (rel: 3, to: "RegFile.east"), | ||||
|     y: (from: "RegFile-port-RD2", to: "in0"), | ||||
|     w: .5, h: 1.5, id: "SrcBMux", | ||||
|     fill: util.colors.blue, | ||||
|     h-ratio: 80% | ||||
|   ) | ||||
|  | ||||
|   element.alu( | ||||
|     x: (rel: 2, to: "SrcBMux.east"), | ||||
|     y: (from: "SrcBMux-port-out", to: "in2"), | ||||
|     w: 1.4, h: 2.8, id: "ALU", | ||||
|     name: rotate("ALU", -90deg), | ||||
|     name-anchor: "name", | ||||
|     fill: util.colors.pink | ||||
|   ) | ||||
|   element.alu( | ||||
|     x: (rel: 2, to: "SrcBMux.east"), | ||||
|     y: (from: "Ext-port-out", to: "in2"), | ||||
|     w: 1.4, h: 2.8, id: "JumpAdd", | ||||
|     name: text("+", size: 1.5em), | ||||
|     name-anchor: "name", | ||||
|     fill: util.colors.pink | ||||
|   ) | ||||
|  | ||||
|   element.block( | ||||
|     x: (rel: 4, to: "ALU.east"), | ||||
|     y: (from: "ALU-port-out", to: "A"), | ||||
|     w: 3, h: 4, id: "DMem", | ||||
|     name: "Data\nMemory", | ||||
|     ports: ( | ||||
|       north: ( | ||||
|         (id: "clk", clock: true, small: true), | ||||
|         (id: "dummy1"), | ||||
|         (id: "WE", name: "WE") | ||||
|       ), | ||||
|       west: ( | ||||
|         (id: "A", name: "A"), | ||||
|         (id: "WD", name: "WD") | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "RD", name: "RD"), | ||||
|         (id: "dummy2") | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       north: (-10%, -10%), | ||||
|       west: (-20%, -30%), | ||||
|       east: (-10%, -20%) | ||||
|     ), | ||||
|     fill: util.colors.green | ||||
|   ) | ||||
|  | ||||
|   element.multiplexer( | ||||
|     x: (rel: 3, to: "DMem.east"), | ||||
|     y: (from: "DMem-port-RD", to: "in1"), | ||||
|     w: .5, h: 1.5, id: "ResMux", | ||||
|     entries: 2, | ||||
|     fill: util.colors.blue, | ||||
|     h-ratio: 80% | ||||
|   ) | ||||
|  | ||||
|   element.block( | ||||
|     x: (rel: 0, to: "RegFile.west"), | ||||
|     y: 3.5, w: 2.5, h: 5, id: "Ctrl", | ||||
|     name: "Control\nUnit", | ||||
|     name-anchor: "north", | ||||
|     ports: ( | ||||
|       west: ( | ||||
|         (id: "op", name: "op"), | ||||
|         (id: "funct3", name: "funct3"), | ||||
|         (id: "funct7", name: [funct7#sub("[5]")]), | ||||
|         (id: "zero", name: "Zero"), | ||||
|       ), | ||||
|       east: ( | ||||
|         (id: "PCSrc"), | ||||
|         (id: "ResSrc"), | ||||
|         (id: "MemWrite"), | ||||
|         (id: "ALUCtrl"), | ||||
|         (id: "ALUSrc"), | ||||
|         (id: "ImmSrc"), | ||||
|         (id: "RegWrite"), | ||||
|       ) | ||||
|     ), | ||||
|     ports-margins: ( | ||||
|       west: (40%, 0%) | ||||
|     ), | ||||
|     fill: util.colors.orange | ||||
|   ) | ||||
|  | ||||
|   // Wires | ||||
|   wire.wire( | ||||
|     "wPCNext", ("PCMux-port-out", "PCBuf-port-in"), | ||||
|     name: "PCNext" | ||||
|   ) | ||||
|   wire.stub("PCBuf-port-clk", "north", name: "clk", length: 0.25) | ||||
|   wire.wire( | ||||
|     "wPC1", ("PCBuf-port-out", "IMem-port-A"), | ||||
|     name: "PC" | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wPC2", ("PCBuf-port-out", "JumpAdd-port-in1"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 1 | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wPC3", ("PCBuf-port-out", "PCAdd-port-in1"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 1 | ||||
|   ) | ||||
|   wire.intersection("wPC2.zig") | ||||
|   wire.intersection("wPC2.zag") | ||||
|   wire.stub("PCAdd-port-in2", "west", name: "4", length: 1.5) | ||||
|   wire.wire( | ||||
|     "wPC+4", ("PCAdd-port-out", "PCMux-port-in0"), | ||||
|     style: "dodge", | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-y: -7.5, | ||||
|     dodge-margins: (1.2, .5), | ||||
|     name: "PC+4", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|    | ||||
|   let mid = ("IMem-port-RD", 50%, "RegFile-port-A1") | ||||
|   wire.wire( | ||||
|     "wInstr", ("IMem-port-RD", mid), | ||||
|     bus: true, | ||||
|     name: "Instr", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|   draw.hide({ | ||||
|     draw.line(name: "bus-top", | ||||
|       mid, | ||||
|       (horizontal: (), vertical: "Ctrl-port-op") | ||||
|     ) | ||||
|     draw.line(name: "bus-bot", | ||||
|       mid, | ||||
|       (horizontal: (), vertical: "Ext-port-in") | ||||
|     ) | ||||
|   }) | ||||
|   wire.wire( | ||||
|     "wInstrBus", ("bus-top.end", "bus-bot.end"), | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wOp", ("Ctrl-port-op", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (6, 0) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wF3", ("Ctrl-port-funct3", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (14, 12) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wF7", ("Ctrl-port-funct7", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (30,) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wA1", ("RegFile-port-A1", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (19, 15) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wA2", ("RegFile-port-A2", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (24, 20) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wA3", ("RegFile-port-A3", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (11, 7) | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wExt", ("Ext-port-in", (horizontal: mid, vertical: ())), | ||||
|     bus: true, | ||||
|     reverse: true, | ||||
|     slice: (31, 7) | ||||
|   ) | ||||
|   wire.intersection("wF3.end") | ||||
|   wire.intersection("wF7.end") | ||||
|   wire.intersection("wA1.end") | ||||
|   wire.intersection("wA2.end") | ||||
|   wire.intersection("wA3.end") | ||||
|  | ||||
|   wire.stub("RegFile-port-clk", "north", name: "clk", length: 0.25) | ||||
|   wire.wire("wRD2", ("RegFile-port-RD2", "SrcBMux-port-in0")) | ||||
|   wire.wire( | ||||
|     "wWD", ("RegFile-port-RD2", "DMem-port-WD"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 1.5, | ||||
|     name: "WriteData", | ||||
|     name-pos: "end" | ||||
|   ) | ||||
|   wire.intersection("wWD.zig") | ||||
|  | ||||
|   wire.wire( | ||||
|     "wImmALU", ("Ext-port-out", "SrcBMux-port-in1"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 2.5, | ||||
|     name: "ImmExt", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wImmJump", ("Ext-port-out", "JumpAdd-port-in2") | ||||
|   ) | ||||
|   wire.intersection("wImmALU.zig") | ||||
|   wire.wire( | ||||
|     "wJumpPC", ("JumpAdd-port-out", "PCMux-port-in1"), | ||||
|     style: "dodge", | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-y: -8, | ||||
|     dodge-margins: (1, 1), | ||||
|     name: "PCTarget", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wSrcA", ("RegFile-port-RD1", "ALU-port-in1"), | ||||
|     name: "SrcA", | ||||
|     name-pos: "end" | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wSrcB", ("SrcBMux-port-out", "ALU-port-in2"), | ||||
|     name: "SrcB", | ||||
|     name-pos: "end" | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wZero", ( | ||||
|       ("ALU.north-east", 50%, "ALU-port-out"), | ||||
|       "Ctrl-port-zero" | ||||
|     ), | ||||
|     style: "dodge", | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-y: 3, | ||||
|     dodge-margins: (1.5, 1), | ||||
|     name: "Zero", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wALURes1", ("ALU-port-out", "DMem-port-A"), | ||||
|     name: "ALUResult", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wALURes2", ("ALU-port-out", "ResMux-port-in0"), | ||||
|     style: "dodge", | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-y: 2, | ||||
|     dodge-margins: (3, 2) | ||||
|   ) | ||||
|   wire.intersection("wALURes2.start2") | ||||
|  | ||||
|   wire.stub("DMem-port-clk", "north", name: "clk", length: 0.25) | ||||
|   wire.wire( | ||||
|     "wRD", ("DMem-port-RD", "ResMux-port-in1"), | ||||
|     name: "ReadData", | ||||
|     name-pos: "start" | ||||
|   ) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wRes", ("ResMux-port-out", "RegFile-port-WD3"), | ||||
|     style: "dodge", | ||||
|     dodge-sides: ("east", "west"), | ||||
|     dodge-y: -7.5, | ||||
|     dodge-margins: (1, 2) | ||||
|   ) | ||||
|   draw.content( | ||||
|     "wRes.dodge-start", | ||||
|     "Result", | ||||
|     anchor: "south-east", | ||||
|     padding: 5pt | ||||
|   ) | ||||
|  | ||||
|   // Other wires | ||||
|   draw.group({ | ||||
|     draw.stroke(util.colors.blue) | ||||
|     draw.line(name: "wPCSrc",  | ||||
|       "Ctrl-port-PCSrc", | ||||
|       (horizontal: "RegFile.east", vertical: ()), | ||||
|       (horizontal: (), vertical: (rel: (0, 0.5), to: "Ctrl.north")), | ||||
|       (horizontal: "PCMux.north", vertical: ()), | ||||
|       "PCMux.north" | ||||
|     ) | ||||
|     draw.line(name: "wResSrc", | ||||
|       "Ctrl-port-ResSrc", | ||||
|       (horizontal: "ResMux.north", vertical: ()), | ||||
|       "ResMux.north" | ||||
|     ) | ||||
|     draw.line(name: "wMemWrite", | ||||
|       "Ctrl-port-MemWrite", | ||||
|       (horizontal: "DMem-port-WE", vertical: ()), | ||||
|       "DMem-port-WE" | ||||
|     ) | ||||
|     draw.line(name: "wALUCtrl", | ||||
|       "Ctrl-port-ALUCtrl", | ||||
|       (horizontal: "ALU.north", vertical: ()), | ||||
|       "ALU.north" | ||||
|     ) | ||||
|     draw.line(name: "wALUSrc", | ||||
|       "Ctrl-port-ALUSrc", | ||||
|       (horizontal: "SrcBMux.north", vertical: ()), | ||||
|       "SrcBMux.north" | ||||
|     ) | ||||
|     draw.line(name: "wImmSrc", | ||||
|       "Ctrl-port-ImmSrc", | ||||
|       (rel: (1, 0), to: (horizontal: "RegFile.east", vertical: ())), | ||||
|       (horizontal: (), vertical: (rel: (0, -.5), to: "RegFile.south")), | ||||
|       (horizontal: "Ext.north", vertical: ()), | ||||
|       "Ext.north" | ||||
|     ) | ||||
|     draw.line(name: "wRegWrite", | ||||
|       "Ctrl-port-RegWrite", | ||||
|       (rel: (.5, 0), to: (horizontal: "RegFile.east", vertical: ())), | ||||
|       (horizontal: (), vertical: ("Ctrl.south", 50%, "RegFile.north")), | ||||
|       (horizontal: "RegFile-port-WE3", vertical: ()), | ||||
|       "RegFile-port-WE3" | ||||
|     ) | ||||
|  | ||||
|     let names = ( | ||||
|       "PCSrc": "PCSrc", | ||||
|       "ResSrc": "ResultSrc", | ||||
|       "MemWrite": "MemWrite", | ||||
|       "ALUCtrl": [ALUControl#sub("[2:0]")], | ||||
|       "ALUSrc": "ALUSrc", | ||||
|       "ImmSrc": [ImmSrc#sub("[1:0]")], | ||||
|       "RegWrite": "RegWrite" | ||||
|     ) | ||||
|     for (port, name) in names { | ||||
|       draw.content("Ctrl-port-"+port, name, anchor: "south-west", padding: 3pt) | ||||
|     } | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								gallery/test6.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								gallery/test6.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 76 KiB | 
							
								
								
									
										194
									
								
								gallery/test6.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,194 @@ | ||||
| #import "@preview/cetz:0.2.2": draw, vector | ||||
| #import "../src/lib.typ": * | ||||
|  | ||||
| #set page(width: auto, height: auto, margin: .5cm) | ||||
|  | ||||
| #circuit({ | ||||
|   element.multiplexer( | ||||
|     x: 10, y: 0, w: 1, h: 6, id: "ResMux", | ||||
|     entries: ("000", "001", "010", "011", "101"), | ||||
|     h-ratio: 90%, | ||||
|     fill: util.colors.blue | ||||
|   ) | ||||
|   element.extender( | ||||
|     x: (rel: -3, to: "ResMux.west"), | ||||
|     y: (from: "ResMux-port-in4", to: "out"), | ||||
|     w: 2, h: 1, id: "Ext", | ||||
|     name: "Zero Ext", | ||||
|     name-anchor: "south", | ||||
|     fill: util.colors.green | ||||
|   ) | ||||
|   gates.gate-or( | ||||
|     x: (rel: -2, to: "ResMux.west"), | ||||
|     y: (from: "ResMux-port-in3", to: "out"), | ||||
|     w: 1, h: 1, id: "Or" | ||||
|   ) | ||||
|   gates.gate-and( | ||||
|     x: (rel: -2, to: "ResMux.west"), | ||||
|     y: (from: "ResMux-port-in2", to: "out"), | ||||
|     w: 1, h: 1, id: "And" | ||||
|   ) | ||||
|   element.alu( | ||||
|     x: (rel: -2.5, to: "Ext.west"), | ||||
|     y: (from: "ResMux-port-in0", to: "out"), | ||||
|     w: 1.5, h: 3, id: "Add", | ||||
|     name: text("+", size: 1.5em), | ||||
|     name-anchor: "name", | ||||
|     fill: util.colors.pink | ||||
|   ) | ||||
|   element.multiplexer( | ||||
|     x: (rel: -1.5, to: "Add.west"), | ||||
|     y: (from: "Add-port-in1", to: "out"), | ||||
|     w: 0.5, h: 1.5, id: "NotMux", | ||||
|     h-ratio: 80%, | ||||
|     fill: util.colors.blue | ||||
|   ) | ||||
|   gates.gate-not( | ||||
|     x: (rel: -2, to: "NotMux.west"), | ||||
|     y: (from: "NotMux-port-in1", to: "out"), | ||||
|     w: 1, h: 1, id: "Not" | ||||
|   ) | ||||
|    | ||||
|   draw.hide( | ||||
|     draw.line(name: "l1", | ||||
|       "Not-port-in0", | ||||
|       (rel: (-2, 0), to: ()), | ||||
|       (horizontal: (), vertical: "NotMux-port-in0") | ||||
|     ) | ||||
|   ) | ||||
|   let b = "l1.end" | ||||
|   draw.hide( | ||||
|     draw.line(name: "l2", | ||||
|       b, | ||||
|       (horizontal: (), vertical: "Add-port-in2") | ||||
|     ) | ||||
|   ) | ||||
|   let a = "l2.end" | ||||
|  | ||||
|   wire.wire("wB0", (b, "NotMux-port-in0"), bus: true) | ||||
|   wire.wire( | ||||
|     "wB1", (b, "Not-port-in0"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 1.5, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wB2", (b, "And-port-in0"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 1, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wB3", (b, "Or-port-in0"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 1, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.intersection("wB1.zig") | ||||
|   wire.intersection("wB2.zig") | ||||
|   wire.intersection("wB2.zag") | ||||
|  | ||||
|   wire.wire("wNot", ("Not-port-out", "NotMux-port-in1"), bus: true) | ||||
|   wire.wire("wAddA", ("NotMux-port-out", "Add-port-in1"), bus: true) | ||||
|  | ||||
|   wire.wire("wA0", (a, "Add-port-in2"), bus: true) | ||||
|   wire.wire( | ||||
|     "wA1", (a, "And-port-in1"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 0.5, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire( | ||||
|     "wA2", (a, "Or-port-in1"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 0.5, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.intersection("wA1.zig") | ||||
|   wire.intersection("wA1.zag") | ||||
|  | ||||
|   wire.wire("wMux0", ("Add-port-out", "ResMux-port-in0"), bus: true) | ||||
|   wire.wire( | ||||
|     "wMux1", ("Add-port-out", "ResMux-port-in1"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 2, | ||||
|     bus: true | ||||
|   ) | ||||
|   wire.wire("wMux2", ("And-port-out", "ResMux-port-in2"), bus: true) | ||||
|   wire.wire("wMux3", ("Or-port-out", "ResMux-port-in3"), bus: true) | ||||
|   wire.wire("wMux4", ("Ext-port-out", "ResMux-port-in4"), bus: true) | ||||
|  | ||||
|   wire.wire( | ||||
|     "wAdd", ("Add-port-out", "Ext-port-in"), | ||||
|     style: "zigzag", | ||||
|     zigzag-ratio: 0.5, | ||||
|     bus: true | ||||
|   ) | ||||
|  | ||||
|   wire.intersection("wMux1.zig") | ||||
|   wire.intersection("wAdd.zig") | ||||
|  | ||||
|   let c = (rel: (0, 2), to: "ResMux.north") | ||||
|   wire.wire("wResCtrl", (c, "ResMux.north"), bus: true) | ||||
|   wire.wire( | ||||
|     "wAddCtrl", (c, "Add.north"), | ||||
|     style: "zigzag", | ||||
|     zigzag-dir: "horizontal" | ||||
|   ) | ||||
|  | ||||
|   let d = (rel: (1, 0), to: "ResMux-port-out") | ||||
|   wire.wire("wRes", ("ResMux-port-out", d), bus: true) | ||||
|  | ||||
|   draw.content( | ||||
|     "wAddCtrl.zag", | ||||
|     [ALUControl#sub("[1]")], | ||||
|     anchor: "south-west", | ||||
|     padding: 3pt | ||||
|   ) | ||||
|    | ||||
|   wire.wire( | ||||
|     "wCout", ("Add.south", (horizontal: (), vertical: "Ext.north-east")) | ||||
|   ) | ||||
|   draw.content( | ||||
|     "wCout.end", | ||||
|     [C#sub("out")], | ||||
|     angle: 90deg, | ||||
|     anchor: "east", | ||||
|     padding: 3pt | ||||
|   ) | ||||
|   draw.content( | ||||
|     a, | ||||
|     [A], | ||||
|     angle: 90deg, | ||||
|     anchor: "south", | ||||
|     padding: 3pt | ||||
|   ) | ||||
|   draw.content( | ||||
|     b, | ||||
|     [B], | ||||
|     angle: 90deg, | ||||
|     anchor: "south", | ||||
|     padding: 3pt | ||||
|   ) | ||||
|   draw.content( | ||||
|     c, | ||||
|     [ALUControl#sub("[2:0]")], | ||||
|     angle: 90deg, | ||||
|     anchor: "west", | ||||
|     padding: 3pt | ||||
|   ) | ||||
|   draw.content( | ||||
|     d, | ||||
|     [Result], | ||||
|     angle: 90deg, | ||||
|     anchor: "north", | ||||
|     padding: 3pt | ||||
|   ) | ||||
|   draw.content( | ||||
|     ("wAdd.zig", 0.2, "wAdd.zag"), | ||||
|     text("[N-1]", size: 0.8em), | ||||
|     angle: 90deg, | ||||
|     anchor: "north-east", | ||||
|     padding: 3pt | ||||
|   ) | ||||
| }) | ||||
							
								
								
									
										
											BIN
										
									
								
								manual.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										198
									
								
								manual.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,198 @@ | ||||
| #import "@preview/tidy:0.3.0" | ||||
| #import "@preview/cetz:0.2.2": draw, canvas | ||||
| #import "src/lib.typ" | ||||
| #import "doc/examples.typ" | ||||
| #import "src/circuit.typ": circuit | ||||
| #import "src/element.typ" | ||||
| #import "src/gates.typ" | ||||
| #import "src/util.typ" | ||||
| #import "src/wire.typ" | ||||
|  | ||||
| #set heading(numbering: (..num) => if num.pos().len() < 4 { | ||||
|   numbering("1.1", ..num) | ||||
| }) | ||||
| #{ | ||||
|   outline(indent: true, depth: 3) | ||||
| } | ||||
|  | ||||
| #show link: set text(blue) | ||||
| #show heading.where(level: 3): it => context { | ||||
|   let cnt = counter(heading) | ||||
|   let i = cnt.get().at(it.depth) - 1 | ||||
|   let color = util.colors.values().at(i) | ||||
|   block(width: 100%)[ | ||||
|     #grid( | ||||
|       columns: (auto, 1fr), | ||||
|       column-gutter: 1em, | ||||
|       align: horizon, | ||||
|       it, | ||||
|       { | ||||
|         place(horizon)[ | ||||
|           #line( | ||||
|             start: (0%, 0%), | ||||
|             end: (100%, 0%), | ||||
|             stroke: color + 1pt | ||||
|           ) | ||||
|         ] | ||||
|         place(horizon)[ | ||||
|           #circle(radius: 3pt, stroke: none, fill: color) | ||||
|         ] | ||||
|         place(horizon+right)[ | ||||
|           #circle(radius: 3pt, stroke: none, fill: color) | ||||
|         ] | ||||
|       } | ||||
|     ) | ||||
|   ] | ||||
| } | ||||
|  | ||||
| #set page(numbering: "1/1", header: align(right)[circuiteria #sym.dash.em v#lib.version]) | ||||
| #set page( | ||||
|   header: locate(loc => { | ||||
|     let txt = [circuiteria #sym.dash.em v#lib.version] | ||||
|     let cnt = counter(heading) | ||||
|     let cnt-val = cnt.get() | ||||
|     if cnt-val.len() < 2 { | ||||
|       align(left, txt) | ||||
|       return | ||||
|     } | ||||
|     let i = cnt-val.at(1) - 1 | ||||
|     grid( | ||||
|       columns: (auto, 1fr), | ||||
|       column-gutter: 1em, | ||||
|       align: horizon, | ||||
|       txt, | ||||
|       place(horizon + left)[ | ||||
|         #rect(width: 100%, height: .5em, radius: .25em, stroke: none, fill: util.colors.values().at(i)) | ||||
|       ] | ||||
|     ) | ||||
|   }), | ||||
|   footer: locate(loc => { | ||||
|     let cnt = counter(heading) | ||||
|     let cnt-val = cnt.get() | ||||
|     if cnt-val.len() < 2 { return } | ||||
|     let i = cnt-val.at(1) - 1 | ||||
|     grid( | ||||
|       columns: (1fr, auto), | ||||
|       column-gutter: 1em, | ||||
|       align: horizon, | ||||
|       place(horizon + left)[ | ||||
|         #rect(width: 100%, height: .5em, radius: .25em, stroke: none, fill: util.colors.values().at(i)) | ||||
|       ], | ||||
|       counter(page).display("1/1", both: true) | ||||
|     ) | ||||
|   }) | ||||
| ) | ||||
|  | ||||
| #let doc-ref(target, full: false, var: false) = { | ||||
|   let (module, func) = target.split(".") | ||||
|   let label-name = module + func | ||||
|   let display-name = func | ||||
|   if full { | ||||
|     display-name = target | ||||
|   } | ||||
|   if not var { | ||||
|     label-name += "()" | ||||
|     display-name += "()" | ||||
|   } | ||||
|   link(label(label-name))[#display-name] | ||||
| } | ||||
|  | ||||
| = Introduction | ||||
|  | ||||
| This package provides a way to make beautiful block circuit diagrams using the CeTZ package. | ||||
|  | ||||
| = Usage | ||||
|  | ||||
| Simply import #link("src/lib.typ") and call the `circuit` function: | ||||
| #pad(left: 1em)[```typ | ||||
| #import "src/lib.typ" | ||||
| #lib.circuit({ | ||||
|   import lib: * | ||||
|   ... | ||||
| }) | ||||
| ```] | ||||
|  | ||||
| = Reference | ||||
|  | ||||
| #let circuit-docs = tidy.parse-module( | ||||
|   read("src/circuit.typ"), | ||||
|   name: "circuit", | ||||
|   require-all-parameters: true | ||||
| ) | ||||
| #tidy.show-module(circuit-docs) | ||||
|  | ||||
| #pagebreak() | ||||
|  | ||||
| #let util-docs = tidy.parse-module( | ||||
|   read("src/util.typ"), | ||||
|   name: "util", | ||||
|   require-all-parameters: true, | ||||
|   scope: ( | ||||
|     util: util, | ||||
|     canvas: canvas, | ||||
|     draw: draw | ||||
|   ) | ||||
| ) | ||||
| #tidy.show-module(util-docs) | ||||
|  | ||||
| #pagebreak() | ||||
|  | ||||
| #let wire-docs = tidy.parse-module( | ||||
|   read("src/wire.typ"), | ||||
|   name: "wire", | ||||
|   require-all-parameters: true, | ||||
|   scope: ( | ||||
|     wire: wire, | ||||
|     circuit: circuit, | ||||
|     draw: draw, | ||||
|     examples: examples, | ||||
|     doc-ref: doc-ref | ||||
|   ) | ||||
| ) | ||||
| #tidy.show-module(wire-docs) | ||||
|  | ||||
| #pagebreak() | ||||
|  | ||||
| #let element-docs = tidy.parse-module( | ||||
|   read("src/elements/element.typ") + "\n" + | ||||
|   read("src/elements/alu.typ") + "\n" + | ||||
|   read("src/elements/block.typ") + "\n" + | ||||
|   read("src/elements/extender.typ") + "\n" + | ||||
|   read("src/elements/multiplexer.typ") + "\n" + | ||||
|   read("src/elements/group.typ"), | ||||
|   name: "element", | ||||
|   scope: ( | ||||
|     element: element, | ||||
|     circuit: circuit, | ||||
|     draw: draw, | ||||
|     wire: wire, | ||||
|     tidy: tidy, | ||||
|     examples: examples, | ||||
|     doc-ref: doc-ref | ||||
|   ) | ||||
| ) | ||||
|  | ||||
| #tidy.show-module(element-docs, sort-functions: false) | ||||
|  | ||||
| #pagebreak() | ||||
|  | ||||
| #let gates-docs = tidy.parse-module( | ||||
|   read("src/elements/logic/gate.typ") + "\n" + | ||||
|   read("src/elements/logic/and.typ") + "\n" + | ||||
|   read("src/elements/logic/buf.typ") + "\n" + | ||||
|   read("src/elements/logic/or.typ") + "\n" + | ||||
|   read("src/elements/logic/xor.typ"), | ||||
|   name: "gates", | ||||
|   scope: ( | ||||
|     element: element, | ||||
|     circuit: circuit, | ||||
|     gates: gates, | ||||
|     draw: draw, | ||||
|     wire: wire, | ||||
|     tidy: tidy, | ||||
|     examples: examples, | ||||
|     doc-ref: doc-ref | ||||
|   ) | ||||
| ) | ||||
|  | ||||
| #tidy.show-module(gates-docs, sort-functions: false) | ||||
							
								
								
									
										14
									
								
								src/circuit.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| #import "@preview/cetz:0.2.2": canvas | ||||
| #import "@preview/tidy:0.3.0" | ||||
|  | ||||
| /// Draws a block circuit diagram | ||||
| /// | ||||
| /// This function is also available at the package root | ||||
| /// | ||||
| /// - body (none, array, element): A code block in which draw functions have been called | ||||
| /// - length (length, ratio): Optional base unit | ||||
| /// -> none | ||||
| #let circuit(body, length: 2em) = { | ||||
|   set text(font: "Source Sans 3") | ||||
|   canvas(length: length, body) | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/element.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| #import "elements/ports.typ": add-ports, add-port | ||||
| #import "elements/element.typ": elmt | ||||
|  | ||||
| #import "elements/alu.typ": alu | ||||
| #import "elements/block.typ": block | ||||
| #import "elements/extender.typ": extender | ||||
| #import "elements/multiplexer.typ": multiplexer | ||||
|  | ||||
| #import "elements/logic/gate.typ": gate | ||||
| #import "elements/logic/and.typ": gate-and, gate-nand | ||||
| #import "elements/logic/or.typ": gate-or, gate-nor | ||||
| #import "elements/logic/xor.typ": gate-xor, gate-xnor | ||||
| #import "elements/logic/buf.typ": gate-buf, gate-not | ||||
|  | ||||
| #import "elements/group.typ": group | ||||
							
								
								
									
										93
									
								
								src/elements/alu.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,93 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "element.typ" | ||||
| #import "ports.typ": add-port | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let p0 = tl | ||||
|   let p1 = (tr, 10%, br) | ||||
|   let p2 = (tr, 90%, br) | ||||
|   let p3 = bl | ||||
|   let p4 = (tl, 55%, bl) | ||||
|   let p5 = (tl, 50%, br) | ||||
|   let p6 = (tl, 45%, bl) | ||||
|    | ||||
|   let f1 = draw.group(name: id, { | ||||
|      | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       close: true, | ||||
|       draw.line(p0, p1, p2, p3, p4, p5, p6) | ||||
|     ) | ||||
|     draw.anchor("north", (p0, 50%, p1)) | ||||
|     draw.anchor("south", (p2, 50%, p3)) | ||||
|     draw.anchor("west", (p0, 50%, p3)) | ||||
|     draw.anchor("east", (p1, 50%, p2)) | ||||
|     draw.anchor("north-west", p0) | ||||
|     draw.anchor("north-east", p1) | ||||
|     draw.anchor("south-east", p2) | ||||
|     draw.anchor("south-west", p3) | ||||
|     draw.anchor("name", (p5, 50%, (p1, 50%, p2))) | ||||
|   }) | ||||
|  | ||||
|   let f2 = add-port(id, "west", (id: "in1"), (p0, 50%, p6)) | ||||
|   let f3 = add-port(id, "west", (id: "in2"), (p3, 50%, p4)) | ||||
|   let f4 = add-port(id, "east", (id: "out"), (p1, 50%, p2)) | ||||
|  | ||||
|   let f = { | ||||
|     f1; f2; f3; f4 | ||||
|   } | ||||
|  | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws an ALU with two inputs | ||||
| /// | ||||
| /// #examples.alu | ||||
| /// For parameters description, see #doc-ref("element.elmt") | ||||
| #let alu( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   name: none, | ||||
|   name-anchor: "center", | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   let ports = ( | ||||
|     west: ( | ||||
|       (id: "in1"), | ||||
|       (id: "in2"), | ||||
|     ), | ||||
|     east: ( | ||||
|       (id: "out"), | ||||
|     ) | ||||
|   ) | ||||
|    | ||||
|   element.elmt( | ||||
|     draw-shape: draw-shape, | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     name: name, | ||||
|     name-anchor: name-anchor, | ||||
|     ports: ports, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     auto-ports: false, | ||||
|     ports-y: ( | ||||
|       in1: (h) => {h * 0.225}, | ||||
|       in2: (h) => {h * 0.775}, | ||||
|       out: (h) => {h * 0.5} | ||||
|     ), | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										49
									
								
								src/elements/block.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "element.typ" | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let f = draw.rect( | ||||
|     radius: 0.5em, | ||||
|     inset: 0.5em, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     name: id, | ||||
|     bl, tr | ||||
|   ) | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws a block element | ||||
| /// | ||||
| /// #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 | ||||
|   ) | ||||
| ) = 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 | ||||
| ) | ||||
							
								
								
									
										134
									
								
								src/elements/element.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,134 @@ | ||||
| #import "@preview/cetz:0.2.2": draw, coordinate | ||||
| #import "ports.typ": add-ports, add-port | ||||
| #import "../util.typ" | ||||
|  | ||||
| #let find-port(ports, id) = { | ||||
|   for (side, side-ports) in ports { | ||||
|     for (i, port) in side-ports.enumerate() { | ||||
|       if port.id == id { | ||||
|         return (side, i) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   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) | ||||
| } | ||||
|  | ||||
| /// Draws an element | ||||
| /// - draw-shape (function): Draw function | ||||
| /// - x (number, dictionary): The x position (bottom-left corner). | ||||
| /// | ||||
| ///   If it is a dictionary, it should be in the format `(rel: number, to: str)`, where `rel` is the offset and `to` the base anchor | ||||
| /// - y (number, dictionary): The y position (bottom-left corner). | ||||
| /// | ||||
| ///   If it is a dictionary, it should be in the format `(from: str, to: str)`, where `from` is the base anchor and `to` is the id of the port to align with the anchor | ||||
| /// - w (number): Width of the element | ||||
| /// - h (number): Height of the element | ||||
| /// - name (none, str): Optional name of the block | ||||
| /// - name-anchor (str): Anchor for the optional name | ||||
| /// - ports (dictionary): Dictionary of ports. The keys are cardinal directions ("north", "east", "south" and/or "west"). The values are arrays of ports (dictionaries) with the following fields: | ||||
| ///   - `id` (`str`): (Required) Port id | ||||
| ///   - `name` (`str`): Optional name displayed *in* the block | ||||
| ///   - `clock` (`bool`): Whether it is a clock port (triangle symbol) | ||||
| ///   - `vertical` (`bool`): Whether the name should be drawn vertically | ||||
| /// - ports-margins (dictionary): Dictionary of ports margins (used with automatic port placement). They keys are cardinal directions ("north", "east", "south", "west"). The values are tuples of (`<start>`, `<end>`) margins (numbers) | ||||
| /// - fill (none, color): Fill color | ||||
| /// - stroke (stroke): Border stroke | ||||
| /// - id (str): The block id (for future reference) | ||||
| /// - auto-ports (bool): Whether to use auto port placements or not. If false, `draw-shape` is responsible for adding the appropiate ports | ||||
| /// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`) | ||||
| /// - debug (dictionary): Dictionary of debug options. | ||||
| /// | ||||
| ///   Supported fields include: | ||||
| ///     - `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, | ||||
|   name: none, | ||||
|   name-anchor: "center", | ||||
|   ports: (:), | ||||
|   ports-margins: (:), | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   auto-ports: true, | ||||
|   ports-y: (:), | ||||
|   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) | ||||
|     } | ||||
|     let used-pct = 100% - margins.at(0) - margins.at(1) | ||||
|     let used-height = height * used-pct / 100% | ||||
|     let top-margin = height * margins.at(0) / 100% | ||||
|      | ||||
|     let dy = used-height * (i + 1) / (ports.at(to-side).len() + 1) | ||||
|  | ||||
|     if not auto-ports { | ||||
|       top-margin = 0 | ||||
|       dy = ports-y.at(to)(height) | ||||
|     } | ||||
|      | ||||
|     let (ctx, from-pos) = coordinate.resolve(ctx, from) | ||||
|     y = from-pos.at(1) + dy - height + top-margin | ||||
|   } | ||||
|  | ||||
|   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 | ||||
|     ) | ||||
|   } | ||||
| }) | ||||
							
								
								
									
										98
									
								
								src/elements/extender.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,98 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "element.typ" | ||||
| #import "ports.typ": add-port | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke, h-ratio: 75%, align-out: true) = { | ||||
|   let (x, y) = bl | ||||
|   let (width, height) = (tr.at(0) - x, tr.at(1) - y) | ||||
|  | ||||
|   let ratio = h-ratio / 100% | ||||
|  | ||||
|   tl = (x, y + height * ratio) | ||||
|   let tr2 = (x + width, y + height * ratio) | ||||
|   let br = (x + width, y) | ||||
|    | ||||
|   if align-out { | ||||
|     (tr, tr2) = (tr2, tr) | ||||
|   } else { | ||||
|     (tr, tr2) = (tr, tr) | ||||
|   } | ||||
|  | ||||
|   let f = draw.group(name: id, { | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       close: true, | ||||
|       draw.line(tl, tr2, br, bl) | ||||
|     ) | ||||
|     draw.anchor("north", (tl, 50%, tr2)) | ||||
|     draw.anchor("south", (bl, 50%, br)) | ||||
|     draw.anchor("west", (tl, 50%, bl)) | ||||
|     draw.anchor("east", (tr2, 50%, br)) | ||||
|     draw.anchor("north-west", tl) | ||||
|     draw.anchor("north-east", tr2) | ||||
|     draw.anchor("south-east", br) | ||||
|     draw.anchor("south-west", bl) | ||||
|   }) | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws a bit extender | ||||
| /// | ||||
| /// #examples.extender | ||||
| /// For other parameters description, see #doc-ref("element.elmt") | ||||
| /// - h-ratio (ratio): The height ratio of the left side relative to the full height | ||||
| /// - align-out (bool): If true, the output and input ports are aligned, otherwise, the output port is centered on the right side | ||||
| #let extender( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   name: none, | ||||
|   name-anchor: "center", | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   h-ratio: 75%, | ||||
|   align-out: true, | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   let ports = ( | ||||
|     west: ( | ||||
|       (id: "in"), | ||||
|     ), | ||||
|     east: ( | ||||
|       (id: "out"), | ||||
|     ) | ||||
|   ) | ||||
|   let out-pct = if align-out {h-ratio / 2} else {50%} | ||||
|   let ports-y = ( | ||||
|     "in": (h) => {h - h * (h-ratio / 200%)}, | ||||
|     "out": (h) => {h - h * (out-pct / 100%)} | ||||
|   ) | ||||
|    | ||||
|   element.elmt( | ||||
|     draw-shape: draw-shape.with(h-ratio: h-ratio, align-out: align-out), | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     name: name, | ||||
|     name-anchor: name-anchor, | ||||
|     ports: ports, | ||||
|     auto-ports: false, | ||||
|     ports-y: ports-y, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     debug: debug | ||||
|   ) | ||||
|  | ||||
|   let in-pos = (rel: (0, h * (h-ratio / 200%)), to: id+".south-west") | ||||
|   let out-pos = (id+".south-east", out-pct, id+".north-east") | ||||
|   add-port(id, "west", ports.west.first(), in-pos) | ||||
|   add-port(id, "east", ports.east.first(), out-pos) | ||||
| } | ||||
							
								
								
									
										80
									
								
								src/elements/group.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,80 @@ | ||||
| #import "@preview/cetz:0.2.2": draw, coordinate | ||||
| #import "../util.typ" | ||||
|  | ||||
| /// Draws a group of elements | ||||
| /// | ||||
| /// #examples.group | ||||
| /// - body (elements, function): Elements to group | ||||
| /// - id (str): see #doc-ref("element.elmt") | ||||
| /// - name (str): The group's name | ||||
| /// - name-anchor (str): The anchor for the name. \ | ||||
| ///     Note: the name will be placed on the *outside* of the group | ||||
| /// - fill (color): see #doc-ref("element.elmt") | ||||
| /// - stroke (stroke): see #doc-ref("element.elmt") | ||||
| /// - padding (float,length,array,dictionary): The inside padding: | ||||
| ///    - float / length: same for all sides | ||||
| ///    - array: either (`<all>`,), (`<vertical>`, `<horizontal>`) or (`<top>`, `<right>`, `<bottom>`, `<left>`) | ||||
| ///    - dictionary: valid keys are "top", "right", "bottom" and "left" | ||||
| /// - radius (number): The corner radius | ||||
| #let group( | ||||
|   body, | ||||
|   id: "", | ||||
|   name: none, | ||||
|   name-anchor: "south", | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   padding: 0.5em, | ||||
|   radius: 0.5em | ||||
| ) = { | ||||
|   let min-x = none | ||||
|   let max-x = none | ||||
|   let min-y = none | ||||
|   let max-y = none | ||||
|  | ||||
|   let pad-top = padding | ||||
|   let pad-bottom = padding | ||||
|   let pad-left = padding | ||||
|   let pad-right = padding | ||||
|  | ||||
|   if type(padding) == array { | ||||
|     if padding.len() == 0 { | ||||
|       panic("Padding array must contain at least one value") | ||||
|     } else if padding.len() == 1 { | ||||
|       pad-top = padding.first() | ||||
|       pad-bottom = padding.first() | ||||
|       pad-left = padding.first() | ||||
|       pad-right = padding.first() | ||||
|     } else if padding.len() == 2 { | ||||
|       pad-top = padding.first() | ||||
|       pad-bottom = padding.first() | ||||
|       pad-left = padding.last() | ||||
|       pad-right = padding.last() | ||||
|     } else if padding.len() == 4 { | ||||
|       (pad-top, pad-right, pad-bottom, pad-left) = padding | ||||
|     } | ||||
|   } else if type(padding) == dictionary { | ||||
|     pad-top = padding.at("top", default: 0.5em) | ||||
|     pad-right = padding.at("right", default: 0.5em) | ||||
|     pad-bottom = padding.at("bottom", default: 0.5em) | ||||
|     pad-left = padding.at("left", default: 0.5em) | ||||
|   } | ||||
|  | ||||
|   draw.hide(draw.group(name: id+"-inner", body)) | ||||
|   draw.rect( | ||||
|     (rel: (-pad-left, -pad-bottom), to: id+"-inner.south-west"), | ||||
|     (rel: (pad-right, pad-top), to: id+"-inner.north-east"), | ||||
|     name: id, | ||||
|     radius: radius, | ||||
|     stroke: stroke, | ||||
|     fill: fill | ||||
|   ) | ||||
|   if name != none { | ||||
|     draw.content( | ||||
|       id + "." + name-anchor, | ||||
|       anchor: util.opposite-anchor(name-anchor), | ||||
|       padding: 5pt, | ||||
|       [*#name*] | ||||
|     ) | ||||
|   } | ||||
|   body | ||||
| } | ||||
							
								
								
									
										93
									
								
								src/elements/logic/and.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,93 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "gate.typ" | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let (x, y) = bl | ||||
|   let (width, height) = (tr.at(0) - x, tr.at(1) - y) | ||||
|  | ||||
|   let t = (x + width / 2, y + height) | ||||
|   let b = (x + width / 2, y) | ||||
|  | ||||
|   let f = draw.group(name: id, { | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       name: id + "-path", | ||||
|       close: true, { | ||||
|         draw.line(bl, tl, t) | ||||
|         draw.arc-through((), (tr , 50%, br), b) | ||||
|         draw.line((), b) | ||||
|       } | ||||
|     ) | ||||
|      | ||||
|     draw.anchor("north", t) | ||||
|     draw.anchor("south", b) | ||||
|   }) | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws an AND gate. This function is also available as `element.gate-and()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-and | ||||
| #let gate-and( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate.gate( | ||||
|     draw-shape: draw-shape, | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: inverted, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
|  | ||||
| /// Draws an NAND gate. This function is also available as `element.gate-nand()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-nand | ||||
| #let gate-nand( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate-and( | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: if inverted != "all" {inverted + ("out",)} else {inverted}, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										89
									
								
								src/elements/logic/buf.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,89 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "gate.typ" | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let (x, y) = bl | ||||
|   let (width, height) = (tr.at(0) - x, tr.at(1) - y) | ||||
|  | ||||
|   let r = (x + width, y + height / 2) | ||||
|  | ||||
|   let f = draw.group(name: id, { | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       name: id + "-path", | ||||
|       close: true, { | ||||
|         draw.line(bl, tl, r) | ||||
|       } | ||||
|     ) | ||||
|      | ||||
|     draw.anchor("north", (tl, 50%, r)) | ||||
|     draw.anchor("south", (bl, 50%, r)) | ||||
|   }) | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws a buffer gate. This function is also available as `element.gate-buf()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-buf | ||||
| #let gate-buf( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 1, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate.gate( | ||||
|     draw-shape: draw-shape, | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: inverted, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
|  | ||||
| /// Draws a NOT gate. This function is also available as `element.gate-not()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-not | ||||
| #let gate-not(x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 1, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate-buf( | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: if inverted != "all" {inverted + ("out",)} else {inverted}, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										117
									
								
								src/elements/logic/gate.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,117 @@ | ||||
| #import "@preview/cetz:0.2.2": draw, coordinate | ||||
| #import "../ports.typ": add-ports, add-port | ||||
| #import "../element.typ" | ||||
|  | ||||
| #let default-draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let f = {draw.rect(tl, br, fill: fill, stroke: stroke)} | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Draws a logic gate. This function is also available as `element.gate()` | ||||
| /// | ||||
| /// - draw-shape (function): see #doc-ref("element.elmt") | ||||
| /// - x (number, dictionary): see #doc-ref("element.elmt") | ||||
| /// - y (number, dictionary): see #doc-ref("element.elmt") | ||||
| /// - w (number): see #doc-ref("element.elmt") | ||||
| /// - h (number): see #doc-ref("element.elmt") | ||||
| /// - inputs (int): The number of inputs | ||||
| /// - fill (none, color): see #doc-ref("element.elmt") | ||||
| /// - stroke (stroke): see #doc-ref("element.elmt") | ||||
| /// - id (str): see #doc-ref("element.elmt") | ||||
| /// - inverted (str, array): Either "all" or an array of port ids to display as inverted | ||||
| /// - inverted-radius (number): The radius of inverted ports dot | ||||
| /// - debug (dictionary): see #doc-ref("element.elmt") | ||||
| #let gate( | ||||
|   draw-shape: default-draw-shape, | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   inverted-radius: 0.1, | ||||
|   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 dy | ||||
|     if to == "out" { | ||||
|       dy = height / 2 | ||||
|     } else { | ||||
|       dy = height * (i + 0.5) / inputs | ||||
|     } | ||||
|      | ||||
|     let (ctx, from-pos) = coordinate.resolve(ctx, from) | ||||
|     y = from-pos.at(1) + dy - height | ||||
|   } | ||||
|  | ||||
|   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 | ||||
|  | ||||
|   let space = 100% / inputs | ||||
|   for i in range(inputs) { | ||||
|     let pct = (i + 0.5) * space | ||||
|     let a = (tl, pct, bl) | ||||
|     let b = (tr, pct, br) | ||||
|     let int-name = id + "i" + str(i) | ||||
|     draw.intersections( | ||||
|       int-name, | ||||
|       func, | ||||
|       draw.hide(draw.line(a, b)) | ||||
|     ) | ||||
|     let port-name = "in" + str(i) | ||||
|     let port-pos = int-name + ".0" | ||||
|     if inverted == "all" or port-name in inverted { | ||||
|       draw.circle(port-pos, radius: inverted-radius, anchor: "east", stroke: stroke) | ||||
|       port-pos = (rel: (-2 * inverted-radius, 0), to: port-pos) | ||||
|     } | ||||
|     add-port( | ||||
|       id, "west", | ||||
|       (id: port-name), port-pos, | ||||
|       debug: debug.ports | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   let out-pos = id + ".east" | ||||
|   if inverted == "all" or "out" in inverted { | ||||
|     draw.circle(out-pos, radius: inverted-radius, anchor: "west", stroke: stroke) | ||||
|     out-pos = (rel: (2 * inverted-radius, 0), to: out-pos) | ||||
|   } | ||||
|   add-port( | ||||
|     id, "east", | ||||
|     (id: "out"), out-pos, | ||||
|     debug: debug.ports | ||||
|   ) | ||||
| }) | ||||
							
								
								
									
										104
									
								
								src/elements/logic/or.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,104 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "gate.typ" | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let (x, y) = bl | ||||
|   let (width, height) = (tr.at(0) - x, tr.at(1) - y) | ||||
|  | ||||
|   let t = (x + width / 2, y + height) | ||||
|   let b = (x + width / 2, y) | ||||
|  | ||||
|   let ctrl-bl = (x + width / 2, y) | ||||
|   let ctrl-br = (x + width * 0.8, y + height * 0.1) | ||||
|   let ctrl-tl = (x + width / 2, y + height) | ||||
|   let ctrl-tr = (x + width * 0.8, y + height * 0.9) | ||||
|  | ||||
|   let l = (x + width * 0.2, y + height / 2) | ||||
|   let r = (x + width, y + height / 2) | ||||
|  | ||||
|   let f = draw.group(name: id, { | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       name: id + "-path", { | ||||
|         draw.bezier-through(bl, l, tl) | ||||
|         draw.bezier((), r, ctrl-tl, ctrl-tr) | ||||
|         draw.bezier((), bl, ctrl-br, ctrl-bl) | ||||
|       } | ||||
|     ) | ||||
|      | ||||
|     draw.intersections("i", | ||||
|       id + "-path", | ||||
|       draw.hide(draw.line(t, b)) | ||||
|     ) | ||||
|     draw.anchor("north", "i.0") | ||||
|     draw.anchor("south", "i.1") | ||||
|   }) | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws an OR gate. This function is also available as `element.gate-or()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-or | ||||
| #let gate-or( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate.gate( | ||||
|     draw-shape: draw-shape, | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: inverted, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
|  | ||||
| /// Draws a NOR gate. This function is also available as `element.gate-nor()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-nor | ||||
| #let gate-nor( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate-or( | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: if inverted != "all" {inverted + ("out",)} else {inverted}, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										111
									
								
								src/elements/logic/xor.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,111 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "gate.typ" | ||||
|  | ||||
| #let space = 10% | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke) = { | ||||
|   let (x, y) = bl | ||||
|   let (width, height) = (tr.at(0) - x, tr.at(1) - y) | ||||
|  | ||||
|   let tl2 = (tl, space, tr) | ||||
|   let bl2 = (bl, space, br) | ||||
|  | ||||
|   let t = (x + width / 2, y + height) | ||||
|   let b = (x + width / 2, y) | ||||
|  | ||||
|   let ctrl-bl = (x + width / 2, y) | ||||
|   let ctrl-br = (x + width * 0.8, y + height * 0.1) | ||||
|   let ctrl-tl = (x + width / 2, y + height) | ||||
|   let ctrl-tr = (x + width * 0.8, y + height * 0.9) | ||||
|  | ||||
|   let l = (x + width * 0.2, y + height / 2) | ||||
|   let l2 = (x + width * (0.2 + space / 100%), y + height / 2) | ||||
|   let r = (x + width, y + height / 2) | ||||
|  | ||||
|   let f = draw.group(name: id, { | ||||
|     draw.bezier-through(bl, l, tl, stroke: stroke) | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       name: id + "-path", { | ||||
|         draw.bezier-through(bl2, l2, tl2) | ||||
|         draw.bezier((), r, ctrl-tl, ctrl-tr) | ||||
|         draw.bezier((), bl2, ctrl-br, ctrl-bl) | ||||
|       } | ||||
|     ) | ||||
|      | ||||
|     draw.intersections("i", | ||||
|       id + "-path", | ||||
|       draw.hide(draw.line(t, b)) | ||||
|     ) | ||||
|     draw.anchor("north", "i.0") | ||||
|     draw.anchor("south", "i.1") | ||||
|   }) | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws a XOR gate. This function is also available as `element.gate-xor()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-xor | ||||
| #let gate-xor( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate.gate( | ||||
|     draw-shape: draw-shape, | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: inverted, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
|  | ||||
| /// Draws a XNOR gate. This function is also available as `element.gate-xnor()` | ||||
| ///  | ||||
| /// For parameters, see #doc-ref("gates.gate") | ||||
| /// #examples.gate-xnor | ||||
| #let gate-xnor( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   inputs: 2, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   inverted: (), | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   gate-xor( | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     inputs: inputs, | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     inverted: if inverted != "all" {inverted + ("out",)} else {inverted}, | ||||
|     debug: debug | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										98
									
								
								src/elements/multiplexer.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,98 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "../util.typ" | ||||
| #import "element.typ" | ||||
| #import "ports.typ": add-port | ||||
|  | ||||
| #let draw-shape(id, tl, tr, br, bl, fill, stroke, h-ratio: 60%) = { | ||||
|   let margin = (100% - h-ratio) / 2 | ||||
|   let tr2 = (tr, margin, br) | ||||
|   let br2 = (br, margin, tr) | ||||
|   let f = draw.group(name: id, { | ||||
|     draw.merge-path( | ||||
|       inset: 0.5em, | ||||
|       fill: fill, | ||||
|       stroke: stroke, | ||||
|       close: true, | ||||
|       draw.line(tl, tr2, br2, bl) | ||||
|     ) | ||||
|     draw.anchor("north", (tl, 50%, tr2)) | ||||
|     draw.anchor("south", (bl, 50%, br2)) | ||||
|     draw.anchor("west", (tl, 50%, bl)) | ||||
|     draw.anchor("east", (tr2, 50%, br2)) | ||||
|     draw.anchor("north-west", tl) | ||||
|     draw.anchor("north-east", tr2) | ||||
|     draw.anchor("south-east", br2) | ||||
|     draw.anchor("south-west", bl) | ||||
|   }) | ||||
|  | ||||
|   return (f, tl, tr, br, bl) | ||||
| } | ||||
|  | ||||
| /// Draws a multiplexer | ||||
| /// | ||||
| /// #examples.multiplexer | ||||
| /// For other parameters description, see #doc-ref("element.elmt") | ||||
| /// - entries (int, array): If it is an integer, it defines the number of input ports (automatically named with their binary index). If it is an array of strings, it defines the name of each input. | ||||
| /// - h-ratio (ratio): The height ratio of the right side relative to the full height | ||||
| #let multiplexer( | ||||
|   x: none, | ||||
|   y: none, | ||||
|   w: none, | ||||
|   h: none, | ||||
|   name: none, | ||||
|   name-anchor: "center", | ||||
|   entries: 2, | ||||
|   h-ratio: 60%, | ||||
|   fill: none, | ||||
|   stroke: black + 1pt, | ||||
|   id: "", | ||||
|   debug: ( | ||||
|     ports: false | ||||
|   ) | ||||
| ) = { | ||||
|   let ports = () | ||||
|   let ports-y = ( | ||||
|     out: (h) => {h * 0.5} | ||||
|   ) | ||||
|  | ||||
|   if (type(entries) == int) { | ||||
|     let nbits = calc.ceil(calc.log(entries, base: 2)) | ||||
|     for i in range(entries) { | ||||
|       let bits = util.lpad(str(i, base: 2), nbits) | ||||
|       ports.push((id: "in" + str(i), name: bits)) | ||||
|     } | ||||
|   } else { | ||||
|     for (i, port) in entries.enumerate() { | ||||
|       ports.push((id: "in" + str(i), name: port)) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   let space = 100% / ports.len() | ||||
|   let l = ports.len() | ||||
|   for (i, port) in ports.enumerate() { | ||||
|     ports-y.insert(port.id, (h) => {h * (i + 0.5) / l}) | ||||
|   } | ||||
|    | ||||
|   element.elmt( | ||||
|     draw-shape: draw-shape.with(h-ratio: h-ratio), | ||||
|     x: x, | ||||
|     y: y, | ||||
|     w: w, | ||||
|     h: h, | ||||
|     name: name, | ||||
|     name-anchor: name-anchor, | ||||
|     ports: (west: ports, east: ((id: "out"),)), | ||||
|     fill: fill, | ||||
|     stroke: stroke, | ||||
|     id: id, | ||||
|     ports-y: ports-y, | ||||
|     auto-ports: false, | ||||
|     debug: debug | ||||
|   ) | ||||
|  | ||||
|   for (i, port) in ports.enumerate() { | ||||
|     let pct = (i + 0.5) * space | ||||
|     add-port(id, "west", port, (id+".north-west", pct, id+".south-west")) | ||||
|   } | ||||
|   add-port(id, "east", (id: "out"), (id+".north-east", 50%, id+".south-east")) | ||||
| } | ||||
							
								
								
									
										116
									
								
								src/elements/ports.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,116 @@ | ||||
| #import "@preview/cetz:0.2.2": draw | ||||
| #import "../util.typ": rotate-anchor | ||||
|  | ||||
| #let add-port( | ||||
|   elmt-id, side, port, pos, | ||||
|   prev: none, | ||||
|   next: none, | ||||
|   debug: false | ||||
| ) = { | ||||
|   let name = port.at("name", default: "") | ||||
|   let name-rotate = port.at("vertical", default: false) | ||||
|  | ||||
|   if (port.at("clock", default: false)) { | ||||
|     if prev == none or next == none { | ||||
|       panic("Clock port must have previous and next positions") | ||||
|     } | ||||
|      | ||||
|     let size = if port.at("small", default: false) {8pt} else {1em} | ||||
|     let offset | ||||
|     if      (side == "north") { offset = (    0, -size) } | ||||
|     else if (side == "east")  { offset = (-size,     0) } | ||||
|     else if (side == "south") { offset = (    0,  size) } | ||||
|     else if (side == "west")  { offset = ( size,     0) } | ||||
|  | ||||
|     let pos1 = (rel: offset, to: pos) | ||||
|  | ||||
|     // TODO: use context or vectors to have the height relative to the width | ||||
|     draw.line(prev, pos1, next) | ||||
|   } | ||||
|   draw.content( | ||||
|     pos, | ||||
|     anchor: if name-rotate {rotate-anchor(side)} else {side}, | ||||
|     padding: 2pt, | ||||
|     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 | ||||
|     ) | ||||
|      | ||||
|   } else { | ||||
|     draw.hide(draw.circle( | ||||
|       pos, | ||||
|       radius: 0, | ||||
|       stroke: none, | ||||
|       name: id | ||||
|     )) | ||||
|   } | ||||
| } | ||||
|  | ||||
| #let add-ports( | ||||
|   elmt-id, | ||||
|   tl, tr, br, bl, | ||||
|   ports, | ||||
|   ports-margins, | ||||
|   debug: false | ||||
| ) = { | ||||
|   let sides = ( | ||||
|     "north": (tl, tr), | ||||
|     "east": (tr, br), | ||||
|     "south": (bl, br), | ||||
|     "west": (tl, bl) | ||||
|   ) | ||||
|  | ||||
|   if type(ports) != dictionary { | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   for (side, props) in sides { | ||||
|     let side-ports = ports.at(side, default: ()) | ||||
|     let space = 100% / (side-ports.len() + 1) | ||||
|  | ||||
|     for (i, port) in side-ports.enumerate() { | ||||
|       let pct = (i + 1) * space | ||||
|       let pt0 = props.at(0) | ||||
|       let pt1 = props.at(1) | ||||
|  | ||||
|       if side in ports-margins { | ||||
|         let (a, b) = (pt0, pt1) | ||||
|         let margins = ports-margins.at(side) | ||||
|         a = (pt0, margins.at(0), pt1) | ||||
|         b = (pt0, 100% - margins.at(1), pt1) | ||||
|         pt0 = a | ||||
|         pt1 = b | ||||
|       } | ||||
|        | ||||
|       let pos = (pt0, pct, pt1) | ||||
|       let pct-prev = (i + 0.5) * space | ||||
|       let pct-next = (i + 1.5) * space | ||||
|       let pos-prev = (pt0, pct-prev, pt1) | ||||
|       let pos-next = (pt0, pct-next, pt1) | ||||
|  | ||||
|       if port.at("small", default: false) { | ||||
|         pos-prev = (pos, 4pt, pt0) | ||||
|         pos-next = (pos, 4pt, pt1) | ||||
|       } | ||||
|  | ||||
|       add-port( | ||||
|         elmt-id, | ||||
|         side, | ||||
|         port, | ||||
|         pos, | ||||
|         prev: pos-prev, | ||||
|         next: pos-next, | ||||
|         debug: debug | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/gates.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| #import "elements/logic/gate.typ": gate | ||||
| #import "elements/logic/and.typ": gate-and, gate-nand | ||||
| #import "elements/logic/or.typ": gate-or, gate-nor | ||||
| #import "elements/logic/xor.typ": gate-xor, gate-xnor | ||||
| #import "elements/logic/buf.typ": gate-buf, gate-not | ||||
							
								
								
									
										7
									
								
								src/lib.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| #let version = version(0, 1, 0) | ||||
|  | ||||
| #import "circuit.typ": circuit | ||||
| #import "element.typ" | ||||
| #import "gates.typ" | ||||
| #import "util.typ" | ||||
| #import "wire.typ" | ||||
							
								
								
									
										76
									
								
								src/util.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,76 @@ | ||||
| /// Predefined color palette | ||||
| /// #let w = 4 | ||||
| /// #box(width: 100%, align(center)[ | ||||
| ///   #canvas(length: 2em, { | ||||
| ///     for (i, color) in util.colors.pairs().enumerate() { | ||||
| ///       let x = calc.rem(i, w) * 2 | ||||
| ///       let y = calc.floor(i / w) * 2 | ||||
| ///       draw.rect((x, -y), (x + 1, -y - 1), fill: color.last(), stroke: none) | ||||
| ///       draw.content((x + 0.5, -y - 1.25), color.first()) | ||||
| ///     } | ||||
| ///   }) | ||||
| /// ]) | ||||
| #let colors = ( | ||||
|   orange: rgb(245, 180, 147), | ||||
|   yellow: rgb(250, 225, 127), | ||||
|   green: rgb(127, 200, 172), | ||||
|   pink: rgb(236, 127, 178), | ||||
|   purple: rgb(189, 151, 255), | ||||
|   blue: rgb(127, 203, 235) | ||||
| ) | ||||
|  | ||||
| /// Pads a string on the left with 0s to the given length | ||||
| /// | ||||
| /// #example(`#util.lpad("0100", 8)`, mode: "markup") | ||||
| /// | ||||
| /// - string (str): The string to pad | ||||
| /// - len (int): The target length | ||||
| /// -> str | ||||
| #let lpad(string, len) = { | ||||
|   let res = "0" * len + string | ||||
|   return res.slice(-len) | ||||
| } | ||||
|  | ||||
| /// Returns the anchor on the opposite side of the given one | ||||
| /// | ||||
| /// #example(`#util.opposite-anchor("west")`, mode: "markup") | ||||
| /// | ||||
| /// - anchor (str): The input anchor | ||||
| /// -> str | ||||
| #let opposite-anchor(anchor) = { | ||||
|   return ( | ||||
|     north: "south", | ||||
|     east: "west", | ||||
|     south: "north", | ||||
|     west: "east", | ||||
|  | ||||
|     north-west: "south-east", | ||||
|     north-east: "south-west", | ||||
|     south-east: "north-west", | ||||
|     south-west: "north-east" | ||||
|   ).at(anchor) | ||||
| } | ||||
|  | ||||
| /// Returns the anchor rotated 90 degrees clockwise relative to the given one | ||||
| /// | ||||
| /// #example(`#util.rotate-anchor("west")`, mode: "markup") | ||||
| /// - anchor (str): The anchor to rotate | ||||
| /// -> str | ||||
| #let rotate-anchor(anchor) = { | ||||
|   return ( | ||||
|     north: "east", | ||||
|     east: "south", | ||||
|     south: "west", | ||||
|     west: "north", | ||||
|  | ||||
|     north-west: "north-east", | ||||
|     north-east: "south-east", | ||||
|     south-east: "south-west", | ||||
|     south-west: "north-west" | ||||
|   ).at(anchor) | ||||
| } | ||||
|  | ||||
| #let valid-anchors = ( | ||||
|   "center", "north", "east", "west", "south", | ||||
|   "north-east", "north-west", "south-east", "south-west" | ||||
| ) | ||||
							
								
								
									
										306
									
								
								src/wire.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,306 @@ | ||||
| #import "@preview/cetz:0.2.2": draw, coordinate | ||||
| #import "util.typ": opposite-anchor | ||||
|  | ||||
| /// List of valid wire styles | ||||
| /// #examples.wires | ||||
| #let wire-styles = ("direct", "zigzag", "dodge") | ||||
| #let signal-width = 1pt | ||||
| #let bus-width = 1.5pt | ||||
|  | ||||
| /// Draws a wire intersection at the given anchor | ||||
| /// #examples.intersection | ||||
| /// - pt (point): A CeTZ compatible point / anchor | ||||
| /// - radius (number): The radius of the intersection | ||||
| /// - fill (color): The fill color | ||||
| #let intersection(pt, radius: .1, fill: black) = { | ||||
|   draw.circle(pt, radius: radius, stroke: none, fill: fill) | ||||
| } | ||||
|  | ||||
| #let get-direct-wire(pts) = { | ||||
|   let anchors = ( | ||||
|     "start": pts.first(), | ||||
|     "end": pts.last() | ||||
|   ) | ||||
|   return (pts, anchors) | ||||
| } | ||||
|  | ||||
| #let get-zigzag-wire(pts, ratio, dir) = { | ||||
|   let start = pts.first() | ||||
|   let end = pts.last() | ||||
|   let mid = if dir == "vertical" { | ||||
|     (start, ratio, (horizontal: end, vertical: ())) | ||||
|   } else { | ||||
|     (start, ratio, (horizontal: (), vertical: end)) | ||||
|   } | ||||
|  | ||||
|   let points = if dir == "vertical" { | ||||
|     ( | ||||
|       start, | ||||
|       (horizontal: mid, vertical: ()), | ||||
|       (horizontal: (), vertical: end), | ||||
|       end | ||||
|     ) | ||||
|   } else { | ||||
|     ( | ||||
|       start, | ||||
|       (horizontal: (), vertical: mid), | ||||
|       (horizontal: end, vertical: ()), | ||||
|       end | ||||
|     ) | ||||
|   } | ||||
|   let anchors = ( | ||||
|     "start": start, | ||||
|     "zig": points.at(1), | ||||
|     "zag": points.at(2), | ||||
|     "end": end | ||||
|   ) | ||||
|   return (points, anchors) | ||||
| } | ||||
|  | ||||
| #let get-dodge-wire(pts, dodge-y, margins, sides, ctx) = { | ||||
|   let start = pts.first() | ||||
|   let end = pts.last() | ||||
|   let (margin-start, margin-end) = margins | ||||
|   let (side-start, side-end) = sides | ||||
|  | ||||
|   let p1 = (start, margin-start, end) | ||||
|   let p2 = (end, margin-end, start) | ||||
|    | ||||
|   let (ctx, p0) = coordinate.resolve(ctx, start) | ||||
|   let (ctx, p3) = coordinate.resolve(ctx, end) | ||||
|   p0 = (x: p0.first(), y: p0.last()) | ||||
|   p3 = (x: p3.first(), y: p3.last()) | ||||
|  | ||||
|   let dx1 = margin-start | ||||
|   let dx2 = margin-end | ||||
|    | ||||
|   if type(margin-start) == ratio { | ||||
|     dx1 = calc.abs(p3.x - p0.x) * margin-start / 100% | ||||
|   } | ||||
|   if type(margin-end) == ratio { | ||||
|     dx2 = calc.abs(p3.x - p0.x) * margin-end / 100% | ||||
|   } | ||||
|   if side-start == "west" { | ||||
|     dx1 *= -1 | ||||
|   } | ||||
|   if side-end == "east" { | ||||
|     dx2 *= -1 | ||||
|   } | ||||
|   p1 = (p0.x + dx1, p0.y) | ||||
|   p2 = (p3.x - dx2, p0.y) | ||||
|  | ||||
|   let points = ( | ||||
|     start, | ||||
|     (horizontal: p1, vertical: ()), | ||||
|     (horizontal: (), vertical: (0, dodge-y)), | ||||
|     (horizontal: p2, vertical: ()), | ||||
|     (horizontal: (), vertical: end), | ||||
|     end | ||||
|   ) | ||||
|   let anchors = ( | ||||
|     "start": start, | ||||
|     "start2": points.at(1), | ||||
|     "dodge-start": points.at(2), | ||||
|     "dodge-end": points.at(3), | ||||
|     "end2": points.at(4), | ||||
|     "end": end | ||||
|   ) | ||||
|  | ||||
|   return (points, anchors) | ||||
| } | ||||
|  | ||||
| /// Draws a wire between two points | ||||
| /// - id (str): The wire's id, for future reference (anchors) | ||||
| /// - pts (array): The two points (as CeTZ compatible coordinates, i.e. XY, relative positions, ids, etc.) | ||||
| /// - bus (bool): Whether the wire is a bus (multiple bits) or a simple signal (single bit) | ||||
| /// - name (none, str, array): Optional name of the wire. If it is an array, the first name will be put at the start of the wire, and the second at the end | ||||
| /// - name-pos (str): Position of the name. One of: "middle", "start" or "end" | ||||
| /// - slice (none, array): Optional bits slice (start and end bit indices). If set, it will be displayed at the start of the wire | ||||
| /// - color (color): The stroke color | ||||
| /// - dashed (bool): Whether the stroke is dashed or not | ||||
| /// - style (str): The wire's style (see #doc-ref("wire.wire-styles", var: true) for possible values) | ||||
| /// - reverse (bool): If true, the start and end points will be swapped (useful in cases where the start point depends on the end point, for example with perpendiculars) | ||||
| /// - directed (bool): If true, the wire will be directed, meaning an arrow will be drawn at the endpoint | ||||
| /// - rotate-name (bool): If true, the name will be rotated according to the wire's slope | ||||
| /// - zigzag-ratio (ratio): Position of the zigzag vertical relative to the horizontal span (only with style "zigzag") | ||||
| /// - zigzag-dir (str): The zigzag's direction. As either "vertical" or "horizontal" (only with dstyle "zigzag") | ||||
| /// - dodge-y (number): Y position to dodge the wire to (only with style "dodge") | ||||
| /// - dodge-sides (array): The start and end sides (going out of the connected element) of the wire (only with style "dodge") | ||||
| /// - dodge-margins (array): The start and end margins (i.e. space before dodging) of the wire (only with style "dodge") | ||||
| #let wire( | ||||
|   id, pts, | ||||
|   bus: false, | ||||
|   name: none, | ||||
|   name-pos: "middle", | ||||
|   slice: none, | ||||
|   color: black, | ||||
|   dashed: false, | ||||
|   style: "direct", | ||||
|   reverse: false, | ||||
|   directed: false, | ||||
|   rotate-name: true, | ||||
|   zigzag-ratio: 50%, | ||||
|   zigzag-dir: "vertical", | ||||
|   dodge-y: 0, | ||||
|   dodge-sides: ("east", "west"), | ||||
|   dodge-margins: (5%, 5%) | ||||
| ) = draw.get-ctx(ctx => { | ||||
|   if not style in wire-styles { | ||||
|     panic("Invalid wire style '" + style + "'") | ||||
|   } | ||||
|  | ||||
|   if pts.len() != 2 { | ||||
|     panic("Wrong number of points (got " + str(pts.len()) + " instead of 2)") | ||||
|   } | ||||
|    | ||||
|   let stroke = ( | ||||
|     paint: color, | ||||
|     thickness: if bus {bus-width} else {signal-width} | ||||
|   ) | ||||
|   if dashed { | ||||
|     stroke.insert("dash", "dashed") | ||||
|   } | ||||
|  | ||||
|   let points = () | ||||
|   let anchors = () | ||||
|  | ||||
|   if style == "direct" { | ||||
|     (points, anchors) = get-direct-wire(pts) | ||||
|      | ||||
|   } else if style == "zigzag" { | ||||
|     (points, anchors) = get-zigzag-wire(pts, zigzag-ratio, zigzag-dir) | ||||
|      | ||||
|   } else if style == "dodge" { | ||||
|     (points, anchors) = get-dodge-wire( | ||||
|       pts, | ||||
|       dodge-y, | ||||
|       dodge-margins, | ||||
|       dodge-sides, | ||||
|       ctx | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   let mark = (fill: color) | ||||
|   if directed { | ||||
|     mark = (end: ">", fill: color) | ||||
|   } | ||||
|   draw.group(name: id, { | ||||
|     draw.line(..points, stroke: stroke, mark: mark) | ||||
|     for (anchor-name, anchor-pos) in anchors { | ||||
|       draw.anchor(anchor-name, anchor-pos) | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   let first-pt = id + ".start" | ||||
|   let last-pt = id + ".end" | ||||
|   let first-pos = points.first() | ||||
|   let second-pos = points.at(1) | ||||
|   if reverse { | ||||
|     (first-pt, last-pt) = (last-pt, first-pt) | ||||
|   } | ||||
|    | ||||
|   let angle = 0deg | ||||
|   if rotate-name { | ||||
|     (ctx, first-pos) = coordinate.resolve(ctx, first-pos) | ||||
|     (ctx, second-pos) = coordinate.resolve(ctx, second-pos) | ||||
|      | ||||
|     if reverse { | ||||
|       (first-pos, second-pos) = (second-pos, first-pos) | ||||
|     } | ||||
|     let (x1, y1, ..) = first-pos | ||||
|     let (x2, y2, ..) = second-pos | ||||
|     angle = calc.atan2(x2 - x1, y2 - y1) | ||||
|   } | ||||
|    | ||||
|   if name != none { | ||||
|     let names = () | ||||
|      | ||||
|     if type(name) == str { | ||||
|       names = ((name, name-pos),) | ||||
|        | ||||
|     } else if type(name) == array { | ||||
|       names = ( | ||||
|         (name.at(0), "start"), | ||||
|         (name.at(1), "end") | ||||
|       ) | ||||
|     } | ||||
|  | ||||
|     for (name, pos) in names { | ||||
|       let point | ||||
|       let anchor | ||||
|        | ||||
|       if pos == "middle" { | ||||
|         point = (first-pt, 50%, last-pt) | ||||
|         anchor = "south" | ||||
|  | ||||
|       } else if pos == "start" { | ||||
|         point = first-pt | ||||
|         anchor = "south-west" | ||||
|  | ||||
|       } else if pos == "end" { | ||||
|         point = last-pt | ||||
|         anchor = "south-east" | ||||
|       } | ||||
|  | ||||
|       draw.content(point, anchor: anchor, padding: 3pt, angle: angle, name) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if slice != none { | ||||
|     let slice-txt = "[" + slice.map(b => str(b)).join(":") + "]" | ||||
|      | ||||
|     draw.content( | ||||
|       first-pt, | ||||
|       anchor: "south-west", | ||||
|       padding: 3pt, | ||||
|       text(slice-txt, size: 0.75em) | ||||
|     ) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| /// Draws a wire stub (useful for unlinked ports) | ||||
| /// | ||||
| /// #examples.stub | ||||
| /// - port-id (str): The port anchor | ||||
| /// - side (str): The side on which the port is (one of "north", "east", "south", "west") | ||||
| /// - name (none, str): Optional name displayed at the end of the stub | ||||
| /// - vertical (bool): Whether the name should be displayed vertically | ||||
| /// - length (number): The length of the stub | ||||
| /// - name-offset (number): The name offset, perpendicular to the stub | ||||
| #let stub(port-id, side, name: none, vertical: false, length: 1em, name-offset: 0) = { | ||||
|   let end-offset = ( | ||||
|     north: (0, length), | ||||
|     east: (length, 0), | ||||
|     south: (0, -length), | ||||
|     west: (-length, 0) | ||||
|   ).at(side) | ||||
|    | ||||
|   let name-offset = ( | ||||
|     north: (name-offset, length), | ||||
|     east: (length, name-offset), | ||||
|     south: (name-offset, -length), | ||||
|     west: (-length, name-offset) | ||||
|   ).at(side) | ||||
|  | ||||
|   draw.line( | ||||
|     port-id, | ||||
|     (rel: end-offset, to: port-id) | ||||
|   ) | ||||
|   if name != none { | ||||
|     let text-anchor = if vertical { | ||||
|       ( | ||||
|         "north": "west", | ||||
|         "south": "east", | ||||
|         "west": "south", | ||||
|         "east": "north" | ||||
|       ).at(side) | ||||
|     } else { opposite-anchor(side) } | ||||
|     draw.content( | ||||
|       anchor: text-anchor, | ||||
|       padding: 0.2em, | ||||
|       angle: if vertical {90deg} else {0deg}, | ||||
|       (rel: name-offset, to: port-id), | ||||
|       name | ||||
|     ) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										14
									
								
								typst.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| [package] | ||||
| name = "circuiteria" | ||||
| version = "0.1.0" | ||||
| compiler = "0.11.0" | ||||
| repository = "https://git.kb28.ch/HEL/circuiteria" | ||||
| entrypoint = "src/lib.typ" | ||||
| authors = [ | ||||
|     "Louis Heredero <https://git.kb28.ch/HEL>" | ||||
| ] | ||||
| categories = [ "visualization" ] | ||||
| license = "Apache-2.0" | ||||
| description = "Drawing block circuits with Typst made easy, using CeTZ" | ||||
| keywords = [ "circuit", "block", "draw" ] | ||||
| exclude = [ "gallery", "gallery.bash", "doc" ] | ||||