Style Request for Testbench with Bus Interfaces

I'm posting this in hopes that folks have some suggestions on how to
handle this in a clean way. I find myself (not surprisingly) writing
testbenches for a lot of very similar bus interfaces. At the simplest
I've got an address signal, data out, data in, and a write enable
signal. On a more complex one I might have the full backend interface
to the Actel PCI core or the Altera Avalon interface.

Regardless, I would really like to be able to write my basic
interaction procedures once and just use them after that. However I'm
running into some difficulty with the language and scoping rules. So
for talking purposes, here's a little skeleton

Alright, I think that's enough to give an idea. So I'd really like my
procedure calls to be something I can abstract preferably into a
package. Also I'd really like to keep the parameter list as minimal
as possible. However I run into scope issues with signals. So if I
use region A for declaring my read/write procedures, I can directly
access all the signals running into the DUT. I don't have to pass
them in. I can simply have an address and data parameters. However
this means every testbench has to declare everything right there in
the process and that's ugly to me.

Now if I use B or C, then I no longer have scope on the signals. If I
want to use a procedure for reading and writing, I've got to pass them
into the procedure. With the example described, it's not really too
big a deal, but consider a PCI interface with all its myriad signals
or another example various PCI core backend interfaces with start,
stop, rdcyc, wrcyc, and various other signals for backpressure. It
seems to me that the parameter list could get unwieldy very quickly.
So I can put the procedures here, but that violates my desire to have
manageable calls.

So, I'm hoping there's some sort of clever way of passing a bus in and
out of procedure declarations to keep code readable and maintainable.
The only way I've thought that might work is to declare a record that
comprises the entire bus interface. Then I could pass in a single
token that represents the entire bus. I haven't really worked out how
that might work with different naming conventions (I'm usually pretty
consistent with signal names, but sometimes there are variations.)
There are very likely other ways to do it, but I haven't had a lot of
exposure to folks who do large scale testbenches and have already
worked out these tricks.

So, in summary does anyone have any technique or best practice for how
to organize bus interfaces for passing in and out of procedure calls?

Advertisements

On 3/29/2011 9:42 AM, M. Norton wrote:
> So, I'm hoping there's some sort of clever way of passing a bus in and
> out of procedure declarations to keep code readable and maintainable.

I haven't seen it, if we include the "readable" part.
I like to use variables and procedures and minimize processes.
Something like:
....
constant reps : natural := 8;
begin -- process main: Top level loop invokes top procedures.
init;
for i in 1 to reps loop
timed_cycle;
end loop;
for i in 1 to reps loop
handshake_cycle;
end loop;
coda;
end process main;

Advertisements

On Mar 29, 11:42 am, "M. Norton" <> wrote:
> Hello folks,
>
> I'm posting this in hopes that folks have some suggestions on how to
> handle this in a clean way. I find myself (not surprisingly) writing
> testbenches for a lot of very similar bus interfaces. At the simplest
> I've got an address signal, data out, data in, and a write enable
> signal. On a more complex one I might have the full backend interface
> to the Actel PCI core or the Altera Avalon interface.
>
> Regardless, I would really like to be able to write my basic
> interaction procedures once and just use them after that. However I'm
> running into some difficulty with the language and scoping rules. So
> for talking purposes, here's a little skeleton
>
> use work.generic_bus_if_pkg.all; -- <procedure declaration area C :
> preferred>
>
> entity just_a_testbench is
> end entity just_a_testbench;
>
> architecture behavioral of just_a_testbench is
> <procedure declaration area B>
> signal be_addr : unsigned(15 downto 0);
> signal be_rddata : std_logic_vector(31 downto 0);
> signal be_wrdata : std_logic_vector(31 downto 0);
> signal be_wren : std_logic;
> begin
> DUT : entity work.foo
> port map (
> ....
> be_addr => be_addr,
> be_rddata => be_rddata,
> be_wrdata => be_wrdata,
> be_wren => be_wren,
> ....
> );
>
> BUS_CONTROL : process is
> <procedure declaration region A>
> begin
> .....
> be_read32();
> be_write32();
> .....
> end process BUS_CONTROL;
>
> end architecture behavioral;
>
> Alright, I think that's enough to give an idea. So I'd really like my
> procedure calls to be something I can abstract preferably into a
> package. Also I'd really like to keep the parameter list as minimal
> as possible. However I run into scope issues with signals. So if I
> use region A for declaring my read/write procedures, I can directly
> access all the signals running into the DUT. I don't have to pass
> them in. I can simply have an address and data parameters. However
> this means every testbench has to declare everything right there in
> the process and that's ugly to me.
>
> Now if I use B or C, then I no longer have scope on the signals. If I
> want to use a procedure for reading and writing, I've got to pass them
> into the procedure. With the example described, it's not really too
> big a deal, but consider a PCI interface with all its myriad signals
> or another example various PCI core backend interfaces with start,
> stop, rdcyc, wrcyc, and various other signals for backpressure. It
> seems to me that the parameter list could get unwieldy very quickly.
> So I can put the procedures here, but that violates my desire to have
> manageable calls.
>
> So, I'm hoping there's some sort of clever way of passing a bus in and
> out of procedure declarations to keep code readable and maintainable.
> The only way I've thought that might work is to declare a record that
> comprises the entire bus interface. Then I could pass in a single
> token that represents the entire bus. I haven't really worked out how
> that might work with different naming conventions (I'm usually pretty
> consistent with signal names, but sometimes there are variations.)
> There are very likely other ways to do it, but I haven't had a lot of
> exposure to folks who do large scale testbenches and have already
> worked out these tricks.
>
> So, in summary does anyone have any technique or best practice for how
> to organize bus interfaces for passing in and out of procedure calls?
>
> Thanks for any ideas. I appreciate it.
>
> Best regards,
> Mark Norton

