Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

simple bus transactions

Status
Not open for further replies.

frznchckn

Member level 1
Joined
Jul 20, 2010
Messages
36
Helped
4
Reputation
8
Reaction score
4
Trophy points
1,288
Location
California
Activity points
1,521
I'm working on a testbench environment and need to be pointed in the right direction a bit. Our system is using AXI4, and I know there are BFMs produced by Cadence using Verilog tasks, but we would like to keep everything in VHDL for the moment.

A colleague of mine has written a simple BFM that reads bus transactions from a file. I'm looking for something more along the lines of a verilog task. Would VHDL functions enable this capability?

Any other suggestions?

Thanks.
 

what kind of file? Text io is very simple in VHDL, so you can process them however you want. Data file IO is a little more tool dependent, so stick to text where you can.

If you're driving a bus, you may want to look at procedures. Or for extreme modelling, look at protected types (think of them like a class in C++/Java - completly self contained with their own functions and procedures).

So what is it exactly you are trying to do? you can do quite a lot with VHDL.
 
I'm trying to not use the external text file. Yes, that module is use textio I believe. I'm trying to do more of the following:

Code:
Stimulus : process
begin
  reset <= '1';
  wait for 20 ns;
  reset <= '0';

  axi4Write(<address>,<data>);
  axi4Write(<address>,<data>);
  axi4Write(<address>,<data>);
  axi4Write(<address>,<data>);
  axi4Write(<address>,<data>);

end process Stimulus;

Would a function defined in another package/library (not sure how to do that) work for this?

And I know VHDL, but setting up a nice reusable testbench has never been part of my purview until now. :smile:

Thanks.
 

you want procedures. They allow you to connect in signals, variables, constants, just about anything. Here is an example of a write_spi procedure I have written previously.


Code VHDL - [expand]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---------------------------------------------------
--BFM of writing an 8 bit word to the SPI bus
---------------------------------------------------
procedure write_spi_word(        d         :     unsigned;
                          signal spim_clk  : out std_logic;
                          signal spim_simo : out std_logic
                        ) 
                          is
begin
  for i in d'range loop
    spim_simo             <= d(i);
    
    spim_clk              <= '0';
    wait for CLK_PERIOD_SPI/2;
    spim_clk              <= '1';
    wait for CLK_PERIOD_SPI/2;
    spim_clk              <= '0';
  end loop;
end procedure write_spi_word;



then to call it, you just call it like this (procedure dont return a value, they just do something):


Code VHDL - [expand]
1
2
3
4
5
6
for d in 1 to packet_size*2 loop
  write_spi_word( to_unsigned(stim_gen.get_stim, 8),
                  spim_clk, 
                  spim_simo
                 );        
end loop;



PS. Stim_gen is a protected type that generates random numbers/count sequence/constant for me (depending on generics).
 
Thanks for the push. I'll get my ship sailing now.

A quick clarification though... I've used functions before defined in processes, but I thought you could also define them outside of a process? When would you use a function versus a procedure, or did I get functions wrong?

Thanks again.

---------- Post added at 17:09 ---------- Previous post was at 17:08 ----------

Oh also, how did you get your code to look so awesome in the forum?
 

functions and procedures can be decalred anywhere before a begin, or in a package. Just remember that with a package you declare it in the main part, then define it in the body:

Ps use syntax=vhal tags

Code VHDL - [expand]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package some_pkg is
  function do_something(x : integer) return integer; --no definition here, its not allowed
 
  procedure bfm(variable a : out integer);
end package some_package;
 
package body some_pkg is
 
  function do_something(x : integer) return integer is
    --you can declare another function or procedure here if you want
  begin
    --this calculates the awesomeness of PI
  end function;
 
  procedure bfm(variable a : out integer) is
    --you can declare other functions or procedures here
  begin
    --do somethiong
  end procedure bfm;
 
end package body some_pkg;



A function will take in a load of Constants (inside the function, the parameters are constant is what I mean), and generate a single value, that you can then assign to a signal, variable or constant. You cannot have wait statements inside a function (because functions are meant to return in zero time)

