Jump to content
johnmac

Using struct with sc_inout causes broken simulations

Recommended Posts

I am using sc_inout<struct T> to emulate a handshake flow of 'ready' and 'valid'.
'ready' flows in Responder --> Initiator and 'valid' flows in Initiator --> Responder.

The setup doesnt work and causes the simulation to hang at 'Waiting'. I don't get a waveform because the simulation never completes. I suspect

Can someone tell me if there are things I am overlooking:

  • Can struct contain signals that flow in different directions? 
  • Can sc_inout<struct T> also map directly to another module with the same sc_inout<struct T>? 
  • Am I also setting the struct contents correctly?

I eventually want to expand this handshake to several more signals (20+ more) which will connect directly to another module. Any alternate suggestions will also help a lot.

#include <systemc.h>
#include <iostream>

struct hs {
  bool ready;
  bool valid;

  inline friend void SetValid(hs & pt, bool value) {
    pt.valid = value;
  }

  inline friend void SetReady(hs & pt, bool value) {
    pt.ready = value;
  }

  // inline friend bool GetReady(const hs& pt) {
  inline friend bool GetReady(const hs & pt) {
    return pt.ready;
  }

  inline friend bool GetValid(const hs & pt) {
    return pt.valid;
  }

  inline bool operator == (const hs & rhs) const {
    return false;
  }

  inline hs& operator = (const hs& rhs) {
    ready = rhs.ready;
    valid = rhs.valid;
    return *this;
  }

  inline friend void sc_trace(sc_trace_file *tf, const hs & v,
                              const std::string& NAME ) {
    sc_trace(tf,v.ready, NAME + ".ready");
    sc_trace(tf,v.valid, NAME + ".valid");
  }
  
  inline friend ostream& operator << ( ostream& os,  hs const & v ) {
    os << v.ready << " " << v.valid << endl;
    return os;
  }
};


SC_MODULE(initiator) {
  sc_in <bool> clk;
  sc_inout<hs> handshake;

  void func1();

  SC_CTOR(initiator) {
    SC_THREAD(func1);
    sensitive << clk;
    dont_initialize();
  };  
};

SC_MODULE(responder) {
  sc_in <bool> clk;
  sc_inout<hs> handshake;

  void func2();
  
  SC_CTOR(responder) {
    SC_THREAD(func2)
    sensitive << clk;
    dont_initialize();
  };
};

void initiator::func1()
{
  cout << "Initator started\n";

	SetValid((hs&)handshake, false);
	while(GetReady(handshake) == false)
    {
			cout << "Waiting" << endl;
      wait(clk->posedge_event());
    }
	SetValid((hs&)handshake, true);
  cout << "Initator valid set to HIGH\n";
}


void responder::func2()
{
  int sleep_i;
  SetReady((hs&)handshake, false);
  cout << "Responder started\n";
  for(sleep_i = 0; sleep_i < 40; sleep_i++)
    wait(clk->posedge_event());

  SetReady((hs&)handshake, true);  
}

The instantiation looks like this:

int sc_main(int argc, char* argv[]) {
  
  sc_clock clock("clock", 10, SC_NS);

	sc_signal<hs, SC_MANY_WRITERS> intf;

  initiator init("init");
  responder resp("resp");

  init.handshake(intf);
  resp.handshake(intf);

  init.clk (clock);
  resp.clk (clock);

  // Open a trace file
  sc_trace_file *fp;
  fp = sc_create_vcd_trace_file("wave");
  sc_trace(fp, intf, "intf");

  sc_start();
  sc_start(600, SC_NS);

  sc_close_vcd_trace_file(fp);
  cout << "Simulation finished @ " << sc_time_stamp() << endl;

  sc_stop();

  return 0;
};

 

Share this post


Link to post
Share on other sites

The problem is that you do not write to the signal carrying the struct (and therefore you do not trigger any event). The SetValid() call implicitly triggers a read (thru the overloaded cast) but you never write back to the signal. Either you change the use of SetValid (and SetReady):

{
	hs data = handshake.read();
	SetValid(hs, false);
	handshake.write(data);
}

Here the call to wriite() trigges the respective events.

The other option is to change SetValid() to take a signal reference and move the code into the function.

Best regards

Share this post


Link to post
Share on other sites

Actually, I forgot to answer your quesions, so here we go.

  • Can struct contain signals that flow in different directions? 
    Yes, of course. struct is just a a wrapper which handles datatype as one
  • Can sc_inout<struct T> also map directly to another module with the same sc_inout<struct T>? 
    Well, ports need to connect to signals, there is no way to connect 2 ports directly except you do a hierarchical binding
  • Am I also setting the struct contents correctly?
    This I answer in the last post.

Share this post


Link to post
Share on other sites

Thanks for the response @Eyck

I followed your suggestions, and did something like this. Pls bear with me if I am being thick ?

