Converting records from/to std_logic_vector

B

Brad Smallridge

Hello group,

I have been updating some of my code and started using
records to try to organize and parameterize the code.
Specifically I have a record type, signals, and arrays
like this:

package blobleft_package is
type f1_type is record
left_edge : std_logic_vector(11 downto 0);
left_orig : std_logic_vector(11 downto 0);
done : std_logic;
height : std_logic_vector(8 downto 0);
age : std_logic_vector(8 downto 0);
end record;
end blobleft_package;

I like the way this has improved my code, and the way
the sigs look in ModelSim, except for the problem when I
need to connect to a module that has been previously
defined as an unconstrained std_logic_vector:

f1_di_std(11 downto 0) <= f1_di.left_edge;
f1_di_std(23 downto 12) <= f1_di.left_orig;
f1_di_std(24) <= f1_di.done;
f1_di_std(35 downto 27) <= f1_di.height;
f1_di_std(44 downto 36) <= f1_di.age;

f1_fifox: fifox
port map(
clk => clk,
reset => reset,
wr => f1_wr,
di => f1_di_std,
rd => f1_rd,
do => f1_do_std );

f1_do.left_edge <= f1_do_std(11 downto 0);
f1_do.left_orig <= f1_do_std(23 downto 12);
f1_do.done <= f1_do_std(24);
f1_do.height <= f1_do_std(35 downto 27);
f1_do.age <= f1_do_std(44 downto 36);

Fifox is a first_in_first_out register based
on an inferred RAM and counters.

I suppose I could make the input side of this
more automatic by using a concatenate operator,
but I don't see how to unconcatenate this
automatically on the output side.

I also do not know if it is possible to rewrite
the fifox module with unconstrained record type
inputs.

Some advise on what to do here would be greatly
appreciated.

Regards,

Brad Smallridge
Ai Vision
 
A

Amal

Hello group,

I have been updating some of my code and started using
records to try to organize and parameterize the code.
Specifically I have a record type, signals, and arrays
like this:

package blobleft_package is
type f1_type is record
left_edge : std_logic_vector(11 downto 0);
left_orig : std_logic_vector(11 downto 0);
done : std_logic;
height : std_logic_vector(8 downto 0);
age : std_logic_vector(8 downto 0);
end record;
end blobleft_package;

I like the way this has improved my code, and the way
the sigs look in ModelSim, except for the problem when I
need to connect to a module that has been previously
defined as an unconstrained std_logic_vector:

f1_di_std(11 downto 0) <= f1_di.left_edge;
f1_di_std(23 downto 12) <= f1_di.left_orig;
f1_di_std(24) <= f1_di.done;
f1_di_std(35 downto 27) <= f1_di.height;
f1_di_std(44 downto 36) <= f1_di.age;

f1_fifox: fifox
port map(
clk => clk,
reset => reset,
wr => f1_wr,
di => f1_di_std,
rd => f1_rd,
do => f1_do_std );

f1_do.left_edge <= f1_do_std(11 downto 0);
f1_do.left_orig <= f1_do_std(23 downto 12);
f1_do.done <= f1_do_std(24);
f1_do.height <= f1_do_std(35 downto 27);
f1_do.age <= f1_do_std(44 downto 36);

Fifox is a first_in_first_out register based
on an inferred RAM and counters.

I suppose I could make the input side of this
more automatic by using a concatenate operator,
but I don't see how to unconcatenate this
automatically on the output side.

I also do not know if it is possible to rewrite
the fifox module with unconstrained record type
inputs.

Some advise on what to do here would be greatly
appreciated.

Regards,

Brad Smallridge
Ai Vision

You can write two functions to pack the record into a std_logic_vector
and unpack a std_logic_vector back to the record you defined.

