systemverilog interface connects RTL and TML

K

Kecheng

Guest
Could anyone give we an example how to use sysytemverilog interface to
connect to level component. For instance, component A is defined in
RTL level and component B is defined in TML, how to connect these
components?

Thanks in advance.
 
On Wed, 11 Jul 2007 17:04:55 -0000,
Kecheng <kechenghao@gmail.com> wrote:

Could anyone give we an example how to use sysytemverilog interface to
connect to level component. For instance, component A is defined in
RTL level and component B is defined in TML, how to connect these
components?
There are many possible ways to attack this, but SystemVerilog
interfaces provide at least one interesting possibility.

A transaction level model (I guess that's what you meant by "TML"?)
communicates by calling functions in other models, or by other
models calling functions in it.

Am RTL model communicates by causing and observing signal changes.

A SystemVerilog interface can do both sorts of communication:
* It can contain signals (nets and variables); these signals
can be made visible to an RTL module through the module's
port, using a modport on the interface.

* It can contain tasks and functions that can be called by
a connected transaction-level model; and it can call
tasks and functions in a connected transaction-level model.

Here's a ridiculously simple example. Suppose we have a clocked
serial communication scheme: one bit is conveyed on each
rising edge of the clock. An RTL module would work like this:

always @(posedge CLK) begin
next_input_bit = IN_BIT;
OUT_BIT <= next_output_bit;
end

A transaction-level (TL) model, by contrast, would have
subprograms:

task TLM_receive(bit IB);
// the sender calls this task in our model to
// give us the new data bit IB
if (IB) ...
else ...
endtask

// This task will be called by code in the TL
// model whenever it has a new bit to send
task TLM_send(bit OB);
//???
endtask

In practice, the task "send_output_bit" is almost certainly
NOT in the TL model at all, but rather it's in whatever
model is the destination for the serial data.

So let's make a SV interface to support both kinds of
modelling:

interface Serial_Adaptor_Intf (input bit CLK);

// RTL-style signals
bit data_from_TLM_to_RTL;
bit data_from_RTL_to_TLM;

// Task that can be called by the TL model
task TLM_send(bit OB);
@(posedge CLK) data_from_TLM_to_RTL <= OB;
endtask

// Interface gathers bits from the line and
// sends them to the TL model
always @(posedge CLK)
// Call a task in the TL model
TLM_receive(data_from_RTL_to_TLM);

// How will the RTL model connect to this
// interface?
modport RTL ( input CLK, data_from_TLM_to_RTL,
output data_from_RTL_to_TLM );

// How will the TL model connect?
modport TLM ( export task TLM_receive(bit IB),
import task TLM_send(bit OB) );

endinterface

Now we need to write our RTL model to use the interface:

module RTL_serial ( interface.RTL r );
always @(posedge r.CLK) begin
next_input_bit = r.data_from_TLM_to_RTL;
r.data_from_RTL_to_TLM <= next_output_bit;
end
// and lots of code to deal with the data, of course.
endmodule

And modify the TL model:

module TLM_serial ( interface.TLM t );
// Here's the task that will be called by the interface:
task t.TLM_receive(bit IB); // note the strange name
// do whatever you want with the new data bit IB
endtask

/// Here's some code that generates output data...
initial begin
...
// we've got a new bit; send it to the interface
t.TLM_send(b);
...
end
endmodule

Finally, stitch it all together:

module Top;
bit clk; // and a clock generator, of course
...
Serial_Adaptor_Intf SAI (clk);
TLM_serial T ( SAI.TLM ); // connect to TLM modport
RTL_serial R ( SAI.RTL ); // connect to RTL modport
endmodule

Now, your RTL module is happily driving and receiving
signals, and it knows nothing about the transaction-level
tasks. Your TLM code sees incoming and outgoing task
calls, and knows nothing about clocks or signals.
Everyone's happy (except for the poor people who must
write the adaptor interface, which in reality is
a pretty tough job).

If you decide, in the future, to implement both sides
of the communication link as RTL, then you can easily
create another interface that has two RTL-style modports.
Similarly, you could migrate both halves of the model
to TLM if you prefer. The interface soaks up the
differences.