I've used a single record with an inout port on the procedure(s). All
of the elements of the record must be resolved types. I declare an
"undriven" constant of that record such that the elements that are
never used as outputs by each process are harmlessly driven to 'Z'.

On Mar 29, 2:24 pm, Mike Treseler <> wrote:
> I haven't seen it, if we include the "readable" part.
> I like to use variables and procedures and minimize processes.
> Something like:
....
> for details, see the testbench here:http://mysite.ncnetwork.net/reszotzl/

Yeah, this is essentially what I'm doing right now, one process
describing test over time (with loops and whatnot as need requires)
and then a lot of local procedures to handle the transactions. The
only trouble I've got is that I end up repeating myself quite a lot
over a number of testbenches that use identical or more-likely near-
identical protocols. All it takes is a few names changed and my cut
and paste of previously written transaction procedures gets smoked.

I suppose I'm glad to know I'm not too far off in what I've come up
with but it'd be nice to find something that's more reusable. Thanks
for the information!

On Mar 29, 2:51 pm, Andy <> wrote:
> I've used a single record with an inout port on the procedure(s). All
> of the elements of the record must be resolved types. I declare an
> "undriven" constant of that record such that the elements that are
> never used as outputs by each process are harmlessly driven to 'Z'.

I've read and reread this a bit and while I get the theory of what
you're doing with the constant, I'm not sure how it's being applied.
So if we've got a package with a record containing the signal elements
of a bus, including control lines, that would allow harnessing up a
generic procedure to a testbench component. However when do you apply
the constant that's got things set to high impedance? Does that
happen inside the procedure at the beginning of the procedure and then
subsequent assignments override it?

Then during that procedure call, all the my_bus.rd_cyc, my_bus.rd_stb,
etc would remain Z. I will have to try that out and see how it goes.
Seems like it might do what I want (assuming I have your intent
divined correctly).

On Tue, 29 Mar 2011 09:42:05 -0700 (PDT), "M. Norton" wrote:
>I'm posting this in hopes that folks have some suggestions on how to
>handle this in a clean way. I find myself (not surprisingly) writing
>testbenches for a lot of very similar bus interfaces.[...]
>I would really like to be able to write my basic
>interaction procedures once and just use them after that. However I'm
>running into some difficulty with the language and scoping rules.

Yup.
>Alright, I think that's enough to give an idea. So I'd really like my
>procedure calls to be something I can abstract preferably into a
>package. Also I'd really like to keep the parameter list as minimal
>as possible.

But, as you've found, a procedure in a package can
only hit signals through its arguments (parameters).
Which may be numerous, for any reasonably complex
model.

There are other issues too. Your bus model probably
needs to keep track of some persistent state, which
you can't comfortably do with a package either.

So, here's what I would regard as the preferred
way to start. Others will doubtless have different
opinions, of course.

First off, consider encapsulating your bus model
not as a package but as an entity. That way it
can have persistent PER-INSTANCE state, i.e.
you can use the same model for numerous different
buses in the same testbench with no difficulty.
And you can provide ports on the entity that
will connect to the physical pins of your
interface. As a super-simple example we can
model an asynchronous serial data (UART)
protocol where the DUT is a receiver, and
your testbench is the transmitter. So the
physical interface is just two wires: TxD
from TB to DUT, and CTS (Clear to Send) from
DUT to TB. Something like this (yes I know it's
no use like this: bear with me as I build up
the example.) Lots of details and declarations
missing, but I'm sure you can fill it all in
yourself.

