Best testbench style for microprocessor bus simulation

D

Doug Miller

Guest
I'm looking for suggestions for improving the brevity and clarity of my
testbenches when I'm simulating software initialization and interrupt
handling over a microprocessor bus. Currently, I use a pair of processes
such as the following:

bus_simplifier: process(addr,din,op)
begin
if (op = write) then
reg_write <= '1';
reg_read <= '0';
reg_en <= '1';
reg_addr <= addr;
reg_data <= din;
elsif (op = read) then
-- etc....
elsif (op = nop) then
-- etc....
end if;
end process;

up_stim: process
variable up_stim_initflag : std_logic := '0';
begin
if (up_stim_initflag = '0') then
-- Startup conditions
op <= nop;
int_vector <= (others => 'U');
wait until reset='1';
wait until reset='0';

-- Read from register
wait until up_clk='1'
wait for 0.1 ns;
addr <= "X"FC000000";
op <= read;
wait for UP_CLK_PERIOD;
op <= nop;
wait for UP_CLK_PERIOD;

-- Read and write lots more registers...

up_stim_initflag :='1'; -- Set flag so initialization code only
executes once

end if; -- end of initialization

-- Start of interrupt processing

wait until interrupt = '1';

-- Read and write lots of registers to imitate software interrupt
processing

end process;

Can anyone suggest a better way to encapsulate and compact this in a better
way? Ideally, I'd like to reduce the 7 lines below the "--Read from
register" line to a single line, while still making the address, data and
operation type easily readable, so that when I need to initialize lots of
registers, I can write it efficiently.

Thanks,
Doug Miller
 
Doug,
What it sounds like you want is a transaction based
testbench. Using procedures is a good start:

-- Baud Rate = 115200. ExtClk = 20 MHz. Divisor = 11
CpuWrite(CpuRec, UART_DIVISOR_HIGH, X"0000");
CpuWrite(CpuRec, UART_DIVISOR_LOW, X"000A");

-- Configure for Even Parity, One Stop Bit, and 8 Data Bits
CpuWrite(CpuRec, UART_CFG1, X"00" & "00" &
PARITY_EVEN & STOP_BITS_1 & DATA_BITS_8);
-- Disable all interrupts (default)
CpuWrite(CpuRec, UART_RX_INT_MASK, X"0000");
CpuWrite(CpuRec, UART_TX_INT_MASK, X"0000");

-- Make it Active and Select EXT CLK
CpuWrite(CpuRec, UART_CFG2, X"00C0");

wait for 5 * tperiod_Clk ;
-- Clear Interrupt Status
CpuRead (CpuRec, UART_RX_INT_STAT, DataO);
CpuRead (CpuRec, UART_TX_INT_STAT, DataO);

. . .

Want to know more? I am giving a half day tutorial on
transaction based testbenches at both DesignCon (February)
and DVCon (March). Details are at:

DesignCon: http://www.designcon.com/conference/tf2.html
DVCon: http://www.dvcon.org/tutorial6.html


Need a more detailed class with labs? SynthWorks is
offering its 3 Day, VHDL Testbenches and Verification
class at public locations. The code shown above is
from the lab files in the class. The next class date
is Feb 25-27 in Dallas, Tx.
Description: http://www.synthworks.com/vhdl_testbench_verification.htm
Public Classes: http://www.synthworks.com/public_vhdl_courses.htm


Best Regards,
Jim Lewis
--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jim Lewis
Director of Training mailto:Jim@SynthWorks.com
SynthWorks Design Inc. http://www.SynthWorks.com
800-505-8435 (800-505-VHDL) / 503-590-4787

Expert VHDL Training for Hardware Design and Verification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



Doug Miller wrote:

I'm looking for suggestions for improving the brevity and clarity of my
testbenches when I'm simulating software initialization and interrupt
handling over a microprocessor bus. Currently, I use a pair of processes
such as the following:

