Signal written by more than one continuous statement

Hello,

I am in the process of porting a simulation from Vivado’s simulator (XSim if I am correct) to QuestaSim.
I am using Vivado 2017.2 and QuestaSim 10.5C.

The design uses SystemVerilog interfaces heavily, and the simulation code also uses classes defined within those to generate stimulus.
The system is based on the mechanism (polymorphism) outlined in this article:
http://verificationhorizons.verificationacademy.com/volume-7_issue-3/articles/stream/polymorphic-interfaces-an-alternative-for-systemverilog-interfaces_vh-v7-i3.pdf

The thing is the interfaces used can all be used to simulate the master and/or the slave of that interface. When simulating the master transactions can be issued by calling “read()” “write()” and when simulating the slave the interface emulates a “memory” and data can be retrieved by “read_mem()” and “write_mem()”.

Now the way I have made this works until now is by having a two variables inside the interface, called intf_is_master_bfm and intf_is_slave_bfm which are both initialized to zero. Then, in the begining of the simulation some of this variables might be setted throught setter functions. Then, inside the interface I have something like this:


	assign AWAddr	= intf_is_master_bfm	? awaddr	: 'z;
	assign AWId	= ((PROTOCOL == "AXI4LITE") || (WID_WIDTH == 0)) ? 1'b0 : intf_is_master_bfm ? awid	: 'z;
	assign AWLen	= intf_is_master_bfm	? awlen		: 'z;
	assign AWSize	= intf_is_master_bfm	? awsize	: 'z;
	assign AWBurst	= intf_is_master_bfm	? awburst	: 'z;
	assign AWLock	= intf_is_master_bfm	? awlock	: 'z;
	assign AWCache	= intf_is_master_bfm	? awcache	: 'z;
	assign AWProt	= intf_is_master_bfm	? awprot	: 'z;
	assign AWValid	= intf_is_master_bfm	? awvalid	: 'z;
	assign AWReady	= intf_is_slave_bfm	? awready	: 'z;
	(...)

With this I can ensure the interface ports are only driven from the simulation code if the appropriate variable is set. Note that I actually use the lowercase signals (awaddr, awlen etc) from the simulation code.

Now the problem is Vivado has never given me an error with this (note I assign a 'Z to the signal if the variable is not set). However, whith question I get the following errors:


#    Time: 0 ps  Iteration: 0  Instance: /.../i_Module/mmAxiIf File: X:/.../AxiMmIf.sv
# ** Error: (vsim-3837) X:/.../AxiMmIf.sv(599): Variable '/.../ModuleGen[1]/i_Module/mmAxiIf/AWLock' written by more than one continuous assignment. See X:/.../<some_file>.sv(139).
#    Time: 0 ps  Iteration: 0  Instance: /.../i_Module/mmAxiIf File: X:/.../AxiMmIf.sv
# ** Error: (vsim-3837) X:/.../AxiMmIf.sv(598): Variable '/.../ModuleGen[1]/i_Module/mmAxiIf/AWBurst' written by more than one continuous assignment. See X:/.../<some_file>.sv(138).

Basically an error is thrown for every signal of every interface, which is at least connected to actual logic signals at one end.

How could I solve this?

In reply to arquer:

Use wires instead of variables inside your interface.

In reply to dave_59:

We use abstract BFMs within our interfaces to connect up our designs to testsbenches as that paper suggests too. Going further, we also synthesize our interfaces within Vivado. So our solution must be compatible with Synthesis as well.

I’d thought I’d share how we solve this problem. Most of this was all based on things Dave Rich and Jonathan Bromley wrote about in the DVCon 2008 paper.

We define all our interface members as net types. When we first were playing around with them, we experimented with adding a real mux, similar to @arquer solution. However, Vivado chokes on any “active” logic within interfaces.

In the end, here’s how we solved it. All the code below is within the interface definition “axi_if.sv”. First declare all the interface members as net types (as Dave suggested):

  interface axi_if
#( ... )();
  wire awvalid;
  wire awready;
  wire [ `NON_NEG_MSB( CFG.ID_WIDTH ) : 0 ] awid;
  wire [ CFG.ADDR_WIDTH - 1 : 0 ] awaddr;
...

Then, to hookup to the testbench objects, we create “_tb” versions of all the signals again, but declared as variable types.


 //synthesis translate_off
  // create tb drivers
  bit awvalid_tb_driver;
  bit awready_tb_driver;
  bit [ `NON_NEG_MSB( CFG.ID_WIDTH ) : 0 ] awid_tb_driver;
  bit [ CFG.ADDR_WIDTH - 1 : 0 ] awaddr_tb_driver;

The _tb_driver signals are usually driven by class objects (defined in the interface) through a clocking block.

Then conditionally overdrive the DUT signals with testbench signals.

  always @*
  begin
    if( tb_master_driving )
    begin : proc_tb_drive_master
      force awvalid = awvalid_tb_driver;
      force awaddr = awaddr_tb_driver;
      force awid = awid_tb_driver;
      ...
    end
    else
    begin : proc_tb_release_master
      release awvalid;
      release awaddr;
      release awid;
      ...
    end
    if( tb_slave_driving )
    begin : proc_tb_drive_slave
      force awready = awready_tb_driver;
      ...
    end
    else
    begin : proc_tb_release_slave
      release awready;
      ...
    end
  end

The force/release was used so as to be able to “overdrive” active DUT connections when we instead desire the testbench to drive the interface signals. This way our testbench can dynamically control test/probe points without any changes to the DUT (and avoid using any klunky cross module references).

It is a bit repetitive to create the “normal” active interface members, and then also the tb versions to enable the classes to drive it. But in the end, worth it to use - we’ve got literally hundreds of AXI, and AXIS interfaces in our designs, and this little extra effort (done once in the shared axi_if.sv file) saves us tons of work and enables some advanced testbench environments.

Regards,

Mark