I am running into an error that looks like this:

		SystemC 2.3.1-Accellera --- Sep  7 2015 10:55:18
        Copyright (c) 1996-2014 by all Contributors,
        ALL RIGHTS RESERVED
Initator started
Waiting
Responder started

Error: (E115) sc_signal<T> cannot have more than one driver: 
 signal `signal_0' (sc_signal)
 first driver `init.func1'  (sc_thread_process)
 second driver `resp.func2' (sc_thread_process)
 first conflicting write in delta cycle 1
In file: ../../../../src/sysc/communication/sc_signal.cpp:73
In process: resp.func2 @ 0 s

I have the interfacing signal declared with SC_MANY_WRITERS.

	sc_signal<hs, SC_MANY_WRITERS> intf;
#include <systemc.h>
#include <iostream>

struct hs {
  bool ready;
  bool valid;

  inline friend void SetReady(hs & pt, bool value) {
    pt.ready = value;
  }

  inline friend void SetValid(hs & pt, bool value) {
    pt.valid = value;
  }

  inline bool operator == (const hs & rhs) const {
    return false;
  }

  inline hs& operator = (const hs& rhs) {
    ready = rhs.ready;
    valid = rhs.valid;
    return *this;
  }

  inline friend void sc_trace(sc_trace_file *tf, const hs & v,
                              const std::string& NAME ) {
    sc_trace(tf,v.ready, NAME + ".ready");
    sc_trace(tf,v.valid, NAME + ".valid");
  }
  
  inline friend ostream& operator << ( ostream& os,  hs const & v ) {
    os << v.ready << " " << v.valid << endl;
    return os;
  }
};


SC_MODULE(initiator) {
  sc_in <bool> clk;
  sc_inout<hs> handshake;

  void func1();

  SC_CTOR(initiator) {
    SC_THREAD(func1);
    sensitive << clk;
    dont_initialize();
  };  
};

SC_MODULE(responder) {
  sc_in <bool> clk;
	sc_inout<hs> handshake;

  void func2();
  
  SC_CTOR(responder) {
    SC_THREAD(func2)
    sensitive << clk;
    dont_initialize();
  };
};

void initiator::func1()
{
  cout << "Initator started\n";
	hs tmp_hs;

	tmp_hs = handshake;
	SetValid(tmp_hs, false);
	handshake = tmp_hs;
	while(true)
    {
			tmp_hs = handshake.read();
			if (tmp_hs.ready == false)
				{
					cout << "Waiting" << endl;
					wait(clk->posedge_event());
				}
			else
				{
					break;
				}
    }
	tmp_hs = handshake;
	SetValid(tmp_hs, true);
	handshake = tmp_hs;

	wait(clk->posedge_event());
  cout << "Initator valid set to HIGH\n";
}


void responder::func2()
{
  int sleep_i;
	hs tmp_hs;
  cout << "Responder started\n";	

	tmp_hs = handshake;
	SetReady(tmp_hs, false);
	handshake = tmp_hs;

  for(sleep_i = 0; sleep_i < 40; sleep_i++)
    wait(clk->posedge_event());
	
	tmp_hs = handshake;
	SetReady(tmp_hs, true);
	handshake = tmp_hs;
}

 

Share this post


Link to post
Share on other sites

Hi johnmac,

What you try to do is conceptually wrong: Ready and Valid should be two separate signals. Initiator drives the Valid signal, and responder drives the Ready.

There is an example of ready-valid channel that comes together with SystemC, check in systemc/examples/sysc/2.3/sc_rvd

If your final goal is synthesizable code,  then both Mentor and Cadence synthesis tools already have it implemented in their libraries. Check vendor documentation.

If you still want to simulate your design, you can try to use SC_UNCHECKED_WRITERS instead of SC_MANY_WRITERS. This will disable a check for multiple drivers in a single delta cycle.

Share this post


Link to post
Share on other sites
On 10/22/2018 at 10:42 AM, johnmac said:

Thanks for all the comments.

Will using a std::mutex lock() and unlock() around the critical section help when accessing the struct help ? 

SystemC is single threaded, you don't need std::mutex.  However SystemC does not guarantee any order for processes executed in the same delta cycle.  SystemC is a discrete-event simulator, and I suggest you to learn the concepts of simulation semantics from Paragraph 4 "Elaboration and simulation semantics" of IEEE-1666 SystemC standard.

The purpose of primitive channels, like sc_signal, is to remove non-determinism by separating channel update request from actual update of value into two different simulation phases. But it only works if there is a single writing process during a delta cycle. In your case if initiator and responder threads are executed on same cycle, they both will read the same "current value" of signal. Initiator will request to set signal value to (valid = 1, ready = 0) and responder will request to set it to (valid = 0, ready = 1).  Since there is no guarantee on order between processes, you will either get (1,0) or (0,1)  on the next cycle.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×