Author SHA1 Message Date
purpl3F0x 0c1272e35e Add Verilog parsing and rendering support
- Introduced `verilog.typ` for parsing Verilog/SystemVerilog source code, supporting both ANSI and non-ANSI port styles.
- Implemented functions to analyze modules, infer AXI4, AXI4-Lite, and AXI4-Stream buses, as well as clock and reset signals.
- Added rendering capabilities for Verilog modules, allowing for visual representation of ports and buses.
- Created a new `gallery/verilog.typ` file to facilitate the drawing of Verilog blocks in diagrams.

Signed-off-by: Stavros Avramidis <stavros9899@gmail.com>
2026-09-10 12:17:54 +01:00
HEL c9aa81af2e Merge pull request 'Release v0.2.1' (#25) from dev into main
Reviewed-on: #25
2026-07-03 23:37:38 +00:00
HEL 6871053810 Merge pull request 'Release v0.2.1' (#24) from release/0.2.1 into dev
Reviewed-on: #24
2026-07-03 23:36:46 +00:00
HEL 73b7348eed chore: remove duplicate alt text 2026-07-04 01:35:35 +02:00
HEL b71e822bb8 chore: use preview import paths in examples 2026-06-30 21:40:09 +02:00
HEL 4c1025b1eb chore: remove "Typst" from description 2026-06-30 21:31:55 +02:00
HEL b35c04a7d9 chore: add alt text on README images 2026-06-30 21:29:01 +02:00
HEL 0612e9b6ec chore: bump to 0.2.1 2026-06-30 21:13:41 +02:00
HEL 42d6915b15 Merge pull request 'fix: handle y port alignment with gates' (#22) from fix/15-gate-y-port-alignment into dev
Reviewed-on: #22
2026-06-30 19:08:48 +00:00
HEL b646d24930 fix: handle y port alignment with gates 2026-06-30 21:07:52 +02:00
HEL 1533b5ccd4 Merge pull request 'Update manual and gallery' (#21) from feat/update-manual-and-gallery into dev
Reviewed-on: #21
2026-06-30 17:18:26 +00:00
HEL 2633b35f39 chore: remove gallery PDFs 2026-06-30 19:16:17 +02:00
HEL 415029e0c3 chore: update gallery examples 2026-06-30 19:15:26 +02:00
HEL 091ec8c08a chore: update manual 2026-06-30 19:13:19 +02:00
HEL ffd3356ab2 docs: add radius parameter for block 2026-06-30 19:02:08 +02:00
HEL 02e59c5d7a Merge pull request 'Update CeTZ' (#20) from feat/18-update-cetz into dev
Reviewed-on: #20
2026-06-30 16:54:30 +00:00
HEL 96dd9b0543 chore: bump compiler version to 0.13.1 2026-06-30 18:52:01 +02:00
HEL da9c2b568b chore: update cetz to 0.3.4 2026-06-30 18:50:22 +02:00
HEL 459892bb13 refactor: centralize cetz import 2026-06-30 18:48:34 +02:00
HEL 255eec7f35 Merge pull request 'chore: expose package name' (#19) from feat/17-expose-name into dev
Reviewed-on: #19
2026-06-30 16:34:28 +00:00
HEL 536f5a075d chore: expose package name 2026-06-30 18:31:51 +02:00
HEL d73215c889 Merge pull request 'feature: added guided wire style' (#14) from Shmerlard/circuiteria:dev into dev
Reviewed-on: #14
Reviewed-by: Louis Heredero <louis@herdac.ch>
2025-08-24 11:01:02 +00:00
Shmerlard 87643983ec added guided wire 2025-08-17 22:05:36 +03:00
HEL 94fef0a245 Merge pull request 'Add radius parameter to block' (#12) from Shmerlard/circuiteria:main into dev
Reviewed-on: #12
Reviewed-by: Louis Heredero <louis@herdac.ch>
2025-08-17 12:36:04 +00:00
Shmerlard 1a0a659ace no font is set in circuit 2025-08-17 15:31:09 +03:00
Shmerlard f2ce91ec39 small fix for radius 2025-08-17 14:33:31 +03:00
Shmerlard 043bb339fe font arguement, radius arguement
now in `block.typ` the radius is optional
and the font can be set in the canvas
2025-08-17 14:00:21 +03:00
HEL 693676d61a Merge pull request 'feature: add IEC gates' (#11) from bono/circuiteria:add_iec_gates into dev
Reviewed-on: #11
Reviewed-by: Louis Heredero <louis@herdac.ch>
2025-06-29 13:19:56 +00:00
bono 94d5d6b854 add examples and add iec gates to manual 2025-06-28 13:56:22 -03:00
bono 71f128f6c9 fix typo in xnor gate 2025-06-28 13:55:14 -03:00
bono ea8277ee5b rename iec gates to have consistent naming 2025-06-28 12:28:28 -03:00
bono c5e4f8039a improve iec-gate 2025-06-28 12:10:00 -03:00
bono 8c91ccdd54 improve default-draw-shape by drawing a rectangle 2025-06-28 11:40:36 -03:00
bono 9966656e8b add examples 2025-06-22 14:49:11 -03:00
bono 3ccb79c6c2 add iec gates 2025-06-22 14:46:52 -03:00
52 changed files with 1912 additions and 934 deletions
+31 -8
View File
@@ -3,7 +3,7 @@
Circuiteria is a [Typst](https://typst.app) package for drawing block circuit diagrams using the [CeTZ](https://typst.app/universe/package/cetz) package. 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"> <p align="center">
<img src="./gallery/platypus.png" alt="Perry the platypus"> <img src="./gallery/platypus.png" alt="A platypus / Perry the Platypus meme made with colored rounded rectangles">
</p> </p>
## Examples ## Examples
@@ -11,17 +11,17 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
<tr> <tr>
<td colspan="2"> <td colspan="2">
<a href="./gallery/test.typ"> <a href="./gallery/test.typ">
<img src="./gallery/test.png" width="500px"> <img src="./gallery/test.png" width="500px" alt="Block diagram of a multi-cycle RISC-V FPGA processor">
</a> </a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2">A bit of eveything</td> <td colspan="2">A bit of everything</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<a href="./gallery/test5.typ"> <a href="./gallery/test5.typ">
<img src="./gallery/test5.png" width="500px"> <img src="./gallery/test5.png" width="500px" alt="Block diagram of a single-cycle RISC-V processor">
</a> </a>
</td> </td>
</tr> </tr>
@@ -31,12 +31,12 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
<tr> <tr>
<td> <td>
<a href="./gallery/test4.typ"> <a href="./gallery/test4.typ">
<img src="./gallery/test4.png" width="250px"> <img src="./gallery/test4.png" width="250px" alt="Top-level block diagram of a single-cycle RISC-V processor">
</a> </a>
</td> </td>
<td> <td>
<a href="./gallery/test6.typ"> <a href="./gallery/test6.typ">
<img src="./gallery/test6.png" width="250px"> <img src="./gallery/test6.png" width="250px" alt="Block diagram of a simple single-cycle RISC-V ALU">
</a> </a>
</td> </td>
</tr> </tr>
@@ -44,6 +44,16 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
<td>Groups</td> <td>Groups</td>
<td>Rotated</td> <td>Rotated</td>
</tr> </tr>
<tr>
<td colspan="2">
<a href="./gallery/verilog.typ">
<img src="./gallery/verilog.png" width="500px" alt="Block automatically generated from a Verilog module">
</a>
</td>
</tr>
<tr>
<td colspan="2">Generated from a Verilog module</td>
</tr>
</table> </table>
> **Note**\ > **Note**\
@@ -56,9 +66,22 @@ 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: To use this package, simply import [circuiteria](https://typst.app/universe/package/circuiteria) and call the `circuit` function:
```typ ```typ
#import "@preview/circuiteria:0.2.0" #import "@preview/circuiteria:0.2.1"
#circuiteria.circuit({ #circuiteria.circuit({
import circuiteria: * import circuiteria: *
... ...
}) })
``` ```
### Verilog modules
Blocks can be generated automatically from Verilog/SystemVerilog sources.
AXI4, AXI4-Lite and AXI4-Stream signals are grouped into single bus ports, and
clock/reset signals are detected and placed together:
```typ
#circuiteria.circuit({
import circuiteria: *
verilog.block(read("my_module.v"), x: 0, y: 0)
})
```
The port ids are the signal names, or the bus prefixes for inferred buses
(e.g. `"my_module-port-s_axi"`).
+46 -2
View File
@@ -56,7 +56,7 @@ for i in range(3) {
```) ```)
#let wires = example(``` #let wires = example(```
for i in range(3) { for i in range(4) {
draw.circle((i * 3, 0), radius: .1, name: "p" + str(i * 2)) 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.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))) draw.content((i * 3 + 1, -1), raw(wire.wire-styles.at(i)))
@@ -65,6 +65,10 @@ wire.wire("w1", ("p0", "p1"), style: "direct")
wire.wire("w2", ("p2", "p3"), style: "zigzag") wire.wire("w2", ("p2", "p3"), style: "zigzag")
wire.wire("w3", ("p4", "p5"), style: "dodge", wire.wire("w3", ("p4", "p5"), style: "dodge",
dodge-y: -0.5, dodge-margins: (0.5, 0.5)) dodge-y: -0.5, dodge-margins: (0.5, 0.5))
wire.wire("w4", ("p6","p7"), style: "guided",
guided-center:(20%, 40%), guided-margins: (90%,87%),
guided-sides: ("north","south"))
```, vertical: true) ```, vertical: true)
#let stub = example(``` #let stub = example(```
@@ -115,6 +119,46 @@ 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") gates.gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true) ```, vertical: true)
#let iec-gate-and = example(```
gates.iec-gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-nand = example(```
gates.iec-gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-or = example(```
gates.iec-gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-nor = example(```
gates.iec-gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-xor = example(```
gates.iec-gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-xnor = example(```
gates.iec-gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-buf = example(```
gates.iec-gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let iec-gate-not = example(```
gates.iec-gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
gates.iec-gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
```, vertical: true)
#let group = example(``` #let group = example(```
element.group( element.group(
id: "g1", name: "Group 1", stroke: (dash: "dashed"), id: "g1", name: "Group 1", stroke: (dash: "dashed"),
@@ -147,4 +191,4 @@ wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
wire.wire("w2", ((0, 0), (1, -.5)), wire.wire("w2", ((0, 0), (1, -.5)),
style: "zigzag", zigzag-ratio: 80%) style: "zigzag", zigzag-ratio: 80%)
wire.intersection("w1.zig") wire.intersection("w1.zig")
```) ```)
+51
View File
@@ -0,0 +1,51 @@
`timescale 1ns / 1ps
// Simple AXI-Stream to AXI4-Lite bridge used as a demo for the Verilog parser
module axi_demo #(
parameter integer DATA_WIDTH = 32,
parameter integer ADDR_WIDTH = 12
) (
input wire aclk,
input wire aresetn,
// AXI4-Lite slave interface
input wire [ADDR_WIDTH-1:0] s_axil_awaddr,
input wire [2:0] s_axil_awprot,
input wire s_axil_awvalid,
output wire s_axil_awready,
input wire [31:0] s_axil_wdata,
input wire [3:0] s_axil_wstrb,
input wire s_axil_wvalid,
output wire s_axil_wready,
output wire [1:0] s_axil_bresp,
output wire s_axil_bvalid,
input wire s_axil_bready,
input wire [ADDR_WIDTH-1:0] s_axil_araddr,
input wire [2:0] s_axil_arprot,
input wire s_axil_arvalid,
output wire s_axil_arready,
output wire [31:0] s_axil_rdata,
output wire [1:0] s_axil_rresp,
output wire s_axil_rvalid,
input wire s_axil_rready,
// AXI4-Stream sink
input wire [63:0] s_axis_tdata,
input wire [7:0] s_axis_tkeep,
input wire s_axis_tvalid,
output wire s_axis_tready,
input wire s_axis_tlast,
// AXI4-Stream source
output wire [63:0] m_axis_tdata,
output wire [7:0] m_axis_tkeep,
output wire m_axis_tvalid,
input wire m_axis_tready,
output wire m_axis_tlast,
input wire enable,
output wire [3:0] status,
output wire irq
);
endmodule
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#import "../src/lib.typ": * #import "@preview/circuiteria:0.2.1": *
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
Binary file not shown.
-286
View File
@@ -1,286 +0,0 @@
#import "../src/lib.typ": *
#import "/src/cetz.typ": draw
#set text(font: "Source Sans 3")
#set page(width: auto, height: auto, margin: .5cm)
#circuit({
element.block(
size: (1.5, 2.2),
id: "PCBuf",
fill: util.colors.orange,
ports: (
west: "PCNext",
north: (id: "CLK", clock: true),
east: "PC",
south: (("EN", "EN"),)
)
)
wire.stub("PCBuf.CLK", name: "CLK")
wire.stub("PCBuf.EN", name: "PCWrite")
element.multiplexer(
pos: (
3, (align: "in0", with: "PCBuf.PC")
),
size: (1, 2),
id: "AdrSrc-MP",
fill: util.colors.orange,
entries: 2
)
wire.wire(
"PCBuf.PC",
"AdrSrc-MP.in0",
id: "wPCBuf-InstDataMgr",
name: "PC",
bus: true
)
wire.stub("AdrSrc-MP.north", side: "north", name: "AdrSrc")
element.block(
pos: (
6, (align: "A", with: "AdrSrc-MP.out")
),
size: (3, 4),
id: "InstDataMgr",
fill: util.colors.yellow,
ports: (
west: (
("A", "A"),
("WD", "WD")
),
north: (
(id: "CLK", clock: true),
(id: "WE", name: "WE", vertical: true),
(id: "IRWrite", name: "IRWrite", vertical: true)
),
east: (
("Instr", "Instr."),
("RD", "RD")
)
),
ports-margins: (
west: (30%, 0%),
east: (40%, 0%)
)
)
wire.wire(
"AdrSrc-MP.out",
"InstDataMgr.A",
id: "wAdrSrcMP-InstDataMgr",
name: (none, "Adr"),
bus: true
)
wire.stub("InstDataMgr.CLK", name: "CLK")
wire.stub("InstDataMgr.WE")
wire.stub("InstDataMgr.IRWrite")
wire.stub("InstDataMgr.WD")
element.block(
pos: (
15, (align: "WD3", with: "InstDataMgr.RD")
),
size: (3, 4),
id: "RegFile",
fill: util.colors.pink,
ports: (
west: (
("A1", "A1"),
("A2", "A2"),
("A3", "A3"),
("WD3", "WD3"),
),
north: (
(id: "CLK", clock: true),
(id: "WE3", name: "WE3", vertical: true)
),
east: (
("RD1", "RD1"),
("RD2", "RD2"),
)
),
ports-margins: (
east: (20%, 20%)
)
)
wire.stub("RegFile.CLK", name: "CLK")
wire.stub("RegFile.WE3", name: "Regwrite", name-offset: 0.6)
wire.stub("RegFile.A2")
wire.stub("RegFile.RD2")
element.extender(
pos: (15, -3.5),
size: (3, 1),
id: "Extender",
fill: util.colors.green
)
wire.wire(
"Extender.north",
(18, -2),
id: "wExtender-ImmSrc",
style: "zigzag",
zigzag-ratio: 0%,
name: (none, "ImmSrc"),
bus: true
)
let mid = ("InstDataMgr.east", 50%, "RegFile.west")
wire.wire(
"InstDataMgr.Instr",
(vertical: (), horizontal: mid),
id: "wInstDataMgr-Bus",
name: ("Instr", none),
bus: true
)
wire.wire(
(v => (v.at(0), -3.5), mid),
(horizontal: (), vertical: (0, 3.5)),
id: "wBus",
bus: true
)
wire.wire(
"RegFile.A1",
(horizontal: mid, vertical: ()),
id: "wBus-RegFile-A1",
name: (none, "RS1"),
slice: (19, 15),
reverse: true,
bus: true
)
wire.wire(
"RegFile.A3",
(horizontal: mid, vertical: ()),
id: "wBus-RegFile-A3",
name: (none, "RD"),
slice: (11, 7),
reverse: true,
bus: true
)
wire.wire(
"Extender.in",
(horizontal: mid, vertical: ()),
id: "wBus-Extender",
slice: (31, 7),
reverse: true,
bus: true
)
element.alu(
pos: (
22, (align: "in1", with: "RegFile.RD1")
),
size: (1, 2),
id: "ALU",
fill: util.colors.purple
)
wire.wire(
"RegFile.RD1",
"ALU.in1",
id: "wRegFile-ALU",
name: ("A", "SrcA"),
bus: true
)
wire.stub("ALU.north", side: "north")
element.block(
pos: (
26, (align: "in", with: "ALU.out")
),
size: (1.5, 2),
id: "OutBuf",
fill: util.colors.orange,
ports: (
west: "in",
north: (id: "CLK", clock: true),
east: "out"
)
)
wire.stub("OutBuf.CLK", name: "CLK")
wire.wire(
"ALU.out",
"OutBuf.in",
id: "wALU-OutBuf",
name: "ALUResult",
bus: true
)
element.multiplexer(
pos: (
30, (align: "in0", with: "OutBuf.out")
),
size: (1, 2.5),
id: "Res-MP",
fill: util.colors.orange,
entries: 3
)
wire.stub("Res-MP.north", side: "north", name: "ResultSrc")
wire.stub("Res-MP.in2")
wire.wire(
"OutBuf.out",
"Res-MP.in0",
id: "wOutBuf-ResMP",
name: "ALUOut",
bus: true
)
wire.wire(
"Extender.out",
"ALU.in2",
id: "wExt-ALU",
name: ("ImmExt", "SrcB"),
bus: true,
style: "zigzag",
zigzag-ratio: 60%
)
wire.wire(
"InstDataMgr.RD",
"Res-MP.in1",
id: "wInstDataMgr-ResMP",
style: "dodge",
dodge-y: -4,
dodge-sides: ("east", "west"),
name: ("Data", none),
bus: true
)
wire.wire(
"Res-MP.out",
"AdrSrc-MP.in1",
id: "wResMP-AdrSrc",
style: "dodge",
dodge-y: -5,
dodge-sides: ("east", "west"),
dodge-margins: (0.5, 1),
bus: true
)
wire.wire(
"Res-MP.out",
"RegFile.WD3",
id: "wResMP-RegFile",
style: "dodge",
dodge-y: -5,
dodge-sides: ("east", "west"),
dodge-margins: (0.5, 1),
bus: true
)
wire.wire(
"Res-MP.out",
"PCBuf.PCNext",
id: "wResMP-PCBuf",
style: "dodge",
dodge-y: -5,
dodge-sides: ("east", "west"),
dodge-margins: (0.5, 1.5),
name: (none, "PCNext"),
bus: true
)
wire.intersection("wResMP-RegFile.dodge-end", radius: .2)
wire.intersection("wResMP-AdrSrc.dodge-end", radius: .2)
})
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 160 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#import "../src/lib.typ": circuit, element, util, wire #import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 160 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
#import "../src/lib.typ": circuit, element, util, wire #import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

+1 -2
View File
@@ -1,5 +1,4 @@
#import "/src/cetz.typ": draw #import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#import "../src/lib.typ": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 195 KiB

+1 -1
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "../src/lib.typ": * #import "@preview/circuiteria:0.2.1": *
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 274 KiB

+1 -1
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "../src/lib.typ": * #import "@preview/circuiteria:0.2.1": *
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

+2 -2
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw, vector #import "/src/cetz.typ": draw
#import "../src/lib.typ": * #import "@preview/circuiteria:0.2.1": *
#set page(width: auto, height: auto, margin: .5cm) #set page(width: auto, height: auto, margin: .5cm)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+97
View File
@@ -0,0 +1,97 @@
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
#set page(width: auto, height: auto, margin: .5cm)
#circuit({
element.iec-gate-buf(
x: 0,
y: 0,
w: 2,
h: 2,
id: "iec-buf",
inputs: 1,
)
wire.stub("iec-buf-port-in0", "west")
element.iec-gate-not(
x: 3,
y: 0,
w: 2,
h: 2,
id: "iec-not",
inputs: 1,
)
wire.stub("iec-not-port-in0", "west")
element.iec-gate-and(
id: "iec-and",
x: 0,
y: -3,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-and-port-in" + str(i), "west")
}
element.iec-gate-nand(
id: "iec-nand",
x: 3,
y: -3,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-nand-port-in" + str(i), "west")
}
element.iec-gate-or(
id: "iec-or",
x: 0,
y: -6,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-or-port-in" + str(i), "west")
}
element.iec-gate-nor(
id: "iec-nor",
x: 3,
y: -6,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-nor-port-in" + str(i), "west")
}
element.iec-gate-xor(
id: "iec-xor",
x: 0,
y: -9,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-xor-port-in" + str(i), "west")
}
element.iec-gate-xnor(
id: "iec-nxor",
x: 3,
y: -9,
w: 2,
h: 2,
inputs: 2,
)
for i in range(2) {
wire.stub("iec-nxor-port-in" + str(i), "west")
}
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+12
View File
@@ -0,0 +1,12 @@
#import "/src/lib.typ" as circuiteria: circuit, util, verilog
#set page(width: auto, height: auto, margin: .5cm)
#circuit({
verilog.block(
read("axi_demo.v"),
x: 0, y: 0,
// fill: util.colors.yellow,
// radius: 0.05em
)
})
BIN
View File
Binary file not shown.
+27 -3
View File
@@ -105,7 +105,7 @@ This package provides a way to make beautiful block circuit diagrams using the C
Simply import Circuiteria and call the `circuit` function: Simply import Circuiteria and call the `circuit` function:
#pad(left: 1em)[```typ #pad(left: 1em)[```typ
#import "@preview/circuiteria:0.2.0" #import "@preview/circuiteria:0.2.1"
#circuiteria.circuit({ #circuiteria.circuit({
import circuiteria: * import circuiteria: *
... ...
@@ -195,7 +195,12 @@ If you have installed Circuiteria directly in your project, import #link("src/li
read("src/elements/logic/and.typ") + "\n" + read("src/elements/logic/and.typ") + "\n" +
read("src/elements/logic/buf.typ") + "\n" + read("src/elements/logic/buf.typ") + "\n" +
read("src/elements/logic/or.typ") + "\n" + read("src/elements/logic/or.typ") + "\n" +
read("src/elements/logic/xor.typ"), read("src/elements/logic/xor.typ") + "\n" +
read("src/elements/logic/iec_gate.typ") + "\n" +
read("src/elements/logic/iec_and.typ") + "\n" +
read("src/elements/logic/iec_buf.typ") + "\n" +
read("src/elements/logic/iec_or.typ") + "\n" +
read("src/elements/logic/iec_xor.typ"),
name: "gates", name: "gates",
old-syntax: true, old-syntax: true,
scope: ( scope: (
@@ -210,4 +215,23 @@ If you have installed Circuiteria directly in your project, import #link("src/li
) )
) )
#tidy.show-module(gates-docs, sort-functions: false) #tidy.show-module(gates-docs, sort-functions: false)
#pagebreak()
#let verilog-docs = tidy.parse-module(
read("src/verilog.typ"),
name: "verilog",
old-syntax: true,
scope: (
element: element,
circuit: circuit,
draw: draw,
wire: wire,
tidy: tidy,
examples: examples,
doc-ref: doc-ref
)
)
#tidy.show-module(verilog-docs, sort-functions: false)
+2 -49
View File
@@ -9,52 +9,5 @@
/// - length (length, ratio): Optional base unit /// - length (length, ratio): Optional base unit
/// -> none /// -> none
#let circuit(body, length: 2em) = { #let circuit(body, length: 2em) = {
let next-id = 0 canvas(length: length, body)
let elements = (:) }
let ids = ()
for e in body {
if type(e) == dictionary and "id" in e {
ids.push(e.id)
}
}
for element in body {
let internal = type(element) == dictionary and "id" in element
let eid = if internal {element.id} else {auto}
if eid == auto {
while str(next-id) in ids {
next-id += 1
}
eid = str(next-id)
ids.push(eid)
if internal {
element.id = eid
}
next-id += 1
}
if eid in elements {
panic("An element with the id '" + eid + "' already exists. Please use a different id")
}
elements.insert(eid, element)
}
for element in elements.values() {
if type(element) == dictionary and "pre-process" in element {
elements = (element.pre-process)(elements, element)
assert(
type(elements) == dictionary,
message: "The `pre-process` method of element '" + element.id + "' did not return the elements dictionary"
)
}
}
canvas(length: length, {
for element in elements.values() {
if type(element) == dictionary and "draw" in element {
(element.draw)(element)
} else {
(element,)
}
}
})
}
+5
View File
@@ -11,5 +11,10 @@
#import "elements/logic/or.typ": gate-or, gate-nor #import "elements/logic/or.typ": gate-or, gate-nor
#import "elements/logic/xor.typ": gate-xor, gate-xnor #import "elements/logic/xor.typ": gate-xor, gate-xnor
#import "elements/logic/buf.typ": gate-buf, gate-not #import "elements/logic/buf.typ": gate-buf, gate-not
#import "elements/logic/iec_gate.typ": iec-gate
#import "elements/logic/iec_and.typ": iec-gate-and, iec-gate-nand
#import "elements/logic/iec_buf.typ": iec-gate-buf, iec-gate-not
#import "elements/logic/iec_or.typ": iec-gate-or, iec-gate-nor
#import "elements/logic/iec_xor.typ": iec-gate-xor, iec-gate-xnor
#import "elements/group.typ": group #import "elements/group.typ": group
+59 -37
View File
@@ -1,40 +1,45 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "element.typ" #import "element.typ"
#import "ports.typ": add-port #import "ports.typ": add-port
#import "../util.typ"
#let draw-shape(elmt, bounds) = {
let tl = bounds.tl
let tr = bounds.tr
let bl = bounds.bl
let br = bounds.br
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
let p0 = tl let p0 = tl
let p1 = util.lerp(tr, 10%, br) let p1 = (tr, 10%, br)
let p2 = util.lerp(tr, 90%, br) let p2 = (tr, 90%, br)
let p3 = bl let p3 = bl
let p4 = util.lerp(tl, 55%, bl) let p4 = (tl, 55%, bl)
let p5 = util.lerp(tl, 50%, br) let p5 = (tl, 50%, br)
let p6 = util.lerp(tl, 45%, bl) let p6 = (tl, 45%, bl)
let bounds2 = element.complete-bounds(elmt, (
tl: p0,
tr: p1,
br: p2,
bl: p3
))
let f = { let f1 = draw.group(name: id, {
draw.line(
p0, p1, p2, p3, p4, p5, p6, draw.merge-path(
fill: elmt.fill, inset: 0.5em,
stroke: elmt.stroke, fill: fill,
close: true 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))) 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, bounds2) return (f, tl, tr, br, bl)
} }
/// Draws an ALU with two inputs /// Draws an ALU with two inputs
@@ -42,7 +47,18 @@
/// #examples.alu /// #examples.alu
/// For parameters description, see #doc-ref("element.elmt") /// For parameters description, see #doc-ref("element.elmt")
#let alu( #let alu(
..args x: none,
y: none,
w: none,
h: none,
name: none,
name-anchor: "center",
fill: none,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
) = { ) = {
let ports = ( let ports = (
west: ( west: (
@@ -54,18 +70,24 @@
) )
) )
return element.elmt( element.elmt(
draw-shape: draw-shape, draw-shape: draw-shape,
x: x,
y: y,
w: w,
h: h,
name: name,
name-anchor: name-anchor,
ports: ports, ports: ports,
ports-pos: ( fill: fill,
west: ( stroke: stroke,
in1: l => l * 0.225, id: id,
in2: l => l * 0.775, auto-ports: false,
), ports-y: (
east: ( in1: (h) => {h * 0.225},
out: l => l * 0.5 in2: (h) => {h * 0.775},
) out: (h) => {h * 0.5}
), ),
..args debug: debug
) )
} }
+38 -12
View File
@@ -1,25 +1,51 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "element.typ" #import "element.typ"
#let draw-shape(elmt, bounds) = { #let draw-shape(id, tl, tr, br, bl, fill, stroke, radius: 0.5em) = {
let f = draw.rect( let f = draw.rect(
radius: 0.5em, radius: radius,
inset: 0.5em, inset: 0.5em,
fill: elmt.fill, fill: fill,
stroke: elmt.stroke, stroke: stroke,
bounds.bl, bounds.tr name: id,
bl, tr
) )
return (f, bounds) return (f, tl, tr, br, bl)
} }
/// Draws a block element /// Draws a block element
/// ///
/// #examples.block /// #examples.block
/// For parameters description, see #doc-ref("element.elmt") /// For other parameters description, see #doc-ref("element.elmt")
/// - radius (number, length, ratio, dictionary): The corner radius of the block. See CeTZ documentation for more information
#let block( #let block(
..args x: none,
y: none,
w: none,
h: none,
name: none,
name-anchor: "center",
ports: (),
ports-margins: (),
fill: none,
radius: 0.5em,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
) = element.elmt( ) = element.elmt(
cls: "block", draw-shape: draw-shape.with(radius: radius),
draw-shape: draw-shape, x: x,
..args 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
)
+89 -267
View File
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw, coordinate, matrix, vector #import "/src/cetz.typ": draw, coordinate
#import "ports.typ": add-ports, add-port, get-port-pos, get-port-idx #import "ports.typ": add-ports, add-port
#import "../util.typ" #import "../util.typ"
#let find-port(ports, id) = { #let find-port(ports, id) = {
@@ -10,234 +10,13 @@
} }
} }
} }
panic("Could not find port with id '" + str(id) + "' in ports " + repr(ports)) panic("Could not find port with id " + str(id))
} }
#let local-to-global(origin, u, v, points) = { #let default-draw-shape(id, tl, tr, br, bl, fill, stroke) = {
return points-real = points.map(p => { return ({}, tl, tr, br, bl)
let (pu, pv) = p
return vector.add(
origin,
vector.add(
vector.scale(u, pu),
vector.scale(v, pv)
)
)
})
} }
#let default-draw-shape(elmt, bounds) = {
return ({}, bounds)
}
#let default-pre-process(elements, element) = {
return elements
}
#let resolve-offset(ctx, offset, from, axis) = {
let (ctx, pos) = coordinate.resolve(
ctx,
(rel: offset, to: from)
)
return pos.at(axis)
}
#let resolve-align(ctx, elmt, bounds, align, with, axis) = {
let (align-side, i) = find-port(elmt.ports, align)
let margins = (0%, 0%)
if align-side in elmt.ports-margins {
margins = elmt.ports-margins.at(align-side)
}
let parallel-sides = (
("north", "south"),
("west", "east")
).at(axis)
let ortho-sides = (
("west", "east"),
("north", "south")
).at(axis)
let dl
let start-margin
let len = elmt.size.at(axis)
if align-side in parallel-sides {
let used-pct = 100% - margins.at(0) - margins.at(1)
let used-len = len * used-pct / 100%
start-margin = len * margins.at(0) / 100%
//dl = used-len * (i + 1) / (elmt.ports.at(align-side).len() + 1)
dl = get-port-pos(elmt, bounds, align-side, align, get-port-idx(elmt, align, side: align-side))
/*if not elmt.auto-ports {
start-margin = 0
dl = elmt.ports-pos.at(align)(len)
}*/
} else if align-side == ortho-sides.first() {
dl = 0
start-margin = 0
} else {
dl = len
start-margin = 0
}
if axis == 1 {
dl = len - dl
}
let (ctx, with-pos) = coordinate.resolve(ctx, with)
return with-pos.at(axis) - dl + start-margin
}
#let resolve-coordinate(ctx, elmt, bounds, coord, axis) = {
if type(coord) == dictionary {
let offset = coord.at("offset", default: none)
let from = coord.at("from", default: none)
let align = coord.at("align", default: none)
let with = coord.at("with", default: none)
if none not in (offset, from) {
if type(offset) != array {
let a = (0, 0)
a.at(axis) = offset
offset = a
}
return resolve-offset(ctx, offset, from, axis)
} else if none not in (align, with) {
return resolve-align(ctx, elmt, bounds, align, with, axis)
} else {
panic("Dictionnary must either provide both 'offset' and 'from', or 'align' and 'with'")
}
}
if type(coord) not in (int, float, length) {
panic("Invalid " + "xy".at(axis) + " coordinate: " + repr(coord))
}
return coord
}
#let complete-bounds(elmt, bounds) = {
let b = bounds
bounds += (
center: (
(b.br.at(0) + b.tl.at(0))/2,
(b.br.at(1) + b.tl.at(1))/2
),
b: (
(b.br.at(0) + b.bl.at(0))/2,
(b.br.at(1) + b.bl.at(1))/2
),
t: (
(b.tr.at(0) + b.tl.at(0))/2,
(b.tr.at(1) + b.tl.at(1))/2
),
l: (
(b.bl.at(0) + b.tl.at(0))/2,
(b.bl.at(1) + b.tl.at(1))/2
),
r: (
(b.br.at(0) + b.tr.at(0))/2,
(b.br.at(1) + b.tr.at(1))/2
),
sides: (
north: (bounds.tl, bounds.tr),
south: (bounds.bl, bounds.br),
west: (bounds.tl, bounds.bl),
east: (bounds.tr, bounds.br),
),
lengths: (
north: (bounds.tr.at(0) - bounds.tl.at(0)),
south: (bounds.br.at(0) - bounds.bl.at(0)),
west: (bounds.tl.at(1) - bounds.bl.at(1)),
east: (bounds.tr.at(1) - bounds.br.at(1)),
),
ports: (:)
)
for (side, props) in bounds.sides.pairs() {
let props2 = props
if side in elmt.ports-margins {
let (pt0, pt1) = props
let margins = elmt.ports-margins.at(side)
let a = util.lerp(pt0, margins.at(0), pt1)
let b = util.lerp(pt0, 100% - margins.at(1), pt1)
props2 = (a, b)
}
bounds.ports.insert(side, props2)
}
return bounds
}
#let make-bounds(elmt, x, y, w, h) = {
let w2 = w / 2
let h2 = h / 2
let bounds = (
bl: (x, y),
tl: (x, y + h),
tr: (x + w, y + h),
br: (x + w, y),
)
return complete-bounds(elmt, bounds)
}
#let render(draw-shape, elmt) = draw.group(name: elmt.id, ctx => {
let width = elmt.size.first()
let height = elmt.size.last()
let x = elmt.pos.first()
let y = elmt.pos.last()
let bounds = make-bounds(elmt, 0, 0, width, height)
x = resolve-coordinate(ctx, elmt, bounds, x, 0)
y = resolve-coordinate(ctx, elmt, bounds, y, 1)
bounds = make-bounds(elmt, x, y, width, height)
// Workaround because CeTZ needs to have all draw functions in the body
let func = {}
let res = draw-shape(elmt, bounds)
assert(
type(res) == array and res.len() == 2,
message: "The drawing function of element '" + elmt.id + "' did not return a function and new bounds"
)
(func, bounds) = res
if type(func) == function {
func = (func,)
}
assert(
type(bounds) == dictionary,
message: "The drawing function of element '" + elmt.id + "' did not return the correct bounds dictionary"
)
func
draw.anchor("north", bounds.t)
draw.anchor("south", bounds.b)
draw.anchor("west", bounds.l)
draw.anchor("east", bounds.r)
draw.anchor("north-west", bounds.tl)
draw.anchor("north-east", bounds.tr)
draw.anchor("south-east", bounds.br)
draw.anchor("south-west", bounds.bl)
if elmt.name != none {
draw.content(
(name: elmt.id, anchor: elmt.name-anchor),
anchor: if elmt.name-anchor in util.valid-anchors {elmt.name-anchor} else {"center"},
padding: 0.5em,
align(center)[*#elmt.name*]
)
}
add-ports(elmt, bounds)
if elmt.debug.bounds {
draw.line(
bounds.tl, bounds.tr, bounds.br, bounds.bl,
stroke: red,
close: true
)
}
})
/// Draws an element /// Draws an element
/// - draw-shape (function): Draw function /// - draw-shape (function): Draw function
/// - x (number, dictionary): The x position (bottom-left corner). /// - x (number, dictionary): The x position (bottom-left corner).
@@ -254,6 +33,7 @@
/// - `id` (`str`): (Required) Port id /// - `id` (`str`): (Required) Port id
/// - `name` (`str`): Optional name displayed *in* the block /// - `name` (`str`): Optional name displayed *in* the block
/// - `clock` (`bool`): Whether it is a clock port (triangle symbol) /// - `clock` (`bool`): Whether it is a clock port (triangle symbol)
/// - `invert` (`bool`): Whether it is an active-low port (bubble symbol)
/// - `vertical` (`bool`): Whether the name should be drawn vertically /// - `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) /// - 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 /// - fill (none, color): Fill color
@@ -266,59 +46,101 @@
/// Supported fields include: /// Supported fields include:
/// - `ports`: if true, shows dots on all ports of the element /// - `ports`: if true, shows dots on all ports of the element
#let elmt( #let elmt(
cls: "element",
draw-shape: default-draw-shape, draw-shape: default-draw-shape,
pre-process: default-pre-process, x: none,
pos: (0, 0), y: none,
size: (1, 1), w: none,
h: none,
name: none, name: none,
name-anchor: "center", name-anchor: "center",
ports: (:), ports: (:),
ports-margins: (:), ports-margins: (:),
fill: none, fill: none,
stroke: black + 1pt, stroke: black + 1pt,
id: auto, id: "",
ports-pos: auto, auto-ports: true,
ports-y: (:),
debug: ( debug: (
bounds: false,
ports: false ports: false
), )
extra: (:) ) = draw.get-ctx(ctx => {
) = { let width = w
for (key, side-ports) in ports.pairs() { let height = h
if type(side-ports) == str {
side-ports = ((id: side-ports),) let x = x
} else if type(side-ports) == dictionary { let y = y
side-ports = (side-ports,) if x == none { panic("Parameter x must be set") }
if y == none { panic("Parameter y must be set") }
if w == none { panic("Parameter w must be set") }
if h == none { panic("Parameter h must be set") }
if (type(x) == dictionary) {
let offset = x.rel
let to = x.to
let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to))
x = to-pos.at(0)
}
if (type(y) == dictionary) {
let from = y.from
let to = y.to
let (to-side, i) = find-port(ports, to)
let margins = (0%, 0%)
if to-side in ports-margins {
margins = ports-margins.at(to-side)
} }
for (i, port) in side-ports.enumerate() { let dy
if type(port) == array { let top-margin
side-ports.at(i) = ( if to-side in ("east", "west") {
id: port.at(0, default: ""), let used-pct = 100% - margins.at(0) - margins.at(1)
name: port.at(1, default: "") let used-height = height * used-pct / 100%
) top-margin = height * margins.at(0) / 100%
} else if type(port) == str {
side-ports.at(i) = (id: port) dy = used-height * (i + 1) / (ports.at(to-side).len() + 1)
if not auto-ports {
top-margin = 0
dy = ports-y.at(to)(height)
} }
} else if to-side == "north" {
dy = 0
top-margin = 0
} else if to-side == "south" {
dy = height
top-margin = 0
} }
ports.at(key) = side-ports
let (ctx, from-pos) = coordinate.resolve(ctx, from)
y = from-pos.at(1) + dy - height + top-margin
} }
return (( let tl = (x, y + height)
cls: cls, let tr = (x + width, y + height)
id: id, let br = (x + width, y)
draw: render.with(draw-shape), let bl = (x, y)
pre-process: pre-process,
pos: pos, // Workaround because CeTZ needs to have all draw functions in the body
size: size, let func = {}
name: name, (func, tl, tr, br, bl) = draw-shape(id, tl, tr, br, bl, fill, stroke)
name-anchor: name-anchor, func
ports: ports,
ports-margins: ports-margins, if (name != none) {
fill: fill, draw.content(
stroke: stroke, (name: id, anchor: name-anchor),
ports-pos: ports-pos, anchor: if name-anchor in util.valid-anchors {name-anchor} else {"center"},
debug: debug padding: 0.5em,
) + extra,) align(center)[*#name*]
} )
}
if auto-ports {
add-ports(
id,
tl, tr, br, bl,
ports,
ports-margins,
debug: debug.ports
)
}
})
+75 -33
View File
@@ -1,25 +1,41 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "element.typ" #import "element.typ"
#import "ports.typ": add-port #import "ports.typ": add-port
#import "../util.typ"
#let draw-shape(elmt, bounds) = { #let draw-shape(id, tl, tr, br, bl, fill, stroke, h-ratio: 75%, align-out: true) = {
let ratio = elmt.l-ratio / 100% let (x, y) = bl
let tl2 = util.lerp(bounds.bl, elmt.l-ratio, bounds.tl) let (width, height) = (tr.at(0) - x, tr.at(1) - y)
let bounds2 = element.complete-bounds(elmt, (
tl: tl2,
tr: bounds.tr,
br: bounds.br,
bl: bounds.bl,
))
let f = draw.line( let ratio = h-ratio / 100%
bounds2.tl, bounds2.tr, bounds2.br, bounds2.bl,
fill: elmt.fill, tl = (x, y + height * ratio)
stroke: elmt.stroke, let tr2 = (x + width, y + height * ratio)
close: true let br = (x + width, y)
)
return (f, bounds2) 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 /// Draws a bit extender
@@ -29,28 +45,54 @@
/// - h-ratio (ratio): The height ratio of the left side relative to the full height /// - 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 /// - align-out (bool): If true, the output and input ports are aligned, otherwise, the output port is centered on the right side
#let extender( #let extender(
l-ratio: 75%, 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, align-out: true,
..args debug: (
ports: false
)
) = { ) = {
let ports = ( let ports = (
west: ((id: "in"),), west: (
east: ((id: "out"),) (id: "in"),
),
east: (
(id: "out"),
)
) )
let out-pct = if align-out {l-ratio / 2} else {50%} let out-pct = if align-out {h-ratio / 2} else {50%}
let ports-pos = ( let ports-y = (
"east": (l, i) => {l - l * (out-pct / 100%)} "in": (h) => {h - h * (h-ratio / 200%)},
"out": (h) => {h - h * (out-pct / 100%)}
) )
return element.elmt( element.elmt(
cls: "extender", draw-shape: draw-shape.with(h-ratio: h-ratio, align-out: align-out),
draw-shape: draw-shape, x: x,
y: y,
w: w,
h: h,
name: name,
name-anchor: name-anchor,
ports: ports, ports: ports,
ports-pos: ports-pos, auto-ports: false,
extra: ( ports-y: ports-y,
l-ratio: l-ratio, fill: fill,
align-out: align-out stroke: stroke,
), id: id,
..args 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)
} }
+6
View File
@@ -63,6 +63,12 @@
if to == "out" { if to == "out" {
dy = height / 2 dy = height / 2
} else { } else {
let all-ports = range(inputs).map(i => "in" + str(i))
let i = all-ports.position(p => p == to)
assert(
i != none,
message: "Unknown port '" + y.to + "' must be one of " + repr(all-ports)
)
dy = height * (i + 0.5) / inputs dy = height * (i + 0.5) / inputs
} }
+70
View File
@@ -0,0 +1,70 @@
#import "/src/cetz.typ": draw
// #import "iec_gate.typ" as iec-gate
#import "iec_gate.typ" as iec-gate
/// Draws an IEC-AND gate. This function is also available as `element.iec-gate-and()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-and
#let iec-gate-and(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
),
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: $amp$,
)
}
/// Draws an IEC-NAND gate. This function is also available as `element.iec-gate-nand()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-nand
#let iec-gate-nand(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
),
) = {
iec-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,
)
}
+68
View File
@@ -0,0 +1,68 @@
#import "/src/cetz.typ": draw
#import "iec_gate.typ" as iec-gate
/// Draws an IEC buffer gate. This function is also available as `element.iec-gate-buf()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-buf
#let iec-gate-buf(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false,
),
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: "1",
)
}
/// Draws an IEC NOT gate. This function is also available as `element.iec-gate-not()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-not
#let iec-gate-not(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false,
),
) = {
iec-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,
)
}
+125
View File
@@ -0,0 +1,125 @@
#import "/src/cetz.typ": draw, coordinate
#import "../ports.typ": add-ports, add-port
#import "../element.typ"
#let default-draw-shape(id, tl, tr, br, bl, fill, stroke, symbol) = {
let shapes = draw.rect(
inset: 0.5em,
fill: fill,
stroke: stroke,
name: id,
bl, tr
)
shapes += draw.content(
id + ".center",
[*$ symbol $*]
)
return (shapes, tl, tr, br, bl)
}
/// Draws a logic gate. This function is also available as `element.iec-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")
/// - symbol (str): The symbol to display at the center of the gate
#let iec-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
),
symbol: "",
) = 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, symbol)
func
let space = 100% / inputs
for i in range(inputs) {
let pct = (i + 0.5) * space
let port-pos = (tl, pct, bl)
let port-name = "in" + str(i)
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
)
})
+67
View File
@@ -0,0 +1,67 @@
#import "/src/cetz.typ": draw
#import "iec_gate.typ" as iec-gate
/// Draws an IEC-OR gate. This function is also available as `element.iec-gate-or()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-or
#let iec-gate-or(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: $>= 1$,
)
}
/// Draws an IEC-NOR gate. This function is also available as `element.iec-gate-nor()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-nor
#let iec-gate-nor(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-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
)
}
+67
View File
@@ -0,0 +1,67 @@
#import "/src/cetz.typ": draw
#import "iec_gate.typ" as iec-gate
/// Draws an IEC-XOR gate. This function is also available as `element.iec-gate-xor()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-xor
#let iec-gate-xor(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-gate.iec-gate(
x: x,
y: y,
w: w,
h: h,
inputs: inputs,
fill: fill,
stroke: stroke,
id: id,
inverted: inverted,
debug: debug,
symbol: $= 1$,
)
}
/// Draws an IEC-XNOR gate. This function is also available as `element.iec-gate-xnor()`
///
/// For parameters, see #doc-ref("gates.iec-gate")
/// #examples.iec-gate-xnor
#let iec-gate-xnor(
x: none,
y: none,
w: none,
h: none,
inputs: 2,
fill: none,
stroke: black + 1pt,
id: "",
inverted: (),
debug: (
ports: false
)
) = {
iec-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
)
}
+59 -34
View File
@@ -3,24 +3,29 @@
#import "element.typ" #import "element.typ"
#import "ports.typ": add-port #import "ports.typ": add-port
#let draw-shape(elmt, bounds) = { #let draw-shape(id, tl, tr, br, bl, fill, stroke, h-ratio: 60%) = {
let margin = (100% - elmt.l-ratio) / 2 let margin = (100% - h-ratio) / 2
let tr2 = util.lerp(bounds.tr, margin, bounds.br) let tr2 = (tr, margin, br)
let br2 = util.lerp(bounds.br, margin, bounds.tr) let br2 = (br, margin, tr)
let bounds2 = element.complete-bounds(elmt, ( let f = draw.group(name: id, {
tl: bounds.tl, draw.merge-path(
bl: bounds.bl, inset: 0.5em,
tr: tr2, fill: fill,
br: br2, stroke: stroke,
)) close: true,
let f = draw.line( draw.line(tl, tr2, br2, bl)
bounds2.tl, bounds2.tr, bounds2.br, bounds2.bl, )
close: true, draw.anchor("north", (tl, 50%, tr2))
fill: elmt.fill, draw.anchor("south", (bl, 50%, br2))
stroke: elmt.stroke 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, bounds2) return (f, tl, tr, br, bl)
} }
/// Draws a multiplexer /// Draws a multiplexer
@@ -30,44 +35,64 @@
/// - 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. /// - 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 /// - h-ratio (ratio): The height ratio of the right side relative to the full height
#let multiplexer( #let multiplexer(
x: none,
y: none,
w: none,
h: none,
name: none,
name-anchor: "center",
entries: 2, entries: 2,
l-ratio: 60%, h-ratio: 60%,
..args fill: none,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
) = { ) = {
let in-ports = () let ports = ()
let ports-pos = ( let ports-y = (
"east": auto, out: (h) => {h * 0.5}
) )
if (type(entries) == int) { if (type(entries) == int) {
let nbits = calc.ceil(calc.log(entries, base: 2)) let nbits = calc.ceil(calc.log(entries, base: 2))
for i in range(entries) { for i in range(entries) {
let bits = util.lpad(str(i, base: 2), nbits) let bits = util.lpad(str(i, base: 2), nbits)
in-ports.push((id: "in" + str(i), name: bits)) ports.push((id: "in" + str(i), name: bits))
} }
} else { } else {
for (i, port) in entries.enumerate() { for (i, port) in entries.enumerate() {
in-ports.push((id: "in" + str(i), name: port)) ports.push((id: "in" + str(i), name: port))
} }
} }
let n = in-ports.len() let space = 100% / ports.len()
ports-pos.insert("west", (l, i) => {l * (i + 0.5) / n}) let l = ports.len()
for (i, port) in ports.enumerate() {
ports-y.insert(port.id, (h) => {h * (i + 0.5) / l})
}
element.elmt( element.elmt(
cls: "multiplexer", draw-shape: draw-shape.with(h-ratio: h-ratio),
draw-shape: draw-shape, x: x,
ports: (west: in-ports, east: ((id: "out"),)), y: y,
ports-pos: ports-pos, w: w,
extra: (l-ratio: l-ratio), h: h,
..args 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() { for (i, port) in ports.enumerate() {
let pct = (i + 0.5) * space let pct = (i + 0.5) * space
add-port(id, "west", port, (id+".north-west", pct, id+".south-west")) 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")) add-port(id, "east", (id: "out"), (id+".north-east", 50%, id+".south-east"))
*/
} }
+52 -100
View File
@@ -1,76 +1,6 @@
#import "/src/cetz.typ": draw #import "/src/cetz.typ": draw
#import "../util.typ": rotate-anchor #import "../util.typ": rotate-anchor
#let get-port-side(elmt, port) = {
for (side, ports) in elmt.ports {
for p in ports {
if p.id == port {
return side
}
}
}
panic(
"Unknown port '" + port + "' on element '" + elmt.id + "', could not automatically determine side"
)
}
#let get-port-idx(elmt, port, side: auto) = {
if side == auto {
side = get-port-side(elmt, port)
}
assert(
side in elmt.ports,
message: "No ports on side '" + side + "' of element '" + elmt.id + "'"
)
let i = elmt.ports.at(side).position(p => p.id == port)
assert(
i != none,
message: "Could not find port '" + port + "' on side '" + side + "' of element '" + elmt.id + "'"
)
return i
}
#let get-port-pos(elmt, bounds, side, port, port-i) = {
let (pt0, pt1) = bounds.ports.at(side)
let side-len = if side in ("north", "south") {
pt1.at(0) - pt0.at(0)
} else {
pt0.at(1) - pt1.at(1)
}
let offset = if (
elmt.ports-pos == auto or
elmt.ports-pos.at(side, default: auto) == auto
) {
let space = 100% / (elmt.ports.at(side, default: ()).len() + 1)
(port-i + 1) * space
} else {
assert(
side in elmt.ports-pos,
message: "Could not reliably compute port position (missing side)"
)
let side-pos = elmt.ports-pos.at(side)
if type(side-pos) == function {
(side-pos)(side-len, port-i)
} else if type(side-pos) == array {
(side-pos.at(i))(side-len)
} else if type(side-pos) == dictionary {
assert(
port in side-pos,
message: "Could not reliably compute port position (missing port)"
)
(side-pos.at(port))(side-len)
} else {
panic("Could not reliably compute port position (invalid type)")
}
}
if type(offset) == ratio {
offset = offset * side-len / 100%
}
return offset
}
#let add-port( #let add-port(
elmt-id, side, port, pos, elmt-id, side, port, pos,
prev: none, prev: none,
@@ -97,6 +27,21 @@
// TODO: use context or vectors to have the height relative to the width // TODO: use context or vectors to have the height relative to the width
draw.line(prev, pos1, next) draw.line(prev, pos1, next)
} }
if (port.at("invert", default: false)) {
let r = 0.1
let offset
if (side == "north") { offset = ( 0, r) }
else if (side == "east") { offset = ( r, 0) }
else if (side == "south") { offset = ( 0, -r) }
else if (side == "west") { offset = ( -r, 0) }
draw.circle(
(rel: offset, to: pos),
radius: r,
fill: white,
stroke: black + 1pt
)
}
draw.content( draw.content(
pos, pos,
anchor: if name-rotate {rotate-anchor(side)} else {side}, anchor: if name-rotate {rotate-anchor(side)} else {side},
@@ -104,10 +49,12 @@
angle: if name-rotate {90deg} else {0deg}, angle: if name-rotate {90deg} else {0deg},
name name
) )
let id = elmt-id + "-port-" + port.at("id")
if debug { if debug {
draw.circle( draw.circle(
pos, pos,
name: id,
radius: .1, radius: .1,
stroke: none, stroke: none,
fill: red fill: red
@@ -117,48 +64,53 @@
draw.hide(draw.circle( draw.hide(draw.circle(
pos, pos,
radius: 0, radius: 0,
stroke: none stroke: none,
name: id
)) ))
} }
draw.anchor(port.id, pos)
} }
#let add-ports( #let add-ports(
elmt, elmt-id,
bounds tl, tr, br, bl,
ports,
ports-margins,
debug: false
) = { ) = {
let debug = elmt.debug.at("ports", default: false) let sides = (
"north": (tl, tr),
"east": (tr, br),
"south": (bl, br),
"west": (tl, bl)
)
if type(elmt.ports) != dictionary { if type(ports) != dictionary {
return return
} }
for (side, props) in bounds.ports { for (side, props) in sides {
let side-ports = elmt.ports.at(side, default: ()) let side-ports = ports.at(side, default: ())
let space = 100% / (side-ports.len() + 1) let space = 100% / (side-ports.len() + 1)
let (pt0, pt1) = props
let side-len = if side in ("north", "south") {
pt1.at(0) - pt0.at(0)
} else {
pt0.at(1) - pt1.at(1)
}
for (i, port) in side-ports.enumerate() {
let offset = get-port-pos(elmt, bounds, side, port.id, i)
let pos = (pt0, offset, pt1) for (i, port) in side-ports.enumerate() {
let offset-prev = if type(offset) == ratio { let pct = (i + 1) * space
offset - space / 2 let pt0 = props.at(0)
} else { let pt1 = props.at(1)
offset - space * side-len / 200%
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 offset-next = if type(offset) == ratio {
offset + space / 2 let pos = (pt0, pct, pt1)
} else { let pct-prev = (i + 0.5) * space
offset + space * side-len / 200% let pct-next = (i + 1.5) * space
} let pos-prev = (pt0, pct-prev, pt1)
let pos-prev = (pt0, offset-prev, pt1) let pos-next = (pt0, pct-next, pt1)
let pos-next = (pt0, offset-next, pt1)
if port.at("small", default: false) { if port.at("small", default: false) {
pos-prev = (pos, 4pt, pt0) pos-prev = (pos, 4pt, pt0)
@@ -166,7 +118,7 @@
} }
add-port( add-port(
elmt.id, elmt-id,
side, side,
port, port,
pos, pos,
+6 -1
View File
@@ -2,4 +2,9 @@
#import "elements/logic/and.typ": gate-and, gate-nand #import "elements/logic/and.typ": gate-and, gate-nand
#import "elements/logic/or.typ": gate-or, gate-nor #import "elements/logic/or.typ": gate-or, gate-nor
#import "elements/logic/xor.typ": gate-xor, gate-xnor #import "elements/logic/xor.typ": gate-xor, gate-xnor
#import "elements/logic/buf.typ": gate-buf, gate-not #import "elements/logic/buf.typ": gate-buf, gate-not
#import "elements/logic/iec_gate.typ": iec-gate
#import "elements/logic/iec_and.typ": iec-gate-and, iec-gate-nand
#import "elements/logic/iec_or.typ": iec-gate-or, iec-gate-nor
#import "elements/logic/iec_buf.typ": iec-gate-buf, iec-gate-not
#import "elements/logic/iec_xor.typ": iec-gate-xor, iec-gate-xnor
+3 -1
View File
@@ -1,7 +1,9 @@
#let version = version(0, 2, 0) #let version = version(0, 2, 1)
#let name = "circuiteria"
#import "circuit.typ": circuit #import "circuit.typ": circuit
#import "element.typ" #import "element.typ"
#import "gates.typ" #import "gates.typ"
#import "util.typ" #import "util.typ"
#import "verilog.typ"
#import "wire.typ" #import "wire.typ"
+1 -8
View File
@@ -73,11 +73,4 @@
#let valid-anchors = ( #let valid-anchors = (
"center", "north", "east", "west", "south", "center", "north", "east", "west", "south",
"north-east", "north-west", "south-east", "south-west" "north-east", "north-west", "south-east", "south-west"
) )
#let lerp(pt0, ratio, pt1) = {
return (
(pt1.at(0) - pt0.at(0)) * ratio / 100% + pt0.at(0),
(pt1.at(1) - pt0.at(1)) * ratio / 100% + pt0.at(1)
)
}
+704
View File
@@ -0,0 +1,704 @@
#import "/src/cetz.typ": draw
#import "elements/block.typ": block as block-element
#import "util.typ"
#import "wire.typ"
// ---------------------------------------------------------------------------
// Low level helpers
// ---------------------------------------------------------------------------
#let _ident = "(?:\\\\\\S+|[A-Za-z_][A-Za-z0-9_$]*)"
#let _net-kw = "(?:var|wire|reg|logic|bit|tri|tri0|tri1|triand|trior|wand|wor|uwire|supply0|supply1|signed|unsigned)"
#let _strip-comments(src) = {
let s = src.replace(regex("(?s)/\\*.*?\\*/"), " ")
s = s.replace(regex("//[^\n]*"), " ")
return s
}
// Finds the index of the closing bracket matching the opening one at `start`
#let _find-close(chars, start) = {
let pairs = ("(": ")", "[": "]", "{": "}")
let open = chars.at(start)
let close = pairs.at(open)
let depth = 0
let i = start
while i < chars.len() {
let c = chars.at(i)
if c == open {
depth += 1
} else if c == close {
depth -= 1
if depth == 0 { return i }
}
i += 1
}
return none
}
// Splits a text on commas that are not nested inside brackets
#let _split-commas(text) = {
let items = ()
let depth = 0
let cur = ()
for c in text.clusters() {
if c in ("(", "[", "{") {
depth += 1
} else if c in (")", "]", "}") {
depth -= 1
}
if c == "," and depth <= 0 {
items.push(cur.join())
cur = ()
} else {
cur.push(c)
}
}
if cur.join().trim() != "" { items.push(cur.join()) }
return items.filter(it => it.trim() != "")
}
// Evaluates a very simple integer expression (`4`, `32-1`, `8+2`)
#let _eval-int(expr) = {
if expr == none { return none }
let e = expr.replace(regex("\\s+"), "")
if e.match(regex("^[0-9]+$")) != none { return int(e) }
let m = e.match(regex("^([0-9]+)([+*-])([0-9]+)$"))
if m == none { return none }
let a = int(m.captures.at(0))
let b = int(m.captures.at(2))
let op = m.captures.at(1)
if op == "+" { return a + b }
if op == "-" { return a - b }
return a * b
}
// Extracts the first packed range (`[7:0]`) of a dimension list
#let _first-range(dims) = {
if dims == none { return none }
let m = dims.match(regex("\\[([^\\]]*)\\]"))
if m == none { return none }
return m.captures.at(0).trim()
}
// Computes the width of a range text (`7:0` -> 8), `none` if not constant
#let _range-width(range) = {
if range == none { return 1 }
let m = range.match(regex("^([^:]+):([^:]+)$"))
if m == none { return none }
let hi = _eval-int(m.captures.at(0))
let lo = _eval-int(m.captures.at(1))
if hi == none or lo == none { return none }
return calc.abs(hi - lo) + 1
}
#let _make-port(name, dir, dims) = {
let range = _first-range(dims)
return (
name: name,
dir: dir,
range: range,
width: _range-width(range)
)
}
// ---------------------------------------------------------------------------
// Parsing
// ---------------------------------------------------------------------------
#let _parse-ansi-ports(portlist) = {
let ports = ()
let last-dir = "input"
let last-dims = none
for item in _split-commas(portlist) {
let m = item.match(regex(
"^\\s*(?:(input|output|inout)\\s+)?(?:" + _net-kw + "\\s+)*" +
"((?:\\[[^\\]]*\\]\\s*)*)(" + _ident + ")"
))
if m == none { continue }
let (dir, dims, name) = (m.captures.at(0), m.captures.at(1), m.captures.at(2))
if dir != none {
last-dir = dir
last-dims = dims
} else if dims == none or dims.trim() == "" {
dims = last-dims
}
ports.push(_make-port(name, last-dir, dims))
}
return ports
}
#let _parse-body-ports(portlist, body) = {
let decls = (:)
for m in body.matches(regex(
"(?s)\\b(input|output|inout)\\b((?:\\s+" + _net-kw + ")*)\\s*" +
"((?:\\[[^\\]]*\\]\\s*)*)([^;]*);"
)) {
let dir = m.captures.at(0)
let dims = m.captures.at(2)
for n in _split-commas(m.captures.at(3)) {
let nm = n.match(regex("^\\s*(" + _ident + ")\\s*$"))
if nm == none { continue }
decls.insert(nm.captures.at(0), _make-port(nm.captures.at(0), dir, dims))
}
}
let ports = ()
for item in _split-commas(portlist) {
let m = item.match(regex("(" + _ident + ")\\s*$"))
if m == none { continue }
let name = m.captures.at(0)
if name in decls { ports.push(decls.at(name)) }
}
// Declared but not listed (or unparsable port list): append the rest
for (name, port) in decls {
if ports.find(it => it.name == name) == none { ports.push(port) }
}
return ports
}
#let _parse-module(chunk) = {
let m = chunk.match(regex("^\\s*module\\s+(" + _ident + ")"))
if m == none { return none }
let name = m.captures.at(0)
let chars = chunk.slice(m.end).clusters()
let i = 0
while i < chars.len() and chars.at(i).trim() == "" { i += 1 }
let params = ()
if i < chars.len() and chars.at(i) == "#" {
while i < chars.len() and chars.at(i) != "(" { i += 1 }
let close = _find-close(chars, i)
if close == none { return none }
for p in _split-commas(chars.slice(i + 1, close).join()) {
let pm = p.match(regex("(" + _ident + ")\\s*=\\s*([^,]+)$"))
if pm != none {
params.push((name: pm.captures.at(0), value: pm.captures.at(1).trim()))
}
}
i = close + 1
}
while i < chars.len() and chars.at(i) != "(" and chars.at(i) != ";" { i += 1 }
let portlist = ""
if i < chars.len() and chars.at(i) == "(" {
let close = _find-close(chars, i)
if close == none { return none }
portlist = chars.slice(i + 1, close).join()
i = close + 1
}
let body = if i < chars.len() { chars.slice(i).join() } else { "" }
let ports = if portlist.match(regex("\\b(input|output|inout)\\b")) != none {
_parse-ansi-ports(portlist)
} else {
_parse-body-ports(portlist, body)
}
return (name: name, params: params, ports: ports)
}
/// Parses Verilog/SystemVerilog source code and returns the declared modules.
///
/// Both ANSI (`module m(input clk, ...)`) and non-ANSI
/// (`module m(clk, ...); input clk;`) port styles are supported.
///
/// ```typ
/// #let modules = verilog.parse(read("my_module.v"))
/// ```
///
/// - source (str): The Verilog source code, typically obtained with `read()`
/// -> array
#let parse(source) = {
let src = _strip-comments(source)
let modules = ()
for m in src.matches(regex("\\bmodule\\b")) {
let rest = src.slice(m.start)
let pos = rest.slice(6).position(regex("\\bendmodule\\b"))
let chunk = if pos == none { rest } else { rest.slice(0, 6 + pos) }
let parsed = _parse-module(chunk)
if parsed != none { modules.push(parsed) }
}
return modules
}
/// Returns a single module from Verilog source code.
///
/// - source (str, array, dictionary): Verilog source code, an array of parsed modules or an already parsed module
/// - name (none, str): Name of the wanted module. If `none`, the first module is returned
/// -> dictionary
#let get-module(source, name: none) = {
let modules = if type(source) == str {
parse(source)
} else if type(source) == dictionary {
(source,)
} else {
source
}
if modules.len() == 0 { panic("No Verilog module found") }
if name == none { return modules.first() }
let found = modules.find(it => it.name == name)
if found == none { panic("Could not find Verilog module " + name) }
return found
}
// ---------------------------------------------------------------------------
// Bus / clock / reset inference
// ---------------------------------------------------------------------------
#let axis-suffixes = (
"tvalid", "tready", "tdata", "tlast", "tkeep", "tstrb",
"tuser", "tid", "tdest", "twakeup"
)
#let axi-suffixes = (
"awid", "awaddr", "awlen", "awsize", "awburst", "awlock", "awcache",
"awprot", "awqos", "awregion", "awuser", "awvalid", "awready",
"wid", "wdata", "wstrb", "wlast", "wuser", "wvalid", "wready",
"bid", "bresp", "buser", "bvalid", "bready",
"arid", "araddr", "arlen", "arsize", "arburst", "arlock", "arcache",
"arprot", "arqos", "arregion", "aruser", "arvalid", "arready",
"rid", "rdata", "rresp", "rlast", "ruser", "rvalid", "rready"
)
// Signals that only exist on full AXI4 (used to tell AXI4 from AXI4-Lite)
#let axi-full-only = (
"awid", "awlen", "awsize", "awburst", "awlock", "awcache", "awqos",
"awregion", "wid", "wlast", "bid",
"arid", "arlen", "arsize", "arburst", "arlock", "arcache", "arqos",
"arregion", "rid", "rlast"
)
#let _clock-re = regex("(?i)(^|[^a-z0-9])(a?clk|clock)([0-9]*)($|[^a-z0-9])")
#let _reset-re = regex("(?i)(^|[^a-z0-9])(a?rst|a?reset)(_?n)?([0-9]*)($|[^a-z0-9])")
#let _is-clock(name) = name.match(_clock-re) != none
#let _is-reset(name) = name.match(_reset-re) != none
#let _is-active-low(name) = name.match(regex("(?i)(_n|_b|_l|resetn|rstn)$")) != none
// Splits a signal name into (prefix, suffix) using the given suffix table
#let _match-suffix(name, suffixes) = {
let lower = lower(name)
let best = none
for s in suffixes {
if not lower.ends-with(s) { continue }
if lower.len() == s.len() {
if best == none or s.len() > best.len() { best = s }
continue
}
let sep = lower.at(lower.len() - s.len() - 1)
if sep != "_" { continue }
if best == none or s.len() > best.len() { best = s }
}
if best == none { return none }
let prefix = name.slice(0, name.len() - best.len())
return (prefix: prefix.trim("_", at: end), suffix: best)
}
#let _group-signals(ports) = {
let groups = (:)
let rest = ()
for port in ports {
let kind = none
let m = _match-suffix(port.name, axis-suffixes)
if m != none {
kind = "axi-stream"
} else {
m = _match-suffix(port.name, axi-suffixes)
if m != none { kind = "axi" }
}
if m == none {
rest.push(port)
continue
}
let key = kind + "|" + lower(m.prefix)
let group = groups.at(key, default: (
kind: kind,
prefix: m.prefix,
signals: (:)
))
group.signals.insert(m.suffix, port)
groups.insert(key, group)
}
return (groups: groups, rest: rest)
}
#let _bus-width(group, ..names) = {
for n in names.pos() {
if n in group.signals {
return group.signals.at(n).width
}
}
return none
}
#let _finalize-bus(group) = {
let sig = group.signals
let kind = group.kind
if kind == "axi-stream" {
if "tvalid" not in sig and not ("tdata" in sig and "tready" in sig) {
return none
}
} else {
if sig.len() < 4 { return none }
if "awvalid" not in sig and "arvalid" not in sig { return none }
let lite = sig.keys().find(it => it in axi-full-only) == none
if lite or lower(group.prefix).contains(regex("axil|lite")) {
kind = "axi-lite"
}
}
let ref = if kind == "axi-stream" {
sig.at("tvalid", default: sig.at("tdata", default: none))
} else {
sig.at("awvalid", default: sig.at("arvalid", default: none))
}
let role = if ref != none and ref.dir == "output" { "master" } else { "slave" }
return (
kind: kind,
prefix: group.prefix,
role: role,
dir: if role == "master" { "output" } else { "input" },
data-width: _bus-width(group, "tdata", "wdata", "rdata"),
addr-width: _bus-width(group, "awaddr", "araddr"),
signals: sig,
ports: sig.values()
)
}
/// Analyses the ports of a module and infers AXI4, AXI4-Lite and AXI4-Stream
/// buses as well as clock and reset signals.
///
/// The returned dictionary contains:
/// - `buses`: array of inferred buses. Each bus has the fields `kind`
/// (`"axi"`, `"axi-lite"` or `"axi-stream"`), `prefix`, `role`
/// (`"master"` or `"slave"`), `dir`, `data-width`, `addr-width` and `signals`
/// - `clocks`: array of clock ports
/// - `resets`: array of reset ports (with an additional `active-low` field)
/// - `signals`: array of the remaining ports
///
/// - module (str, array, dictionary): Verilog source code or a parsed module
/// - name (none, str): Name of the wanted module (when several are present)
/// -> dictionary
#let analyze(module, name: none) = {
let module = get-module(module, name: name)
let clocks = ()
let resets = ()
let others = ()
for port in module.ports {
if _is-clock(port.name) {
clocks.push(port)
} else if _is-reset(port.name) {
resets.push(port + (active-low: _is-active-low(port.name)))
} else {
others.push(port)
}
}
let (groups, rest) = _group-signals(others)
let buses = ()
let signals = rest
for (_, group) in groups {
let bus = _finalize-bus(group)
if bus == none {
signals += group.signals.values()
} else {
buses.push(bus)
}
}
buses = buses.sorted(key: it => it.prefix)
return (
name: module.name,
params: module.params,
buses: buses,
clocks: clocks,
resets: resets,
signals: signals,
ports: module.ports
)
}
// ---------------------------------------------------------------------------
// Rendering
// ---------------------------------------------------------------------------
#let bus-kind-names = (
"axi": "AXI4",
"axi-lite": "AXI4-Lite",
"axi-stream": "AXI4-Stream"
)
#let _bus-label(bus, show-widths: true, show-info: false) = {
let title = if bus.prefix != "" { bus.prefix } else { bus-kind-names.at(bus.kind) }
let info = ()
if show-info {
if bus.prefix != "" { info.push(bus-kind-names.at(bus.kind)) }
if show-widths and bus.data-width != none {
info.push(str(bus.data-width) + "b")
}
}
if info.len() == 0 {
return (label: title, len: title.len())
}
let sub = info.join(" ")
return (
label: box(align(left)[#title \ #text(size: 0.7em, sub)]),
len: calc.max(title.len(), int(sub.len() * 0.7))
)
}
#let _signal-label(port, show-widths: true) = {
let label = if show-widths and port.range != none {
port.name + "[" + port.range + "]"
} else {
port.name
}
return (label: label, len: label.len())
}
// Approximate width (in canvas units) of a label of `len` characters
#let _text-width(len) = 0.28 * len
/// Builds the ports dictionary of a Verilog module, ready to be given to
/// #doc-ref("element.elmt").
///
/// Inputs (and slave buses) are placed on the west side, outputs (and master
/// buses) on the east side, clocks and resets on top of the west side.
///
/// - module (str, array, dictionary): Verilog source code or a parsed module
/// - name (none, str): Name of the wanted module (when several are present)
/// - show-widths (bool): Whether to append the signal widths to their names
/// - show-bus-info (bool): Whether to display the bus kind and data width below the bus name
/// - clock-side (str): Side on which clock ports are placed
/// - reset-side (str): Side on which reset ports are placed
/// -> dictionary
#let module-ports(
module,
name: none,
show-widths: true,
show-bus-info: false,
clock-side: "west",
reset-side: "west"
) = {
let info = if type(module) == dictionary and "buses" in module {
module
} else {
analyze(module, name: name)
}
let ports = (north: (), east: (), south: (), west: ())
for bus in info.buses {
let side = if bus.dir == "output" { "east" } else { "west" }
let label = _bus-label(bus, show-widths: show-widths, show-info: show-bus-info)
ports.at(side).push((
id: bus.prefix,
name: label.label,
label-len: label.len,
bus: true,
kind: bus.kind,
multiline: type(label.label) != str
))
}
for port in info.clocks {
let vertical = clock-side in ("north", "south")
ports.at(clock-side).push((
id: port.name,
// indented so that the label does not collide with the clock triangle
name: if vertical { "" } else { box(inset: (left: 0.7em), port.name) },
label-len: port.name.len() + 2,
clock: true,
small: true
))
}
for port in info.resets {
ports.at(reset-side).push((
id: port.name,
name: port.name,
label-len: port.name.len(),
invert: port.active-low,
vertical: reset-side in ("north", "south")
))
}
for port in info.signals {
let side = if port.dir == "output" { "east" } else { "west" }
let label = _signal-label(port, show-widths: show-widths)
ports.at(side).push((
id: port.name,
name: label.label,
label-len: label.len,
invert: _is-active-low(port.name),
bus: port.width == none or port.width > 1
))
}
return ports
}
/// Draws a block from a Verilog module description.
///
/// AXI4, AXI4-Lite and AXI4-Stream signals are grouped into single bus ports,
/// and clock/reset signals are automatically detected and grouped together.
///
/// ```typ
/// #circuiteria.circuit({
/// circuiteria.verilog.block(read("axi_dma.v"), x: 0, y: 0)
/// })
/// ```
///
/// The port ids are the signal names (or the bus prefixes), so wires can be
/// attached with `"<id>-port-<signal>"`.
///
/// - module (str, array, dictionary): Verilog source code or a parsed module
/// - module-name (none, str): Name of the wanted module (when several are present)
/// - x (number, dictionary): The x position (bottom-left corner)
/// - y (number, dictionary): The y position (bottom-left corner)
/// - w (auto, number): Width of the block. Computed from the port names if `auto`
/// - h (auto, number): Height of the block. Computed from the port count if `auto`
/// - id (auto, str): The block id. Defaults to the module name
/// - name (auto, none, str, content): Name displayed in the block. Defaults to the module name
/// - name-anchor (str): Anchor for the displayed name
/// - show-widths (bool): Whether to append the signal widths to their names
/// - show-bus-info (bool): Whether to display the bus kind and data width below the bus name
/// - stubs (bool): Whether to draw an unnamed wire stub on every port
/// - stub-length (number, length): Length of the automatic stubs
/// - bus-markers (bool): Whether to draw the bus stubs as double lines
/// - clock-side (str): Side on which clock ports are placed
/// - reset-side (str): Side on which reset ports are placed
/// - fill (none, color): Fill color
/// - stroke (stroke): Border stroke
/// - radius (number, length, ratio, dictionary): The corner radius of the block
/// - ports-margins (dictionary): Dictionary of ports margins
/// - debug (dictionary): Dictionary of debug options
#let block(
module,
module-name: none,
x: 0,
y: 0,
w: auto,
h: auto,
id: auto,
name: auto,
name-anchor: "south",
show-widths: true,
show-bus-info: false,
stubs: true,
stub-length: 1em,
bus-markers: true,
clock-side: "west",
reset-side: "west",
fill: none,
stroke: black + 1pt,
radius: 0.5em,
ports-margins: (:),
debug: (ports: false)
) = {
let info = if type(module) == dictionary and "buses" in module {
module
} else {
analyze(module, name: module-name)
}
let ports = module-ports(
info,
show-widths: show-widths,
show-bus-info: show-bus-info,
clock-side: clock-side,
reset-side: reset-side
)
let id = if id == auto { info.name } else { id }
let name = if name == auto { raw(info.name) } else { name }
let max-len(side) = {
let lens = ports.at(side).map(it => it.at("label-len", default: 8))
if lens.len() == 0 { 0 } else { calc.max(..lens) }
}
let sum-len(side) = {
ports.at(side).fold(0, (acc, it) => acc + it.at("label-len", default: 8))
}
// Two-line (bus) labels need more vertical room
let rows-weight(side) = {
ports.at(side).fold(0, (acc, it) => acc + if it.at("multiline", default: false) { 1.6 } else { 1 })
}
let vert-count = calc.max(ports.north.len(), ports.south.len(), 0)
let margins = ports-margins
for side in ("west", "east") {
if side not in margins { margins.insert(side, (8%, 14%)) }
}
let h = if h != auto { h } else {
let rows = calc.max(rows-weight("west"), rows-weight("east"), 1)
calc.max(1.8, rows * 1.05 / 0.72, if vert-count > 0 { 2.2 } else { 0 })
}
let w = if w != auto { w } else {
let inner = _text-width(max-len("west") + max-len("east")) + 0.8
let title = _text-width(info.name.len() * 1.2) + 0.8
let vert-len = calc.max(sum-len("north"), sum-len("south"))
let vert = _text-width(vert-len) + 0.5 * vert-count + 0.4
calc.max(2.5, inner, title, vert)
}
block-element(
x: x,
y: y,
w: w,
h: h,
id: id,
name: name,
name-anchor: name-anchor,
ports: ports,
ports-margins: margins,
fill: fill,
stroke: stroke,
radius: radius,
debug: debug
)
if stubs {
// active-low ports carry a bubble, so their stub starts past it
let bubble = (north: (0, 0.2), east: (0.2, 0), south: (0, -0.2), west: (-0.2, 0))
let ends = (north: (0, stub-length), east: (stub-length, 0), south: (0, -stub-length), west: (-stub-length, 0))
let gap = 0.1
for (side, side-ports) in ports {
for port in side-ports {
let anchor = id + "-port-" + port.id
let is-bus = port.at("bus", default: false)
if bus-markers and "kind" in port {
let perp = if side in ("east", "west") { (0, gap) } else { (gap, 0) }
// starts past the block border so the fill does not cover it
let o = 0.02
let edge = (north: (0, o), east: (o, 0), south: (0, -o), west: (-o, 0)).at(side)
draw.rect(
(rel: (edge.at(0) - perp.at(0), edge.at(1) - perp.at(1)), to: anchor),
(rel: ends.at(side), to: (rel: perp, to: anchor)),
fill: luma(80%),
stroke: none
)
for k in (1, 0, -1) {
let start = (rel: (perp.at(0) * k, perp.at(1) * k), to: anchor)
draw.line(start, (rel: ends.at(side), to: start), stroke: wire.signal-width)
}
} else if port.at("invert", default: false) {
draw.line(
(rel: bubble.at(side), to: anchor),
(rel: ends.at(side), to: anchor),
stroke: if is-bus { wire.bus-width } else { wire.signal-width }
)
} else {
wire.stub(anchor, side, length: stub-length, bus: is-bus)
}
}
}
}
}
+141 -81
View File
@@ -1,11 +1,9 @@
#import "/src/cetz.typ": draw, coordinate #import "/src/cetz.typ": draw, coordinate
#import "util.typ": opposite-anchor #import "util.typ": opposite-anchor
#import "elements/ports.typ": get-port-side
#import "elements/element.typ"
/// List of valid wire styles /// List of valid wire styles
/// #examples.wires /// #examples.wires
#let wire-styles = ("direct", "zigzag", "dodge") #let wire-styles = ("direct", "zigzag", "dodge", "guided")
#let signal-width = 1pt #let signal-width = 1pt
#let bus-width = 1.5pt #let bus-width = 1.5pt
@@ -111,6 +109,88 @@
return (points, anchors) return (points, anchors)
} }
#let get-guided-wire(pts, margins, sides, center-guides, ctx) = {
let start = pts.first()
let end = pts.last()
let (margin-start, margin-end) = margins
let (side-start, side-end) = sides
let (center_horizontal, center_vertical) = center-guides
let (ctx, p0) = coordinate.resolve(ctx, start)
let (ctx, p6) = coordinate.resolve(ctx, end)
p0 = (x: p0.first(), y: p0.at(1))
p6 = (x: p6.first(), y: p6.at(1))
let box_width = calc.abs(p6.x - p0.x)
let box_height = calc.abs(p6.y - p0.y)
// finding correct dx and dy
let dx1 = box_width * margin-start / 100%
if side-start == "west" {
dx1 *= -1
} else if side-start == "north" or side-start == "south" { dx1 = 0}
let dx2 = box_width * margin-end / 100%
if side-end == "west" {
dx2 *= -1
} else if side-end == "north" or side-end == "south" { dx2 = 0}
let dy1 = box_height * margin-start / 100%
if side-start == "south" {
dy1 *= -1
} else if side-start == "west" or side-start == "east" { dy1 = 0}
let dy2 = box_height * margin-end / 100%
if side-end == "south" {
dy2 *= -1
} else if side-end == "west" or side-end == "east" { dy2 = 0}
// points that are closest to the edge points
let p1 = (p0.x + dx1, p0.y + dy1)
let p5 = (p6.x + dx2, p6.y + dy2)
// middle point
let center_x = p0.x + box_width * center_horizontal / 100%
let center_y = p0.y + box_height * center_vertical / 100%
let p3 = (center_x, center_y)
// setting up the points for that touch the guides
let p2 = (0,0)
let p4 = (0,0)
if side-start in ("north", "south") {
p2 = (horizontal: p3, vertical: p1)
} else {
p2 = (horizontal: p1, vertical: p3)
}
if side-end in ("north", "south") {
p4 = (horizontal: p3, vertical: p5)
} else if side-end in ("east", "west") {
p4 = (horizontal: p5, vertical: p3)
}
// returning
let points = (
start,
p1,
p2,
p3,
p4,
p5,
end
)
let anchors = (
"start": start,
"start2": points.at(1),
"guide-start": points.at(2),
"center": points.at(3),
"guide-end": points.at(4),
"end2": points.at(5),
"end": end
)
return (points, anchors)
}
/// Draws a wire between two points /// Draws a wire between two points
/// - id (str): The wire's id, for future reference (anchors) /// - 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.) /// - pts (array): The two points (as CeTZ compatible coordinates, i.e. XY, relative positions, ids, etc.)
@@ -129,31 +209,37 @@
/// - dodge-y (number): Y position to dodge the wire to (only with style "dodge") /// - 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-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") /// - dodge-margins (array): The start and end margins (i.e. space before dodging) of the wire (only with style "dodge")
/// - guided-center (array): the horizontal and vertical guides of the center guides (only with style "guided")
/// - guided-margins (array): the start and end of guided margins of the wire (only with style "guided")
/// - guided-sides (array): the side of start and end array (must be either "north", "south", "west", "east") (only work with style "guided")
#let wire( #let wire(
pt0, id, pts,
pt1,
id: none,
bus: false, bus: false,
name: none, name: none,
name-pos: "middle",
slice: none, slice: none,
color: black, color: black,
dashed: false, dashed: false,
style: "direct", style: "direct",
reverse: false, reverse: false,
guided-center: (50%, 50%),
guided-margins: (5%, 5%),
guided-sides: ("east", "west"),
directed: false, directed: false,
rotate-name: true, rotate-name: true,
zigzag-ratio: 50%, zigzag-ratio: 50%,
zigzag-dir: "vertical", zigzag-dir: "vertical",
dodge-y: 0, dodge-y: 0,
dodge-sides: ("east", "west"), dodge-sides: ("east", "west"),
dodge-margins: (5%, 5%), dodge-margins: (5%, 5%)
..args
) = draw.get-ctx(ctx => { ) = draw.get-ctx(ctx => {
if not style in wire-styles { if not style in wire-styles {
panic("Invalid wire style '" + style + "'") panic("Invalid wire style '" + style + "'")
} }
let pts = (pt0, pt1) if pts.len() != 2 {
panic("Wrong number of points (got " + str(pts.len()) + " instead of 2)")
}
let stroke = ( let stroke = (
paint: color, paint: color,
@@ -180,6 +266,14 @@
dodge-sides, dodge-sides,
ctx ctx
) )
} else if style == "guided" {
(points, anchors) = get-guided-wire(
pts,
guided-margins,
guided-sides,
guided-center,
ctx
)
} }
let mark = (fill: color) let mark = (fill: color)
@@ -218,7 +312,7 @@
let names = () let names = ()
if type(name) == str { if type(name) == str {
names = ((name, "middle"),) names = ((name, name-pos),)
} else if type(name) == array { } else if type(name) == array {
names = ( names = (
@@ -228,9 +322,6 @@
} }
for (name, pos) in names { for (name, pos) in names {
if name == none {
continue
}
let point let point
let anchor let anchor
@@ -272,73 +363,42 @@
/// - vertical (bool): Whether the name should be displayed vertically /// - vertical (bool): Whether the name should be displayed vertically
/// - length (number): The length of the stub /// - length (number): The length of the stub
/// - name-offset (number): The name offset, perpendicular to the stub /// - name-offset (number): The name offset, perpendicular to the stub
#let stub(anchor, side: auto, name: none, vertical: false, length: 1em, name-offset: 0) = { /// - bus (bool): Whether the stub is a bus (thicker line)
if "." not in anchor { #let stub(port-id, side, name: none, vertical: false, length: 1em, name-offset: 0, bus: false) = {
panic("`anchor` must be a valid anchor of an element") let end-offset = (
} north: (0, length),
let parts = anchor.split(".") east: (length, 0),
let port-id = parts.last() south: (0, -length),
let port-elmt-id = parts.slice(0, -1).join(".") 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)
let pre-process = (elements, elmt) => { draw.line(
let eid = elmt.id port-id,
(rel: end-offset, to: port-id),
let side = side stroke: if bus { bus-width } else { signal-width }
if side == auto {
if port-elmt-id not in elements {
panic("Unknown element " + port-elmt-id)
}
let port-elmt = elements.at(port-elmt-id)
side = get-port-side(port-elmt, port-id)
}
elements.at(eid).insert("side", side)
return elements
}
let draw-func(elmt, bounds) = {
let side = elmt.side
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)
let shapes = ()
shapes += draw.line(
anchor,
(rel: end-offset, to: anchor)
)
if name != none {
let text-anchor = if vertical {
(
"north": "west",
"south": "east",
"west": "south",
"east": "north"
).at(side)
} else { opposite-anchor(side) }
shapes += draw.content(
anchor: text-anchor,
padding: 0.2em,
angle: if vertical {90deg} else {0deg},
(rel: name-offset, to: anchor),
name
)
}
return (shapes, bounds)
}
return element.elmt(
draw-shape: draw-func,
pre-process: pre-process
) )
} 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
)
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "circuiteria" name = "circuiteria"
version = "0.2.0" version = "0.2.1"
compiler = "0.13.0" compiler = "0.13.1"
repository = "https://git.kb28.ch/HEL/circuiteria" repository = "https://git.kb28.ch/HEL/circuiteria"
entrypoint = "src/lib.typ" entrypoint = "src/lib.typ"
authors = [ authors = [
@@ -9,6 +9,6 @@ authors = [
] ]
categories = [ "visualization" ] categories = [ "visualization" ]
license = "Apache-2.0" license = "Apache-2.0"
description = "Drawing block circuits with Typst made easy, using CeTZ" description = "Drawing block circuits made easy, using CeTZ"
keywords = [ "circuit", "block", "draw" ] keywords = [ "circuit", "block", "draw" ]
exclude = [ "gallery", "justfile", "doc" ] exclude = [ "gallery", "justfile", "doc" ]