Skip to main content

Good uses of System Verilog DPI..

What is DPI??

Well, The easiest answer is Direct Programming Interface. The DPI is used to communicate the system verilog/ Verilog code to any other language. Well DPI has its own advantages over PLI. 
You can check out the differences between the DPI and PLI  HERE.

With DPI, you can directly call the c-functions from system verilog code and vice versa. With lot of SOCs and complicated chips in the silicon industry, sometimes you cannot completely live with the generic UVM/System verilog to write complete stimulus for complete chip. 

Basics of DPI, Basics of DPI can be found when you google it. 

There are some interesting cases where you will find the uses of DPI. I will get through some advanced take aways from IEEE 1800.2012 LRM.
  • You cannot export the objects with in class.
  • You can export only from static objects.
  • exporting the tasks/functions should be in a module/program/interface scope.[or any static scope]
  • .

..... Bear with me as this post might be some what long ......

An Example of using the multiple DPI functions of same task in multiple modules:

Sometimes you have a common module which is instanced many times. You may want to have a function/task which is exported to be called by the foreign language (Mostly C). Well as you have a module hierarchy on the verilog side, you cannot have the similar hierarchy on the C side as well. 
So how does the system verilog provide a solution to this:
There is concept of scope in which the DPI operates in.
Modules/Interfaces have the static context in which they are operating. 
Classes does not have any context. 

Excerpt from the IEEE-1800.2012:
SECTION H.9:
DPI imported tasks and functions follow the same model as native SystemVerilog tasks and functions. They execute in the context of their surrounding declarative scope, rather than the context of their call sites. This type of context is termed DPI context

Because imports with diverse instantiated scopes can export the same subroutine, multiple instances of such an export can exist after elaboration. Prior to any invocations of svSetScope, these export instances would have different contexts, which would reflect their imported caller’s instantiated scope. 

Imports Into System verilog:

Well what that means is that whenever you call an import in a module, the scope/context of that import pertains to that particular instance of the module. This happens during elaboration.
Now what about classes? You cannot do an import of external function in a class. The import should happen at the global name space. [out of any module scope]

Exports from System Verilog:

You can export tasks/functions from system verilog.
You cannot export tasks/functions from classes.

Code as an example:

Please go through the link http://www.edaplayground.com/x/4t7u
This has an example of having multiple instances of exports and calling the DPIs back and forth.



Code below:
Verilog code:
module modA(startAddr, endAddr);
  input reg [31:0] startAddr;
  input reg [31:0] endAddr;
  //Import the function to register this module..
  int values[int];
  import "DPI-C" context function void registerMe(int startAddr, int endAddr);
  export "DPI-C" function write_reg;
  
  function void sv_write_reg(int addr, int value);
    values[addr] = value;
    $display("Write_reg[%m]:: addr=%0d, value=%0d", addr, value);
  endfunction
  
  
  initial begin
    #0;
    $display("registerMe[%m](%x,%x)",startAddr, endAddr);
    registerMe(startAddr, endAddr);
  end
  
endmodule

module top;
  
  modA modA1(.startAddr(32'd100),.endAddr(32'd200));
  modA modA2(.startAddr(32'd201),.endAddr(32'd300));
  modA modA3(.startAddr(32'd301),.endAddr(32'd400));
  
  
endmodule

module test;

  import "DPI-C" function void c_write_reg(int addr, int value);
  
  initial
  begin
    #1;
    //Now write the registers.
    c_write_reg(100,10);
    c_write_reg(210,21);
    c_write_reg(500,50);
  end
  
  top t1();

endmodule
C Code:
#include <iostream>
#include <svdpi.h>
#include <string.h>

using namespace std;

char scopes[100][100];
int startAddress[100];
int endAddress[100];
int currentPointer = 0;

int findAddressPointer(int addr);

