Miloš Rašić
Miloš Rašić

Reputation: 2279

How to use a constant calculated from generic parameter in a port declaration in VHDL?

An example would be a generic register file which I'm trying to implement like this:

entity register_file is
generic(reg_width: integer := 32; reg_num: integer := 16);
constant sel_num: integer := integer(CEIL(LOG(Real(reg_num))));
port (
    data_in: in std_logic_vector(reg_width - 1 downto 0);
    data_out: out std_logic_vector(reg_width - 1 downto 0);
    rd_sel: in std_logic_vector(sel_num - 1 downto 0);
    wr_sel: in std_logic_vector(sel_num - 1 downto 0);
    rd_enable: in std_logic;
    wr_enable: in std_logic;
    clock: in std_logic;
);
end register_file;

This doesn't work because it seems generic and port have to be the first two declarations, followed by the others. If I move type and constant after the port declaration, they are not visible when the port declaration is being processed.

I'm new to VHDL and I think this should be a common problem, but I can't find the solution. Obviously, I would like to avoid the copy-paste solution.

Upvotes: 6

Views: 18352

Answers (4)

Martin Thompson
Martin Thompson

Reputation: 16812

An alternative to consider (I'm not necessarily recommending it in this situation, it somewhat depends on how it's going to be instantiated)

... drop the generics and make use of unconstrained vectors:

entity register_file is
port (
    data_in: in std_logic_vector;
    data_out: out std_logic_vector;
    rd_sel: in unsigned;
    wr_sel: in unsigned;
    rd_enable: in std_logic;
    wr_enable: in std_logic;
    clock: in std_logic;
);
end register_file;

This way, the number of bits you choose in the module which instantiates the register file chooses the arrangement.

You can use data_in'range to define other internal signals or variables which match. And define a constant for the number of registers which is set to 2 ** rd_sel'length.

Finally you can use asserts in your architecture to ensure that data_in'length = data_out'length and that wr_sel'length = rd_sel'length


(Note I made the sel ports unsigned as you'll no doubt end up using them as an index (ie number) and it'll save some conversions. You could also make them integers, then the width of the register file would be defined by the range of the integer passed in)

Upvotes: 1

Morten Zilmer
Morten Zilmer

Reputation: 15924

The issue you point out often occur for generic modules, for example making a generic RAM with a number of entries and an address bus with length as addr_len to point out a specific entry. The number of entries is then usually 2 ** addr_len, but can be less depending on the implementation.

As Brian Drummond points out, it may be very useful to have a function that can convert a value with number of entries to a value giving the number of required bits, thus ceil(log2(entries)), and such could be in a common package:

package common is
  function ceil_log2(i : natural) return natural;
end package;

library ieee;
use ieee.math_real.all;
package body common is
  function ceil_log2(i : natural) return natural is
  begin
    return integer(ceil(log2(real(i))));  -- Example using real calculation
  end function;
end package body;

If the number of entries in the register file is always reg_num = 2 ** len, then the entity using the ceil_log2 function may be:

library ieee;
use ieee.std_logic_1164.all;

library work;
use work.common.all;

entity register_file is
  generic(
    reg_width : integer := 32;
    reg_num   : integer := 16);
  port (
    data_in   : in  std_logic_vector(reg_width - 1 downto 0);
    data_out  : out std_logic_vector(reg_width - 1 downto 0);
    rd_sel    : in  std_logic_vector(ceil_log2(reg_num) - 1 downto 0);
    wr_sel    : in  std_logic_vector(ceil_log2(reg_num) - 1 downto 0);
    rd_enable : in  std_logic;
    wr_enable : in  std_logic;
    clock     : in  std_logic);
end register_file;

However, the module using register_file will also have to know the length of rd_sel/wr_sel, in order to make signals that connects the entity, and thus will have to call ceil_log2 to make sure that the correct length is used. So, to make the interface more explicit and simpler one way is to simply provide both reg_num and sel_num:

entity register_file is
  generic(
    ...
    reg_num   : integer := 16;
    sel_num   : integer :=  4);
  port (
    ...
    rd_sel    : in  std_logic_vector(sel_num - 1 downto 0);
    wr_sel    : in  std_logic_vector(sel_num - 1 downto 0);
    ...
...
architecture syn of register_file is
  ...
begin
  ...
  assert 2 ** sel_num >= reg_num report "sel_num to small to address all registers given by reg_num";
end architecture;

The assert will make sure that the entity is used correctly.

Upvotes: 1

rick
rick

Reputation: 1646

Just for the record, here's an alternative, though it may not be the best choice in this case. You can't declare a constant and use it in a port declaration, but you can declare a generic whose value depends on a previously declared generic. Your code would look like:

entity register_file is
    generic(
        reg_width: integer := 32;
        reg_num: integer := 16;
        sel_num: integer := integer(CEIL(LOG(Real(reg_num))))
    );
    port (
        data_in: in std_logic_vector(reg_width - 1 downto 0);
        data_out: out std_logic_vector(reg_width - 1 downto 0);
        rd_sel: in std_logic_vector(sel_num - 1 downto 0);
        wr_sel: in std_logic_vector(sel_num - 1 downto 0);
        rd_enable: in std_logic;
        wr_enable: in std_logic;
        clock: in std_logic
    );

begin
    assert sel_num = integer(CEIL(LOG(Real(reg_num))));
end;

Because whoever instantiates your entity could mess with the values, an assertion at the end of the architecture ensures they are still consistent.

Upvotes: 2

user1818839
user1818839

Reputation:

If there is no other use of reg_num, simply make sel_num the generic.

Otherwise, write a function called sel to convert reg_num to another natural. The question is, where to put the function?

In my designs I tend to put a lot of common datatypes, declarations, functions and the clock period (*) in a package called common.

package common is
   function sel(n : natural) return natural;
   constant clock_period : time := 1 sec / 32000000;

   constant num_regs : natural := 16;
   subtype sel_word is std_logic_vector(sel(num_regs) downto 0);
end common;  -- package body contains the function body

Then my entity looks like

use Work.common.all;

entity register_file is
generic(reg_width: integer := 32; reg_num: integer := 16);
port (
    rd_sel: in std_logic_vector(sel(reg_num) downto 0);  -- OK
    wr_sel: in sel_word;                                 -- better?
...

If different parts of a design need different values of "regnum" then the generic approach is to be preferred.

Otherwise, locating these details in "common" allows you to parameterise the whole design with a single file, and no risk of hard-coded (and possibly wrong) values for some of the generics.

(*) why the clock frequency? It's unbelievably useful to scale all the timing delays, baud rate parameters, memory wait states etc from one parameter, knowing they will all remain correct when I change the clock...

Upvotes: 6

Related Questions