Reputation: 2279
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
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
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
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
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