05-12-2019 06:51 AM
I have a package with a defined record as:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; package TestPkg is constant regfile_length : integer:=(4*32); type t_regfile is record PID0 : STD_LOGIC_VECTOR(63 downto 0); PID1 : STD_LOGIC_VECTOR(regfile_length-1 downto 66); --NOTE THE UNASSIGNED RANGE 65-64 !! end record t_regfile; function t_regfile_to_slv(L: t_regfile) return std_logic_vector; function slv_to_t_regfile(L: std_logic_vector(regfile_length-1 downto 0) ) return t_regfile; end package; package body TestPkg is function t_regfile_to_slv(L: t_regfile) return std_logic_vector is variable RetVal: std_logic_vector(regfile_length-1 downto 0):=(others=>'0'); begin RetVal(L.PID0'range) := L.PID0; RetVal(L.PID1'range) := L.PID1; return(RetVal); end function t_regfile_to_slv; function slv_to_t_regfile(L: std_logic_vector(regfile_length-1 downto 0) ) return t_regfile is variable RetVal: t_regfile; begin RetVal := ( PID0 => L(RetVal.PID0'range), PID1 => L(RetVal.PID1'range) ); return(RetVal); end function slv_to_t_regfile; end TestPkg;
Then I define a RAM of such records as:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; use work.TestPkg.all; entity testmem is Port ( Clock: in std_logic; axi_sensor_id: in std_logic_vector(3 downto 0); sensor_id: in std_logic_vector(3 downto 0); axi_write_en : in STD_LOGIC; axi_RegFilein : in t_regfile; to_mc_RegFile : out t_regfile; axi_RegFile : out t_regfile ); end testmem; architecture Behavioral of testmem is type tRegfileArray is array (natural range <>) of std_logic_vector(regfile_length-1 downto 0); signal regfileMem : tRegfileArray(0 to 15); begin MC_SELECT_REGFILE: process( Clock ) begin if rising_edge(Clock) then if (axi_write_en = '1' ) then regfileMem(conv_integer(axi_sensor_id)) <= t_regfile_to_slv(axi_RegFilein); End if; to_mc_RegFile <= slv_to_t_regfile(regfileMem(conv_integer(sensor_id))); axi_RegFile <= slv_to_t_regfile(regfileMem(conv_integer(axi_sensor_id))); end if; end process; end Behavioral;
This does not infer a block RAM, but gives the synthesis info:
INFO:Xst:3218 - HDL ADVISOR - The RAM <Mram_regfileMem> will be implemented on LUTs either because you have described an asynchronous read or because of currently unsupported block RAM features. If you have described an asynchronous read, making it synchronous would allow you to take advantage of available block RAM resources, for optimized device usage and improved timings. Please refer to your documentation for coding guidelines.
But when I re-define the record with
PID1 : STD_LOGIC_VECTOR(regfile_length-1 downto 64);
A block is inferred as expected.
In real life my record has many such unused fields, blocking the RAM inference. How can I direct the synthesizer to ignore those unused ranges and continue with RAM inferring?
A dirty workaround is declaring new fields for each unused range, but this degrade readability and enlarges the code with no value.
05-12-2019 08:22 AM
You have a memory with unconnected bits. Vivado isn't the best with inference, so you're best off using a continuous array with no gaps.
Any reason you're using ranges and simply not simply concatenating the record elements?
05-13-2019 01:49 AM
Concatenation is not an option, it's a memory map with predefined fields. Many bit ranges are preserved for future.
The problem statement is clear, but I need a clean implementation rather than the dirty workaround mentioned above.
05-13-2019 03:25 AM
This does look like a defect with synthesis, as the "holes" should be assigned to '0' by the variable initialisation in the to_slv function. But you're also using XST, so this will not get fixed, so you're going to have to work around it. Vivado might work, but I assume you're using an unsupported chip for vivado.
I think you need to think around the problem a little. Rather than mapping the record directly to your memory map, why not just have the record as an abstract collection of data? That way, the memory map is separated from the data storage and you avoid the problem you are getting by mixing the two. This way you only store the data actually in use, and the holes can be inserted directly at the mapping stage, rather than the ram.
Mixing the mapping into the data types may make it harder in future if you need to expand or move bit around the register map, as one change may affect several type declarations, which may have knock on effects in many places. Having the types as abstract, then these can be left alone and only the mapping needs changing, and it's easy to add fields to the record without affecting any types.