Add APB support

Signed-off-by: Stavros Avramidis <stavros9899@gmail.com>
This commit is contained in:
purpl3F0x committed 2026-11-03 13:03:22 +02:00
1 parent e60bfd4b43
commit 58f821070a
5 files changed
+9486 -36690

No files matched your search

+4 -3
View File
@@ -75,8 +75,8 @@ To use this package, simply import [circuiteria](https://typst.app/universe/pack
### Verilog modules
Blocks can be generated automatically from Verilog/SystemVerilog sources.
AXI4, AXI4-Lite, AXI4-Stream and Vivado BRAM interface signals are grouped
into single bus ports, and clock/reset signals are detected and placed
AXI4, AXI4-Lite, AXI4-Stream, APB and Vivado BRAM interface signals are
grouped into single bus ports, and clock/reset signals are detected and placed
together:
```typ
#circuiteria.circuit({
@@ -85,4 +85,5 @@ together:
})
```
The port ids are the signal names, or the bus prefixes for inferred buses
(e.g. `"my_module-port-s_axi"`).
(e.g. `"my_module-port-s_axi"`). Unprefixed buses (e.g. bare `PADDR`, `PSEL`,
...) are named after their kind (`"my_module-port-apb"`).
+10
View File
@@ -43,6 +43,16 @@ module axi_demo #(
input wire m_axis_tready,
output wire m_axis_tlast,
// APB master
output wire [ADDR_WIDTH-1:0] m_apb_paddr,
output wire m_apb_psel,
output wire m_apb_penable,
output wire m_apb_pwrite,
output wire [31:0] m_apb_pwdata,
input wire [31:0] m_apb_prdata,
input wire m_apb_pready,
input wire m_apb_pslverr,
// 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,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+9428 -36664
View File
File diff suppressed because it is too large. Load diff
+44 -23
View File
@@ -309,6 +309,13 @@
"rid", "rdata", "rresp", "rlast", "ruser", "rvalid", "rready"
)
// AMBA APB3/APB4/APB5 signals ("pslverror" is the SpinalHDL spelling)
#let apb-suffixes = (
"paddr", "pprot", "pnse", "psel", "penable", "pwrite", "pwdata", "pstrb",
"pready", "prdata", "pslverr", "pslverror", "pwakeup",
"pauser", "pwuser", "pruser", "pbuser"
)
#let bram-suffixes = (
"clk", "rst", "en", "we", "wen", "regce", "addr", "din", "dout"
)
@@ -321,8 +328,9 @@
"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])")
// `pclk`/`presetn` are the APB names (a bare `preset` is too common a word)
#let _clock-re = regex("(?i)(^|[^a-z0-9])([ap]?clk|clock)([0-9]*)($|[^a-z0-9])")
#let _reset-re = regex("(?i)(^|[^a-z0-9])(a?rst|a?reset|preset_?n)(_?n)?([0-9]*)($|[^a-z0-9])")
#let _is-clock(name) = name.match(_clock-re) != none
#let _is-reset(name) = name.match(_reset-re) != none
@@ -355,6 +363,17 @@
return (prefix: prefix.trim("_", at: end), suffix: best)
}
// Suffix tables tried in order when grouping ports by name
#let _bus-suffixes = (
("axi-stream", axis-suffixes),
("axi", axi-suffixes),
("apb", apb-suffixes),
("bram", bram-suffixes)
)
// Vivado interface types (from `X_INTERFACE_INFO`) mapped to bus kinds
#let _interface-kinds = ("apb": "apb", "bram": "bram")
#let _group-signals(ports) = {
let groups = (:)
let rest = ()
@@ -363,20 +382,15 @@
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"
if interface != none and interface.kind in _interface-kinds {
kind = _interface-kinds.at(interface.kind)
m = (prefix: interface.name, suffix: interface.signal)
} else {
m = _match-suffix(port.name, axis-suffixes)
if m != none {
kind = "axi-stream"
} else {
m = _match-suffix(port.name, axi-suffixes)
for (k, suffixes) in _bus-suffixes {
m = _match-suffix(port.name, suffixes)
if m != none {
kind = "axi"
} else {
m = _match-suffix(port.name, bram-suffixes)
if m != none { kind = "bram" }
kind = k
break
}
}
}
@@ -415,6 +429,8 @@
}
} else if kind == "bram" {
if "addr" not in sig or ("din" not in sig and "dout" not in sig) { return none }
} else if kind == "apb" {
if "paddr" not in sig or "penable" not in sig { return none }
} else {
if sig.len() < 4 { return none }
if "awvalid" not in sig and "arvalid" not in sig { return none }
@@ -428,6 +444,8 @@
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 if kind == "apb" {
sig.paddr
} else {
sig.at("awvalid", default: sig.at("arvalid", default: none))
}
@@ -438,19 +456,19 @@
prefix: group.prefix,
role: role,
dir: if role == "master" { "output" } else { "input" },
data-width: _bus-width(group, "tdata", "wdata", "rdata", "din", "dout"),
addr-width: _bus-width(group, "awaddr", "araddr", "addr"),
data-width: _bus-width(group, "tdata", "wdata", "rdata", "din", "dout", "pwdata", "prdata"),
addr-width: _bus-width(group, "awaddr", "araddr", "addr", "paddr"),
signals: sig,
ports: sig.values()
)
}
/// Analyses the ports of a module and infers AXI4, AXI4-Lite, AXI4-Stream and
/// BRAM buses as well as clock and reset signals.
/// Analyses the ports of a module and infers AXI4, AXI4-Lite, AXI4-Stream,
/// APB and BRAM 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"`, `"axi-stream"` or `"bram"`), `prefix`, `role`
/// (`"axi"`, `"axi-lite"`, `"axi-stream"`, `"apb"` or `"bram"`), `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)
@@ -507,6 +525,7 @@
"axi": "AXI4",
"axi-lite": "AXI4-Lite",
"axi-stream": "AXI4-Stream",
"apb": "APB",
"bram": "BRAM"
)
@@ -574,7 +593,8 @@
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,
// unprefixed buses (e.g. bare `PADDR`, `PSEL`, ...) are named by their kind
id: if bus.prefix != "" { bus.prefix } else { bus.kind },
name: label.label,
label-len: label.len,
bus: true,
@@ -622,8 +642,8 @@
/// Draws a block from a Verilog module description.
///
/// AXI4, AXI4-Lite, AXI4-Stream and BRAM signals are grouped into single bus
/// ports, and clock/reset signals are automatically detected and grouped
/// AXI4, AXI4-Lite, AXI4-Stream, APB and BRAM signals are grouped into single
/// bus ports, and clock/reset signals are automatically detected and grouped
/// together.
///
/// ```typ
@@ -632,8 +652,9 @@
/// })
/// ```
///
/// The port ids are the signal names (or the bus prefixes), so wires can be
/// attached with `"<id>-port-<signal>"`.
/// The port ids are the signal names (or the bus prefixes, or the bus kind
/// such as `"apb"` for unprefixed buses), 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)