SV DPI; accessing a task within a module in the C code

Hi,
Working on integrating C code for mixed verification of axi4lite BFM so I can test the c code directly.
I have a sv AXIlite bfm I developed a couple years ago and want to access the read / write functions from the c code.
However the top level testbench instantiates the module (AXI master BFM).

In the C code (builds fine in questa 10.6c1) I call as such:

extern void axi_rd(int, int); // the SV function

     void read_register(void)
     {
       int axidata;
       axi_rd(0x0, axidata);
       printf("Data from C code: 0x%8X\r\n", (axidata >> 16) & 0xFFFE);
     }
     

However the top level testbench cannot (naturally) find the axi_rd task (as it’s in a module withing the testbench

   # ** Fatal: (vsim-3758) A scope context for 'axi_rd' could not be found within calling context 'xadc_top_tb'.
   # The nearest DPI import tf up the call chain is at line 353 of file ./repo/rfsoc_test/sim/axi_test/xadc_top_tb.v
   #    Time: 1608 ns  Iteration: 3  Process: /xadc_top_tb/#ALWAYS#348 File: ./repo/rfsoc_test/sim/axi_test/xadc_top_tb.v 

So I tried

extern void bus_mstr_c.axi_rd(int, int); 

and a couple other things to no avail.

I would like to keep the code modular - how can I properly call the SV task without having to take the tasks within my axi model and `include them in the top level tb or such? Can I keep this system modular and use the DPI? I can work around this but don’t want to.

Thanks in advance,
Jerry

In reply to jolup:

You didn’t say how you imported the C tasks that call the exported SystemVerilog tasks. If you do the import from within the same BFM model as the export, the C code gets the context to find the exported tasks.

I have a small example that instantiates multiple “bfm” modules and uses the implicit DPI context. Look at the file risc.v/risc.c and see how it calls the read/write tasks.

Also look at memory.c to see how it uses svGetScopeFromName to set the context.

In reply to dave_59:

Hi Dave,
Poking around for “DPI books” under the SV forum I actually found that earlier (Easy modelsim DPI book - SystemVerilog - Verification Academy) and was going to parse it, thanks for the guidance.

You didn’t say how you imported the C tasks that call the exported SystemVerilog tasks.

So I showed my small c test read function. This is called in the top level tb.
In the instantiated axi4lite module, I used “import “DPI-C” function void read_register();” at the top level (as the read_register c fcn is called therein).

If you do the import from within the same BFM model as the export, the C code gets the context to find the exported tasks.
I think that’s what I am getting at. The read_register() c code is used at the top level (xadc_top_tb) and that c code calls the axi_rd task. That task is one level down in the hierarchy.
From the error “…‘axi_rd’ could not be found within calling context ‘xadc_top_tb’”, I suspect that the the top level tb cannot find the axi_rd task, called by read_register() in the top level testbench as it’s not in the namespace of the top level testbench.

Ok so with that said, I am going to look over your model and digest it.

Do you suggest I reference IEEE 1800? Would it clarify this import - export usage?

If you want the code to look over, I can post it - it’s not really proprietary.
Best, thanks,
Jerry

In reply to jolup:

Hi Dave,
Tried a cheap trick

I wrapped the c function in a sv task that simply calls it.
Now the import / export are at the same level of context, which seems to be what you said above (ok I admit the need to read a certain LRM).

… and now get this:


# ** Fatal: (vsim-3757) The DPI exported task 'axi_rd' must be called from a context imported *task*. A call from a context imported *function* was detected. The caller's scope is xadc_top_tb.bus_mstr_c.
# The nearest DPI import tf up the call chain is at line 268 of file ./repo/rfsoc_test/sim/axi_test/axi4lite_mst_c_skel.v
#    Time: 1608 ns  Iteration: 3  Process: /xadc_top_tb/#ALWAYS#348 File: ./repo/rfsoc_test/sim/axi_test/axi4lite_mst_c_skel.v

Then I set the task up as


   import "DPI-C" context task read_register();

and it crashes when I run (note not load the design). Probably the call.

Ok, I’ll take some time and review your code, thanks for stimulating some thought about it.
Jerry

In reply to jolup:

It would help if you could show a complete skeleton of code that shows the procedural threading and scopes of your DPI application.

In reply to dave_59:

Dave, great, thanks. You’ll probably see the issue quickly.

The module with the c code context import is here:


module axi4lite_mst_c_skel
#(
    parameter module_id     = "AXI4-Lite DPI Master",
    parameter drive_edge    = "rise", 
    parameter datawidth     = 32,     
    parameter addrwidth     = 32,
    parameter drive_dly     = 0      
)
(
    input 			   aclk,
    input 			   aresetn,

    // write addr channel
    input 			   awready,
    output reg 			   awvalid,
    output reg [addrwidth-1:0] 	   awaddr,
    output reg [2:0] 		   awprot,
    // write data channel
    input 			   wready,
    output reg 			   wvalid,
    output reg [datawidth-1:0] 	   wdata,
    output reg [(datawidth/8)-1:0] wstrb,
    // write response
    input 			   bvalid, // valid bresp
    input [1:0] 		   bresp,
    output reg 			   bready,
    // read address channel
    input 			   arready,
    output reg 			   arvalid,
    output reg [addrwidth-1:0] 	   araddr,
    output reg [2:0] 		   arprot,
    // read data channel
    input 			   rvalid,
    input [datawidth-1:0] 	   rdata,
    input [1:0] 		   rresp,
    output reg 			   rready
);

   //---------------------------------------------------------------------------
   // Declarations  
   //---------------------------------------------------------------------------

  
   //---------------------------------------------------------------------------
   // events
   //---------------------------------------------------------------------------
   event           mst_write_req_evt;
   event           mst_write_done_evt;
   event           mst_read_req_evt;
   event           mst_read_done_evt;
   event           slv_write_rdy_evt;
   event           slv_write_ack_evt;
   event           slv_write_resp_evt;
   event           slv_read_rdy_evt;
   event           slv_read_resp_evt;

   //clock events
   event           aclk_rise;
   event           aclk_fall;
   event           aresetn_rise;
   event           aresetn_fall;


   //---------------------------------------------------------------------------
   //
   // This section sets all events
   // ... and has other tasks
   //
   //---------------------------------------------------------------------------
   
   
   export "DPI-C" task axi_rd;

   import "DPI-C" context task read_register();

   // where vsim now crashes, to maintain context at the same level.  This now maintains 
   // import and export in the same BFM.
   // So this is now context imported task calling exported task.  However it's wrapped
   // in a simple rreg task (which is now used in the test sequencer at the top lvl tb.
   task rreg;
   begin
      read_register();
   end
   endtask // read_register
   

   
    //---------------------------------------------------------------------------
    // ISSUE READ TRANSACTION  
    //---------------------------------------------------------------------------
    task axi_rd;
    input  int useraddr;
    output int userdata;
    begin
       if(enable)
       begin
          wait(pending_rd_tx == 0);
             read_addr_t = useraddr;
             @(aclk_rise);
             -> mst_read_req_evt;
             @(mst_read_done_evt);
             userdata = read_data_t; 
             if(debug)  $display("@%10t : [%s] Read Done  [ADDR = %08X, DATA = %8X]", $time, 
                                module_id, useraddr, userdata);
       end
       else // !enable
       begin
          if(debug) $display("@%10t : [%s] Module disabled. Read Transaction NOT done. ", $time, 
                                       module_id);
       end
    end
    endtask


endmodule

And the C code (per original post):


#include <stdio.h>
#include "svdpi.h"

extern void axi_rd(int, int); // the SV function

void read_register(void)
{
  int axidata;
  axi_rd(0x0, axidata);
  printf("Data from C code: 0x%8X\r\n", (axidata >> 16) & 0xFFFE);
}

Finally the top level tb task simply calls bus_mstr_c.rreg. bus_mstr_c is the instance name of
the axi4lite_mst_c_skel module shown above.
bus_mstr is the instance name of the existing sv bfm I’ve used for some time. I am transitioning to c simulation for better SW/HW integration.

I am running the SV BFM (bus_mstr) side by side with the DPI-based system (bus_mstr_c) for functional comparison. Really trivial as I am reading one register once in a long while.


   always @(posedge busclk)
   begin
      if (init_done)
      begin
         bus_mstr.rd(`TEMP_REG, userdata32[0]);
	 bus_mstr_c.rreg;
	 $display("done");
	 
         if (userdata32[0] & 1'b1) begin	     
            $display("[%10t]: New reading: Temp: %04X", $time, userdata32[0][31:16] & 16'hFFFE);
         end
	 else begin
	    $display("[%10t]: Old reading: Temp: %04X", $time, userdata32[0][31:16] & 16'hFFFE);
	 end
	
         #50000000; // read every 50 us
      end
   end

Thanks,
Jerry

In reply to jolup:

By complete skeleton I meant something I could run, like this:

module axi4lite_mst_c_skel;
   export "DPI-C" task axi_rd;
 
   import "DPI-C" context task read_register();
 
   task rreg;
      read_register();
   endtask 

   task axi_rd(
    input int  useraddr,
    output int userdata);
      #10; //consume time
      userdata = $urandom;
      $display("@%10t :  Read Done  [ADDR = %08h, DATA = %8h]", $time, 
                                 useraddr, userdata);
   endtask : axi_rd
endmodule : axi4lite_mst_c_skel

module top;
   
   axi4lite_mst_c_skel bus_mstr_c();
   
      initial repeat(10)
	begin
	   bus_mstr_c.rreg;
  	   bus_mstr_c.read_register; // this works too - no wrapper needed
           #500; // read every 
	end
endmodule
#include < stdio.h>
#include "svdpi.h"
#include "axi_rd.h"
extern int axi_rd(int, int*); // the SV task
 
int read_register(void)
{
  int status;
  int axidata;
  status = axi_rd(0x0, &axidata);
  printf("Data from C code: 0x%8X\r\n",axidata);
  return status;
}

There were a few problems with your original C code.

The second argument to axi_rd is an output, so its type needs to be
int*
. The actual argument you need to pass is
&axidata
. Another issue is all DPI C tasks have an
int
return type. A non-zero return status indicated there was a disabled or killed process in the call chain.

You would have caught these problems with an automatically generated header file that gets #included into your C file. (vlog -dpiheader).

In reply to dave_59:

Dave,
Somehow didn’t see your last post. Filter perhaps. Didn’t know you meant model that can run would have gladly posted it.

Anyway worked on it for a bit today and got it. Yes one of the iterations of my code was axi_rd(int, int*) - jsut not the original try.

So I got it. Had to add svAckDisabledState and return 0.
The last set of issues

This is great - starting to use DPI for simulation the way it should be done in a SoC.

Thanks for your sage counsel.

The community really needs an Ashenden - level or Salemi - level book on SV. I love a good language book.

Best,
Jerry