Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c1272e35e | ||
|
|
c9aa81af2e | ||
|
|
6871053810 | ||
|
|
73b7348eed
|
||
|
|
b71e822bb8
|
||
|
|
4c1025b1eb
|
||
|
|
b35c04a7d9
|
||
|
|
0612e9b6ec
|
||
|
|
42d6915b15 | ||
|
|
b646d24930
|
||
|
|
1533b5ccd4 | ||
|
|
2633b35f39
|
||
|
|
415029e0c3
|
||
|
|
091ec8c08a
|
||
|
|
ffd3356ab2
|
||
|
|
02e59c5d7a | ||
|
|
96dd9b0543
|
||
|
|
da9c2b568b
|
||
|
|
459892bb13
|
||
|
|
255eec7f35 | ||
|
|
536f5a075d
|
||
|
|
d73215c889 | ||
|
|
87643983ec | ||
|
|
94fef0a245 | ||
|
|
1a0a659ace | ||
|
|
f2ce91ec39 | ||
|
|
043bb339fe | ||
|
|
693676d61a | ||
|
|
94d5d6b854 | ||
|
|
71f128f6c9 | ||
|
|
ea8277ee5b | ||
|
|
c5e4f8039a | ||
|
|
8c91ccdd54 | ||
|
|
9966656e8b | ||
|
|
3ccb79c6c2 |
@@ -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.
|
||||
|
||||
<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>
|
||||
|
||||
## Examples
|
||||
@@ -11,17 +11,17 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">A bit of eveything</td>
|
||||
<td colspan="2">A bit of everything</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -31,12 +31,12 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
|
||||
<tr>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -44,6 +44,16 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
|
||||
<td>Groups</td>
|
||||
<td>Rotated</td>
|
||||
</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>
|
||||
|
||||
> **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:
|
||||
```typ
|
||||
#import "@preview/circuiteria:0.2.0"
|
||||
#import "@preview/circuiteria:0.2.1"
|
||||
#circuiteria.circuit({
|
||||
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"`).
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "../src/circuit.typ": circuit
|
||||
#import "../src/util.typ"
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ for i in range(3) {
|
||||
```)
|
||||
|
||||
#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 + 2, 1), radius: .1, name: "p" + str(i * 2 + 1))
|
||||
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("w3", ("p4", "p5"), style: "dodge",
|
||||
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)
|
||||
|
||||
#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")
|
||||
```, 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(```
|
||||
element.group(
|
||||
id: "g1", name: "Group 1", stroke: (dash: "dashed"),
|
||||
@@ -148,21 +192,3 @@ wire.wire("w2", ((0, 0), (1, -.5)),
|
||||
style: "zigzag", zigzag-ratio: 80%)
|
||||
wire.intersection("w1.zig")
|
||||
```)
|
||||
|
||||
#let capacitor = example(```
|
||||
electrical.capacitor(
|
||||
x: 0, y: 0, w: 2, h: 1, id: "a",
|
||||
scales: (100%, 80%), gap: 0.3
|
||||
)
|
||||
|
||||
electrical.capacitor(
|
||||
x: 4, y: -0.5, w: 1, h: 2, id: "b",
|
||||
vertical: true, symbols: ([+], none)
|
||||
)
|
||||
```, vertical: true)
|
||||
|
||||
#let resistor = example(```
|
||||
electrical.resistor(x: 0, y: 0, w: 2, h: 0.5, id: "a", zigzags: 8)
|
||||
electrical.resistor(x: 4, y: -0.5, w: 0.5, h: 2, id: "b", vertical: true)
|
||||
electrical.resistor(x: 6.5, y: 0, w: 2, h: 0.5, id: "c", zigzags: none)
|
||||
```, vertical: true)
|
||||
@@ -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
|
||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
@@ -1,4 +1,4 @@
|
||||
#import "../src/lib.typ": *
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 160 KiB |
@@ -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)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 160 KiB |
@@ -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)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 66 KiB |
@@ -1,5 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#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)
|
||||
|
||||
@@ -81,57 +80,4 @@
|
||||
element.gate-xnor(
|
||||
x: 9, y: -6, w: 2, h: 2, id: "xnor"
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: 0, y: -8, w: 2, h: 0.5, id: "res1"
|
||||
)
|
||||
|
||||
element.capacitor(
|
||||
x: 3, y: (from: "res1-port-1", to: "0"),
|
||||
w: 2, h: 0.6,
|
||||
id: "cap1",
|
||||
scales: (100%, 80%),
|
||||
symbols: ([+], [-])
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: (rel: 1, to: "cap1-port-1"),
|
||||
y: (from: "cap1-port-1", to: "0"),
|
||||
w: 0.5, h: 2,
|
||||
id: "res2",
|
||||
vertical: true,
|
||||
zigzags: 8
|
||||
)
|
||||
|
||||
element.capacitor(
|
||||
x: (rel: 1, to: "res2.east"),
|
||||
y: (from: "res2-port-1", to: "1"),
|
||||
w: 0.5, h: 2,
|
||||
id: "cap2",
|
||||
vertical: true,
|
||||
symbols: ([a], [b])
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: (rel: 1, to: "cap2-port-0"),
|
||||
y: (from: "cap2-port-0", to: "0"),
|
||||
w: 2, h: 0.5,
|
||||
id: "res3",
|
||||
zigzags: none
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: (rel: 1, to: "res3-port-1"),
|
||||
y: (from: "res3-port-1", to: "0"),
|
||||
w: 0.5, h: 2,
|
||||
id: "res4",
|
||||
zigzags: none,
|
||||
vertical: true
|
||||
)
|
||||
|
||||
wire.wire("w4", ("res1-port-1", "cap1-port-0"))
|
||||
wire.wire("w5", ("cap1-port-1", "res2-port-0"))
|
||||
wire.wire("w6", ("res2-port-1", "cap2-port-1"))
|
||||
wire.wire("w7", ("cap2-port-0", "res3-port-0"))
|
||||
wire.wire("w8", ("res3-port-1", "res4-port-0"))
|
||||
})
|
||||
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 195 KiB |
@@ -1,5 +1,5 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../src/lib.typ": *
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 274 KiB |
@@ -1,5 +1,5 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../src/lib.typ": *
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@@ -1,5 +1,5 @@
|
||||
#import "@preview/cetz:0.3.2": draw, vector
|
||||
#import "../src/lib.typ": *
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 40 KiB |
@@ -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")
|
||||
}
|
||||
})
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -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
|
||||
)
|
||||
})
|
||||
@@ -1,10 +1,9 @@
|
||||
#import "@preview/tidy:0.4.1"
|
||||
#import "@preview/cetz:0.3.2": draw, canvas
|
||||
#import "/src/cetz.typ": draw, canvas
|
||||
#import "src/lib.typ"
|
||||
#import "doc/examples.typ"
|
||||
#import "src/circuit.typ": circuit
|
||||
#import "src/element.typ"
|
||||
#import "src/electrical.typ"
|
||||
#import "src/gates.typ"
|
||||
#import "src/util.typ"
|
||||
#import "src/wire.typ"
|
||||
@@ -106,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:
|
||||
#pad(left: 1em)[```typ
|
||||
#import "@preview/circuiteria:0.2.0"
|
||||
#import "@preview/circuiteria:0.2.1"
|
||||
#circuiteria.circuit({
|
||||
import circuiteria: *
|
||||
...
|
||||
@@ -196,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/buf.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",
|
||||
old-syntax: true,
|
||||
scope: (
|
||||
@@ -215,14 +219,13 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
||||
|
||||
#pagebreak()
|
||||
|
||||
#let electrical-docs = tidy.parse-module(
|
||||
read("src/elements/electrical/capacitor.typ") + "\n" +
|
||||
read("src/elements/electrical/resistor.typ") + "\n",
|
||||
name: "electrical",
|
||||
#let verilog-docs = tidy.parse-module(
|
||||
read("src/verilog.typ"),
|
||||
name: "verilog",
|
||||
old-syntax: true,
|
||||
scope: (
|
||||
element: element,
|
||||
circuit: circuit,
|
||||
electrical: electrical,
|
||||
draw: draw,
|
||||
wire: wire,
|
||||
tidy: tidy,
|
||||
@@ -231,4 +234,4 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
||||
)
|
||||
)
|
||||
|
||||
#tidy.show-module(electrical-docs, sort-functions: false)
|
||||
#tidy.show-module(verilog-docs, sort-functions: false)
|
||||
@@ -0,0 +1 @@
|
||||
#import "@preview/cetz:0.3.4": *
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": canvas
|
||||
#import "/src/cetz.typ": canvas
|
||||
#import "@preview/tidy:0.3.0"
|
||||
|
||||
/// Draws a block circuit diagram
|
||||
@@ -9,6 +9,5 @@
|
||||
/// - length (length, ratio): Optional base unit
|
||||
/// -> none
|
||||
#let circuit(body, length: 2em) = {
|
||||
set text(font: "Source Sans 3")
|
||||
canvas(length: length, body)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#import "elements/electrical/capacitor.typ": capacitor
|
||||
#import "elements/electrical/resistor.typ": resistor
|
||||
@@ -11,8 +11,10 @@
|
||||
#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/electrical/resistor.typ": resistor
|
||||
#import "elements/electrical/capacitor.typ": capacitor
|
||||
#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
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "element.typ"
|
||||
#import "ports.typ": add-port
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "element.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke, radius: 0.5em) = {
|
||||
let f = draw.rect(
|
||||
radius: 0.5em,
|
||||
radius: radius,
|
||||
inset: 0.5em,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
@@ -16,7 +16,8 @@
|
||||
/// Draws a block element
|
||||
///
|
||||
/// #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(
|
||||
x: none,
|
||||
y: none,
|
||||
@@ -27,13 +28,14 @@
|
||||
ports: (),
|
||||
ports-margins: (),
|
||||
fill: none,
|
||||
radius: 0.5em,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = element.elmt(
|
||||
draw-shape: draw-shape,
|
||||
draw-shape: draw-shape.with(radius: radius),
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
#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,
|
||||
vertical: false,
|
||||
gap: 0.2,
|
||||
scales: (100%, 100%),
|
||||
symbols: (none, none)
|
||||
) = {
|
||||
let (x0, y0) = tl
|
||||
let (x1, y1) = br
|
||||
|
||||
let w = x1 - x0
|
||||
let h = y1 - y0
|
||||
|
||||
let (o0, s0) = if vertical {(y0, h)} else {(x0, w)}
|
||||
let (o1, s1) = if vertical {(x0, w)} else {(y0, h)}
|
||||
let m1 = o1 + s1 / 2
|
||||
|
||||
let pt(i, j) = if vertical {
|
||||
(j, i)
|
||||
} else {
|
||||
(i, j)
|
||||
}
|
||||
|
||||
let size0 = s1 * scales.first() / 100%
|
||||
let size1 = s1 * scales.last() / 100%
|
||||
|
||||
if type(gap) == ratio {
|
||||
gap = gap / 100%
|
||||
} else {
|
||||
gap = gap / calc.abs(s0)
|
||||
}
|
||||
let r0 = 0.5 - gap / 2
|
||||
let r1 = 0.5 + gap / 2
|
||||
|
||||
// Coordinates in (main axis, secondary axis) format
|
||||
let p0 = pt(o0, m1)
|
||||
let p1 = pt(o0 + r0 * s0, m1)
|
||||
let p2 = pt(o0 + r1 * s0, m1)
|
||||
let p3 = pt(o0 + s0, m1)
|
||||
|
||||
let p4 = pt(o0 + r0 * s0, m1 - size0 / 2)
|
||||
let p5 = pt(o0 + r0 * s0, m1 + size0 / 2)
|
||||
|
||||
let p6 = pt(o0 + r1 * s0, m1 - size1 / 2)
|
||||
let p7 = pt(o0 + r1 * s0, m1 + size1 / 2)
|
||||
|
||||
let line = draw.line.with(stroke: stroke)
|
||||
let f = draw.group(name: id, {
|
||||
line(p0, p1)
|
||||
line(p2, p3)
|
||||
line(p4, p5)
|
||||
line(p6, p7)
|
||||
|
||||
if symbols.first() != none {
|
||||
draw.content(
|
||||
p1,
|
||||
symbols.first(),
|
||||
anchor: if vertical {"south-west"} else {"south-east"},
|
||||
padding: 2pt
|
||||
)
|
||||
}
|
||||
|
||||
if symbols.last() != none {
|
||||
draw.content(
|
||||
p2,
|
||||
symbols.last(),
|
||||
anchor: if vertical {"north-west"} else {"south-west"},
|
||||
padding: 2pt
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return (f, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
/// Draws a capacitor
|
||||
///
|
||||
/// #examples.capacitor
|
||||
/// For other parameters description, see #doc-ref("element.elmt")
|
||||
/// - vertical (bool): Whether the element is vertical or horizontal.
|
||||
/// - If false, port 0 is placed on the west side and port 1 on the east.\
|
||||
/// - If true, they are on the north, respectively the south sides
|
||||
/// - gap (number, ratio): The gap between both sides
|
||||
/// - if it is a number (int or float), it is interpreted as an absolute canvas-unit length
|
||||
/// - if it is a ratio, it is interpreted as proportional to the capacitor's length (width if horizontal, height if vertical)
|
||||
/// - scales (array): A pair of ratios, the sizes of the sides relative to the capacitor's height (width if vertical).
|
||||
/// - symbols (array): A pair of content or strings (or none values) to attach on the sides of the capacitor
|
||||
#let capacitor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
name: none,
|
||||
name-anchor: "center",
|
||||
vertical: false,
|
||||
gap: 0.2,
|
||||
scales: (100%, 100%),
|
||||
symbols: (none, none),
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
let ports = if vertical {(
|
||||
north: ((id: "0"),),
|
||||
south: ((id: "1"),)
|
||||
)} else {(
|
||||
west: ((id: "0"),),
|
||||
east: ((id: "1"),)
|
||||
)}
|
||||
|
||||
element.elmt(
|
||||
draw-shape: draw-shape.with(
|
||||
vertical: vertical,
|
||||
gap: gap,
|
||||
scales: scales,
|
||||
symbols: symbols
|
||||
),
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
name: name,
|
||||
name-anchor: name-anchor,
|
||||
ports: ports,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
#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,
|
||||
zigzags: 6,
|
||||
vertical: false
|
||||
) = {
|
||||
let (x0, y0) = tl
|
||||
let (x1, y1) = br
|
||||
|
||||
let w = x1 - x0
|
||||
let h = y1 - y0
|
||||
|
||||
let (o0, s0) = if vertical {(y0, h)} else {(x0, w)}
|
||||
let (o1, s1) = if vertical {(x0, w)} else {(y0, h)}
|
||||
let m1 = o1 + s1 / 2
|
||||
|
||||
let pt(i, j) = if vertical {
|
||||
(j, i)
|
||||
} else {
|
||||
(i, j)
|
||||
}
|
||||
|
||||
let p0 = pt(o0, m1)
|
||||
let p1 = pt(o0 + 0.2 * s0, m1)
|
||||
let p2 = pt(o0 + 0.8 * s0, m1)
|
||||
let p3 = pt(o0 + s0, m1)
|
||||
|
||||
if zigzags == none {
|
||||
let p4 = pt(o0 + 0.2 * s0, o1)
|
||||
let p5 = pt(o0 + 0.8 * s0, o1 + s1)
|
||||
let f = draw.group(name: id, {
|
||||
draw.line(p0, p1)
|
||||
draw.line(p2, p3)
|
||||
draw.rect(p4, p5, stroke: stroke, fill: fill)
|
||||
})
|
||||
return (f, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
let pts = (p0, p1)
|
||||
|
||||
for i in range(zigzags) {
|
||||
let r = ((i+0.5) / zigzags * 0.6 + 0.2)
|
||||
let pos = pt(o0 + r * s0, o1 + s1 * calc.rem(i, 2))
|
||||
pts.push(pos)
|
||||
}
|
||||
|
||||
pts += (p2, p3)
|
||||
|
||||
let f = draw.group(name: id, {
|
||||
draw.line(..pts, stroke: stroke)
|
||||
})
|
||||
return (f, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
/// Draws a resistor
|
||||
///
|
||||
/// #examples.resistor
|
||||
/// For other parameters description, see #doc-ref("element.elmt")
|
||||
/// - vertical (bool): Whether the element is vertical or horizontal. If false, port 0 is placed on the west side and port 1 on the east. If true, they are on the north, respectively the south sides
|
||||
/// - zigzags (number, none): Number of zigzags to draw. If none, a rectangle is drawn
|
||||
#let resistor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
name: none,
|
||||
name-anchor: "center",
|
||||
vertical: false,
|
||||
zigzags: 6,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
let ports = if vertical {(
|
||||
north: ((id: "0"),),
|
||||
south: ((id: "1"),)
|
||||
)} else {(
|
||||
west: ((id: "0"),),
|
||||
east: ((id: "1"),)
|
||||
)}
|
||||
|
||||
element.elmt(
|
||||
draw-shape: draw-shape.with(
|
||||
vertical: vertical,
|
||||
zigzags: zigzags
|
||||
),
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
name: name,
|
||||
name-anchor: name-anchor,
|
||||
ports: ports,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "ports.typ": add-ports, add-port
|
||||
#import "../util.typ"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
/// - `id` (`str`): (Required) Port id
|
||||
/// - `name` (`str`): Optional name displayed *in* the block
|
||||
/// - `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
|
||||
/// - 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "element.typ"
|
||||
#import "ports.typ": add-port
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "../util.typ"
|
||||
|
||||
/// Draws a group of elements
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "../ports.typ": add-ports, add-port
|
||||
#import "../element.typ"
|
||||
|
||||
@@ -63,6 +63,12 @@
|
||||
if to == "out" {
|
||||
dy = height / 2
|
||||
} 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
})
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let space = 10%
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "../util.typ"
|
||||
#import "element.typ"
|
||||
#import "ports.typ": add-port
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "../util.typ": rotate-anchor
|
||||
|
||||
#let add-port(
|
||||
@@ -27,6 +27,21 @@
|
||||
// TODO: use context or vectors to have the height relative to the width
|
||||
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(
|
||||
pos,
|
||||
anchor: if name-rotate {rotate-anchor(side)} else {side},
|
||||
|
||||
@@ -3,3 +3,8 @@
|
||||
#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/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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#let version = version(0, 2, 0)
|
||||
#let version = version(0, 2, 1)
|
||||
#let name = "circuiteria"
|
||||
|
||||
#import "circuit.typ": circuit
|
||||
#import "electrical.typ"
|
||||
#import "element.typ"
|
||||
#import "gates.typ"
|
||||
#import "util.typ"
|
||||
#import "verilog.typ"
|
||||
#import "wire.typ"
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "util.typ": opposite-anchor
|
||||
|
||||
/// List of valid wire styles
|
||||
/// #examples.wires
|
||||
#let wire-styles = ("direct", "zigzag", "dodge")
|
||||
#let wire-styles = ("direct", "zigzag", "dodge", "guided")
|
||||
#let signal-width = 1pt
|
||||
#let bus-width = 1.5pt
|
||||
|
||||
@@ -109,6 +109,88 @@
|
||||
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
|
||||
/// - 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.)
|
||||
@@ -127,6 +209,9 @@
|
||||
/// - 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")
|
||||
/// - 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(
|
||||
id, pts,
|
||||
bus: false,
|
||||
@@ -137,6 +222,9 @@
|
||||
dashed: false,
|
||||
style: "direct",
|
||||
reverse: false,
|
||||
guided-center: (50%, 50%),
|
||||
guided-margins: (5%, 5%),
|
||||
guided-sides: ("east", "west"),
|
||||
directed: false,
|
||||
rotate-name: true,
|
||||
zigzag-ratio: 50%,
|
||||
@@ -178,6 +266,14 @@
|
||||
dodge-sides,
|
||||
ctx
|
||||
)
|
||||
} else if style == "guided" {
|
||||
(points, anchors) = get-guided-wire(
|
||||
pts,
|
||||
guided-margins,
|
||||
guided-sides,
|
||||
guided-center,
|
||||
ctx
|
||||
)
|
||||
}
|
||||
|
||||
let mark = (fill: color)
|
||||
@@ -267,7 +363,8 @@
|
||||
/// - 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) = {
|
||||
/// - bus (bool): Whether the stub is a bus (thicker line)
|
||||
#let stub(port-id, side, name: none, vertical: false, length: 1em, name-offset: 0, bus: false) = {
|
||||
let end-offset = (
|
||||
north: (0, length),
|
||||
east: (length, 0),
|
||||
@@ -284,7 +381,8 @@
|
||||
|
||||
draw.line(
|
||||
port-id,
|
||||
(rel: end-offset, to: port-id)
|
||||
(rel: end-offset, to: port-id),
|
||||
stroke: if bus { bus-width } else { signal-width }
|
||||
)
|
||||
if name != none {
|
||||
let text-anchor = if vertical {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "circuiteria"
|
||||
version = "0.2.0"
|
||||
compiler = "0.13.0"
|
||||
version = "0.2.1"
|
||||
compiler = "0.13.1"
|
||||
repository = "https://git.kb28.ch/HEL/circuiteria"
|
||||
entrypoint = "src/lib.typ"
|
||||
authors = [
|
||||
@@ -9,6 +9,6 @@ authors = [
|
||||
]
|
||||
categories = [ "visualization" ]
|
||||
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" ]
|
||||
exclude = [ "gallery", "justfile", "doc" ]
|
||||