bus_simplifier: process(addr,din,op)
begin
if (op = write) then
reg_write <= '1';
reg_read <= '0';
reg_en <= '1';
reg_addr <= addr;
reg_data <= din;
elsif (op = read) then
-- etc....
elsif (op = nop) then
-- etc....
end if;
end process;

up_stim: process
variable up_stim_initflag : std_logic := '0';
begin
if (up_stim_initflag = '0') then
-- Startup conditions
op <= nop;
int_vector <= (others => 'U');
wait until reset='1';
wait until reset='0';

-- Read from register
wait until up_clk='1'
wait for 0.1 ns;
addr <= "X"FC000000";
op <= read;
wait for UP_CLK_PERIOD;
op <= nop;
wait for UP_CLK_PERIOD;

-- Read and write lots more registers...

up_stim_initflag :='1'; -- Set flag so initialization code only
executes once

end if; -- end of initialization

-- Start of interrupt processing

wait until interrupt = '1';

-- Read and write lots of registers to imitate software interrupt
processing

end process;

Can anyone suggest a better way to encapsulate and compact this in a better
way? Ideally, I'd like to reduce the 7 lines below the "--Read from
register" line to a single line, while still making the address, data and
operation type easily readable, so that when I need to initialize lots of
registers, I can write it efficiently.

Thanks,
Doug Miller
 
"Jim Lewis" <Jim@SynthWorks.com> wrote in message
news:401861A1.7030904@SynthWorks.com...

What it sounds like you want is a transaction based
testbench. Using procedures is a good start:

-- Baud Rate = 115200. ExtClk = 20 MHz. Divisor = 11
CpuWrite(CpuRec, UART_DIVISOR_HIGH, X"0000");
CpuWrite(CpuRec, UART_DIVISOR_LOW, X"000A");
[...]

Is it just me, or does anyone else find this pretty clumsy?

