This was a weekend project back in 2020 to build IPXACT parser and UVM register Model generator. As an example of the lack of imagination at that time, I decided to call it ‘ipaxctral’. Creative?! I know!

IPXACT Link to heading

I will just copy/paste the wiki here. But full Spec is on Accellera website

IP-XACT is an XML format that defines and describes individual, re-usable electronic circuit designs (individual pieces of intellectual property, or IPs) to facilitate their use in creating integrated circuits (i.e. microchips).

The important section is <ipxact:register> as it specifies the registers and fields.

UVM RAL Link to heading

Well, if you haven’t seen Register model before, I envy you. You had a good life so far. I won’t dive deep into UVM RAL here. I will just put this from UVM user guide

The UVM register layer classes are used to create a high-level, object-oriented model for memory-mapped registers and memories in a design under verification (DUV)

Basically, Once fields and registers are defined, RAL provides a consistent access API for registers read/write.

class foo_csr extends uvm_reg;
rand uvm_reg_field enable;
rand uvm_reg_field ID;
endclass

Design and Implementation Link to heading

The plan was simple:

  • Use xml.etree to parse IPXACT XML and extract registers and fields
  • Build dict for jinja2 context
  • Write Jinja2 templates to consume the context.

Easy enought. Right? I thought so. But XML is really really bad format to iterate. Anyway, I finally managed to build my own tree(nested dict really).

  for reg in addressBlock.findall("ipxact:register", ns):
                    reg_context = {}
                    block_context["registers"].append(reg_context)
                    reg_context["name"] = reg.find("ipxact:name", ns).text
                    reg_context["size"] = reg.find("ipxact:size", ns).text
                    reg_context["addressOffset"] = reg.find(
                        "ipxact:addressOffset", ns
                    ).text
                    reg_context["fields"] = []
                    for field in reg.findall("ipxact:field", ns):
                        field_context = {}
                        reg_context["fields"].append(field_context)
                        field_context["name"] = field.find("ipxact:name", ns).text
                        field_context["description"] = field.find(
                            "ipxact:description", ns
                        ).text
                        field_context["bitOffset"] = field.find(
                            "ipxact:bitOffset", ns
                        ).text
                        field_context["bitWidth"] = field.find(
                            "ipxact:bitWidth", ns
                        ).text
                        field_context["access"] = access2short(
                            field.find("ipxact:access", ns).text
                        )
                        field_context["reset"] = (
                            field.find("ipxact:resets", ns)
                            .find("ipxact:reset", ns)
                            .find("ipxact:value", ns)
                            .text
                        )

Once I have that working, I can pass the contexts to Jinja to render.

                    reg_template = self.jinja_env.get_template("reg.sv.jinja")
                    txt = reg_template.render(context=reg_context)
                    print(txt)
                block_template = self.jinja_env.get_template("reg_block.sv.jinja")
                txt = block_template.render(context=block_context)
                print(txt)

The last step is to write the templates to consume the context.

class {{ context["name"]}} extends uvm_reg;

  {% for field in context["fields"] %}
  rand uvm_reg_field {{ field["name"] }};
  {% endfor %}

  function new (string name = "{{ context["name"] }}" );
    super.new (name, {{ context.size }} , UVM_NO_COVERAGE);
  endfunction


    virtual function void build ();

  {% for field in context["fields"] %}
    this.{{ field["name"] }}   = uvm_reg_field::type_id::create ("{{ field["name"] }}");
    this.{{ field["name"] }}.configure (this, 
                                      {{ field["bitWidth"] }},
                                       0, 
                                       "{{ field["access"] }}", 
                                       0, 
                                       1'h0,
                                       1,
                                       1,
                                       1);
  {% endfor %}

  endfunction
endclass

PS. I made the template flexible enough by passing it on CLI. But what I really wanted was to define a better data structure (context API) for Jinja2 to consume. Well, That’s why i am reviving it :)