VHDL · Chapter 13.4 · Advanced Data Structures
Records as Bus Interfaces
A real bus has a dozen signals, such as address, write data, read data, valid, ready, and strobes, and wiring them individually through a hierarchy is tedious and error-prone. The production fix is to bundle the bus into a record and pass that one typed port between modules. The long port list collapses to a single interface, both sides use the same definition from a package, and adding a field propagates everywhere automatically. The one real subtlety is direction. A plain record flows a single way, but a bus is bidirectional, so the standard pattern splits it into two records, a request bundle from master to slave carrying address and write data, and a response bundle from slave to master carrying read data and ready. This lesson shows how to model bus interfaces with records, the request and response split, and why this makes interconnect maintainable.
Foundation14 min readVHDLRecordsBusInterfacesConnectivityReuse
1. Engineering intuition — wire the bus once, as one thing
Every extra loose signal in a port list is another chance to mis-wire, mis-order, or forget one — and a bus is
many signals that always travel together. So treat the bus as one object: a record that bundles all its
fields. Now a module has a single bus port, the top level connects it with a single signal, and there is no way to
transpose addr and wdata because they live inside one named structure. The only thing you must respect is that
a bus goes both ways — commands out, data back — and a single record only flows one way. The clean answer is two
records: one for each direction.
2. Formal explanation — request/response records for direction
library ieee; use ieee.std_logic_1164.all;
package bus_pkg is
-- REQUEST bundle: everything that flows MASTER → SLAVE.
type bus_req_t is record
addr : std_logic_vector(15 downto 0);
wdata : std_logic_vector(31 downto 0);
write : std_logic;
valid : std_logic;
end record;
-- RESPONSE bundle: everything that flows SLAVE → MASTER.
type bus_rsp_t is record
rdata : std_logic_vector(31 downto 0);
ready : std_logic;
end record;
end package;
-- Each direction is a simple (one-way) record → modes are clean:
-- master: req is OUT, rsp is IN ; slave: req is IN, rsp is OUT.A bus is split into a request record (master→slave) and a response record (slave→master), each declared
once in a package. Because each record now flows a single direction, the in/out port modes are
unambiguous on both sides. (VHDL-2008 adds record mode views to express mixed directions within one record, but
the request/response split is the portable, widely-used idiom.)
3. Production usage — one interface, both sides, the top wires two signals
library work; use work.bus_pkg.all;
library ieee; use ieee.std_logic_1164.all;
-- Master and slave share the SAME interface types; ports collapse to one each per direction.
entity master is port ( clk : in std_logic; req : out bus_req_t; rsp : in bus_rsp_t ); end entity;
entity slave is port ( clk : in std_logic; req : in bus_req_t; rsp : out bus_rsp_t ); end entity;
architecture top of system is
signal req : bus_req_t; -- one signal carries the whole request bundle
signal rsp : bus_rsp_t; -- one signal carries the whole response bundle
begin
u_m : entity work.master port map (clk => clk, req => req, rsp => rsp);
u_s : entity work.slave port map (clk => clk, req => req, rsp => rsp);
-- Inside a module, fields are ordinary signals: req.addr <= a; if req.valid='1' then ...
end architecture;What hardware does this become? Exactly the same wires as listing every bus signal by hand — the record is a
source-level grouping, so req/rsp synthesize to the individual addr/wdata/rdata/ready nets. The win
is at design time: two ports instead of a dozen, no field can be mis-wired, and adding a field (say a byte
strobe) to bus_req_t updates master, slave, and every connection consistently — change one type, not ten port
lists. This is why production interconnect is built from record interfaces.
4. Structural interpretation — request and response records
5. Why this is structural, not timing
A record bus interface is a connectivity/source structure — it bundles the same wires under named fields and
splits direction into request/response — so the structural diagram above tells the story, not a waveform. At run
time the fields behave exactly like the individual bus signals they group (req.valid is just a std_logic,
req.addr a vector), with whatever protocol timing the bus already has. The value is entirely at design time:
fewer ports, no mis-wiring, one place to evolve the interface — a maintainability gain in the netlist's
structure, not a change in behaviour.
6. Debugging example — one record, both directions (the mode clash)
Expected: a clean bidirectional bus interface. Observed: a port-mode error, or read data that cannot be
driven back, when trying to put both directions in a single record port. Root cause: a simple record
flows one way, but the design crammed master-driven and slave-driven fields into one record with a single in/
out mode — so one side cannot drive its fields. Fix: split the bus into two records — a request bundle
(master→slave) and a response bundle (slave→master) — each with a consistent direction and clean modes (or use a
VHDL-2008 record mode view if your tool supports it). Engineering takeaway: never force both directions into
one record port; model a bidirectional bus as request and response records so every field has an unambiguous
direction.
-- BUG: one record with both directions → which way does rdata go? mode clash.
-- type bus_t is record addr, wdata, rdata, ... ; end record; port ( bus : inout bus_t );
-- FIX: two one-way records, clean modes on each side.
port ( req : out bus_req_t; rsp : in bus_rsp_t ); -- master side; slave reverses them7. Common mistakes & what to watch for
- Both directions in one record. A simple record is one-way; split into request/response records (or use 2008 mode views) so modes are clean.
- Declaring the interface per module. Define the record types once in a package; duplicated definitions drift apart.
- Forgetting fields propagate. Adding a field updates everyone using the type — intended, but check all drivers set it (e.g. a new strobe).
- Over-bundling. Group signals that genuinely belong to one interface/direction; do not stuff unrelated signals into a bus record.
- Resolution/driver issues. Each field still follows driver rules; do not multiply-drive a response field from several slaves without arbitration.
8. Engineering insight & continuity
Records as bus interfaces are the production pattern for clean interconnect: bundle a bus into one typed port, share the definition from a package, and let added fields propagate everywhere — solving direction with a request record (master→slave) and a response record (slave→master) so every field's mode is unambiguous. The result is fewer ports, no mis-wiring, and one place to evolve the interface. With composite data structures (arrays of records, multidimensional/unconstrained arrays, record interfaces) in hand, the next lesson applies them to storage in depth — Memory and RAM/ROM Modeling — the coding styles that infer block RAM, distributed RAM, and ROM.