There are MANY other ways to do this. In particular,
you may prefer to write your TL model in an object-
oriented style using SystemVerilog classes. If you
do so, then it makes sense to write your adaptor
in a slightly different way, creating a BFM or
adaptor class; typically such a class would hook
to the interface using clocking blocks and virtual
interfaces.
--
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 Wed, 11 Jul 2007 17:04:55 -0000,
Kecheng <kechenghao@gmail.com> wrote:

Could anyone give we an example how to use sysytemverilog interface to
connect to level component. For instance, component A is defined in
RTL level and component B is defined in TML, how to connect these
components?
In another, rather long response I sketched one possible
way to do this using SystemVerilog interfaces, and suggested
that there are many other possible approaches.

It seems to me that SystemVerilog has huge potential in this
area. Currently, people write transaction-level models in C++
(or something based on it, such as SystemC) and then must
go through a painful rebuilding process when their design
is refined to RTL. Using SystemVerilog there is real hope
of creating a truly seamless refinement path from TLM to
RTL. Is anyone else out there interested in this possibility?
If so, then I believe that the SystemVerilog interface/modport
concept needs some fairly drastic enhancement to fully support
such an idea. I'd love to hear from anyone who's interested
in pursuing this further. Or, of course, if you're doing it
already I'd love to know about that too!
--
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.
 
Thank you very much for your explaination, it make me much more clear
about the TLM design in SystemVerilog, and it make me more intereted
in this methodology. But I'm still have questions, you know the TML
could speed up the simualtion time, it gets rid of timing. this in
SystemVerilog, it seams that the TL interface only encapsulate the
timing into the task, like the example you show me. And I also find an
example from DOULOS. It's an APB bus interface.

interface APB ;
// See also modports and tasks:
//
// modport RTL_slave -- connect any RTL slave model to one of
these
// modport RTL_master -- connect an RTL master model to this (one
only)
// modport TF_master -- Connect a behavioural master to this (one
only)
// task read; -- BFM entry point for behavioural tests
// task write; -- BFM entry point for behavioural tests

//
__________________________________________________________________________

parameter master_Tco = 1;
logic PCLK;
logic psel = 0;

// Address signal from master
//
T_APB_a PADDR;

// Write data, from master
//
T_APB_d PWDATA;

// Write and enable signals from master
//
logic PWRITE = 0;
logic PENABLE = 0;

// Readback data signal, written by selected slave
//
T_APB_d PRDATA;

//
__________________________________________________________________________



// _____________________________________________________________
MODPORTS ___

// Any slave connects like this...
//
modport RTL_slave (
input PCLK, // Slaves get their clock from the bus
//input psel, // Select signal - global for all slaves
input PWRITE, // Active in the clock when address is sent
input PENABLE, // Direction control, sent with the address
input PADDR,
input PWDATA,
output PRDATA
); // modport RTL_slave

// The one and only master connects like this:
//
modport RTL_master (
output PCLK, // Master supplies clock to the bus
output PWRITE, // Active in the clock when address is sent
output PENABLE, // Direction control, sent with the address
output PADDR,
output PWDATA,
input PRDATA
); // modport RTL_master

// Alternatively you can connect a behavioural master
// to this BFM-like modport:
//
modport TF_master (
output PCLK, // Master supplies clock to the bus
import task master_write(),
import task master_read() // Master calls these tasks to do cycles
); // modport TF_master



// _____________________________________________________ BFM ENTRY
POINTS ___

// Non-synthesisable task-call interface for a testbench master
// so that it can exercise slaves on the bus without the need for
// a fully-functional model of a master device.


// Call read and write tasks at the posedge of the clock
// right at the beginning of the cycle. They return just after
// the final posedge of the cycle.

task master_read (
input T_APB_a adrs,
output T_APB_d data
);

time start_delay;

CheckLastClock(start_delay);

#(start_delay)
PADDR = adrs;
PWRITE = 1'b0;
psel = 1'b1;

@(posedge PCLK)
PENABLE <= #master_Tco 1'b1;

