2

Is there a way to emulate the data encapsulation features of a C++ class in VHDL-2008 using just VHDL functions and VHDL records? I've seen this type of thing done many times in languages such as "c", but very rarely for VHDL.

Specifically, I want to make all the class data public to every method of the class just to keep it simple.

Example C++:

class item_t {
   public:
       uint32_t addr;
       uint32_t data;
       bool     valid;
};

class keeper_t {
    public:
        item_t data[100];
        void     put(uint32_t addr, uint32_t data);
        uint32_t get(uint32_t addr);
};

My thoughts on how to emulate the above code in VHDL would be to create a record to hold the class data and to pass it into each function every time to hold the state of the class. The problem that I have with this technique in VHDL, is how to modify the "class" data structure passed into the function? VHDL is really very limited when it comes to returning a structure to the calling process. Example:

library ieee;
use ieee.std_logic_1164.all;

--------------------------------------------------
package ADT is

  constant A_OK        :integer := 0;
  constant max_keepers :natural := 100;

  type keeper_item_t is record
    addr     :std_logic_vector(31 downto 0);
    data     :std_logic_vector(31 downto 0);
    valid    :std_logic;
  end record;  

  type keeper_t is array (0 to max_keepers) of keeper_item_t;

  function keeper_new return keeper_t;
  function keeper_add(signal k :inout keeper_t) return integer;
  function keeper_get(signal k :inout keeper_t; signal kitem: out keeper_item_t) return integer;

end package ADT;

--------------------------------------------------
package body ADT is

  function keeper_new return keeper_t is
      variable rkeeper :keeper_t;
  begin
      rkeeper(0).addr  <= X"AABBCCDD";
      rkeeper(0).data  <= X"10101010";
      rkeeper(0).valid <= '0';
      return rkeeper;
  end function;

  function keeper_add(signal k :inout keeper_t) return integer is
  begin
      k(0).addr  <= X"12345678";
      k(0).data  <= X"BEEF1234";
      k(0).valid <= '1';
      return A_OK;
  end function;

  function keeper_get(signal k :inout keeper_t; signal kitem: out keeper_item_t) return integer is
    variable kitem: keeper_item_t;
  begin
      kitem := k(0);
      return A_OK;
  end function;

end package body;

--------------------------------------------------
entity testbench is
end entity;

--------------------------------------------------
architecture sim of testbench is
begin

process
    variable kp :keeper_t;
    variable ki :keeper_item_t;
    variable ok :integer;
begin
        kp := keeper_new;   
    ok := keeper_put(kp);
    ki := keeper_get(kp);       
    wait;
end process;

end architecture;

The above VHDL code doesn't compile and generated the following syntax error:

