Mapping a std_bit_vector to a record

H

hans.huebner

Hi,

I am working on a microcoded CPU for a FPGA. I intend to use a RAM
core for my microcode, which has a std_logic_vector as data output. My
microcode consists of bit fields, and in order to access the fields by
name I defined a record:

type microinstruction is
record
read_sel : std_logic_vector(4 downto 0);
write_sel : std_logic_vector(4 downto 0);
alu : std_logic_vector(3 downto 0);
....

Now I am looking for a construct to map the output of my microcode ROM
(which is a std_logic_vector) to this record layout. I have thought
about using a conversion function, but the problem with that approach
is that I would have to replicate the record layout in the conversion
function in order to map the correct bits to the fields of the
microinstruction. So my question is: Is there something similar to a
C union that would automatically re-map the std_logic_vector that comes
out of my ROM to the microinstruction record format, without having to
maintain the instruction layout in both the record definition and a
conversion function?

Any advice would be greatly appreciated.

Thanks,
Hans
 
M

Mike Treseler

I am working on a microcoded CPU for a FPGA. I intend to use a RAM
core for my microcode, which has a std_logic_vector as data output. My
microcode consists of bit fields, and in order to access the fields by
name I defined a record:
....

So my question is: Is there something similar to a
C union that would automatically re-map the std_logic_vector that comes
out of my ROM to the microinstruction record format, without having to
maintain the instruction layout in both the record definition and a
conversion function?

The key to doing this sort of thing is to learn how
to apply functions with variables.
Open a book or Google some newsgroup examples:
http://groups.google.com/groups/search?q=vhdl+function+variable+begin+return+end

VHDL has few built-in functions for data structures,
but it's quite easy to convert any object
to any other type in 0 ns, once you know how
to apply functions.

-- Mike Treseler
 
H

hans.huebner

Mike,

Thanks for your prompt reply. I considered using a function to do the
conversion, but that is not what bothers me. I want to avoid having to
replicate the layout of my microcode instruction in both the record
definition and the conversion function.

-Hans
 
M

Mike Treseler

Thanks for your prompt reply. I considered using a function to do the
conversion, but that is not what bothers me. I want to avoid having to
replicate the layout of my microcode instruction in both the record
definition and the conversion function.

Could the microcode type be packaged
and used by both?

-- Mike Treseler
 
H

hans.huebner

Mike said:
Could the microcode type be packaged and used by both?

It surely could, but this would still leave me with the need to
duplicate the record layout. For example, if my record type specified
4 bits for the read selector and another 4 bits for the write selector,
the decoding function would need to extract these specific bits from
the microcode ROM. In a VHDL sketch:

type microinstruction is
record
read_sel : std_logic_vector(3 downto 0);
-- field width specified here
write_sel : std_logic_vector(3 downto 0);
-- same here
end record;

....

function to_microinstruction(bits : std_logic_vector(7 downto 0)
-- length of instruction specified here, could be
-- deduced from the record definition?
) return microinstruction is
variable retval : microinstruction;
begin
retval.read_sel <= bits(3 downto 0);
-- field length and location repeated here
retval.write_sel <= bits(7 downto 4);
-- same here
return retval;
end function;

Thus, if I change the microcode format, I would also need to change the
decoder function so that the right bits are extracted from the
std_logic_vector representing the full microcode instruction. I am
looking for a way to automate this in VHDL, but it could well be that
there is no solution to this and I'd have to live with this for now.
But maybe I am missing something.

Thanks,
Hans
 
M

Mike Treseler

I am
looking for a way to automate this in VHDL, but it could well be that
there is no solution to this and I'd have to live with this for now.

I'm sure there is a vhdl solution,
but I don't yet understand the problem.
But maybe I am missing something.

Maybe it's the fact that functions are timeless.
Only values are manipulated. There are no signal assignments,
only the calculation of return value(s).

-- Mike Treseler
 
D

David R Brooks

