Packaging VHDL-2019 IPs for Vivado IP Integrator
TL;DR. When an IP uses VHDL-2019 mode views or record-typed ports internally, it cannot be packaged for Vivado IP-XACT directly — Vivado 2024.1's
ipx::package_projectparses VHDL in strict VHDL-93 mode. The routertl-blessed convention is a wrapper-for-packaging-only: the IP stays VHDL-2019 with records + views; a thin sibling wrapper with flatstd_logic_vectorports is written specifically for the packaging step. Native routertl consumers bind to the VHDL-2019 entity.
This guide covers the convention, the canonical example shipped with the registry, and when to write a wrapper.
The Vivado Limitation
VHDL-2019 (IEEE 1076-2019) introduced mode views — a clean way to group interface direction on a record type:
view AxisMaster of AxisRec is TData : out; TValid : out; TReady : in; TLast : out; end view;
This is the modern idiom for AXI / AXIS / AXIL-style bus records. It
keeps the entity port list short, makes direction obvious, and matches
the ESA + IEEE 1076.6 synth-subset conventions captured in the
esa-vhdl-* lint profiles.
Vivado 2024.1's ipx::package_project does not parse this syntax.
The packager runs the source through a VHDL-93 parser:
CRITICAL WARNING [HDL 9-1206] Syntax error near view. ERROR [IP_Flow 19-734] Port type viewAxisSlave is not recognized. Only std_logic and std_logic_vector types are allowed for ports.
This is a Vivado limitation, not a routertl bug — and it shows up consistently across Xilinx tool versions. AMD has not publicly committed to a fix timeline.
The Convention: Wrapper-For-Packaging-Only
The routertl-blessed answer (Decision D.113, ratified 2026-05-18):
- Internal
src/stays VHDL-2019. Records, mode views, packages — no compromise. The IP keeps the modern shape. - If the IP needs to ship via IP-XACT, add a sibling
<ip-name>_ip.vhd— a thin VHDL-93-port wrapper that exists only for therr ip packagestep. - The wrapper is packaging-only. Direct routertl consumers
(
rr pkg add, in-projectbuild.ips) still bind to the VHDL-2019 entity with records. There is no flattening tax on native users. rr ip package --ip-dir <ip-wrapper>is the canonical invocation. Point the packager at the wrapper IP's directory, not the underlying VHDL-2019 IP — the wrapper'sip.ymldeclares its ownhdl.top_moduleandsynthesis.sources.
The cost lives at the IP-to-vendor boundary, not at the IP-to-routertl boundary.
Canonical Example: eth_mac_rmii_ip
The vhdl-ethernet repo ships a working example at:
vhdl-ethernet/example/zybo_z7_10_rmii/ip/eth_mac_rmii_ip/ ├── ip.yml └── src/ └── eth_mac_rmii_ip.vhd ← the wrapper (199 lines)
The wrapper instantiates EthMac100MRmii (the VHDL-2019 entity with
records + mode views) and exposes flat std_logic_vector ports that
match Vivado's bus-interface auto-inference patterns:
entity eth_mac_rmii_ip is port ( -- AXI4-Lite slave (Vivado auto-infers from s_axi_* prefix) s_axi_aclk : in std_logic; s_axi_aresetn : in std_logic; s_axi_awaddr : in std_logic_vector(31 downto 0); s_axi_awvalid : in std_logic; s_axi_awready : out std_logic; -- ... rest of the AXI4-Lite channel ... -- RMII PHY pins (external in BD) rmii_txd : out std_logic_vector(1 downto 0); rmii_tx_en : out std_logic; rmii_rxd : in std_logic_vector(1 downto 0); rmii_crs_dv : in std_logic; -- MDIO 3-wire (IOBUF at BD top — IP exposes i/o/t separately) mdc : out std_logic; mdio_i : in std_logic; mdio_o : out std_logic; mdio_t : out std_logic; -- Status + IRQ tx_busy : out std_logic; rx_busy : out std_logic; rx_error : out std_logic; irq : out std_logic ); end entity eth_mac_rmii_ip;
The architecture body packs the flat signals into the AxilRec record
at the boundary, then instantiates EthMac100MRmii. That's the whole
shape — no behavioural logic in the wrapper, just port-shape adaptation.
What Vivado Auto-Infers From The Port Names
| Port-name pattern | Inferred IP-XACT interface |
|---|---|
s_axi_* | AXI4-Lite / AXI4 slave |
m_axi_* | AXI4-Lite / AXI4 master |
*_tdata *_tvalid … | AXI-Stream |
s_axi_aclk | clock, ASSOCIATED_BUSIF=s_axi |
s_axi_aresetn | reset (active-low), assoc with bus |
| Anything else | exposed as an external pin |
Stick to these patterns and Vivado does the heavy lifting in the
address editor — no manual set_property bus_definition_ref chains
needed.
When To Write A Wrapper
Write a <ip-name>_ip.vhd wrapper only if all three are true:
- The IP's top entity has at least one record-typed or view-typed port.
- The IP needs to ship via IP-XACT (for Vivado IP Integrator,
for a customer who consumes IP-XACT, or for the registry's
packagingblock). - The downstream BD / project will instantiate it as a packaged IP (not as a module-reference cell).
If the IP only ever flows into other routertl projects via
rr pkg add, no wrapper is needed. The dependency resolver and
sim backends handle VHDL-2019 records fine; only the IP-XACT packager
chokes on them.
ip.yml Shape For A Wrapper IP
The wrapper IP gets its own ip.yml with:
hdl.top_moduleset to the wrapper entity (not the underlying VHDL-2019 entity).synthesis.sourceslisting the dependency-ordered source set — the underlying VHDL-2019 packages and entity first, then the wrapper last.ip.vendor/ip.library/ip.versionfor IP-XACT identity.
The eth example's manifest:
ip:
name: eth_mac_rmii_ip
vendor: routertl
library: vhdl_ethernet
version: 0.1.0
language: vhdl
hdl:
top_module: eth_mac_rmii_ip # the wrapper entity, NOT EthMac100MRmii
paths:
sources: src
synthesis:
mode: rtl
sources:
# Underlying VHDL-2019 packages + entity (dep-ordered)
- ../../../../src/packages/axil_pkg.vhd
- ../../../../src/packages/axis_pkg.vhd
# ... rest of the lib ...
- ../../../../src/eth/eth_mac_100m_rmii.vhd
# The wrapper itself (last)
- src/eth_mac_rmii_ip.vhd
ips: []
The ../ ascents into the parent repo's HDL library work — rr ip package reads sources relative to this ip.yml (the wrapper IP's
directory), so the resolver follows the chain.
Packaging Invocation
From the project root:
rr ip package --ip-dir ip/eth_mac_rmii_ip --part xc7z020clg400-1
The --ip-dir flag (RTL-P2.579 / D.112) is the recommended shape —
explicit + uniform with the rest of rr. The positional IP_PATH
form still works for back-compat:
rr ip package ip/eth_mac_rmii_ip --part xc7z020clg400-1
Both resolve against the project root (rr chdirs there on startup).
Add --dry-run to inspect the generated TCL before letting Vivado run.
Why Not Alternative Approaches?
Two paths considered and rejected:
Strip-and-substitute in rr ip package. Pre-process the VHDL-2019
sources before handing them to Vivado IPX, replacing mode views with
flat record + port-direction tags. Rejected because it (a) makes
routertl responsible for parsing arbitrary VHDL-2019, (b) hides the
upstream Vivado bug from users, and (c) introduces a mismatch between
what rr lint sees and what the packager sees.
Skip IPX entirely. Use BD create_bd_cell -type module -reference <entity> instead of -type ip -vlnv …. This works inside one project
(Vivado handles VHDL-2019 fine in synth and BD; only the packager
chokes). Rejected as the registry-distribution default because the
output is not IP-XACT and cannot be redistributed to non-routertl
users or shipped to customers expecting component.xml.
The wrapper convention preserves both routertl-native ergonomics (VHDL-2019 stays) and IP-XACT compatibility (Vivado gets what it can parse).
Future Work
rr ip wrappercodegen — synthesise the wrapper automatically from the underlying entity + a declarative hint file mapping records to bus interfaces. Deferred until the manual pattern stabilises across 2–3 IPs (RTL-P3.523 sub-ticket).- Lint rule
vhdl2019-views-without-package-wrapper— warn atrr ip packagetime when the top entity has view-typed ports and no*_ip.vhdcompanion is found in the same IP dir (filed as a follow-up to RTL-P3.523).
References
- RTL-P3.523 — wrapper-for-packaging convention ticket.
- RTL-P2.579 —
rr ip packagewalk-up fix (closed 2026-05-18); the blocker that made wrapper-IP packaging possible. - Decision D.112 —
rr ip packageexplicit--ip-dirflag. - Decision D.113 — wrapper-for-packaging convention (this guide).
- IEEE 1076-2019 — VHDL standard introducing mode views.
- Vivado 2024.1 user guide UG835 —
ipx::TCL reference.