extern "C" void sv_write_reg(int addr, int value);
extern "C" void c_write_reg(int addr, int value) {
  int pointer = findAddressPointer(addr);
  if(pointer != -1) {
    // set the scope to the specific instance.
    svSetScope(svGetScopeFromName(scopes[pointer]));
    write_reg(addr,value);
  }
  else {
    cout << "Invalid address found: " << addr << endl;
  }
  
}
                       
extern "C" void registerMe(int startAddr, int endAddr) {
  // Get Scope....
  startAddress[currentPointer] = startAddr;
  endAddress[currentPointer] = endAddr;
  strcpy(scopes[currentPointer] ,svGetNameFromScope(svGetScope()));
  cout << "Registering Address: " << startAddr << " to " << endAddr << scopes[currentPointer] << endl;
  currentPointer++;
  
}

int findAddressPointer(int addr) {
  for(int i=0;i<currentPointer;i++) {
    if((addr >= startAddress[i]) && (addr <= endAddress[i])) {
      return(i);
    }
  }
  return(-1);
}
                            




Comments

  1. Thanks for sharing this example. There were minor typos (needing to use sv_write_reg instead of write_reg at 2 places), which I fixed in https://www.edaplayground.com/x/34Wg.

    ReplyDelete
  2. To add, note that the import of "c_write_reg" also needs the "context" property. As an exercise, I ported the C++ code to Nim and here's how it looks: https://github.com/kaushalmodi/nim-systemverilog-dpic/commit/70703dcceea6717924b38c65c14f1fd34be45f07

    ReplyDelete

Post a Comment

Popular posts from this blog

Verdi Uses - I

COVERAGE While going through some of the blog posts, i found that the Verdi supports even coverage right out of the box, but from the 2014 Version. Well till now, we relied on the URG reports and the DVE to review the coverage and apply waivers and create exclusion files. Looks verdi supports all the features.. I did not get any hands on the features of this option, soon i will find some time to play around with this option of coverage in Verdi. How to launch the verdi with coverage. $> verdi -cov -covdir <PATH_TO_TB>.simv.vdb -covdir <PATH_TO_TEST1>.simv.vdb -covdir <PATH_TO_TEST2>.simv.vdb...  You can pass multiple covdirs, where in all the results will be merged. References: SYNOPSYS SITE Think Verification  

Sequence Layering in UVM

What is Sequence Layering? Sequence layering is refered to as running sequences inside sequences.  So whats the big deal. There are virtual sequences we will use to run multiple sequences. A generic example of sequence layering. you have two sequences  Interrupt sequence register read write sequence. Generally when you have interrupt, we generally enter into interrupt service routine, which can be implemented as an interrupt sequence. So when you run an interrupt sequence, you might end up running register read /write sequence like polling or clearing interrupts. Well thats easy, just run one sequence in another. class top_seq extends uvm_sequence#(txn_item); //Two sequencers say.. sub sequences. sub_seq1 seq1; sub_seq2 seq2; ... task body(); seq1.start(some_seqr); seq2.start(some_seqr2); endtask endclass Killing sequences: That was great you are running nested sequences. But how to kill them when needed. UVM provides method cal

SystemVerilog: Mailboxes and Queues

Mailboxes and queues are couple of basic data constructs of system verilog language. Lets get to the definition of them: Queue: A Queue in system verilog function as the name suggests. But with a twist. Queue in system verilog is a list of similar elements. Queue is built on top of an array. Delcaration of a queue. < TYPE > < que_name >[ $ ]; Default Behaviour: The default size of the queue in system verilog is " Infinite ". The above declaration will create a que of infinite length. You can add elements to the que until your simulator crashes :) What if you want to create a queue of finite length. Just look at the declaration below: < TYPE > < que_name >[ $ :< LIMIT >]; The above declaration will create a queue of size " LIMIT+1 " Initializing queue with elements Accessing elements of a queue Methods in queue Inserting elements into queue Reoving elements from queue http://www.project-veripage.com/queue_1.ph