High level modeling with SystemVerilog

G

gtwrek@pacbell.net

Guest
Ok, I'm trying now to use Systemverilog to model some higher level
functions, that
we'll later implement in synthesizable code. They're video processing
functions,
so the modules are operating on either pixels, lines, or frames.

I'm trying to figure out the easiest way of modelling the data
movement without getting
my hands dirty with flow control/etc. The SystemVerilog "mailbox"
class looks promising.

Here's an example of a pixel processing block:

module top;

bit [ 2 : 0 ] [ 7 : 0 ] pixel; // RGB pixel
mailbox pixel_in = new();

// in some procedural block
pixel_in.put( pixel );

// Call sub-level block which takes single pixels...
pixel_processor pixel_processor( .pixel_in( pixel_in ) );

endmodule

module pixel_processor
(
input mailbox pixel_in
);

reg [ 2 : 0 ] [ 7 : 0 ] pixel;

initial
forever
begin
pixel_in.get( pixel ); // Will block
$display( "I got a pixel: %0x!", pixel );
// Do some operation on said pixel...
end
endmodule

This work's great - my calling function can push pixels onto a memory,
by submodule will "wake up" and
process anything as soon as it's available. Great.

However, I'm having trouble extending this concept to lines, and
frames. Due to the nature of my
processing, I have no advanced knowledge of line length, or frame
length. So, I need some sort
of dynamic type. Objects push'ed onto mailboxes must be singular
(i.e. static size). I've no problem
defining dynamic arrays for lines:

bit [ 2 : 0 ] [ 7 : 0 ] line [*];

But how to I use this with a mailbox?

I know that a mailbox is just a class, which I can extend. I've
played around with creating classes for
my lines, and frames - but it doesn't seem to fit into the mailbox
concept above.

I'm wondering if anyone else has suggestions here to point me in the
correct direction. Should be
a simple concept - got a feeling I'm missing something obvious.

Mark
 
On Thu, 14 Jun 2007 10:09:43 -0700,
<gtwrek@pacbell.net> wrote:

Objects push'ed onto mailboxes must be singular
(i.e. static size). I've no problem
defining dynamic arrays for lines:

bit [ 2 : 0 ] [ 7 : 0 ] line [*];
I think that should be
... line[]; // dynamic array
or
... line[$]; // queue
You probably don't want an associative array here.

But how to I use this with a mailbox?
I would be inclined to create a class for each of the bigger
structures - you hinted you had already thought about this.
The class can then have a dynamic array as one of its members,
and can also optionally carry around a bunch of other
useful stuff. And a mailbox can easily carry objects of
class type - because it doesn't really carry the objects,
but only references to them...

typedef bit [2:0] [7:0] pixel;

class Video_Line;
pixel line_data [];
time line_synch_time; // or any other useful stuff
...
endclass : Video_Line

typedef mailbox #(Video_Line) Line_MB;

The only thing you need to be careful about is exactly the
fact that the mailbox holds REFERENCES to Video_Line objects,
so you probably want to put a *copy* of the object onto the
mailbox. Classes to the rescue again: inside class Video_Line
create a copy() method ("Manufacture a clone of myself"):

... // inside the Video_Line class
function Video_Line copy();
copy = new this; // shallow copy may be OK
endfunction : copy


... in your procedural code
Line_MB line_mb = new;
Video_Line vline = new; // get a Video_Line object
...
for (however many lines you want) begin
create_line(vline); // maybe randomized?
// assume create_line() fills in the contents appropriately
...
// send the line to downstream processing
line_mb.put(vline); /// BUG HERE!!!
end

The bug, of course, is that you have put on to the mailbox
a REFERENCE to vline, not the object itself. So you then
go around the loop again, mess with the contents of vline,
and the mailbox now contains a reference to the messed
object. The correct solution is

...
// send copy of line to downstream
line_mb.put(vline.copy());

Now you have on the mailbox a reference to a clean, unique
copy of the particular "vline" data you care about. You can
now spin round the loop again, mess-up the contents of vline,
and do the put(vline.copy()) operation again with confidence,
knowing that each reference you've pushed on to the mailbox
is a reference to a DIFFERENT object.
--
Jonathan Bromley, Consultant

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

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

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

I like Jonathan's approach. Since you want to consider a line or frame
as a single unit (at least when passing around), it is appropriate to
create a class.

However, I would prefer to put the reference in the mailbox and not to
copy it. Once your generator puts an instance in the mailbox, have the
top of your generate loop just new() to get a new instance. I have
used this technique in several projects. Note that your driver and
checker will both want a reference to the class, so you may need to
"tap" the mailbox with a second mailbox.

One question, if you are setting up for eventual implementation, why
use mailboxes? Consider making the modules you expect in the real
system and then driving the inputs (and collecting the outputs) with
interfaces. That way, you can replace modules with RTL as they come on-
line.

Don't get me wrong, I love mailboxes, classes, and the other cool
stuff in SV, it just I like to keep the synthesizable stuff separate.

Take Care,
mike at trusster
 
On Fri, 15 Jun 2007 14:39:49 -0000, <mmintz@gmail.com> wrote:

