VHDL SLV dynamic width slice assignment

I have some code that takes in 2^X samples and outputs 2^Y samples, where X and Y are integers and Y is less than X. So for example X=6,Y=4, I ingest 64 samples and output 16 samples.

As of right now, X and Y are generics, but I would like to make them, or the ratio between then, or the log2 of that ratio a signal, so that I might change my operation at runtime via a register write or something.

In my internal processing, using the example values of 64 and 16, I have to do some addressing and counting, and to generate some of the addresses I have to do soemthing like this:

constant pkt_addr_pad : std_logic_vector(15 -Y downto 0) := (others=>'0');
-- In this example is is 12 bits of 0
.
.
.
raddr_short <= pkt_addr_pad & raddr (Y-1 downto 0);


to convert raddr which can contain values from 0 to 63, to raddr_short which only contains values of 0 to 15; effectively discarding bits 4 and 5, masking them to 0, whatever you want to call it.

I'm looking for the best way to be able to have Y change at runtime. obviously the timing for Y is relaxed, as it changes unimaginably less frequently relative to the actual address, and when it does change, the rest of my logic will be in an idle state such that raddr/raddr short won't be changing

I'd really rather avoid the whole:

raddr_short <= x"00" & "00"  & raddr(5 downto 0) when Y = 6 else
x"00" & "000" & raddr(4 downto 0) when Y = 5 else
....
x"00" & "000000" & raddr(1 downto 0) when Y = 2 else
x"0000";


as I've actually 4 of these things running at once, and as such have arrays of slv16 for each of the raddr/raddr_shorts, as well as waddrs, and...

Although I guess I could put the MUX in a function to clean up the code, but there has to be a better way. maybe some thing liek:

constant zeros16 : std_logic_vector(15 downto 0) := (others=>'0');
.
.
.
raddr_short <= zeros(15 downto Y) & raddr (Y-1 downto 0);

• Just use a mask. Something like "mask <= std_logic_vector(to_unsigned(2**Y-1, mask'length));" and "raddr_short <= raddr and mask;". – Jonathan Drolet Mar 12 at 17:06
• Can you explain why this needs to be done at all? The only real benefit of this operation is that it masks off the upper bits of raddr. But do you need to? In your example, if Y=4, then would raddr ever get higher than 15 in the first place...? – Mr. Snrub Mar 12 at 17:36
• @Mr.Snrub because on the input side of things, I ingest a packet of length X (in my example 64), and the counter in my process that generates addresses goes from 0 to 63, and on the output side I need only the lower X bits. that is to say that that I need to map 0 to 15 -> 0 to 15, 16 to 31 -> 0 to 15, 32 to 47 -> 0 to 15, and 48 to 63 -> 0 to 15. And I need both. Yes I could increment for 0 to 15, and then on 15 -> 0 have another counter increment, and then check for rolling over 3->0, but that is the same thing as having a 6 bit counter in the first place – Jotorious Mar 12 at 19:41

1 Answer

As of right now, X and Y are generics, but I would like to make them, or the ratio between then, or the log2 of that ratio a signal, so that I might change my operation at runtime via a register write or something.

Well, since you're implying that you're pretty flexible on how to get this done, then certainly the simplest approach would be to just have a register mask which you simply set to have 1 bits in the appropriate spots -- for example "0000000000001111" or "0000000000111111". Then just apply the mask exactly like @JonathanDrolet suggests, i.e.:

raddr_short <= raddr and mask;


Advantage: super-simple to implement and to understand. Disadvantage: possible to set mask to values that could result in weird operation (but do you really care about "guard against bad user input" in your application)?

If you wanted to get fancier (but again, why?) then you could try the approach suggested by JonathanDrolet, but I'm doubtful that a synthesizer would be able to synthesize the construct std_logic_vector(to_unsigned(2**Y-1, mask'length)) into a reasonable circuit, if it would synthesize at all. But you could use a hybrid approach: you say you have "4 of these things running at once" but if they all share the same Y value then you could calculate the mask once and apply it to all of them:

mask <= "0000000000111111" when Y = 6 else
"0000000000011111" when Y = 5 else
...
"0000000000000011" when Y = 2 else
(others => '0');

raddr1_short <= raddr1 and mask;
raddr2_short <= raddr2 and mask;
...


If it were me, I'd still just go with the simpler programmable mask register, though.