function pack( arg : f1_type ) return std_logic_vector is
variable result : std_logic_vector(44 downto 0);
begin
result(11 downto 0) <= arg.left_edge;
result(23 downto 12) <= arg.left_orig;
result(24) <= arg.done;
result(35 downto 27) <= arg.height;
result(44 downto 36) <= arg.age;
return result;
end function pack;

function unpack( arg : std_logic_vector(44 downto 0) ) return
fl_type is
variable result : fl_type;
begin
result.left_edge <= f1_do_std(11 downto 0);
result.left_orig <= f1_do_std(23 downto 12);
result.done <= f1_do_std(24);
result.height <= f1_do_std(35 downto 27);
result.age <= f1_do_std(44 downto 36);
return result;
end function unpack;

And you can use them like:

f1_di_std <= pack( f1_di );
f1_do <= unpack( f1_do_std );

Unconstrained record types and aggregates would be very nice that will
hopefully come in VHDL 2006. Another would be the addition of
direction on records for ports.

Hope that does help.
-- Amal
 
K

KJ

Hello group,

I have been updating some of my code and started using
records to try to organize and parameterize the code.
Specifically I have a record type, signals, and arrays
like this:

I like the way this has improved my code, and the way
the sigs look in ModelSim, except for the problem when I
need to connect to a module that has been previously
defined as an unconstrained std_logic_vector:
What I'll do is create a To_Std_Logic_Vector and a
From_Std_Logic_Vector function for each of my record types. Since
many times you need to do this to implement a software interface and
bit positions are subject to change I define the record type in such a
way that if I need to insert/delete/move bits around I simply edit the
To/From functions and recompile. I don't need to edit every place
that I 'use' these functions, that way the hard coded bit positions
are all maintained in a single place, in the record definition
itself. Below is an example of the record definition and To/From
functions to use as a model

type t_SPI_DATA_PORT is record
Data_Size_Minus1: std_ulogic_vector(31 downto 28);
Done: std_ulogic_vector(27 downto 27);
Rx_Wait_Timeout: std_ulogic_vector(26 downto 26);
Rx_Wait_On_Miso: std_ulogic_vector(25 downto 25);
Sclk_Select: std_ulogic_vector(24 downto 24);
Reserved: std_ulogic_vector(23 downto 20);
Hold_Cs: std_ulogic_vector(19 downto 19);
Cpha: std_ulogic_vector(18 downto 17);
Cpol: std_ulogic_vector(16 downto 16);
Data: std_ulogic_vector(15 downto 0);
end record;
....
function To_Std_ULogic_Vector(L : t_SPI_DATA_PORT) return
std_ulogic_vector is
variable RetVal: std_ulogic_vector(31 downto 0);
begin
RetVal := (others => '0');