@(posedge PCLK)
data = PRDATA;
PADDR <= {$bits(PADDR){1'bx}};
psel <= 1'b0;
PENABLE <= 1'b0;

endtask // read

task master_write (
input T_APB_a adrs,
input T_APB_d data
);

time start_delay;

CheckLastClock(start_delay);

#(start_delay)
PADDR = adrs;
PWDATA = data;
PWRITE = 1'b1;
psel = 1'b1;

@(posedge PCLK)
PENABLE <= #master_Tco 1'b1;

@(posedge PCLK)
PWDATA <= {$bits(PADDR){1'bx}};
PADDR <= {$bits(PADDR){1'bx}};
psel <= 1'b0;
PENABLE <= 1'b0;

endtask // write

Then, if I define the master at transaction level like this.
module master_TF
(
APB.TF_master apb,
output logic async_reset
);
localparam
period=10,
MEMADDR=16'h0001;

logic signed [15:0] data=16'hFFFF;
logic signed [15:0] newdata=16'hzzzz;

initial
$timeformat (-9,0,"ns",8);

always begin: ClockGenerator
apb.PCLK=0;
apb.PCLK <= #(period/2) 1;
#period;
end //ClockGenerator

initial begin: TestSequence

//reset
async_reset=1;
//apb.idle(4);
@(negedge apb.PCLK) async_reset=0;

//wait for a few rising clcks
//apb.idle(3);

//write operation
apb.master_write(MEMADDR, data);
$display ("write data to the memory");
//read operation
apb.master_read(MEMADDR, newdata);
$display ("read data from the memory new data= %x",newdata);


end //TestSequence

endmodule

And I also can define the master at RTL level.
module master_RTL
(
APB.RTL_master apb,
output logic async_reset
);

localparam
period=10,
MEMADDR=16'h0001,
master_Tco=1;


logic signed [15:0] data=16'hFFFF;
logic signed [15:0] newdata=16'hzzzz;

always begin: ClockGenerator
apb.PCLK=0;
apb.PCLK <= #(period/2) 1;
#period;
end //ClockGenerator

initial begin: TestSequence
//reset
async_reset=1;
//apb.idle(4);
@(negedge apb.PCLK) async_reset=0;

//write operation
write(MEMADDR, data);
$display ("write data to the memory");
//read operation
read(MEMADDR, newdata);
$display ("read data from the memory new data= %x",newdata);
end //TestSequence

task read (
input T_APB_a adrs,
output T_APB_d data
);

// Get to Tco after the most recent possible clock
ClockTcoSync;
apb.PADDR = adrs;
apb.PWRITE = 1'b0;

@(posedge apb.PCLK)
apb.PENABLE <= #master_Tco 1'b1;

@(posedge apb.PCLK)
data = apb.PRDATA;
apb.PADDR <= {$bits(apb.PADDR){1'bx}};
apb.PENABLE <= 1'b0;

endtask // read

// ______________________________________________________ write()
___
//
task write (
input T_APB_a adrs,
input T_APB_d data
);

// Get to Tco after the most recent possible clock
ClockTcoSync;
apb.PADDR = adrs;
apb.PWDATA = data;
apb.PWRITE = 1'b1;

@(posedge apb.PCLK)
apb.PENABLE <= #master_Tco 1'b1;

@(posedge apb.PCLK)
apb.PWDATA <= {$bits(apb.PADDR){1'bx}};
apb.PADDR <= {$bits(apb.PADDR){1'bx}};
apb.PENABLE <= 1'b0;

endtask // write

task ClockTcoSync;

time t;

// If this task was called as the result of a clock edge, we must
// be sure that the LastClock value has already been updated.
// That's why we need a #0 delay prefix here.
//
#0 t = $time - ClockEdgeLogger.LastClock;

// Are we too late to start the bus cycle on the current clock?
//
if (t > master_Tco) begin

// Yes, we're too late. Wait for the next clock.
@(posedge apb.PCLK) t = master_Tco;

end else begin

// No, we're in time. Calculate how long we need to wait
// before applying the right signals.
t = master_Tco - t;

end

// Finally, align to Tco after the chosen clock
#(t);

endtask
always @(posedge apb.PCLK) begin : ClockEdgeLogger

time LastClock;
LastClock = $time;

end // ClockEdgeLogger

endmodule

You see, only the interface is different, but I think it the TL code
won't be faster than the RTL code.

And could you please give me an example of how to define a component
at transaction level.

thanks a bunch!


Best,
Kecheng
 
On Wed, 18 Jul 2007 04:15:03 -0000,
Kecheng <kechenghao@gmail.com> wrote:

I'm still have questions, you know the TML
could speed up the simualtion time, it gets rid of timing. this in
SystemVerilog, it seams that the TL interface only encapsulate the
timing into the task, like the example you show me. And I also find an
example from DOULOS. It's an APB bus interface.
Please, please, please don't post 300 lines of code that's freely
available on the web. Especially when you're replying to the
person who wrote that code :)

Your original post asked about linking TL models to RTL
models. I showed you a simple example of how that can be
done in SystemVerilog.

You see, only the interface is different, but I think it the TL code
won't be faster than the RTL code.
I *think* you are saying: if the TL code is locked to an RTL model,
surely it will not run noticeably faster? And the answer, of course,
is "probably so". TL code typically simulates about two orders
of magnitude faster than RTL. Suppose I have two modules, both
written using RTL. Suppose that the total wall-clock simulation
time is 200 seconds, and exactly 100 seconds of that time is
consumed by each of the two RTL modules. Now let me rip out one
of the RTL modules and replace it with a TL model, using a
SystemVerilog interface to link them as I showed you. The
replacement TL model consumes 1 second of sim time; the
remaining RTL model consumes 100s. Total time, 101 seconds;
speed-up, roughly 2x. This is NOT the fault of the SystemVerilog
interface! It happens because I still have some RTL code in
the model.

The point of using interfaces is to allow each module to work at
its own chosen level of abstraction; the interface provides
translation between the two levels. If both modules are TL,
then the interface can simply pass on each module's function
calls to the other one, and full TL speed is maintained.
If one module is TL and the other is RTL, the interface
provides a function-call interaction on the TL side, and
a signal-level interaction on the RTL side. If both sides
are implemented in RTL, the interface can simply provide
a bunch of signals to link the two sides together.

And could you please give me an example of how to define a component
at transaction level.
Read some material on SystemC. But if you prefer to work in
SystemVerilog, try this:

module Transaction_Level_Memory(); // no ports
integer storage[int]; // associative array
function integer read(int address);
return storage[address];
endfunction
function void write(int address, integer data);
storage[address] = data;
endfunction
endmodule

Hard, huh?

Ah, but it doesn't work with an interface. So let's
try again. Here's (part of) the SV interface that
could be used to link the memory model to the rest of
a system:

interface Memory_Interface();
modport Memory_MP ( // connect the memory model here
export function integer read(int address),
export function void write(int address, integer data)
);
modport Client_MP ( // connect a user of the memory here
import function integer read(int address),
import function void write(int address, integer data)
);
endinterface

Now we need to (slightly) rewrite the memory model so that
it talks to the outside world through the interface:

module Transaction_Level_Memory(Memory_Interface.Memory_MP mp);
integer storage[int]; // associative array
function integer mp.read(int address);
return storage[address];
endfunction
function void mp.write(int address, integer data);
storage[address] = data;
endfunction
endmodule

And now let's write part of another TL model that uses this
memory through the interface:

module Transaction_Level_Client(Memory_Interface.Client_MP mp);
initial begin
// Write to the memory, data 57 at address 42
mp.write(42, 57);
// Read from the memory
if (mp.read(42) !== 57)
$display("Memory failure");
end
endmodule

Everything is now at transaction level; no signals or timing
anywhere. At a later time I can write the memory as an RTL
model, and then I'll need to add TL-to-RTL conversion, and
an RTL-style modport, to the interface - AND I DON'T NEED TO
CHANGE THE CLIENT CODE AT ALL. Progressive refinement!

HTH
--
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.
 

Welcome to EDABoard.com

Sponsor

Back
Top