.. _firmware-address-decoder-generation:
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_
``) 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.
.. code-block:: xml
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:
.. code-block:: vhdl
-- 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_`` - for each node that has a ``fwinfo`` attribute in the above address table
* A 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:
.. code-block:: vhdl
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;