RetVal(L.Data_Size_Minus1'range) := L.Data_Size_Minus1;
RetVal(L.Done'range) := L.Done;
RetVal(L.Rx_Wait_Timeout'range) := L.Rx_Wait_Timeout;
RetVal(L.Sclk_Select'range) := L.Sclk_Select;
RetVal(L.Reserved'range) := L.Reserved;
RetVal(L.Rx_Wait_On_Miso'range) := L.Rx_Wait_On_Miso;
RetVal(L.Hold_Cs'range) := L.Hold_Cs;
RetVal(L.Cpha'range) := L.Cpha;
RetVal(L.Cpol'range) := L.Cpol;
RetVal(L.Data'range) := L.Data;

return(RetVal);
end To_Std_ULogic_Vector;

function From_Std_ULogic_Vector(L : std_ulogic_vector) return
t_SPI_DATA_PORT is
variable RetVal: t_SPI_DATA_PORT;
variable Lx: std_ulogic_vector(L'length - 1 downto 0);
begin
Lx := L;

RetVal.Data_Size_Minus1 := Lx(RetVal.Data_Size_Minus1'range);
RetVal.Done := Lx(RetVal.Done'range);
RetVal.Rx_Wait_Timeout := Lx(RetVal.Rx_Wait_Timeout'range);
RetVal.Sclk_Select := Lx(RetVal.Sclk_Select'range);
RetVal.Reserved := Lx(RetVal.Reserved'range);
RetVal.Rx_Wait_On_Miso := Lx(RetVal.Rx_Wait_On_Miso'range);
RetVal.Hold_Cs := Lx(RetVal.Hold_Cs'range);
RetVal.Cpha := Lx(RetVal.Cpha'range);
RetVal.Cpol := Lx(RetVal.Cpol'range);
RetVal.Data := Lx(RetVal.Data'range);

return(RetVal);
end From_Std_ULogic_Vector;

Kevin Jennings
 
J

Jim Lewis

Brad,
... except for the problem when I
need to connect to a module that has been previously
defined as an unconstrained std_logic_vector:

f1_di_std(11 downto 0) <= f1_di.left_edge;
f1_di_std(23 downto 12) <= f1_di.left_orig;
f1_di_std(24) <= f1_di.done;
f1_di_std(35 downto 27) <= f1_di.height;
f1_di_std(44 downto 36) <= f1_di.age;

f1_fifox: fifox
port map(
clk => clk,
reset => reset,
wr => f1_wr,
di => f1_di_std,
rd => f1_rd,
do => f1_do_std );

f1_do.left_edge <= f1_do_std(11 downto 0);
f1_do.left_orig <= f1_do_std(23 downto 12);
f1_do.done <= f1_do_std(24);
f1_do.height <= f1_do_std(35 downto 27);
f1_do.age <= f1_do_std(44 downto 36);

You can map array ports by element or slice. Just
be sure to map them all. Also there is either a restriction
or a tool bug somewhere that requires you to map them contiguously.
This will reduce the noise some, but not completely:

>
> f1_fifox: fifox
> port map(
> clk => clk,
> reset => reset,
> wr => f1_wr,
di(11 downto 0) => f1_di.left_edge,
di(23 downto 12) => f1_di.left_orig,
di(24) => f1_di.done,
di(35 downto 27) => f1_di.height,
di(44 downto 36) => f1_di.age,
> rd => f1_rd,
do(11 downto 0) => f1_do.left_edge,
do(23 downto 12) => f1_do.left_orig,
do(24) => f1_do.done,
do(35 downto 27) => f1_do.height,
do(44 downto 36) => f1_do.age,
);

Cheers,
Jim Lewis
SynthWorks VHDL Training
 
J

Jonathan Bromley

What I'll do is create a To_Std_Logic_Vector and a
From_Std_Logic_Vector function for each of my record types.

100% agree; conversion functions are good, especially those that
synthesise to nothing at all in hardware.
the hard coded bit positions
are all maintained in a single place, in the record definition

That's very nice. And, since the record definition and the conversion
functions are likely all in the same package, it's very easy to
maintain even if you need to add fields to the record, or remove them.

One small question, though: This sort of thing has always seemed
to me to be a source of obscure hard-to-trace bugs. Suppose,
by some unlucky accident, you mis-typed some of the subscript
values. Then you will get overlaps or gaps in the bit layout,
but no errors that would be detected by any simulation tool.
Is there any way we can protect ourselves against that, perhaps
by intelligent use of assertions? (I have a couple of ideas, but
I don't much like any of them.)

When defining bit field layouts like this, I often like to create
named subtypes for each subscript range:

subtype opcode_bits is natural range 31 downto 28;
subtype condition_bits is natural range 27 downto 24;
subtype src_reg_bits is natural range 23 downto 20;
subtype dst_reg_bits is natural range 19 downto 16;
subtype offset_bits is natural range 15 downto 0;

type instr_code_t is record
opcode: std_logic_vector(opcode_bits);
condition: std_logic_vector(condition_bits);
...
end record;

function to_instr_code(vec: std_logic_vector) return instr_code_t is
variable instr: instr_code_t;
begin
instr.opcode := vec(opcode_bits);
...

It's only cosmetic, but the ranges often come in handy elsewhere too.
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
(e-mail address removed)
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
K

KJ

Jonathan Bromley said:
100% agree; conversion functions are good, especially those that
synthesise to nothing at all in hardware.


That's very nice. And, since the record definition and the conversion
functions are likely all in the same package, it's very easy to
maintain even if you need to add fields to the record, or remove them.

One small question, though: This sort of thing has always seemed
to me to be a source of obscure hard-to-trace bugs. Suppose,
by some unlucky accident, you mis-typed some of the subscript
values. Then you will get overlaps or gaps in the bit layout,
but no errors that would be detected by any simulation tool.
Is there any way we can protect ourselves against that, perhaps
by intelligent use of assertions? (I have a couple of ideas, but
I don't much like any of them.)
For synthesizable code, overlaps are easily detected by simply running
through synthesis. The overlaps will show up as multiple drivers on a net.
Even when I'm nowhere near ready to really synthesize a design for real,
I'll run through synthesis as a background task to use it to find things
such as this. At that stage, I just consider the synthesizer as a different
set of eyes looking at the code instead of only as something that produces
programming bitstreams.

If the record type hppens to be for some software accessible port, then gaps
can get detected in simulation by asserting on an unknown value during a
read/write since the 'To_Std_Logic_Vector' function won't end up assigning
to the 'gap bits'. If the record type is not really visible from the
outside, just part of some internal interface, then the gaps might not
really matter.

Other than that, no I don't have a more foolproof way of catching the gaps
and overlaps using assertions. I have used both of the above techniques to
detect when I did create unintended gaps/overlaps and neither required much
effort to find and fix.
When defining bit field layouts like this, I often like to create
named subtypes for each subscript range:

subtype opcode_bits is natural range 31 downto 28;
subtype condition_bits is natural range 27 downto 24;
subtype src_reg_bits is natural range 23 downto 20;
subtype dst_reg_bits is natural range 19 downto 16;
subtype offset_bits is natural range 15 downto 0;

type instr_code_t is record
opcode: std_logic_vector(opcode_bits);
condition: std_logic_vector(condition_bits);
...
end record;
I considered that but rejected it simply because it involves more typing and
doesn't have any benefit that I've found. The bit positions are already in
one place in the design, changing them involves editing one file and
recompiling. Having more one indirection involved (i.e. the subtypes for
each of the ranges) clutters things up in my opinion and I don't see any
value coming back to anyone as a result of the extra effort.
function to_instr_code(vec: std_logic_vector) return instr_code_t is
variable instr: instr_code_t;
begin
instr.opcode := vec(opcode_bits);
...

It's only cosmetic, but the ranges often come in handy elsewhere too.
The 'elsewhere' though is either some block that talks to the first one or
in a testbench. In both of those cases, the usage tends to be simply to
refer to the entire field, not some subelement i.e.
Inst.opcode <= Run; -- Set the opcode to some enumerated type
or
Some_Port.Count <= std_ulogic_vector(to_unsigned(55,
Some_Port.Count'length)); -- Set the field to '55'

For those situations where you do need to refer to some subelement of the
field you have to do something like the following regardless of whether you
have the additional range subtypes or not

if (Inst.opcode(Inst.opcode'right) = '1').... -- Stripping out the
right most bit of the opcode

Another case would be some code that needs to know the field width for some
reason but otherwise doesn't much care about the field itself. For example,
when it comes to gluing together some code that has a software interface
(i.e. bit positions and such) to some more general purpose code that simply
needs to know an operating range (perhaps a timer that simply needs to know
what the maximum counter value to count up to should be). In that situation
you might have something like the following...

My_Timer : entity work.Timer generic map(
Max_Counter => 2**Some_Port.Count'length -1)
....

Again, if one were to use subtypes on the range it wouldn't make the code
any cleaner. Did you have some other usage in mind for where you've found
the range subtypes to be useful?

Kevin Jennings
 

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,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top