It surely could, but this would still leave me with the need to
duplicate the record layout. For example, if my record type specified
4 bits for the read selector and another 4 bits for the write selector,
the decoding function would need to extract these specific bits from
the microcode ROM. In a VHDL sketch:

type microinstruction is
record
read_sel : std_logic_vector(3 downto 0);
-- field width specified here
write_sel : std_logic_vector(3 downto 0);
-- same here
end record;

...

function to_microinstruction(bits : std_logic_vector(7 downto 0)
-- length of instruction specified here, could be
-- deduced from the record definition?
) return microinstruction is
variable retval : microinstruction;
begin
retval.read_sel <= bits(3 downto 0);
-- field length and location repeated here
retval.write_sel <= bits(7 downto 4);
-- same here
return retval;
end function;

Thus, if I change the microcode format, I would also need to change the
decoder function so that the right bits are extracted from the
std_logic_vector representing the full microcode instruction. I am
looking for a way to automate this in VHDL, but it could well be that
there is no solution to this and I'd have to live with this for now.
But maybe I am missing something.
Something like:
type microinstruction is -- use std_logic_vector instead if you like
record
read_sel : bit_vector(4 downto 0);
write_sel : bit_vector(4 downto 0);
alu : bit_vector(3 downto 0);
end record;

procedure get_field(index : in integer;
bits : in bit_vector;
ans : out bit_vector;
nfield : out integer
) is
variable width : positive := ans'length;
begin
ans := bits((index + width - 1) downto index);
nfield := index + width;
end get_field;

function to_microinstruction(bits : bit_vector(12 downto 0)
) return microinstruction is
variable retval : microinstruction;
variable index : integer := 0;
begin -- Just name the fields in order...
get_field(index, bits, retval.read_sel, index);
get_field(index, bits, retval.write_sel, index);
get_field(index, bits, retval.alu, index);
return retval;
end to_microinstruction;
 
K

KJ

Hi,

I am working on a microcoded CPU for a FPGA. I intend to use a RAM
core for my microcode, which has a std_logic_vector as data output. My
microcode consists of bit fields, and in order to access the fields by
name I defined a record:

type microinstruction is
record
read_sel : std_logic_vector(4 downto 0);
write_sel : std_logic_vector(4 downto 0);
alu : std_logic_vector(3 downto 0);
...

Now I am looking for a construct to map the output of my microcode ROM
(which is a std_logic_vector) to this record layout.

I'm not sure if I quite understand but I think it's similar to what I always
do for defining the register maps for access by a CPU. Something like this
what you're looking for?

type microinstruction is
record
read_sel : std_logic_vector(11 downto 8);
write_sel : std_logic_vector(7 downto 4);
alu : std_logic_vector(3 downto 0);
....
end record;

