Autogenerating address decoder firmware from uHAL address tables¶
The VHDL code for address decoder logic can be automatically generated from uHAL address tables using the gen_ipbus_addr_decode
script (part of the uHAL tools package, found under /opt/cactus/bin/uhal/tools
after installation). This script creates the source code for a VHDL package (named ipbus_decode_<address file name>
) that defines:
integer constants enumerating elements of the bus array after address decoding; and
a function that determines the index of the relevant slave bus from the bus address.
This package can be used in combination with the ipbus_fabric_sel
entity in order to create IPbus fabric that multiplexes the IPbus signals for several slaves, based on the bus address.
Single-layer example¶
Consider the following address table - my_module.xml
- that consists of two registers, two block memories, and a submodule containing three registers.
<node>
<node id="reg1" mode="single" address="0x0000" fwinfo="endpoint;width=0"/>
<node id="reg2" address="0x0002" fwinfo="endpoint;width=0">
<node id="upper" mask="0xffff0000"/>
<node id="lower" mask="0xffff"/>
</node>
<node id="mem1" address="0x1000" mode="incremental" size="1024" fwinfo="endpoint;width=10"/>
<node id="mem2" address="0x1400" mode="incremental" size="1024" fwinfo="endpoint;width=10"/>
<node id="submodule" address="0x8000" fwinfo="endpoint;width=2">
<node id="reg1" address="0x0"/>
<node id="reg2" address="0x1"/>
<node id="reg3" address="0x2"/>
</node>
</node>
The nodes in the address table that correspond to separate slave buses in the firmware are identified with the fwinfo
attribute. Each ‘leaf’ node from the node tree must either define the fwinfo
attribute itself, or have an ancestor node that defines this attribute. Where this attribute is defined, its value must be either endpoint
or endpoint;width=N
, where N
is the bitwidth of the address range used by slaves connected to that branch of the bus; the width must be specified in this attribute for each ‘leaf’ endpoint in the bus decode tree.
The gen_ipbus_addr_decode
script crawls down the node tree, and creates address decode logic for multiplexing to the first node with a fwinfo
attribute it encounters along all paths down the node tree. For example given the above address table, by running gen_ipbus_addr_decode my_module.xml
, the following VHDL will be printed to stdout:
-- Address decode logic for ipbus fabric
--
-- This file has been AUTOGENERATED from the address table - do not hand edit
--
-- We assume the synthesis tool is clever enough to recognise exclusive conditions
-- in the if statement.
--
-- Dave Newbold, February 2011
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.numeric_std.all;
package ipbus_decode_my_module is
constant IPBUS_SEL_WIDTH: positive := 3;
subtype ipbus_sel_t is std_logic_vector(IPBUS_SEL_WIDTH - 1 downto 0);
function ipbus_sel_my_module(addr : in std_logic_vector(31 downto 0)) return ipbus_sel_t;
-- START automatically generated VHDL the Thu Feb 7 22:57:04 2019
constant N_SLV_REG1: integer := 0;
constant N_SLV_REG2: integer := 1;
constant N_SLV_MEM1: integer := 2;
constant N_SLV_MEM2: integer := 3;
constant N_SLV_SUBMODULE: integer := 4;
constant N_SLAVES: integer := 5;
-- END automatically generated VHDL
end ipbus_decode_my_module;
package body ipbus_decode_my_module is
function ipbus_sel_my_module(addr : in std_logic_vector(31 downto 0)) return ipbus_sel_t is
variable sel: ipbus_sel_t;
begin
-- START automatically generated VHDL the Thu Feb 7 22:57:04 2019
if std_match(addr, "----------------0--0-0--------0-") then
sel := ipbus_sel_t(to_unsigned(N_SLV_REG1, IPBUS_SEL_WIDTH)); -- reg1 / base 0x00000000 / mask 0x00009402
elsif std_match(addr, "----------------0--0-0--------1-") then
sel := ipbus_sel_t(to_unsigned(N_SLV_REG2, IPBUS_SEL_WIDTH)); -- reg2 / base 0x00000002 / mask 0x00009402
elsif std_match(addr, "----------------0--1-0----------") then
sel := ipbus_sel_t(to_unsigned(N_SLV_MEM1, IPBUS_SEL_WIDTH)); -- mem1 / base 0x00001000 / mask 0x00009400
elsif std_match(addr, "----------------0--1-1----------") then
sel := ipbus_sel_t(to_unsigned(N_SLV_MEM2, IPBUS_SEL_WIDTH)); -- mem2 / base 0x00001400 / mask 0x00009400
elsif std_match(addr, "----------------1--0-0----------") then
sel := ipbus_sel_t(to_unsigned(N_SLV_SUBMODULE, IPBUS_SEL_WIDTH)); -- submodule / base 0x00008000 / mask 0x00009400
-- END automatically generated VHDL
else
sel := ipbus_sel_t(to_unsigned(N_SLAVES, IPBUS_SEL_WIDTH));
end if;
return sel;
end function ipbus_sel_my_module;
end ipbus_decode_my_module;
You can see that this VHDL defines a package, ipbus_decode_my_module
, containing:
One integer constant - named
N_SLV_<node id path>
- for each node that has afwinfo
attribute in the above address tableA function,
ipbus_sel_my_module
, that returns the value of the appropriate constant when given the bus address
This automatically-generated package can then be used in combination with the ipbus_fabric_sel
entity in order to multiplex IPbus signals to slaves in VHDL as shown below:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use work.ipbus.all;
use work.ipbus_decode_my_module.all;
entity my_module is
port(
ipb_clk: in std_logic;
ipb_rst: in std_logic;
ipb_in: in ipb_wbus;
ipb_out: out ipb_rbus
);
end my_module;
architecture rtl of my_module is
signal ipbw: ipb_wbus_array(N_SLAVES - 1 downto 0);
signal ipbr: ipb_rbus_array(N_SLAVES - 1 downto 0);
begin
-- ipbus address decode
fabric: entity work.ipbus_fabric_sel
generic map(
NSLV => N_SLAVES,
SEL_WIDTH => IPBUS_SEL_WIDTH)
port map(
ipb_in => ipb_in,
ipb_out => ipb_out,
sel => ipbus_decode_my_module(ipb_in.ipb_addr),
ipb_to_slaves => ipbw,
ipb_from_slaves => ipbr
);
-- Registers
reg1: entity work.ipbus_reg_v
port map(
clk => ipb_clk,
reset => ipb_rst,
ipbus_in => ipbw(N_SLV_REG1),
ipbus_out => ipbr(N_SLV_REG1),
q => open
);
reg2: entity work.ipbus_reg_v
port map(
clk => ipb_clk,
reset => ipb_rst,
ipbus_in => ipbw(N_SLV_REG2),
ipbus_out => ipbr(N_SLV_REG2),
q => open
);
-- Block memories
mem1: entity work.ipbus_dpram
generic map(ADDR_WIDTH => 10)
port map(
clk => ipb_clk,
reset => ipb_rst,
ipbus_in => ipbw(N_SLV_MEM1),
ipbus_out => ipbr(N_SLV_MEM1),
rclk => ipb_clk,
q => open,
addr => (Others => '0')
);
mem2: entity work.ipbus_dpram
generic map(ADDR_WIDTH => 10)
port map(
clk => ipb_clk,
reset => ipb_rst,
ipbus_in => ipbw(N_SLV_MEM2),
ipbus_out => ipbr(N_SLV_MEM2),
rclk => ipb_clk,
q => open,
addr => (Others => '0')
);
-- submodule
submod: entity work.my_submodule
port map(
clk => ipb_clk,
reset => ipb_rst,
ipbus_in => ipbw(N_SLV_SUBMODULE),
ipbus_out => ipbr(N_SLV_SUBMODULE)
);
end rtl;