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 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:

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;