A procedure is like a list of instructions. Signals must connect to signals, variables must connect to variables, and constants (only inputs) can connect to anything. Inside the procedure they behave as declared. So inside a procedure you can have waits, signal assignments etc. You can even pass files into a procedure. Be aware, if you assign signals in your procedure, the procedure can only be called inside a process.

You could technically delcare your functions impure, and access any signal or variable you like within scope, and do almost whatever you like, but then you wont be able to put them in a package (and thats not what impure functions are really for).

With what you are doing, you could get a really awesome BFM if you wanted to spend a bit of time on it. You could open a file with the transaction list inside a protected type, and then in your testbench all you would see would be something like this:


Code VHDL - [expand]
1
2
3
4
5
6
7
8
9
10
--inside a process
axi4.loadfile("somefile.txt");
 
while not AXI4.done_all_commands loop
 
    AXI4_BFM.do_next_command( --signal interface)
 
end loop;
 
wait;



And then it would be the file, rather than the testbench code, that determined the read/write/other functions. You would only need a single testbench then.
 
Thanks, that was all a very nice and concise explanation. Big thanks. Just curious, but how long have you been doing this stuff? I think you also helped me a year ago with setting up a ROM for twiddle factors. That solution was priceless.

Cheers.
 

started vhdl 8 yrs ago at uni. since then just been reading and coding, with an advanced doulos course about 5 yrs ago (invaluable). it was my phd friend that taught me vhdl. my tutor was pretty useless.
 
Thanks for the push. I'll get my ship sailing now.

A quick clarification though... I've used functions before defined in processes, but I thought you could also define them outside of a process? When would you use a function versus a procedure, or did I get functions wrong?

To add a bit more to TrickyDicky's good advice, let me add that you might actually want to have two (or more) layers of procedures. The first layer would have all of the signal needed to interface to the actual object. This set of procedures can be put almost anywhere (package, architecture, process). The second layer would have a nice clean interface with only the relevant things as parameters. This second layer procedure would be basically a wrapper around the first layer, but could only be defined within a process (or multiple processes with copy/paste if needed in multiple places). Something like the following:

