data type choice


A

alb

Hi everyone,

I'm trying to define my own set of types in order to have a more
readable code and try to represent my data structures at a higher level.
This code is used for testbench only and I'm using it to define my
packets to be transmitted on my serial link.

Eventually somewhere there is a procedure handling these data types and
'sending' a std_logic signal to a DUT.

<code>
constant WORD_LENGTH : natural := 16;
constant MAX_BUF_LEN : natural := 1024;

-- data word over the link interface are 16 bit wide.
subtype data_word_t is bit_vector (WORD_LENGTH - 1 downto 0);

type buff_t is array (0 to MAX_BUF_LEN - 1) of data_word_t;

type data_buffer_t is record
len : natural; -- length of the data
buf : buff_t; -- data buffer
end record;
</code>

the intent of this gymnastic is to deal with bits and non negative
integers for packet lengths, but I'm facing lots of troubles when I
start to manipulate these datatypes because of conversion.

Since the header of my packets include the length I thought that simply
doing:

<code>
variable data_buffer : data_buffer_t;
variable data_word : data_word_t;
data_word := some_conversion_function(data_buffer.len);
</code>

would have been enough, but it seems to me I'm too stupid to find the
right combination of conversions.

Moreover I start to think that maybe my original choice of types is not
optimal and some people around here can direct me to a better choice of
types.

Should I build a set of functions that allow me to manipulate these types?

Any suggestion is welcome.

Al
 
Ad

Advertisements

J

Jim Lewis

Hi Al,
If I were not using std_logic_vector, I would be using integer. I am planning on migrating toward using integers in my testbenches. Especially if we can get larger than 32 bit integers in the next revision of the language. OSVVM likers integers.

If you continue using bit_vector, math operations are in numeric_bit_unsigned and numeric_bit. Conversions to between std_logic family are in std_logic_1164:
function To_bit (s : STD_ULOGIC; xmap : BIT := '0') return BIT;
function To_bitvector (s : STD_ULOGIC_VECTOR; xmap : BIT := '0') return BIT_VECTOR;

function To_StdULogic (b : BIT) return STD_ULOGIC;
function To_StdLogicVector (b : BIT_VECTOR) return STD_LOGIC_VECTOR;

In my testbenches, I like communicating through a single record. Bit_vector does not work with this approach since it does not have a resolution function. Of course, you could write one. I did this for integer, time, and real.

Jim
 
A

alb

Hi Al, If I were not using std_logic_vector, I would be using
integer. I am planning on migrating toward using integers in my
testbenches. Especially if we can get larger than 32 bit integers in
the next revision of the language. OSVVM likers integers.

unfortunately I cannot get rid of the slv since this is how the DUT
interface is defined. It seems to me that slv is a common choice with RTL.

I may understand why OSVVM likes integers and I would like to profit of
that as well, but sooner or later you need to face with the conversion
to slv and this is where I hoped for the bv to be more friendly.
If you continue using bit_vector, math operations are in numeric_bit_unsigned and numeric_bit. Conversions to between std_logic family are in std_logic_1164:
function To_bit (s : STD_ULOGIC; xmap : BIT := '0') return BIT;
function To_bitvector (s : STD_ULOGIC_VECTOR; xmap : BIT := '0') return BIT_VECTOR;

function To_StdULogic (b : BIT) return STD_ULOGIC;
function To_StdLogicVector (b : BIT_VECTOR) return STD_LOGIC_VECTOR;

thanks for the hint. My choice for bv is that it seems to me well
representing bit fields in the protocol.

What I'm doing is breaking down the packet format to each individual
field, so I can randomly set each field and see how this effects my DUT.
How do you eventually convert the integers into your '16bit word header'
for example?
In my testbenches, I like communicating through a single record.

I have not yet moved to the single record transaction and therefore keep
using two (one to the server and one from the server). As of now I do
not have a case for that need.
Bit_vector does not work with this approach since it does not have a
resolution function. Of course, you could write one. I did this for
integer, time, and real.

Writing the resolution function shouldn't be complex. I'll postpone this
effort though since I'm not yet convinced I need it :)
 
T

Tricky

Its not quite clear to me - is this data_buffer_t a single packet, or does it contain multiple packets?

