Scripts to maintain list of addresses in VHDL core communicating with SW via a set of control and status registers

Started by Wojciech M. ZaboŇāotny February 11, 2018
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
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)); 
Le dimanche 11 f&eacute;vrier 2018 10:48:55 UTC-5, wza...@gmail.com a
&eacute;crit&nbsp;:
> 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.
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