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.
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]
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:
C Code:
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
#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); }
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.
ReplyDeleteTo 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