--line:20:  function keeper_get(signal k :inout keeper_t; signal kitem: out 
keeper.vhd:20:30: mode of a function parameter cannot be inout or out
keeper.vhd:21:30: mode of a function parameter cannot be inout or out
keeper.vhd:21:56: mode of a function parameter cannot be inout or out

To get around this problem, I'm thinking I need to pass in a VHDL "access pointer" instead of the keeper_t structure itself. if I do this then I am passing a readonly pointer into the function that in turn points to the actual data structure that can be modified?

    type Keeper_Ptr_t is access keeper_t;

    --Dereferencing a VHDL Access Type: The suffix .all is 
    --  used to de-reference a pointer, i.e. it gives 
    --  you the thing pointed to by the pointer.

So I try this method as well of using an access type:

library ieee;
use ieee.std_logic_1164.all;

--------------------------------------------------
package ADT is

  constant A_OK        :integer := 0;
  constant max_keepers :natural := 100;

  type keeper_item_t is record
    addr     :std_logic_vector(31 downto 0);
    data     :std_logic_vector(31 downto 0);
    valid    :std_logic;
  end record;  

  type keeper_t is array (0 to max_keepers) of keeper_item_t;

  type keeper_ptr_t is access keeper_t;

  function keeper_new return keeper_t;
  function keeper_add(kp: keeper_ptr_t) return integer;
  function keeper_get(kp: keeper_ptr_t) return keeper_item_t;

end package ADT;

--------------------------------------------------
package body ADT is

  function keeper_new return keeper_t is
      variable rkeeper :keeper_t;
  begin
      rkeeper(0).addr  <= X"AABBCCDD";
      rkeeper(0).data  <= X"10101010";
      rkeeper(0).valid <= '0';
      return rkeeper;
  end function;

  function keeper_add(kp: keeper_ptr_t) return integer is
  begin
      kp.all(0).addr  <= X"12345678";
      kp.all(0).data  <= X"BEEF1234";
      kp.all(0).valid <= '1';
      return A_OK;
  end function;

  function keeper_get(kp: keeper_ptr_t) return keeper_item_t is
  begin
      return kp.all(0);
  end function;

end package body;

--------------------------------------------------
entity testbench is
end entity;

--------------------------------------------------
architecture sim of testbench is
begin

process
    variable kp :keeper_t;
    variable ki :keeper_item_t;
    variable ok :integer;
begin
    kp := keeper_new;   
    ok := keeper_put(kp);
    ki := keeper_get(kp);       
    wait;
end process;

end architecture;

This version fails with the error:

keeper.vhd:22:23: type of constant interface "kp" cannot be access type "keeper_ptr_t"
keeper.vhd:23:23: type of constant interface "kp" cannot be access type "keeper_ptr_t"
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • http://www.eda-twiki.org/vhdl-200x/vhdl-200x-ft/proposals/WP002-associative-arrays.pdf – Bimo Jul 25 '19 at 16:40
  • There are two classes of subprograms in VHDL. Functions whose calls are expressions which leads to the limitations on parameter mode and procedures whose calls are statements with parameters that can be mode out or in out as well. VHDL doesn't have expression-statements. For verification purposes protected types can include methods giving exclusive access to 'private' shared variables. –  Jul 25 '19 at 16:42
  • https://stackoverflow.com/questions/53694255/error-passing-type-access-to-function-in-vhdl – Bimo Jul 25 '19 at 17:17
  • so what... this is testbench code... – Bimo Jul 25 '19 at 17:40
  • Possible duplicate of [Error passing type access to function in VHDL](https://stackoverflow.com/questions/53694255/error-passing-type-access-to-function-in-vhdl) –  Jul 25 '19 at 17:54
  • Note that "put" is what C++ calls a "void function", i.e. a procedure. And procedures have no such restrictions on parameter modes. So starting from the first attempt, the fix to "put" should be much simpler than messing around with pointers. ("Get" should probably be a function taking only an IN mode "addr" and returning a record, similar to the C++ prototype; OR convert the current version to a procedure too, since it only "returns" a constant 0. –  Jul 26 '19 at 15:12

4 Answers4

2

What I learnt from this:

(1) pass an access pointer of a record into procedures as a "variable" type. and don't use functions for class methods.

(2) use "impure function" to create the new method for the "class", return a newly allocated record using the "new" keyword.

You can actually do better. VUnit has implemented references that acts like pointers but are constants. With constants there are no limitations on what you can do. You can use them with functions, you can build arrays from them, you can share them without any constraints, you can have them in generics etc. One example is the integer array which is built using the basic integer_vector_ptr_t type. It can serve as an example on how you can build your own types. There are also other data types built on the same principle like queues and dictionaries.

VHDL-2019 will fix some of the issues with protected types but they will not be as flexible as constants. The references in VUnit even works with VHDL-93.

You read more about it here.

Disclaimer: I'm one of the authors.

lasplund
  • 1,350
  • 1
  • 8
  • 17
1

You also want to look into protected types from vhdl 2002. They are similar to classes in that they can have member functions and procedures with internal variables. There are some limitations in vhdl 2008, like not being able to pass in/out access types, orc have arrays of them (both fixed in vhdl 2019). But it's still perfectly possible to build things like queues or randomised packet generators.

Type pt is protected
  Procedure add_one;
  Function get_val return integer;
End protected pt;

Type pt is protected body
  Variable x : integer;

  Procedure add_one is
  Begin
   X := x + 1;
  End  procedure;

  Function get_val return integer is
  Begin
    Return x;
  End function get_val;
End protected body pt;

.....

Process
  Variable ptv : pt;
Begin
  Ptv.add_one;
  Ptv.add_one;

  Report to_string(ptv.get_val);
  Wait;
End process;

OSVVM is a good example that has extensive use of protected types (and 2008 features in general).

Tricky
  • 3,791
  • 3
  • 10
  • 23
0

Its possible!

What I learnt from this:

(1) pass an access pointer of a record into procedures as a "variable" type. and don't use functions for class methods.

(2) use "impure function" to create the new method for the "class", return a newly allocated record using the "new" keyword.


library ieee;
use ieee.std_logic_1164.all;

--------------------------------------------------
package ADT is

  constant A_OK        :integer := 0;
  constant max_keepers :natural := 100;

  type keeper_item_t is record
    addr     :std_logic_vector(31 downto 0);
    data     :std_logic_vector(31 downto 0);
    valid    :std_logic;
  end record;  

  type keeper_t is array (0 to max_keepers) of keeper_item_t;

  type keeper_ptr_t is access keeper_t;

  impure function keeper_new return keeper_ptr_t;
  procedure keeper_add(variable kp: inout keeper_ptr_t);
  procedure keeper_get(variable kp: inout keeper_ptr_t; variable item: out keeper_item_t);

end package ADT;

--------------------------------------------------
package body ADT is

    impure function keeper_new return keeper_ptr_t is
        variable kp : keeper_ptr_t;
    begin
        kp := new keeper_t;
        kp.all(0).addr  := X"AABBCCDD";
        kp.all(0).data  := X"10101010";
        kp.all(0).valid := '0';
        return kp;
    end function;

    procedure keeper_add(variable kp: inout keeper_ptr_t) is
    begin
        kp.all(0).addr  := X"12345678";
        kp.all(0).data  := X"BEEF1234";
        kp.all(0).valid := '1';
    end procedure;

    procedure keeper_get(variable kp: inout keeper_ptr_t; variable item: out keeper_item_t) is
    begin
        item := kp.all(0);
    end procedure;

end package body;

use work.ADT.all;

--------------------------------------------------
entity testbench is
end entity;

--------------------------------------------------
architecture sim of testbench is
begin

process
    variable km :keeper_t;
    variable kp :keeper_ptr_t;
    variable ki :keeper_item_t;
    variable ok :integer;
begin
    kp := keeper_new;
    keeper_add(kp);
    keeper_get(kp, ki);     
    wait;
end process;

end architecture;

Bimo
  • 5,987
  • 2
  • 39
  • 61
0

Another Example of class emulated using VHDL and access pointer:

-- Behavorial Fifo for use in testbench
--------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

--------------------------------------------------
package pkg_simfifo is
    subtype uint32_t is unsigned(31 downto 0);

    constant max_simfifos :natural := 100;

    type simfifo_item_t is record
       addr     :std_logic_vector(31 downto 0);
       data     :std_logic_vector(31 downto 0);
       valid    :std_logic;
    end record;  

    type simfifo_array_t is array (0 to max_simfifos) of simfifo_item_t;

    type simfifo_class_t is record
        karray :simfifo_array_t;
        count  :integer;
    end record;

    type simfifo_ptr_t is access simfifo_class_t;

    impure function simfifo_new return simfifo_ptr_t;

    procedure simfifo_purge(
        variable that :inout  simfifo_ptr_t
    );

    -- Push Onto Fifo
    procedure simfifo_push(
        variable that :inout  simfifo_ptr_t;
                 addr :in     std_logic_vector(31 downto 0);
                 data :in     std_logic_vector(31 downto 0);        
        variable ok   :out    std_logic
    );
    procedure simfifo_push(
        variable that :inout  simfifo_ptr_t;
                 addr :in     unsigned(31 downto 0);
                 data :in     unsigned(31 downto 0);
        variable ok   :out    std_logic
    );

    -- Pop from Fifo
    procedure simfifo_pop(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    std_logic_vector(31 downto 0);
        variable data  :out    std_logic_vector(31 downto 0);
        variable valid :out    std_logic
    );
    procedure simfifo_pop(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    unsigned(31 downto 0);
        variable data  :out    unsigned(31 downto 0);
        variable valid :out    std_logic
    );

    -- Peak without Popping
    procedure simfifo_peek(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    std_logic_vector(31 downto 0);
        variable data  :out    std_logic_vector(31 downto 0);
        variable valid :out    std_logic
    );

    -- Peak without Popping
    procedure simfifo_peek(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    unsigned(31 downto 0);
        variable data  :out    unsigned(31 downto 0);
        variable valid :out    std_logic
    );

    procedure simfifo_get_count(
        variable that  :inout  simfifo_ptr_t; 
                 count :out    integer
    );

end package;

--------------------------------------------------
package body pkg_simfifo is

    impure function simfifo_new return simfifo_ptr_t is
        variable that : simfifo_ptr_t;
    begin
        that := new simfifo_class_t;
        simfifo_purge(that);        
        return that;
    end function;

    procedure simfifo_purge(
        variable that   :inout  simfifo_ptr_t
    ) is
    begin        
        that.all.count := 0;                

        for i in 0 to max_simfifos loop
            that.all.karray(0).addr  := (others => '0');
            that.all.karray(0).data  := (others => 'X');
            that.all.karray(0).valid := '0';
        end loop;

    end procedure;

    -- Push Onto Fifo
    procedure simfifo_push(
        variable that :inout  simfifo_ptr_t;
                 addr :in     std_logic_vector(31 downto 0);
                 data :in     std_logic_vector(31 downto 0);        
        variable ok   :out    std_logic
    ) is  
        variable i    :integer;
    begin

        --insert address at end of array
        if (that.all.count < max_simfifos) then
             i := that.all.count;
            that.all.karray(i).addr  := addr;
            that.all.karray(i).data  := data;
            that.all.karray(i).valid := '1';
            that.all.count := that.all.count + 1;
            ok := '1';
            return;
        end if;

        -- no more space in array
        ok := '0';
        return;        
    end procedure;

    -- Push Onto Fifo
    procedure simfifo_push(
        variable that :inout  simfifo_ptr_t;
                 addr :in     unsigned(31 downto 0);
                 data :in     unsigned(31 downto 0);
        variable ok   :out    std_logic
    ) is
        variable slv_addr :std_logic_vector(31 downto 0);
        variable slv_data :std_logic_vector(31 downto 0);        
    begin
        simfifo_push(
            that, 
            std_logic_vector(addr),
            std_logic_vector(data),
            ok
        );
        return;        
    end procedure;


    -- Pop from Fifo
    procedure simfifo_pop(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    std_logic_vector(31 downto 0);
        variable data  :out    std_logic_vector(31 downto 0);
        variable valid :out    std_logic
    ) is
    begin
        addr   := that.all.karray(0).addr;
        data   := that.all.karray(0).data;
        valid  := that.all.karray(0).valid;

        -- Shift Down
        for i in 0 to max_simfifos-1 loop
            --report "max_simfifos" & integer'image(max_simfifos) & " i:" & integer'image(i) & " i+1:" & integer'image(i+1);
            that.all.karray(i) := that.all.karray(i+1);
            if (that.all.karray(i+1).valid = '0') then
                exit;
            end if;            
        end loop;
        that.all.karray(max_simfifos-1).valid := '0';
        that.all.karray(max_simfifos-1).data  := (others => 'X');
        that.all.karray(max_simfifos-1).addr  := (others => 'X');


        if (that.all.count /= 0) then
            that.all.count := that.all.count - 1;
        end if;

    end procedure;

    -- Pop from Fifo
    procedure simfifo_pop(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    unsigned(31 downto 0);
        variable data  :out    unsigned(31 downto 0);
        variable valid :out    std_logic
    ) is
        variable slv_addr :std_logic_vector(31 downto 0);
        variable slv_data :std_logic_vector(31 downto 0);
    begin
        simfifo_pop(
            that,
            slv_addr,
            slv_data,
            valid
        );

        addr := unsigned(slv_addr);    
        data := unsigned(slv_data);
    end procedure;

    -- Peek from Fifo
    procedure simfifo_peek(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    std_logic_vector(31 downto 0);
        variable data  :out    std_logic_vector(31 downto 0);
        variable valid :out    std_logic
    ) is
    begin
        addr   := that.all.karray(0).addr;
        data   := that.all.karray(0).data;
        valid  := that.all.karray(0).valid;
    end procedure;

    -- Pop from Fifo
    procedure simfifo_peek(
        variable that  :inout  simfifo_ptr_t; 
        variable addr  :out    unsigned(31 downto 0);
        variable data  :out    unsigned(31 downto 0);
        variable valid :out    std_logic
    ) is
        variable slv_addr :std_logic_vector(31 downto 0);
        variable slv_data :std_logic_vector(31 downto 0);
    begin
        simfifo_peek(
            that,
            slv_addr,
            slv_data,
            valid
        );

        addr := unsigned(slv_addr);
        data := unsigned(slv_addr);
    end procedure;

    -- Peek from Fif    
    procedure simfifo_get_count(
        variable that  :inout  simfifo_ptr_t; 
                 count :out    integer
    ) is
    begin
        count := that.all.count;
    end procedure;

end package body;

--library ieee;
--use ieee.std_logic_1164.all;
--use work.pkg_simfifo.all;
--
----------------------------------------------------
--entity testbench is
--end entity;
--
----------------------------------------------------
--architecture sim of testbench is
--begin
--
--process
--  variable that   :simfifo_ptr_t;
--    variable wvalid :std_logic;
--    variable raddr  :std_logic_vector(31 downto 0);
--    variable rdata  :std_logic_vector(31 downto 0);
--    variable rvalid :std_logic;
--begin
--    that := simfifo_new;
--    
--    simfifo_push  (that, X"10102323", X"AAAABBBB", wvalid);
--    simfifo_push  (that, X"10102334", X"EEEECCCC", wvalid);
--    
--    simfifo_pop   (that, raddr, rdata, rvalid);        
--    report "pop: " & to_hstring(rdata);
--    
--    simfifo_pop   (that, raddr, rdata, rvalid);        
--    report "pop: " & to_hstring(rdata);
--    
--    simfifo_pop   (that, raddr, rdata, rvalid);        
--    report "pop: " & to_hstring(rdata);
--    
--    wait;
--end process;
--
--end architecture;

Bimo
  • 5,987
  • 2
  • 39
  • 61