forked from HEL/circuiteria
Enhance Verilog support by adding Vivado BRAM interface handling and updating documentation
Signed-off-by: Stavros Avramidis <stavros9899@gmail.com>
This commit is contained in:
1 parent
0c1272e35e
commit
e60bfd4b43
5 files changed
+20858
-20353
No files matched your search
@@ -75,8 +75,9 @@ To use this package, simply import [circuiteria](https://typst.app/universe/pack
|
|||||||
|
|
||||||
### Verilog modules
|
### Verilog modules
|
||||||
Blocks can be generated automatically from Verilog/SystemVerilog sources.
|
Blocks can be generated automatically from Verilog/SystemVerilog sources.
|
||||||
AXI4, AXI4-Lite and AXI4-Stream signals are grouped into single bus ports, and
|
AXI4, AXI4-Lite, AXI4-Stream and Vivado BRAM interface signals are grouped
|
||||||
clock/reset signals are detected and placed together:
|
into single bus ports, and clock/reset signals are detected and placed
|
||||||
|
together:
|
||||||
```typ
|
```typ
|
||||||
#circuiteria.circuit({
|
#circuiteria.circuit({
|
||||||
import circuiteria: *
|
import circuiteria: *
|
||||||
|
|||||||
@@ -43,6 +43,21 @@ module axi_demo #(
|
|||||||
input wire m_axis_tready,
|
input wire m_axis_tready,
|
||||||
output wire m_axis_tlast,
|
output wire m_axis_tlast,
|
||||||
|
|
||||||
|
// Vivado BRAM controller interface
|
||||||
|
(* X_INTERFACE_INFO = "xilinx.com:interface:bram:1.0 tile_ram CLK",
|
||||||
|
X_INTERFACE_PARAMETER = "MASTER_TYPE BRAM_CTRL, MEM_ECC NONE, MEM_WIDTH 128,
|
||||||
|
MEM_SIZE 65536, READ_WRITE_MODE READ_ONLY, READ_LATENCY 3" *)
|
||||||
|
output wire tile_ram_clk,
|
||||||
|
(* X_INTERFACE_INFO = "xilinx.com:interface:bram:1.0 tile_ram RST",
|
||||||
|
X_INTERFACE_PARAMETER = "POLARITY ACTIVE_HIGH" *)
|
||||||
|
output wire tile_ram_rst,
|
||||||
|
(* X_INTERFACE_INFO = "xilinx.com:interface:bram:1.0 tile_ram EN" *)
|
||||||
|
output wire tile_ram_en,
|
||||||
|
(* X_INTERFACE_INFO = "xilinx.com:interface:bram:1.0 tile_ram ADDR" *)
|
||||||
|
output wire [15:0] tile_ram_addr,
|
||||||
|
(* X_INTERFACE_INFO = "xilinx.com:interface:bram:1.0 tile_ram DOUT" *)
|
||||||
|
input wire [127:0] tile_ram_dout,
|
||||||
|
|
||||||
input wire enable,
|
input wire enable,
|
||||||
output wire [3:0] status,
|
output wire [3:0] status,
|
||||||
output wire irq
|
output wire irq
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 40 KiB |
+20722
-20310
File diff suppressed because it is too large.
Load diff
+118
-41
@@ -40,19 +40,33 @@
|
|||||||
#let _split-commas(text) = {
|
#let _split-commas(text) = {
|
||||||
let items = ()
|
let items = ()
|
||||||
let depth = 0
|
let depth = 0
|
||||||
|
let attr-depth = 0
|
||||||
|
let in-string = false
|
||||||
let cur = ()
|
let cur = ()
|
||||||
for c in text.clusters() {
|
let chars = text.clusters()
|
||||||
if c in ("(", "[", "{") {
|
let i = 0
|
||||||
|
while i < chars.len() {
|
||||||
|
let c = chars.at(i)
|
||||||
|
let next = if i + 1 < chars.len() { chars.at(i + 1) } else { none }
|
||||||
|
if c == "\"" {
|
||||||
|
in-string = not in-string
|
||||||
|
}
|
||||||
|
if not in-string and c == "(" and next == "*" {
|
||||||
|
attr-depth += 1
|
||||||
|
} else if not in-string and c == "*" and next == ")" and attr-depth > 0 {
|
||||||
|
attr-depth -= 1
|
||||||
|
} else if not in-string and attr-depth == 0 and c in ("(", "[", "{") {
|
||||||
depth += 1
|
depth += 1
|
||||||
} else if c in (")", "]", "}") {
|
} else if not in-string and attr-depth == 0 and c in (")", "]", "}") {
|
||||||
depth -= 1
|
depth -= 1
|
||||||
}
|
}
|
||||||
if c == "," and depth <= 0 {
|
if c == "," and depth <= 0 and attr-depth == 0 and not in-string {
|
||||||
items.push(cur.join())
|
items.push(cur.join())
|
||||||
cur = ()
|
cur = ()
|
||||||
} else {
|
} else {
|
||||||
cur.push(c)
|
cur.push(c)
|
||||||
}
|
}
|
||||||
|
i += 1
|
||||||
}
|
}
|
||||||
if cur.join().trim() != "" { items.push(cur.join()) }
|
if cur.join().trim() != "" { items.push(cur.join()) }
|
||||||
return items.filter(it => it.trim() != "")
|
return items.filter(it => it.trim() != "")
|
||||||
@@ -92,13 +106,42 @@
|
|||||||
return calc.abs(hi - lo) + 1
|
return calc.abs(hi - lo) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#let _make-port(name, dir, dims) = {
|
#let _parse-attributes(text) = {
|
||||||
|
let attrs = (:)
|
||||||
|
for m in text.matches(regex("(?s)\\(\\*\\s*(.*?)\\s*\\*\\)")) {
|
||||||
|
let body = m.captures.at(0)
|
||||||
|
let info = body.match(regex("X_INTERFACE_INFO\\s*=\\s*\"([^\"]+)\""))
|
||||||
|
if info != none {
|
||||||
|
let value = info.captures.at(0)
|
||||||
|
attrs.insert("x-interface-info", value)
|
||||||
|
let parsed = value.match(regex("(?i)^[^\\s:]+:interface:([^:\\s]+):([^\\s]+)\\s+(\\S+)\\s+(\\S+)"))
|
||||||
|
if parsed != none {
|
||||||
|
attrs.insert("interface", (
|
||||||
|
kind: lower(parsed.captures.at(0)),
|
||||||
|
version: parsed.captures.at(1),
|
||||||
|
name: parsed.captures.at(2),
|
||||||
|
signal: lower(parsed.captures.at(3))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let params = body.match(regex("X_INTERFACE_PARAMETER\\s*=\\s*\"([^\"]+)\""))
|
||||||
|
if params != none {
|
||||||
|
attrs.insert("x-interface-parameter", params.captures.at(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
#let _strip-attributes(text) = text.replace(regex("(?s)\\(\\*.*?\\*\\)"), " ")
|
||||||
|
|
||||||
|
#let _make-port(name, dir, dims, attrs: (:)) = {
|
||||||
let range = _first-range(dims)
|
let range = _first-range(dims)
|
||||||
return (
|
return (
|
||||||
name: name,
|
name: name,
|
||||||
dir: dir,
|
dir: dir,
|
||||||
range: range,
|
range: range,
|
||||||
width: _range-width(range)
|
width: _range-width(range),
|
||||||
|
attrs: attrs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +154,9 @@
|
|||||||
let last-dir = "input"
|
let last-dir = "input"
|
||||||
let last-dims = none
|
let last-dims = none
|
||||||
for item in _split-commas(portlist) {
|
for item in _split-commas(portlist) {
|
||||||
let m = item.match(regex(
|
let attrs = _parse-attributes(item)
|
||||||
|
let clean = _strip-attributes(item)
|
||||||
|
let m = clean.match(regex(
|
||||||
"^\\s*(?:(input|output|inout)\\s+)?(?:" + _net-kw + "\\s+)*" +
|
"^\\s*(?:(input|output|inout)\\s+)?(?:" + _net-kw + "\\s+)*" +
|
||||||
"((?:\\[[^\\]]*\\]\\s*)*)(" + _ident + ")"
|
"((?:\\[[^\\]]*\\]\\s*)*)(" + _ident + ")"
|
||||||
))
|
))
|
||||||
@@ -123,7 +168,7 @@
|
|||||||
} else if dims == none or dims.trim() == "" {
|
} else if dims == none or dims.trim() == "" {
|
||||||
dims = last-dims
|
dims = last-dims
|
||||||
}
|
}
|
||||||
ports.push(_make-port(name, last-dir, dims))
|
ports.push(_make-port(name, last-dir, dims, attrs: attrs))
|
||||||
}
|
}
|
||||||
return ports
|
return ports
|
||||||
}
|
}
|
||||||
@@ -131,15 +176,16 @@
|
|||||||
#let _parse-body-ports(portlist, body) = {
|
#let _parse-body-ports(portlist, body) = {
|
||||||
let decls = (:)
|
let decls = (:)
|
||||||
for m in body.matches(regex(
|
for m in body.matches(regex(
|
||||||
"(?s)\\b(input|output|inout)\\b((?:\\s+" + _net-kw + ")*)\\s*" +
|
"(?s)((?:\\(\\*.*?\\*\\)\\s*)*)\\b(input|output|inout)\\b((?:\\s+" + _net-kw + ")*)\\s*" +
|
||||||
"((?:\\[[^\\]]*\\]\\s*)*)([^;]*);"
|
"((?:\\[[^\\]]*\\]\\s*)*)([^;]*);"
|
||||||
)) {
|
)) {
|
||||||
let dir = m.captures.at(0)
|
let attrs = _parse-attributes(m.captures.at(0))
|
||||||
let dims = m.captures.at(2)
|
let dir = m.captures.at(1)
|
||||||
for n in _split-commas(m.captures.at(3)) {
|
let dims = m.captures.at(3)
|
||||||
|
for n in _split-commas(m.captures.at(4)) {
|
||||||
let nm = n.match(regex("^\\s*(" + _ident + ")\\s*$"))
|
let nm = n.match(regex("^\\s*(" + _ident + ")\\s*$"))
|
||||||
if nm == none { continue }
|
if nm == none { continue }
|
||||||
decls.insert(nm.captures.at(0), _make-port(nm.captures.at(0), dir, dims))
|
decls.insert(nm.captures.at(0), _make-port(nm.captures.at(0), dir, dims, attrs: attrs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +309,10 @@
|
|||||||
"rid", "rdata", "rresp", "rlast", "ruser", "rvalid", "rready"
|
"rid", "rdata", "rresp", "rlast", "ruser", "rvalid", "rready"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#let bram-suffixes = (
|
||||||
|
"clk", "rst", "en", "we", "wen", "regce", "addr", "din", "dout"
|
||||||
|
)
|
||||||
|
|
||||||
// Signals that only exist on full AXI4 (used to tell AXI4 from AXI4-Lite)
|
// Signals that only exist on full AXI4 (used to tell AXI4 from AXI4-Lite)
|
||||||
#let axi-full-only = (
|
#let axi-full-only = (
|
||||||
"awid", "awlen", "awsize", "awburst", "awlock", "awcache", "awqos",
|
"awid", "awlen", "awsize", "awburst", "awlock", "awcache", "awqos",
|
||||||
@@ -278,6 +328,14 @@
|
|||||||
#let _is-reset(name) = name.match(_reset-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
|
#let _is-active-low(name) = name.match(regex("(?i)(_n|_b|_l|resetn|rstn)$")) != none
|
||||||
|
|
||||||
|
#let _reset-active-low(port) = {
|
||||||
|
let attrs = port.at("attrs", default: (:))
|
||||||
|
let params = attrs.at("x-interface-parameter", default: "")
|
||||||
|
if params.match(regex("(?i)POLARITY\\s+ACTIVE_LOW")) != none { return true }
|
||||||
|
if params.match(regex("(?i)POLARITY\\s+ACTIVE_HIGH")) != none { return false }
|
||||||
|
return _is-active-low(port.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Splits a signal name into (prefix, suffix) using the given suffix table
|
// Splits a signal name into (prefix, suffix) using the given suffix table
|
||||||
#let _match-suffix(name, suffixes) = {
|
#let _match-suffix(name, suffixes) = {
|
||||||
let lower = lower(name)
|
let lower = lower(name)
|
||||||
@@ -302,12 +360,25 @@
|
|||||||
let rest = ()
|
let rest = ()
|
||||||
for port in ports {
|
for port in ports {
|
||||||
let kind = none
|
let kind = none
|
||||||
let m = _match-suffix(port.name, axis-suffixes)
|
let m = none
|
||||||
|
let attrs = port.at("attrs", default: (:))
|
||||||
|
let interface = attrs.at("interface", default: none)
|
||||||
|
if interface != none and interface.kind == "bram" {
|
||||||
|
kind = "bram"
|
||||||
|
m = (prefix: interface.name, suffix: interface.signal)
|
||||||
|
} else {
|
||||||
|
m = _match-suffix(port.name, axis-suffixes)
|
||||||
if m != none {
|
if m != none {
|
||||||
kind = "axi-stream"
|
kind = "axi-stream"
|
||||||
} else {
|
} else {
|
||||||
m = _match-suffix(port.name, axi-suffixes)
|
m = _match-suffix(port.name, axi-suffixes)
|
||||||
if m != none { kind = "axi" }
|
if m != none {
|
||||||
|
kind = "axi"
|
||||||
|
} else {
|
||||||
|
m = _match-suffix(port.name, bram-suffixes)
|
||||||
|
if m != none { kind = "bram" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if m == none {
|
if m == none {
|
||||||
rest.push(port)
|
rest.push(port)
|
||||||
@@ -342,6 +413,8 @@
|
|||||||
if "tvalid" not in sig and not ("tdata" in sig and "tready" in sig) {
|
if "tvalid" not in sig and not ("tdata" in sig and "tready" in sig) {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
} else if kind == "bram" {
|
||||||
|
if "addr" not in sig or ("din" not in sig and "dout" not in sig) { return none }
|
||||||
} else {
|
} else {
|
||||||
if sig.len() < 4 { return none }
|
if sig.len() < 4 { return none }
|
||||||
if "awvalid" not in sig and "arvalid" not in sig { return none }
|
if "awvalid" not in sig and "arvalid" not in sig { return none }
|
||||||
@@ -353,6 +426,8 @@
|
|||||||
|
|
||||||
let ref = if kind == "axi-stream" {
|
let ref = if kind == "axi-stream" {
|
||||||
sig.at("tvalid", default: sig.at("tdata", default: none))
|
sig.at("tvalid", default: sig.at("tdata", default: none))
|
||||||
|
} else if kind == "bram" {
|
||||||
|
sig.at("addr", default: sig.at("din", default: sig.at("dout", default: none)))
|
||||||
} else {
|
} else {
|
||||||
sig.at("awvalid", default: sig.at("arvalid", default: none))
|
sig.at("awvalid", default: sig.at("arvalid", default: none))
|
||||||
}
|
}
|
||||||
@@ -363,19 +438,19 @@
|
|||||||
prefix: group.prefix,
|
prefix: group.prefix,
|
||||||
role: role,
|
role: role,
|
||||||
dir: if role == "master" { "output" } else { "input" },
|
dir: if role == "master" { "output" } else { "input" },
|
||||||
data-width: _bus-width(group, "tdata", "wdata", "rdata"),
|
data-width: _bus-width(group, "tdata", "wdata", "rdata", "din", "dout"),
|
||||||
addr-width: _bus-width(group, "awaddr", "araddr"),
|
addr-width: _bus-width(group, "awaddr", "araddr", "addr"),
|
||||||
signals: sig,
|
signals: sig,
|
||||||
ports: sig.values()
|
ports: sig.values()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyses the ports of a module and infers AXI4, AXI4-Lite and AXI4-Stream
|
/// Analyses the ports of a module and infers AXI4, AXI4-Lite, AXI4-Stream and
|
||||||
/// buses as well as clock and reset signals.
|
/// BRAM buses as well as clock and reset signals.
|
||||||
///
|
///
|
||||||
/// The returned dictionary contains:
|
/// The returned dictionary contains:
|
||||||
/// - `buses`: array of inferred buses. Each bus has the fields `kind`
|
/// - `buses`: array of inferred buses. Each bus has the fields `kind`
|
||||||
/// (`"axi"`, `"axi-lite"` or `"axi-stream"`), `prefix`, `role`
|
/// (`"axi"`, `"axi-lite"`, `"axi-stream"` or `"bram"`), `prefix`, `role`
|
||||||
/// (`"master"` or `"slave"`), `dir`, `data-width`, `addr-width` and `signals`
|
/// (`"master"` or `"slave"`), `dir`, `data-width`, `addr-width` and `signals`
|
||||||
/// - `clocks`: array of clock ports
|
/// - `clocks`: array of clock ports
|
||||||
/// - `resets`: array of reset ports (with an additional `active-low` field)
|
/// - `resets`: array of reset ports (with an additional `active-low` field)
|
||||||
@@ -387,32 +462,32 @@
|
|||||||
#let analyze(module, name: none) = {
|
#let analyze(module, name: none) = {
|
||||||
let module = get-module(module, name: name)
|
let module = get-module(module, name: name)
|
||||||
|
|
||||||
let clocks = ()
|
let (groups, rest) = _group-signals(module.ports)
|
||||||
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 buses = ()
|
||||||
let signals = rest
|
let ungrouped = rest
|
||||||
for (_, group) in groups {
|
for (_, group) in groups {
|
||||||
let bus = _finalize-bus(group)
|
let bus = _finalize-bus(group)
|
||||||
if bus == none {
|
if bus == none {
|
||||||
signals += group.signals.values()
|
ungrouped += group.signals.values()
|
||||||
} else {
|
} else {
|
||||||
buses.push(bus)
|
buses.push(bus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buses = buses.sorted(key: it => it.prefix)
|
buses = buses.sorted(key: it => it.prefix)
|
||||||
|
|
||||||
|
let clocks = ()
|
||||||
|
let resets = ()
|
||||||
|
let signals = ()
|
||||||
|
for port in ungrouped {
|
||||||
|
if _is-clock(port.name) {
|
||||||
|
clocks.push(port)
|
||||||
|
} else if _is-reset(port.name) {
|
||||||
|
resets.push(port + (active-low: _reset-active-low(port)))
|
||||||
|
} else {
|
||||||
|
signals.push(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
name: module.name,
|
name: module.name,
|
||||||
params: module.params,
|
params: module.params,
|
||||||
@@ -431,7 +506,8 @@
|
|||||||
#let bus-kind-names = (
|
#let bus-kind-names = (
|
||||||
"axi": "AXI4",
|
"axi": "AXI4",
|
||||||
"axi-lite": "AXI4-Lite",
|
"axi-lite": "AXI4-Lite",
|
||||||
"axi-stream": "AXI4-Stream"
|
"axi-stream": "AXI4-Stream",
|
||||||
|
"bram": "BRAM"
|
||||||
)
|
)
|
||||||
|
|
||||||
#let _bus-label(bus, show-widths: true, show-info: false) = {
|
#let _bus-label(bus, show-widths: true, show-info: false) = {
|
||||||
@@ -546,8 +622,9 @@
|
|||||||
|
|
||||||
/// Draws a block from a Verilog module description.
|
/// Draws a block from a Verilog module description.
|
||||||
///
|
///
|
||||||
/// AXI4, AXI4-Lite and AXI4-Stream signals are grouped into single bus ports,
|
/// AXI4, AXI4-Lite, AXI4-Stream and BRAM signals are grouped into single bus
|
||||||
/// and clock/reset signals are automatically detected and grouped together.
|
/// ports, and clock/reset signals are automatically detected and grouped
|
||||||
|
/// together.
|
||||||
///
|
///
|
||||||
/// ```typ
|
/// ```typ
|
||||||
/// #circuiteria.circuit({
|
/// #circuiteria.circuit({
|
||||||
@@ -669,7 +746,7 @@
|
|||||||
// active-low ports carry a bubble, so their stub starts past it
|
// 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 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 ends = (north: (0, stub-length), east: (stub-length, 0), south: (0, -stub-length), west: (-stub-length, 0))
|
||||||
let gap = 0.1
|
let gap = 0.2
|
||||||
for (side, side-ports) in ports {
|
for (side, side-ports) in ports {
|
||||||
for port in side-ports {
|
for port in side-ports {
|
||||||
let anchor = id + "-port-" + port.id
|
let anchor = id + "-port-" + port.id
|
||||||
@@ -687,7 +764,7 @@
|
|||||||
)
|
)
|
||||||
for k in (1, 0, -1) {
|
for k in (1, 0, -1) {
|
||||||
let start = (rel: (perp.at(0) * k, perp.at(1) * k), to: anchor)
|
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)
|
draw.line(start, (rel: ends.at(side), to: start), stroke:( thickness: wire.signal-width, dash: if k != 0 { "solid" } else { "densely-dashed"}) , )
|
||||||
}
|
}
|
||||||
} else if port.at("invert", default: false) {
|
} else if port.at("invert", default: false) {
|
||||||
draw.line(
|
draw.line(
|
||||||
|
|||||||
Reference in new issue
Block a user