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:
purpl3F0x committed 2026-10-25 11:28:17 +02:00
1 parent 0c1272e35e
commit e60bfd4b43
5 files changed
+20858 -20353

No files matched your search

+3 -2
View File
@@ -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: *
+15
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+118 -41
View File
@@ -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(