If its the latter, wouldnt it be better to make a single packet type, and then have some sort of controller to convert the packet into your serial data stream, rather than have a big fixed bus in your testbench, that isnt easily seperable into packets (if you wanted to inspect them in modelsim).

What I have done before in a homemade BFM for PCIe over an avalon streamingbus was to create a linked list of packets (acting as my event queue) handled by a protected type, and a wraparound entity controlled the data flow (with random wait times between packets to try and simulate some form of worse case or reality. Meant I could pump thounsands of PCIe packets into my controller, and from the top level all I needed were procedures like send_rd_req(addr) send_wr_req(addr, data), and never worry about overflowing any controllers in the testbench (only in the design).
 
A

alb

Its not quite clear to me - is this data_buffer_t a single packet, or
does it contain multiple packets?

data_buffer_t is a single packet. I intend to break down the fields more
clearly but for the time being you can assume that the packet is just a
sequence of data_word_t types. The 'controller', as you call it, does
the job of converting the sequence into the appropriate bits (I call it
server).
If its the latter, wouldnt it be better to make a single packet type,
and then have some sort of controller to convert the packet into your
serial data stream, rather than have a big fixed bus in your
testbench, that isnt easily seperable into packets (if you wanted to
inspect them in modelsim).

this is indeed my intent. The client simply sends a random sequence of
packets, varying randomly the various fields of the packet. Since OSVVM
allows you to do so very easily I intended to profit of this.

The fact is that in the stream of serial data out I need to 'merge'
information coming from data_buffer_t elements and combine them in a
sequence of bits on a std_logic port. This 'merging' is now very awkward
due to the casting (from/to integers, bit and std_logic).
What I have done before in a homemade BFM for PCIe over an avalon
streaming bus was to create a linked list of packets (acting as my
event queue) handled by a protected type, and a wraparound entity
controlled the data flow (with random wait times between packets to
try and simulate some form of worse case or reality. Meant I could
pump thounsands of PCIe packets into my controller, and from the top
level all I needed were procedures like send_rd_req(addr)
send_wr_req(addr, data), and never worry about overflowing any
controllers in the testbench (only in the design).

My issue is not 'overflowing' the controller on the testbench. I simply
need to stick to a selection of datatypes which are coherent with the
number of conversion to and from std_logic.

If I use integers *and* bits than I'm kind of stuck when I want to
combine them, if I use only integers than it is not quite readable when
you need to deal with words which have various fields, if I use only
bits than a length field may appear not so readable (101011110 instead
of 350, with unsigned representation). Assume you have a data word which
is split in some fields like the following:

- add(15 downto 12)
- crc(11)
- ext(10)
- len(9 downto 0)

a second word of length comes if ext is set. A crc flag indicates the
presence of crc at the end of the message. If all the fields are
integers (better be natural in this case) then my word to be sent will
look like this:

data_word = (add * 2**12) + (crc * 2**11) + (ext * 2**10) + len

which I find rather ugly. And again at a certain point I should convert
the data_word integer to a std_logic (passing through an unsigned).
 
K

KJ

Assume you have a data word which
is split in some fields like the following:

- add(15 downto 12)
- crc(11)
- ext(10)
- len(9 downto 0)

Then presumably you would have defined a record like this...

type t_packet is record
add: std_ulogic_vector(15 downto 12);
crc: std_ulogic_vector(11 downto 11);
ext: std_ulogic_vector(10 downto 10);
len: std_ulogic_vector(9 downto 0);
end record;

Then I would define functions to convert between records and std_ulogic_vectors like this...
function to_std_ulogic_vector(L: t_packet) return std_ulogic_vector is
variable RetVal: std_ulogic_vector(15 downto 0);
begin
RetVal(L.add'range) := L.add;
RetVal(L.crc'range) := L.crc;
RetVal(L.ext'range) := L.ext;
RetVal(L.len'range) := L.len;
return(RetVal);
end function to_std_ulogic_vector;

function from_std_ulogic_vector(L: std_ulogic_vector) return t_packet;
-- Left as an exercise for the reader, but similar in style with to_std_ulogic_vector

a second word of length comes if ext is set. A crc flag indicates the
presence of crc at the end of the message. If all the fields are
integers (better be natural in this case) then my word to be sent will
look like this:

data_word = (add * 2**12) + (crc * 2**11) + (ext * 2**10) + len

which I find rather ugly. And again at a certain point I should convert
the data_word integer to a std_logic (passing through an unsigned).

But data_word <= to_std_ulogic_vector(L); looks much better. If you want to convert it to
integer than wrap to_integer(unsigned()) around it.

Kevin Jennings
 
Ad

Advertisements

A

alb

Hi KJ,

Then presumably you would have defined a record like this...

type t_packet is record
add: std_ulogic_vector(15 downto 12);
crc: std_ulogic_vector(11 downto 11);
ext: std_ulogic_vector(10 downto 10);
len: std_ulogic_vector(9 downto 0);
end record;

That is more or less where I was aiming to, but I would have preferred
to define the len element as an integer (or even better a natural) instead.
At that point I believe that in to_std_ulogic_vector I could change this:

- RetVal(L.len'range) := L.len;

into something along these lines:

- RetVal(log2(L.len'length) downto 0) := to_unsigned(L.len, ???);

uhm... I'm again lost! :)

Actually your example triggers another question: why std_ulogic_vector
and not a bit_vector? At the packet definition level I do not really
need the 'Z', 'W', 'L'... values of the type. I may blindly adopt the
std_ulogic (and why not std_logic instead?), I just would like to know
what is important and what might be neglected when deciding what type of
data we choose.
Then I would define functions to convert between records and std_ulogic_vectors like this...
function to_std_ulogic_vector(L: t_packet) return std_ulogic_vector is
variable RetVal: std_ulogic_vector(15 downto 0);
begin
RetVal(L.add'range) := L.add;
RetVal(L.crc'range) := L.crc;
RetVal(L.ext'range) := L.ext;
RetVal(L.len'range) := L.len;
return(RetVal);
end function to_std_ulogic_vector;

function from_std_ulogic_vector(L: std_ulogic_vector) return t_packet;
-- Left as an exercise for the reader, but similar in style with to_std_ulogic_vector

I think I got your message, by writing a to/from pair of functions which
hides the nuances of the type casting and formatting I can simply
manipulate my records.

BTW I did not know the 'range attribute would return (15 downto 12) for
L.add, I was convinced it would simply return a generic (3 downto
0)...thanks for the hint.
 
Ad

Advertisements

K

KJ

That is more or less where I was aiming to, but I would have preferred
to define the len element as an integer (or even better a natural) instead.
At that point I believe that in to_std_ulogic_vector I could change this:

- RetVal(L.len'range) := L.len;

into something along these lines:

- RetVal(log2(L.len'length) downto 0) := to_unsigned(L.len, ???);

uhm... I'm again lost! :)

While you can put other data types in, one of the whole points of this is to
come up with a generic type that can be passsed through any conduit. In this
case, the generic type is 'std_ulogic_vector'. The 'conduit' is anything that
you want that is generic in nature but you need for your application. A couple
examples would be memory or a fifo or even an interconnect interface. You don't
need to write the code for a memory to store your t_packets when you already have
one that is debugged and works for std_logic_vector or std_ulogic_vector. All
you have to do to reuse that working component is convert to a vector. If you
put those to/from vector conversion functions right at the input/output of that
conduit you would typically not need to worry very often about just which bit
in the memory is used to represent a particular field.

Even something simple like 'ext' which you have as a one bit field. Normally, one
would think to first use 'std_logic/std_ulogic' rather than a one bit wide vector
for this field. However, try writing the to/from functions where you change 'ext'
to something else and you'll find yourself having some trouble expressing the
proper bit position. You can do it, but it won't be as clean as what I've shown.
By making it a vector and putting that right in the record you've documented it
precisely and portably.

The only manual check you have here is that you don't double up and assign
the same bit to multiple fields. A big plus is that the to/from conversion
functions get written once and would be put into the same package as the record
so when the bits in that record get packed differently you're editing one file. No
edits are needed when simply changing the bit definitions, only if you add/remove
fields.

Actually your example triggers another question: why std_ulogic_vector
and not a bit_vector? At the packet definition level I do not really
need the 'Z', 'W', 'L'... values of the type.

I like std_ulogic because of the metavalues. You never know where you're going to
reuse something and debugging why some signal is unknown will almost always be
fixing a design error. Using bit_vector you can fool yourself into thinking that
something is designed correctly when you're actually dependent on the simulator's
initialization of that bit_vector which may or may not agree with what the hardware
is actually doing.

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

Top