OK, this is cool; we just create an instance of this
thing, hook its ports to our DUT interface, and, errm,
call the procedure... oh dear, we can't because the
procedure is hidden away inside a process, inside
the instance. So, how do we call that procedure
from OUTSIDE the UART_TX entity? Answer: provide
a port on said entity that allows your testbench
to command it to do things. This port won't connect
to DUT wires; it will be hooked to a very abstract
signal in the TB, conveying commands. So we can
usefully create a record type that represents a
command. In our case that's kinda simple (just
a byte) but you get the idea. While we're writing
a package that defines this record, we can also
write a procedure to encapsulate the whole business
of getting the UART_TX to do something for us.

Now, of course, we must modify our UART_TX so that
it can cope with this request/response protocol.
That costs a couple more ports, but thanks to the
record type, there will ONLY be two such ports
no matter how complex the protocol.

Now we're really ready to go. In your TB, create an
instance of UART_TX with its TXD and CTS signals
connected up to the DUT in the obvious way. Of course,
in your real world there will be many more signals than
two - but the same principles apply. Then, in the TB,
provide signals for the REQ and RSP ports of your UART_TX
instance. And then a process in the TB can generate
stimulus like this:

Now the send_message procedure (defined in the package)
has only a small number of arguments, regardless of the
complexity of your physical interface - the complexity
is abstracted-away in the transaction record type.

If you find yourself calling the procedure send_message()
many times in the same process, with the same signal
arguments every time, you can tidy that up too:

