A flower

Using VHDL-2008 Unconstrained Arrays in Quartus Lite

Motivation

So there I was, copy pasting nearly a dozen VHDL bus ports again. I don’t know about you, but I hate having to do the same thing more than once. To avoid repetition, any generic bus entities I implement, like arbiters and memory adapters, have generics to define the address and data widths.

entity wb_sram is
  generic (
    addr_width : natural;
    data_width : natural
  );
  port (
    clk_i : in    std_logic;
    rst_i : in    std_logic;

    -- Let me put these in a record!
    wb_cyc_i   : in    std_logic;
    wb_dat_i   : in    std_logic_vector(data_width - 1 downto 0);
    wb_dat_o   : out   std_logic_vector(data_width - 1 downto 0);
    wb_ack_o   : out   std_logic;
    wb_addr_i  : in    std_logic_vector(addr_width - 1 downto 0);
    wb_stall_o : out   std_logic;
    wb_sel_i   : in    std_logic_vector((data_width / 8) - 1 downto 0);
    wb_stb_i   : in    std_logic;
    wb_we_i    : in    std_logic;

    -- ...
  );
end entity wb_sram;

My dream is to put all the bus ports into a pair of records. There's just one problem: VHDL records don't accept generics.

A Simple Solution

As I sit there, reconsidering my life choices, a thought occurs to me. VHDL-2008 is old enough to drive a car. Heck, next year it will be old enough to vote! Maybe now is the time.

Not wanting to get my hopes too high, I pick just a single entity and squash all its bus ports into a pair of records with some unconstrained std_logic_vector members in them.

type wb_controller_t is record
  cyc  : std_logic;
  dat  : std_logic_vector;
  addr : std_logic_vector;
  sel  : std_logic_vector;
  stb  : std_logic;
  we   : std_logic;
end record wb_controller_t;

type wb_target_t is record
  dat   : std_logic_vector;
  ack   : std_logic;
  stall : std_logic;
end record wb_target_t;

-- With those records, now my entity can just be:

entity wb_sram is
  port (
    clk_i : in    std_logic;
    rst_i : in    std_logic;

    wb_controller_i : in    wb_controller_t;
    wb_target_o     : out   wb_target_t;

    -- ...
  );
end entity wb_sram;

I run it through the simulator (nvc, great tool btw) and…it works? Now my heart is racing. Could this be the end of my suffering? I try to maintain a healthy sense of skepticism. While the simulator might have been happy, the real fight was still yet to come.

Quartus

Quartus. Why did I pick a dev board that required Quartus? Well maybe it will work. It accepts VHDL-2008 well enough to not need component declarations for everything. It’ll be fine, right?

I wire the pieces up, cross my fingers, and run the build command.

Error (10446): VHDL Type Declaration error at wb_pkg.vhd(8):
  record element cannot have an unconstrained array type
Darth Vader yelling "Noooo!"

The dream is over. I’ve been sent to hell, and my fate is to copy paste bus signals for all of eternity. I search the web desperately for answers, but I only find posts about the same issue from a decade ago. Poor souls damned to the same destiny as me.

A few days pass. I start to think about other tasks. I really need to learn to write timing constraints on I/O ports. Or maybe I’ll fix the bugs in my serial debugger. Running some formal verification on that might help. What’s the toolchain for that again? Yosys. Wait! Yosys! That’s it!

Open Source Software Saves the Day Again

Yosys, combined with GHDL, can read in VHDL and write out the synthesizable portion in all sorts of formats. Including Verilog. While I could never stomach writing Verilog myself, I suppose there’s no harm in generating it behind the scenes as part of the build process.

yosys -m ghdl -p \
  "ghdl --std=08 ...vhdl-files... -e top_level; write_verilog top_level.v"

GHDL isn’t quite as advanced as nvc. But it’s miles ahead of Quartus’s VHDL support. And feeding it my records with unconstrained arrays, it was happy to accept them.

There’s just one complication. GHDL doesn’t understand any of the Quartus IP. Not that I would expect it to. There’s a workaround though. If I swap the IP instantiation from entity to component, GHDL and Yosys are happy to ignore the missing IP definitions and carry them forward into the generated Verilog.

-- This won't work with GHDL, since it doesn't understand the Quartus
-- IP that creates the PLL component

u_sys_pll : entity work.sys_pll
  port map (
    inclk0 => clock_50_i,
    c0     => clk_sys,
    locked => open
  );

-- But using an old style component declaration, GHDL just ignores the
-- missing definition. When Yosys generates the Verilog, sys_pll will
-- remain an undefined component which Quartus can later fill in.

component sys_pll is
  port (
    inclk0 : in    std_logic;
    c0     : out   std_logic;
    locked : out   std_logic
  );
end component sys_pll;

u_sys_pll : component sys_pll
  port map (
    inclk0 => clock_50_i,
    c0     => clk_sys,
    locked => open
  );

Now in the Quartus project instead of seeing all the VHDL files like before, Quartus only sees the IP files and a single generated Verilog file with the rest of the project squashed into it.

Doing a build with this, everything seems to work. I converted the rest of the bus signals in the project to use the new records. Finally I can live in peace, with just two ports to deal with instead of a dozen.

Conclusions

Open Source Software once again comes to the rescue. Paid vendor tools can't be bothered to support a 17 year old VHDL standard. But some online enthusiants sure can!

This isn’t to say there’s no downsides to this approach. The biggest of which is the generated Verilog is quite hard to read. There’s a lot of noise in it. Luckily Yosys and GHDL preserved the component and signal names. So paths from systems like timing analysis are still relatively easy to trace through.