Code:
[syntax=vhdl]
package my_tb_package is
  procedure Write(
      -- Long list of parameters and signals for connecting to the entity under test
  ... -- Other procedures
package my_tb_package;
package body my_tb_package
  procedure Write(
      -- This would be the place where you would implement the protocol to perform
      -- the requested task, in this case 'Write'
  ... -- Other procedures
end package my_tb_package;
...
architecture rtl of my_testbench is
   -- Alternatively, the procedure mentioned above for the package could be placed here
   -- Whether you should put it in a package or not depends on whether you think those
   -- procedures have potential future value besides the current project you're working on.
   -- If so, put them in the package so you can reuse the procedures in that new project
   -- simply with a 'use work...."
begin
   process
      -- Create shorthand procedures that don't require the use of a long list of signals.
      -- The example here is that you want to be able to read and write some registers
      -- inside the device being tested.

      -- First create read/write procedures that have a simple address/data interface
      procedure Write(Address, Data: std_ulogic_vector) is
      ...
      begin
          Write(
             Addr => Address,
             Write_Data => Data,
             ... -- Long list of signals as required by the write procedure in the package
          );
   end procedure Write;
   impure function Read(Address: std_ulogic_vector) return std_ulogic_vector is
      -- You get the idea
   end function Read;

   -- The above procedures and functions can also be overridden to allow different
   -- parameters; maybe you'd like the address and data to be integers or something
   -- To do this, you would declare new procedures and functions like the ones above
   -- that are in this process, but it's even less work than one might think.  Since you 
   -- now already have a method for writing if the parameters are std_ulogic_vector, 
   -- then simply cast yourintegers into std_ulogic_vector via the usual functions from 
   -- numeric_std.  Build on what you have, avoid copy/paste whenever possible

    -- Now consider that maybe you've defined record types for all of these registers 
    -- inside your design to define the bit positions of the register.  Hopefully those record 
    -- types have been put into a package...if not you should do so.  Let's say that you
    -- have a record type called t_MY_CNTL_REG that defines the bit fields of some control
    -- register in your design.  Let's assume also that you had the foresight to also include
    -- a 'To_std_ulogic_vector' and a 'From_std_ulogic_vector' function in the package
    -- that contains the t_MY_CNTL_REG definition.  These functions convert t_MY_CNTL_REG
    -- things to/from std_ulogic_vectors.  Given all that, now you could define read and
    -- write procedures that have an even simpler interface
    procedure Write(Reg: t_MY_CNTL_REG) is
       constant Address: integer := xxx;  -- The address of the control register
    begin
       write(Address, To_std_ulogic_vector(Reg));
    end procedure Write;
    impure function Read return t_MY_CNTL_REG is
      constant Address: integer := xxx;  -- The address of the control register
      variable Reg_Data: std_ulogic_vector(...);
   begin
      Reg_Data := Read(Address);
      return(From_std_ulogic_vector(Reg_Data));
   end function Read;
begin
   -- Now the fun and easy part.  You can read and write addresses by manually entering the correct addresses and data
   Write(20, 40);  -- Easy, but rather lacking in describing what you're doing
   Cntl_Reg := Read(20);  -- Easy here, but consider that the address of 20 needs to be manually maintained to be the same

   -- A better approach would for the address to be obtained from a constant in the package
   Write(CNTL_PORT_ADDRESS, 40);
   Cntl_Reg := Read(CNTL_PORT_ADDRESS);
   
   -- Even better approach...the last layer of procedures intentionally removes the address from the interface so that there
   -- is no chance of getting the address incorrect in this area of the code
   Write(Cntl_Reg);
   Cntl_Reg := Read;
end rtl;
[/syntax]

This approach continually builds on stuff you have already written
- Basic low level procedures that manipulate the signals
- Shorthand procedures to hide the signals and present a simpler interface
- Even shorter shorthand procedures that insure that you're calling the correct procedures based on the data types

Also note that you don't have to get wildly creative on the procedure/function naming front. In fact, the simpler the better. In all of the above I used the name 'Write' and 'Read' leading to multiple 'Write' and 'Read' definitions. This is completely legit. VHDL allows for overloading. The compiler will sort out which procedure to call based on the types of the parameters (and the return type for a function). This makes for cleaner code in your testbench.

Also ponder on the general usefulness of a function that translates between your custom types and the usual std_ulogic_vector...a 'to_std_ulogic_vector' and a 'from_std_ulogic_vector' has a lot of uses besides here. In many cases, where I define a custom record type, I also define these two functions right then because I'm pretty darn sure I'll need them at some point in some testbench.

The only down side comes when multiple processes need to use the same shorthand procedures. There is no way around it, you will have to copy/paste those shorthand procedures into each process. Practically speaking this isn't actually much of a limitation in many situations, but it does come up.

Oh also, how did you get your code to look so awesome in the forum?
Look at the top of this forum above all of the postings for the instructions

Kevin Jennings

---------- Post added at 16:50 ---------- Previous post was at 16:19 ----------

With what you are doing, you could get a really awesome BFM if you wanted to spend a bit of time on it. You could open a file with the transaction list inside a protected type, and then in your testbench all you would see would be something like this:


Code VHDL - [expand]
1
2
3
4
5
6
7
--inside a process
axi4.loadfile("somefile.txt");
 
while not AXI4.done_all_commands loop
    AXI4_BFM.do_next_command( --signal interface)
end loop;
wait;



And then it would be the file, rather than the testbench code, that determined the read/write/other functions. You would only need a single testbench then.

This won't work because wait statements are not allowed within a protected type body. Other than that though, good post.

Kevin Jennings
 
boo, didn't think about that. you could just wrap it inside an entity instead.
 

Hmm, my last post from yesterday didn't take....

Thanks guys. It's going to take me a bit to wrap my head all around this. Typically, on the teams I've been on, the test env was already set up. Right now I'm trying to automate and improve upon something that "works" right now, but isn't as easy as it should be. I'll be exploring this stuff on my own time.

Thanks again for the push.
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top