process
....
-- Simplified local version of the package procedure
procedure send_message(V: unsigned(7 downto 0);
begin
-- Call the package procedure, with appropriate
-- signals provided as arguments. These signals
-- must be declared in the architecture, of course.
send_message(V, REQ_signal, RSP_signal);
end;
begin -- main body of process
-- calls the local procedure, which fills in the signals
send_message("00001111"); -- it's that simple
send_message("10101010");
...

I'm aware that this example has been quite sketchy,
and uses some "advanced" (whatever that means)
tricks like 'transaction, but I hope at least
it points you in some interesting directions.
Many details yet to fill in - initialization,
declarations, you name it. Over to you.

You can make this work the other way, too, for
models that monitor (rather than drive) a DUT
interface. Same idea applies: convert the messy
signal-wiggles into a transaction record, and work
with that in the TB. Capture the signal-to-record
converter block as an entity, instantiate it with
ports connected to DUT signals and just a couple
of ports to expose the collected transaction
record. Hook up those latter ports to TB-only
signals so that the rest of the TB can see them.

There are several interesting variants on this
theme: for example, the blocks can be coded as
procedures and "instanced" as concurrent
procedure calls. That's convenient, but it
causes some trouble with the 'transaction
trickery, so personally I prefer to use entities.

Enjoy, and thanks for asking all the right questions.
--
Jonathan Bromley

On Mar 29, 4:08 pm, Jonathan Bromley <>
wrote:
> There are other issues too. Your bus model probably
> needs to keep track of some persistent state, which
> you can't comfortably do with a package either.

Glad you mentioned this. I hadn't even gotten to the point where I
was concerned about persistent state. My initial examples were pretty
straightforward and simple, but I do have in mind trying to create a
PCI bus model to try to test this core we seem to be using and reusing
and that absolutely would be a far more complex driver and would
require persistent state for some transactions.
> I'm aware that this example has been quite sketchy,
> and uses some "advanced" (whatever that means)
> tricks like 'transaction, but I hope at least
> it points you in some interesting directions.
> Many details yet to fill in - initialization,
> declarations, you name it. Over to you.

Absolutely, sketchy is fine. I'm looking for theory mainly and this
has given me a lot to chew on. In the past I have used entities as
DUT drivers, usually for complex packet data, but I hadn't really
thought of abstracting it a level further and giving myself command
hooks into it. This also neatly dodges the issue of clocks. I was
thinking about that when I wrote the little snippet asking about what
Andy suggested, and was realizing I would need to pass in the clock as
well as everything else, which seems a little on the messy side.
Ideally the procedure call would be transaction and message related
information only.

And having a variety of tools in the toolbox is never a bad thing.
Seems to me there's times when Mike's strategy is simplest, and then
Andy's and then this feels like the sledgehammer.
> Enjoy, and thanks for asking all the right questions.

On 3/29/2011 1:26 PM, M. Norton wrote:
> Yeah, this is essentially what I'm doing right now, one process
> describing test over time (with loops and whatnot as need requires)
> and then a lot of local procedures to handle the transactions. The
> only trouble I've got is that I end up repeating myself quite a lot
> over a number of testbenches that use identical or more-likely near-
> identical protocols. All it takes is a few names changed and my cut
> and paste of previously written transaction procedures gets smoked.

I try to do the heavy lifting in functions, which are easily packaged
with required and default parameters.
I use simple procedures with no parameters when possible for readable
code.
> I suppose I'm glad to know I'm not too far off in what I've come up
> with but it'd be nice to find something that's more reusable. Thanks
> for the information!

Functions are easily packaged and reused.
Procedures can at least eliminate the local
cut and paste.

On Mar 29, 12:42 pm, "M. Norton" <> wrote:
>
> So, in summary does anyone have any technique or best practice for how
> to organize bus interfaces for passing in and out of procedure calls?
>

1. Similar to what Jonathon suggested, but a bit easier (I think) to
draw good boundary lines for the testing entity is to model real
parts. FPGAs in real applications do not exist untethered to the rest
of the world, they are connected to real parts on a circuit board so
your 'FPGA testbench' could (I think 'should') be a model of that PCBA
(and surrounding systems)...which means that if you model the parts
that go on that PCBA and connect them per the netlist then you're
modelling your real system. In your case, you had mentioned in later
posts about modelling a PCI bus. I'm guessing that there is a
processor on the board, so model that processor and you'll have your
PCI bus. Maybe for starters, the PCI bus is about all that you will
model, the rest can come later when needed. In any case, by doing
this, you'll be refining your cheezy processor model over time and
making it closer to the real thing which will make that model better
and better over time.

What I've also found is that as I model more of the real system, there
comes less of a need for that 'magic communications interface' between
two parts that Jonathon mentioned in his post. If needed, I simply
put that interface into a 'magic comms' package which is project
specific (but can be named the same for each project). For the most
part, simulations become watching the system perform whatever it is
that you set it up to do without too much 'magic' communications
between components.

2. Standardize on a comm interface. For the most part, interfaces
simply need to read and write. Use Avalon as a guide, at the low
level you can write once and use over and over that handshake for any
interface. Then the task becomes to create an 'Avalon to PCI' widget
(as an example). If that same type of PCI interface is needed in a
different application that is a totally different processor, then
guess what? You can use that Avalon to PCI widget that you created in
your new processor. Both processors could use the same Avalon
protocol on the one side with PCI on the other.

3. As has been pointed out and you've discovered, removing the
signals from procedures means putting them into a process which can be
a pain. However, if you implement #2, then this becomes a
straightforward wrapper to portion of your your part model from #1.
That description is likely unique to that model so you wouldn't really
have a need to 'reuse it' somewhere else other than to plop down your
part model from #1. However, there can still be cutting and pasting
within that part model. Consider a processor model where you
implement the equivalent of several 'threads' that wake up and run at
appropriate times. Each thread would be a process, therefore each
'thread' would have to have the shorthand procedures cut and pasted.
A pain, but again those procedures are only of use to that part model
so you're likely editing a single file.

On Mar 29, 3:36 pm, "M. Norton" <> wrote:
> On Mar 29, 2:51 pm, Andy <> wrote:
>
> > I've used a single record with an inout port on the procedure(s). All
> > of the elements of the record must be resolved types. I declare an
> > "undriven" constant of that record such that the elements that are
> > never used as outputs by each process are harmlessly driven to 'Z'.
>
> I've read and reread this a bit and while I get the theory of what
> you're doing with the constant, I'm not sure how it's being applied.
> So if we've got a package with a record containing the signal elements
> of a bus, including control lines, that would allow harnessing up a
> generic procedure to a testbench component. However when do you apply
> the constant that's got things set to high impedance? Does that
> happen inside the procedure at the beginning of the procedure and then
> subsequent assignments override it?
>
> So, possibly something like this?
>
> procedure my_generic_write( ... ; signal my_bus :
> T_BUS_RECORD; ... ) is
> begin
> my_bus <= C_HARMLESSLY_DRIVEN_TO_Z;
> wait until rising_edge(some_clk);
> my_bus.address <= some_address;
> my_bus.wr_cyc <= '1';
> wait until rising_edge(some_clk);
> my_bus.data <= some_data;
> my_bus.wren <= '1';
> wait until rising_edge(some_clk);
> my_bus_wren <= '0';
> wait until rising_edge(some_clk);
> my_bus.wr_cyc <= '0';
> wait until rising_edge(some_clk);
> my_bus <= C_HARMLESSLY_DRIVEN_TO_Z;
> end procedure my_generic_write;
>
> Then during that procedure call, all the my_bus.rd_cyc, my_bus.rd_stb,
> etc would remain Z. I will have to try that out and see how it goes.
> Seems like it might do what I want (assuming I have your intent
> divined correctly).
>
> Thanks for the information!
> Mark

Yes, you'd use it like that in the procedure(s), but you also need to
make sure that processes in the DUT that interact with the record also
drive the constant onto the signal, so that the DUT does not end up
driving 'U' on input elements of the record.

Share This Page

Welcome to The Coding Forums!

Welcome to the Coding Forums, the place to chat about anything related to programming and coding languages.

Please join our friendly community by clicking the button below - it only takes a few seconds and is totally free. You'll be able to ask questions about coding or chat with the community and help others.
Sign up now!