function To_Std_Logic_Vector(L: microinstruction) return std_logic_vector is
variable RetVal: std_logic_vector(11 downto 0); -- <<
Unfortunately you need to know the overall size
begin
RetVal(L.read_sel'range) := L.read_sel;
RetVal(L.write_sel'range) := L.write_sel;
RetVal(L.alu'range) := L.alu;
return(RetVal);
end To_Std_Logic_Vector;

In the 'To_Std_Logic_Vector' function you don't have to 'retype' and
basically have the bit encodings in two places which is what I think you're
trying to avoid. As a general rule whenever I need to convert record types
to std_logic_vectors I also need to go the other way so I will also have a
'From_Std_Logic_Vector' function. To write that one, you pretty much just
'flip' the equations around and flip the type of the input and return types.
I'll leave that as an 'exercise to the reader'.

As you can see, the 'trick' is that the ranges of the elements of the record
are not 'x downto 0' but correspond to however it is you would like to order
them within a std_logic_vector. Generally this causes no problem when you
go to use it in the code since you would typically be referring to the
entire record element and not a sub-element:

i.e.

if (X.alu = NOP) then...

One place where it gums up the code is if you do need to refer to specific
subelements

For example to get at the least significant bit of the alu element one might
be tempted to write..

if (X.alu(0) = '0') then...

This would not be a 'good' way to do it since if move the 'alu' element
around in the record you won't have an element '0'. The 'clean' way to do
it, but it is somewhat ugly would be...

if (X.alu(X'alu'low)) = '0') then....

The nice thing about this approach is...
- Bit mappings are all defined in one place, the record definition.
- Conflicts about bit position are easily avoided (just look to see that you
don't have any overlapping ranges in the record definition). It's not an
error from the perspective of VHDL, but it probably is an error from the
perspective of how you intend to use the record type when going to/from
std_logic_vectors.
- Writing the 'To_Std_Logic_Vector' and 'From_Std_Logic_Vector' functions is
straight forward but still doesn't need to have anything explicit typed in
about the bit positions (the use of 'range is what does this, so it puts the
burden on the compiler to figure out the bit mappings).

KJ
 
K

KJ

Oops, typos on the line about the 'clean' way to refer to the least
significant bit of the alu element of a signal.

I had

if (X.alu(X'alu'low)) = '0') then....

Should be..

if (X.alu(X.alu'low) = '0') then....


KJ
 
A

Andy

Actually, you would probably want to use x.alu'right, because
regardless of to or downto ordering, the rightmost bit is lsb
numerically, per the standard package definitions.

Also, in the conversion function, can you do something like:

for i in L loop
retval(L.i'range) <= L.i;
end loop;

and similar for the conversion from slv to record?

That way, you'd never have to modify you conversion functions when
adding/changing fields to the record.

Andy
 
K

KJ

Ummm...not quite. Your code produces a few errors. Code and Modelsim
errors below. I 'believe' there is a way to kind of sequence through
the elements of a record type but this isn't it. I would like to see
the way to step through it though since it is something that comes up
quite a bit on my end and I usually end up punting and just listing the
elements in my 'To_Std_Logic_Vector' and 'From_Std_Logic_Vector'
functions myself.

Creating those functions are copy/paste/run a macro operations which
poofs them out in a matter of seconds, but I like the idea of simply
sequencing through and getting rid of the 'run a macro' part since when
the record type does change the sequencing approach would cut down on
maintenance on the 'To/From' functions.

KJ
-------- Start of VHDL --------------
type microinstruction is record
read_sel: std_logic_vector(11 downto 8);
write_sel: std_logic_vector(7 downto 4);
alu: std_logic_vector(3 downto 0);
end record;
function To_Std_Logic_Vector(L:microinstruction) return
std_logic_vector is
variable RetVal: std_logic_vector(11 downto 0);
begin
for i in L loop
RetVal(L.i'range) := L.i;
end loop;
return(RetVal);
end To_Std_Logic_Vector;
---------- End of VHDL--------------
---------- Start of Modelsim errors ------------
** Error:
C:/Sim/UniPM22/Sim_Model/ITU_T6_Compressor/ITU_T6_Encoder/Testbench/tb_ITU_T6_Encoder.vhd(95):
Range of a parameter specification must be of a discrete type.
** Error:
C:/Sim/UniPM22/Sim_Model/ITU_T6_Compressor/ITU_T6_Encoder/Testbench/tb_ITU_T6_Encoder.vhd(95):
Constraints cannot be applied to record types.
** Error:
C:/Sim/UniPM22/Sim_Model/ITU_T6_Compressor/ITU_T6_Encoder/Testbench/tb_ITU_T6_Encoder.vhd(96):
Unknown record element "i".
** Error:
C:/Sim/UniPM22/Sim_Model/ITU_T6_Compressor/ITU_T6_Encoder/Testbench/tb_ITU_T6_Encoder.vhd(96):
Attribute "range" requires an array prefix.
** Error:
C:/Sim/UniPM22/Sim_Model/ITU_T6_Compressor/ITU_T6_Encoder/Testbench/tb_ITU_T6_Encoder.vhd(96):
Unknown record element "i".
** Error:
C:/Sim/UniPM22/Sim_Model/ITU_T6_Compressor/ITU_T6_Encoder/Testbench/tb_ITU_T6_Encoder.vhd(213):
VHDL Compiler exiting
---------- End of Modelsim errors ------------
 
M

Mike Treseler

KJ said:
type microinstruction is
record
read_sel : std_logic_vector(11 downto 8);
write_sel : std_logic_vector(7 downto 4);
alu : std_logic_vector(3 downto 0);
end record;
function To_Std_Logic_Vector(L: microinstruction) return std_logic_vector is

I'll call it "to_vec" :)
variable RetVal: std_logic_vector(11 downto 0);
begin
RetVal(L.read_sel'range) := L.read_sel;
RetVal(L.write_sel'range) := L.write_sel;
RetVal(L.alu'range) := L.alu;
return(RetVal);
end to_vec;

I get it.
So I could declare an instruction like this:

constant loadA : microinstruction :=
(
read_sel => x"A",
write_sel => x"B",
alu => x"C"
);

And convert or slice it like this:

constant data : std_logic_vector := to_vec(loadA);
constant data_slice : std_logic_vector := loadA.alu;

The declaration and usage looks clean to me.


-- Mike Treseler
 
J

Jeremy Ralph

If you create constants for all the bitfields specs (i.e.
READ_SEL_BITWIDTH, READ_SEL_BITOFFSET), perhaps encapsulated into a
record, and use these in your type defs and accessor function. Then
you would only have a constant specification record for each bitfield
and an instruction record to maintain. Your accessor function(s) (or
procedures) could be generic, operating on a given instruction
bitfield, as specified by the constant bitfield specifier. The
bitfield specifier would be passed into the function/procedure upon
invocation. Not sure if the synthesizer would like this though.
 
K

KJ

function To_Std_Logic_Vector(L: microinstruction) return std_logic_vector is
I'll call it "to_vec" :)

Gee, usually I agree with you Mike and if the good folks who decided on
the name had called the type 'vec' instead of 'std_logic_vector' I
would've agreed with you on this one as well....but since they
didn't.... ;)

KJ
 
K

KJ

If you create constants for all the bitfields specs (i.e.
READ_SEL_BITWIDTH, READ_SEL_BITOFFSET), perhaps encapsulated into a
record...

The additional named constants aren't necessary. The bit field
constants are defined only place in the record definition and are easy
to maintain. The conversion functions are pretty explicit, take only
one argument (hard to get it wrong) and are clear about what they do
just by name without having to look closely into the function before
using it. Creating the conversion functions is straight forward
(although if VHDL had some sort of 'template' mechanism as in C++ they
could be made even easier...but it doesn't appear that that is the
case, at least not that I've found.

No difficulties with synthesis using this approach.

KJ
 
M

Mike Treseler

KJ said:
Gee, usually I agree with you Mike and if the good folks who decided on
the name had called the type 'vec' instead of 'std_logic_vector' I
would've agreed with you on this one as well....but since they
didn't.... ;)

Thanks for working out the example.
I'm just a lazy typist when I'm in firefox.


-- Mike Treseler
 
J

Jeremy Ralph

No, I suppose there isn't much of an advantage with named constants. I
was thinking in terms of being able to convert to_slv and from_slv, but
your method should work both ways. Your solution is pretty slick. Too
bad there wasn't a way to iterate the fields of the struct in VHDL -
this might be useful in the conversion functions.
 
A

Andy

I usually declare an unconstrained subtype of std_logic_vector called
slv:

subtype slv is std_logic_vector; -- abbreviation

Then I can do things like:

signal bus : slv(7 downto 0);
signal data : unsigned(7 downto 0);

bus <= slv(data);

Basically, you can use "slv" anywhere you would use "std_logic_vector".

Andy
 
K

KJ

subtype slv is std_logic_vector; -- abbreviation
Basically, you can use "slv" anywhere you would use "std_logic_vector".

You could also do...
subtype Q is std_logic_vector;
and then use "Q" anywhere you would use "std_logic_vector"

But the reason you shouldn't is the same reason you shouldn't use "slv"
either.

KJ
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,162
Latest member
GertrudeMa
Top