Just to recap, the problem is...
1.We want these procedures to go in a package, for the sake
of reusability.
2.Procedures in a package can't access signals in an architecture
directly, of course, because those signals aren't visible
(don't exist!) when the procedure is analyzed.
3.So whenever we invoke the procedure, we need to provide as
actual parameters not only the stuff that's unique for this
call to the procedure (UART_DIVISOR_HIGH, X"0000") but also
stuff describing the signals that the procedure will activate
(CpuRec).
4.Respectable and reliable authors (Bergeron, Lewis, Cohen, even
our training materials) then advise packaging all these signals
in a record, so it's easier to change the details and less hassle
to invoke the procedure.

So far, so traditional. But I still find it offensive to have to
name the connections every time I invoke the procedure. Am I alone?

I like to add one additional layer of encapsulation, adding a
"wrapper" procedure in the stimulus-generator process...

-- architecture contains the CPU-interface record CpuRec,
signal CpuRec: CpuRec_Type;
-- and this process that does the work...
CPU_stimulus_generator: process

-- Wrapper procedure:
procedure CpuWrite(in REG: Reg_Adrs_Type; in DATA: Data_Type) is
begin
CpuWrite(CpuRec, REG, DATA);
end;

-- and more procedures for CpuRead, etc, etc

begin
...
CpuWrite(UART_DIVISOR_HIGH, X"0000");
CpuWrite(UART_DIVISOR_LOW, X"000A");
...
end process;

The wrapper procedures simply take general-purpose package
procedures like CpuWrite(rec, adrs, data) and specialise them
with the signals that will be used by that specific process.
There's no loss of portability, and IMHO a great gain in clarity
of the mainstream stimulus generator code. Even in complicated
cases, the wrapper procedures are simple and short.

Criticism welcome!
--

Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * Perl * Tcl/Tk * Verification * Project Services

Doulos Ltd. Church Hatch, 22 Market Place, Ringwood, Hampshire, BH24 1AW, UK
Tel: +44 (0)1425 471223 mail: jonathan.bromley@doulos.com
Fax: +44 (0)1425 471573 Web: http://www.doulos.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
Jonathan Bromley wrote:

So far, so traditional. But I still find it offensive to have to
name the connections every time I invoke the procedure. Am I alone?
I agree. To get around that I
keep the procedures in the
process scope instead
of packaging them.



I like to add one additional layer of encapsulation, adding a
"wrapper" procedure in the stimulus-generator process...
That sounds like a better way to do it.


The wrapper procedures simply take general-purpose package
procedures like CpuWrite(rec, adrs, data) and specialise them
with the signals that will be used by that specific process.
There's no loss of portability, and IMHO a great gain in clarity
of the mainstream stimulus generator code. Even in complicated
cases, the wrapper procedures are simple and short.

Criticism welcome!

Can't think of a criticism.
Looks clean to me.
I will try it out.
Thanks for the posting.



-- Mike Treseler
 
Mike Treseler wrote:
Jonathan Bromley wrote:

So far, so traditional. But I still find it offensive to have to
name the connections every time I invoke the procedure. Am I alone?


I agree. To get around that I
keep the procedures in the
process scope instead
of packaging them.
Every engineering problem probably has the right thing
that works for it. For me, this approach works well for
tests where there is only one test in the test suite and
there is only one engineer runnig the verification.
My experience with this was that once you have more than
one test (or more than one verification engineer), then
you can end up with a maintence issue. Find a bug in a
procedure, fix it in many places. To get around this on
one project, I split the file into multiple pieces and then
merged them to create an entire testbench - yuck.

In addition, from an organizational point of view, having
the procedures in a package makes it easier to identify the
test from the support code.

Going further, for my system-level testbenches (which I also
use for subblock testing), the procedure only packs
transactions into record. I use a separate bus functional
model to handle to implement the signal protocol, as well
as other functionality, such as protocol checkers (looking
for bad things happening on the interface - for example
in a memory model I have one that looks for simultaneous
read and write).

It is also possible to architect a testbench that the additional
things I like putting in the BFM live in a separate model. One
nice thing about having them in a BFM with the functionality is
that I can keep an error count of each type, and then at the end
issue a transaction to the model that causes the error count to
be printed for each separate, and then return an accumulated
value.

Cheers,
Jim
--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jim Lewis
Director of Training mailto:Jim@SynthWorks.com
SynthWorks Design Inc. http://www.SynthWorks.com
1-503-590-4787

Expert VHDL Training for Hardware Design and Verification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
VHDL-200X DTA topic hits about the middle of this thread.

Jim Lewis <Jim@SynthWorks.com> wrote:
What it sounds like you want is a transaction based
testbench. Using procedures is a good start:

-- Baud Rate = 115200. ExtClk = 20 MHz. Divisor = 11
CpuWrite(CpuRec, UART_DIVISOR_HIGH, X"0000");
CpuWrite(CpuRec, UART_DIVISOR_LOW, X"000A");
Jonathan Bromley wrote:
[...]

Is it just me, or does anyone else find this pretty clumsy?
.... Snip ...

I like to add one additional layer of encapsulation, adding a
"wrapper" procedure in the stimulus-generator process...

-- architecture contains the CPU-interface record CpuRec,
signal CpuRec: CpuRec_Type;
-- and this process that does the work...
CPU_stimulus_generator: process

-- Wrapper procedure:
procedure CpuWrite(in REG: Reg_Adrs_Type; in DATA: Data_Type) is
begin
CpuWrite(CpuRec, REG, DATA);
end;

-- and more procedures for CpuRead, etc, etc

begin
...
CpuWrite(UART_DIVISOR_HIGH, X"0000");
CpuWrite(UART_DIVISOR_LOW, X"000A");
...
end process;

The wrapper procedures simply take general-purpose package
procedures like CpuWrite(rec, adrs, data) and specialise them
with the signals that will be used by that specific process.
There's no loss of portability, and IMHO a great gain in clarity
of the mainstream stimulus generator code. Even in complicated
cases, the wrapper procedures are simple and short.

Criticism welcome!
First, I feel your pain. However, since we have
worked the procedures down to only having one extra
signal, the record, I don't feel it that much. To
remove this one signal name, you propose to add the
clutter of the translation procedures and potential
simulation inefficiency of the additional layer of
procedure calls.

Engineering wise, there is probably a balance point.
Your suggestion probably works great if there are only
a few procedures and if no additional procedures will be
added.

I think I would always be willing to type the extra
name. If I was really that concerned about it, I would
make the record name C.

The language is up for revision, so rather than argue
about the merits of the wrapper approach, I would
prefer to brain-storm on language changes that would
help this situation.

The VHDL-200X Data Types and Abstractions (DTA) group
(headed by Peter Ashenden) is looking at adding generics
to packages (including type generics). One of the
things that comes with this is instantiatable
packages. So I have been wondering, if I instantiate
a package in a process declaration region, can the
package see signals in the process environment and
can procedures in the instantiated package see signals
by side-effect? This most would solve the same problem.
Currently it is planned that this feature will be based
on the SUAVE proposal (but perhaps with some syntax
changes). For links to the SUAVE work, go to:
http://www.eda.org/vhdl-200x/vhdl-200x-dta/proposals/proposals.html


Another problem I have with using a single record is that
the record must be inout (both the transaction source and
the BFMs drive elements of the record). This limits the
record to use of types with a resolution function - and in
my case I take the easy way out by using the std_logic
family. Currently there is a fast track proposal to address
this issue by making it possible to specify modes of record
elements individually. For more information see:
http://www.eda.org/vhdl-200x/vhdl-200x-ft/proposals/ft17_composite_interface_mode.txt

Comments welcome. :)

