Reply by KJ February 16, 20182018-02-16
For maintenance of addresses, bus width and stuff I use a spreadsheet as the code generator then simply copy/paste into the VHDL.  While not as automatic as using some other language as a code generator, the bar is much lower for someone else to pick up and run with starting from a spreadsheet than from a possibly new, foreign language.

For instantiation of the bus interface logic itself, I instantiate one instance inside a generate statement indexed by the enumeration that lists the components being connected for the 'write' data path. Very simple.  For the 'read' data path, a single component that works with an array of data to mux together to produce the final readback data.

Kevin Jennings
Reply by Benjamin Couillard February 16, 20182018-02-16
Le dimanche 11 février 2018 10:48:55 UTC-5, wza...@gmail.com a écrit :
> Of course the line: > stat_reg(tad_addr.addr_ver) <= std_logic_vector(to_unsigned(32,ADDR_VERSION)); > should be replaced with: > stat_reg(tad_addr.addr_ver) <= std_logic_vector(to_unsigned(ADDR_VERSION,32));
There is a way do it in VHDL only. Check out this thread https://groups.google.com/d/topic/comp.lang.vhdl/0JrSaPFUJ1k/discussion Hats off to Jonathan Bromley for this clever solution. I used it about 8 years ago and I used Python to parse the register list to create the header files for the driver.
Reply by February 11, 20182018-02-11
Of course the line:
stat_reg(tad_addr.addr_ver) <= std_logic_vector(to_unsigned(32,ADDR_VERSION));
should be replaced with:
stat_reg(tad_addr.addr_ver) <= std_logic_vector(to_unsigned(ADDR_VERSION,32)); 
Reply by Wojciech M. Zabołotny February 11, 20182018-02-11
Last time I had to prepare a firmware for FPGA, that contained a complex hierarchy of blocks and subbblocks, containing registers and arrays of registers on different levels of hierarchy. Those registers were connected to simple block driven by IPbus ( https://github.com/ipbus/ipbus-firmware ) or AXI-Lite slave (e.g., generated with Tools/Create and Package New IP/Ctreate AXI4 Peripheral), that provides certain number of R/W - "Control" and RO - "Status" registers.
Until the design was small, it was easy to allocate the register addresses by hand and to connect them with manually written HDL.
However, as the design grew up, this task became more tiring and error prone.
Therefore, I have decided to prepare something to automate this task. Initially I tried to prepare my own metalanguage to describe the structure, and parse it with "pyparsing".
However, finally I have found a simpler solution, where the structure of design is described in pure Python.
Lets assume, that our design contains the top entity "Top", that contains 3 registers, N_OF_A blocks "ABlock" and, N_OF_B blocks "BBlock".
The nested blocks also have a complex structure, as shown below:

TOP
|
+-SREG: top_status
+-CREG: sys_control
+-CREG: resets
|
+-N_OF_A x ABlocks
| |
| +-SREG: A_status
| +-CREG: A_control
| +-N_OF_I2C_SLAVES x I2CBlock
| | |
| | +-CREG: I2C_Config
| | +-SREG: I2C_Status
| | +-CREG: I2C_Command
| |
| +-N_OF_SPI_SLAVES x SPIBlock
|   |
|   +-CREG: SPI_Config
|   +-SREG: SPI_Status
|   +-CREG: SPI_Tx
|   +-SREG: SPI_Rx
|
+-N_OF_B x BBlocks
  |
  +- N_OF_CELLS x CREG: Out_data
  +- N_OF_CELLS x SREG: In_data
  +- SREG: B_status
  +- CREG: B_config

Maintaining of addresses in the above structure, especially when the constants "N_OF..." are changing, is really a nightmare.
In my proposed solution, the abover structure may be described in a simple Python file:

#!/usr/bin/python3
from addr_gen import *

#Definitions of constants used in the package
c.ADDR_VERSION=int(time.time())
c.N_OF_A = 13
c.N_OF_I2C_SLAVES = 6
c.N_OF_SPI_SLAVES = 8
c.N_OF_B = 5
c.N_OF_CELLS = 12

#Define registers in the BBlock
bbl_def=aobj("BBLOCK",[
  ("out_data",sreg_def,c.N_OF_CELLS),
  ("in_data",sreg_def,c.N_OF_CELLS),
])

#Define registers in SPI block
spi_def=aobj("SPI",[
  ("spi_config",creg_def),
  ("spi_status",sreg_def),
  ("spi_tx",creg_def),
  ("spi_rx",sreg_def),
])

#Define registers in I2C block
i2c_def=aobj("I2C",[
  ("i2c_config",creg_def),
  ("i2c_status",sreg_def),
  ("i2c_command",creg_def),
])

#Define registers and subblocks in the ABlock
abl_def=aobj("ABLOCK",[
  ("a_status",creg_def),
  ("a_control",creg_def,2), #Two registers, supporting up to 64 links
  ("spi",spi_def,c.N_OF_SPI_SLAVES),
  ("i2c",spi_def,c.N_OF_I2C_SLAVES),
])

#Define registers and subblocks in the TOP block
top_def=aobj("TOP",[
  ("addr_ver",sreg_def),
  ("top_st",sreg_def),
  ("sys_ctrl",sreg_def),
  ("resets",creg_def),
  ("ab",abl_def,c.N_OF_A),
  ("bb",bbl_def,c.N_OF_B),
])

#Generate package with constants
gen_vhdl_const_package("top_const_pkg")

#Generate package with address related types and addresses
gen_vhdl_addr_package("top_adr_pkg","",crob_def,0,0)

#Generate Python module with addresses
gen_python_addr_module("top_adr",crob_def,0,0)

Running the above, generates the appropriate VHDL packages with constants and addresses, and python module with addresses.
Connection of signals in the VHDL code may be done as follows:
(I assume, that each blocks provides inputs from control registers and output to status registers in hierarchical records like below:
  type T_I2C_CTRL is record
    i2c_config : std_logic_vector(31 downto 0);
    i2c_command : std_logic_vector(31 downto 0);
  end record T_I2C_CTRL;
  type T_I2C_CTRL_ARR is array(natural range<>) of T_I2C_CTRL;

  type T_SPI_CTRL is record
    spi_config : std_logic_vector(31 downto 0);
    spi_command : std_logic_vector(31 downto 0);
  end record T_SPI_CTRL;
  type T_SPI_CTRL_ARR is array(natural range<>) of T_SPI_CTRL;

  type T_ABL_CTRL is record
    a_control: std_logic_vector(31 downto 0);
    spi : T_SPI_CTRL_ARR(0 to N_OF_SPI_SLAVES-1);
    i2c : T_I2C_CTRL_ARR(0 to N_OF_I2C_SLAVES-1);
  end record T_ABL_CTRL;
)

-- Process for connecting the signals
    process (all) is
    begin  -- process
      stat_reg(tad_addr.addr_ver) <= std_logic_vector(to_unsigned(32,ADDR_VERSION));
      stat_reg(tad_addr.top_st) <= s_top_status;
      s_top_control <= ctrl_reg(tad_addr.sys_ctrl);
      s_resets <= ctrl_reg(tad_addr.resets);
      for an in 0 to N_OF_A-1 loop
         stat_reg(tad_addr.ab(an).a_status)<=s_a_stat(an).a_status;
         s_a_ctrl(an)<=ctrl_reg(tad_addr.ab(an).a_control);
         for spin in 0 to N_OF_SPI_SLAVES loop
            s_a_ctrl(an).spi(spin).spi_config <= ctrl_reg(tad_addr.ab(an).spi(spin).spi_config;
            stat_reg(tad_addr.ab(an).spi(spin).spi_status) <= s_a_stat(an).spi(spin).spi_status;
            s_a_ctrl(an).spi(spin).spi_command <= ctrl_reg(tad_addr.ab(an).spi(spin).spi_command;
         end loop; -- spin
         -- Similar loop for I2C slaves
      end loop;  -- an
      -- Similar loop for B Blocks
    end process;

The presented approach allows to maintain addresses allocation during the long term development of the design.
I'll appreciate any improvements, suggestions and corrections.
I publish that code as Public Domain or Creative Commons CC0 license, whatever better suits your needs.
I hope that it will be useful or inspiring for somebode, but I do not provide warranty of any kind.

The described scripts are posted to alt.sources as https://groups.google.com/forum/#!topic/alt.sources/aw1SINsSHiM
I will also keep a maintained copy in https://github.com/wzab/wzab-hdl-library/tree/master/addr_gen

With best regards,
Wojtek