data type choice

Discussion in 'VHDL' started by alb, Aug 30, 2013.

  1. alb

    alb Guest

    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

    --
    A: Because it fouls the order in which people normally read text.
    Q: Why is top-posting such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    alb, Aug 30, 2013
    #1
    1. Advertising

  2. alb

    Jim Lewis Guest

    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
    Jim Lewis, Aug 30, 2013
    #2
    1. Advertising

  3. alb

    alb Guest

    On 30/08/2013 18:59, Jim Lewis wrote:
    > 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 :)
    alb, Sep 2, 2013
    #3
  4. alb

    Tricky Guest

    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).
    Tricky, Sep 3, 2013
    #4
  5. alb

    alb Guest

    On 03/09/2013 11:27, Tricky wrote:
    > 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).
    alb, Sep 3, 2013
    #5
  6. alb

    KJ Guest

    On Tuesday, September 3, 2013 6:01:28 AM UTC-4, alb wrote:
    > 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
    KJ, Sep 3, 2013
    #6
  7. alb

    alb Guest

    Hi KJ,

    On 03/09/2013 13:32, KJ wrote:
    > On Tuesday, September 3, 2013 6:01:28 AM UTC-4, alb wrote:
    >> 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;


    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.
    alb, Sep 3, 2013
    #7
  8. alb

    KJ Guest

    On Tuesday, September 3, 2013 8:33:16 AM UTC-4, alb wrote:
    > On 03/09/2013 13:32, KJ wrote:
    > > On Tuesday, September 3, 2013 6:01:28 AM UTC-4, alb wrote:
    > > 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! :)
    >


    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
    KJ, Sep 5, 2013
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. luna
    Replies:
    1
    Views:
    6,814
  2. Mickey Segal
    Replies:
    0
    Views:
    868
    Mickey Segal
    Feb 2, 2004
  3. Jeremy Watts

    choice of data structure

    Jeremy Watts, Jun 14, 2007, in forum: Java
    Replies:
    5
    Views:
    312
    Jeremy Watts
    Jun 15, 2007
  4. metaperl
    Replies:
    0
    Views:
    544
    metaperl
    Aug 22, 2007
  5. miles.jg
    Replies:
    16
    Views:
    866
    Alf P. Steinbach
    Nov 14, 2007
Loading...

Share This Page