However, I would prefer to put the reference in the mailbox and not to
copy it. Once your generator puts an instance in the mailbox, have the
top of your generate loop just new() to get a new instance.
Very interesting... I've been arguing with myself about this for
some considerable time. Eventually I have decided that I like
my "copy-on-post" approach best. My reasoning goes like this;
I'd be delighted to hear from anyone who disagrees or has a
different spin on it.

(1) The argument from robustness
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If I can be truly, positively, 100% sure that I have given
no-one else a reference to my object, and I will never
extend the code in my stimulus-generation loop to give
anyone such a reference, then it's obviously fine to do
as Mike suggests. But it worries me, because I know that
code grows and changes, and assumptions made today are
unlikely to hold tomorrow. If I copy-on-post then there
is _nothing_ I can do to the code that could break my trust
in the uniqueness of the reference that's on the mailbox.
What's more, the copy on the mailbox is then inviolable
and the client at the other end of the mailbox won't get
any nasty surprises because of someone else messing with it.

(2) The argument from code conciseness
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I agree that it's a tad clunky to be obliged to do
mb.put(thing.copy())
every time. But there's nothing to stop me extending
the mailbox class and overriding its put() method
(or providing an alternative, "copy_put") so that
the copy operation is encapsulated there.

(3) The argument from efficiency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is worryingly expensive to do a copy every time you
want to post something on the mailbox. But this is, in
fact, a largely specious argument. In the simple case
Mike describes, the cost of a new() each time around
the loop is almost as large as the cost of the shallow
copy in my .copy() method; recall that new() in SV must
initialise all the new object's properties to default
values, so it is a modest overhead to initialise them
from an existing object instead. In more complex
cases, it's essential to clone the object *somewhere*
in any case.

(4) The argument from self-preservation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I'm a trainer by trade. It's important to me to be
able to offer methodologies and techniques that allow
others to create reliable code without them needing to
turn their brains inside out. I can offer this sane,
universally-applicable advice to people for whom this
stuff is novel, and I'll get fewer follow-up calls:

Whenever you're giving away a reference to an object
to some agent that might mess with the object at
some time in the future, then you better be sure that
at least one of the following things is true:
- The messing-with-the-object is being done on your
behalf, with your consent, and you will get to
be notified when it's done; OR
- You have a copper-bottomed guarantee that you, and
all your helper processes, are never going to look
at that object ever again.
By far the easiest way of ensuring the second of these
criteria is to arrange that the object that you gave
away is a freshly-minted copy of the object you care about.

Thoughts, anyone?
--
Jonathan Bromley, Consultant

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

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
On Jun 14, 1:29 pm, Jonathan Bromley <jonathan.brom...@MYCOMPANY.com>
wrote:
On Thu, 14 Jun 2007 10:09:43 -0700,

gtw...@pacbell.net> wrote:
Objects push'ed onto mailboxes must be singular
(i.e. static size). I've no problem
defining dynamic arrays for lines:

bit [ 2 : 0 ] [ 7 : 0 ] line [*];

I think that should be
... line[]; // dynamic array
or
... line[$]; // queue
You probably don't want an associative array here.
Actually, I did. Dynamic array isn't as nice, as my array is randomly
growing. I don't want to keep new() the (size+1) array. I think
that's how I would need to do it with a dynamic array.

I didn't think I wanted a queue, cause I didn't read up on a queue
close enough. I needed random access to the data, and thought that
the queue didn't give me that. I see now that it does, so I'll use
that.

// mbox example snipped..

Jonathan, thanks soo much for the example. I'm moving forward with
using this methodology. I was slowly moving that way, but your
example pretty much just layed everything out. I had a feeling in the
back of my mind about the "bug" in your example, and thought I'd need
to deal with it sometime. Just using your solution seems to be
working.
 
On Jun 15, 7:39 am, "mmi...@gmail.com" <mmi...@gmail.com> wrote:

One question, if you are setting up for eventual implementation, why
use mailboxes? Consider making the modules you expect in the real
system and then driving the inputs (and collecting the outputs) with
interfaces. That way, you can replace modules with RTL as they come on-
line.

Don't get me wrong, I love mailboxes, classes, and the other cool
stuff in SV, it just I like to keep the synthesizable stuff separate.
Well Mike, it's a matter of work. I spent last week doing a floating-
point model in octave. This week, I wanted a fixed point model that
reflects that hardware pretty closely. I started with a C framework
that our software guys had setup. But I quickly got bogged down
because, as a good hardware guy, I see the solution in my head with
all the inherent parallelism that hardware offers. I tried to fit
this into basically, one big for loop in C. But the lack of
concurrency was killing me. So, back to SystemVerilog.

I started modelling as you suggested, with the modules with the real
inputs, etc. But it got bogged down too, with protocol issues (when
data was available, pushing back, buffering..etc, and state-machine
type details).

So, the high-level transaction type modeling. All the fifoing, and
other kruft is gone, and handled by the mailboxes. The module
boundaries - at least the datapath - still represent the hardware
fairly well. We should be able to use it as a reference model. And a
lot quicker than I'd be able to do other ways... Well, I hope so -
trying to get my first simulation with images going today...

Thanks,

Mark
 

Welcome to EDABoard.com

Sponsor

Back
Top