More information on the VHDL-200X is at:
http://www.eda.org/vhdl-200x

Anyone can participate as an observer. If you are an IEEE member
and a DASC member you can be a voting member and vote on issues.
However, do note people do listen and are concerned about the
issues of observers. Speaking as the team leader of the
modeling and productivity group and co-team leader of
fast track, I would welcome people with language skills like
Jonathan and Mike to contribute to working group proposal
writing.

Cheers,
Jim
--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jim Lewis
Director of Training mailto:Jim@SynthWorks.com
SynthWorks Design Inc. http://www.SynthWorks.com
1-503-590-4787

Expert VHDL Training for Hardware Design and Verification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
"Jim Lewis" <Jim@SynthWorks.com> wrote in
message news:40199736.5070109@SynthWorks.com...

VHDL-200X DTA topic hits about the middle of this thread.
I'll try to get back to following VHDL-200X. I've let it
lapse recently, with other issues taking higher priority -
SystemV*****g, for example :-(

[...]

The wrapper procedures simply take general-purpose package
procedures like CpuWrite(rec, adrs, data) and specialise them
with the signals that will be used by that specific process.
There's no loss of portability, and IMHO a great gain in clarity
of the mainstream stimulus generator code. Even in complicated
cases, the wrapper procedures are simple and short.

Criticism welcome!

First, I feel your pain. However, since we have
worked the procedures down to only having one extra
signal, the record, I don't feel it that much.
I think I hear what you're saying, but I don't find it
very convincing. [On a second reading of what I've
written below, it sounds a bit combative. It's not meant
to be, and please accept my apologies if it seems so.
A deadline looms, and I don't have time to polish the
phrasing of a newsgroup post!]

To remove this one signal name, you propose to add...
VHDL is and will probably remain fairly verbose. I have
no problem with that, and never have had. The argument
that says (in parody) productivity=gates/keystrokes is
a bad joke. No, my motivation is definitely NOT to save
keystrokes. It's an encapsulation/hiding issue. Within
the stimulus generator process, I want calls to the BFM
procedures to be parameterised ONLY ON THE THINGS THAT
ARE DIFFERENT PER-CALL: the connected signal is not,
and should be rolled into the procedure.

Remember partial function application in Pop-II (and
LISP, I think, and some functional languages)?

clutter of the translation procedures
A fair criticism, but I stand by my position that the
specialisation wrappers are invariably simple and small.

and potential
simulation inefficiency of the additional layer of
procedure calls.
BFM procedures are likely to be non-trivial. The
overhead of one more function call and its associated
parameter-passing should be negligible in comparison
with the execution time and memory footprint of the
procedure. Anyhow, any half-decent compiler should
be able to inline calls of that kind that simply pass
on parameters from one procedure to another, especially
as the wrapper procedure is sure to be local to the
process that uses it.

Engineering wise, there is probably a balance point.
Your suggestion probably works great if there are only
a few procedures and if no additional procedures will be
added.
I agree that it gives rise to a maintenance issue when
new BFM procedures are added to the package.

I think I would always be willing to type the extra
name. If I was really that concerned about it, I would
make the record name C.
AARGH!

The language is up for revision, so rather than argue
about the merits of the wrapper approach, I would
prefer to brain-storm on language changes that would
help this situation.
My wrapper trick is just a fudge to get a reasonable
compromise between the style I want, the features
available in VHDL, and the pressing need for re-use.
I don't want to promote it as a solution for the future.

The right solution to this problem IMHO is procedural
entry points into entities (as proposed in OOVHDL, I think).
This would give you the wonderful ability from Verilog
of being able to write a BFM module, with procedures to
invoke its functionality but also with ports to connect
(permanently) to the signals it will drive/receive.
The procedure would then be called by an OO-style
"instancename.BFMprocedure(params)" method call.
What's more, VHDL could easily make a good, robust job
of the mutual exclusion problem, which is always a
major issue when you allow this kind of cross-module
access; we could borrow the tasks/entries/rendezvous
mechanism from Ada - it matches this problem beautifully.

The VHDL-200X Data Types and Abstractions (DTA) group
(headed by Peter Ashenden) is looking at adding generics
to packages (including type generics). One of the
things that comes with this is instantiatable
packages. So I have been wondering, if I instantiate
a package in a process declaration region, can the
package see signals in the process environment and
can procedures in the instantiated package see signals
by side-effect? This most would solve the same problem.
Currently it is planned that this feature will be based
on the SUAVE proposal (but perhaps with some syntax
changes). For links to the SUAVE work, go to:
http://www.eda.org/vhdl-200x/vhdl-200x-dta/proposals/proposals.html
Thanks for the link. Again, I'll try to get up to speed with
it in the next few weeks, as time permits.

Another problem I have with using a single record is that
the record must be inout (both the transaction source and
the BFMs drive elements of the record). This limits the
record to use of types with a resolution function - and in
my case I take the easy way out by using the std_logic
family. Currently there is a fast track proposal to address
this issue by making it possible to specify modes of record
elements individually.
This issue is another reason why I tend to prefer NOT using
records, so that signal-class "port" parameters can be given
their proper directions - then the "wrapper" trick looks
even more useful.

For more information see:
http://www.eda.org/vhdl-200x/vhdl-200x-ft/proposals/ft17_composite_interface
_mode.txt
Comments welcome. :)
Not until I've had a chance to look very much more carefully!

Thanks for your comments and all the references.
--

Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * Perl * Tcl/Tk * Verification * Project Services

Doulos Ltd. Church Hatch, 22 Market Place, Ringwood, Hampshire, BH24 1AW, UK
Tel: +44 (0)1425 471223 mail: jonathan.bromley@doulos.com
Fax: +44 (0)1425 471573 Web: http://www.doulos.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 

Welcome to EDABoard.com

Sponsor

Back
Top