Jump to content

STL container issues in SystemC


whmmy

Recommended Posts

In a module of SystemC, I have a std::array, and there are two functions read() and write(). Every rising edge of clk, I run read to read array, run write to write array, these two functions are in SC_THREAD. However, since SystemC functions are executed in parallel in each cycle, in order to ensure that I can read the old array, I need to run the read function first, then wait(SC_ZERO_TIME), and then run the write function. Or, if I want to read the new value of the array, I need to run the write function first, then wait(SC_ZERO_TIME), and then run the read function. Here is my current implementation.

Here's a possible code:

struct my_struct
{
  bool ready;
  std::array<int, 4> data;
  my_struct(){};
  my_struct(bool ready_, std::array<int, 4> &data_) : ready(ready_), data(data_){};
  friend ostream &operator<<(ostream &os, my_struct const &v)
  {
    os << "(" << v.ready << ";" << v.data[0] << ","
       << v.data[1] << "," << v.data[2] << "," << v.data[3] << ")";
    return os;
  }
};
SC_MODULE(timing)
{
  sc_in_clk clk;
  std::array<my_struct, 10> my_array;
  void write()
  {
    int index = 0;
    std::array<int, 4> write_data;
    while (true)
    {
      wait(clk.posedge_event());
      wait(SC_ZERO_TIME); // wait for read_oldvalue() to execute
      write_data.fill(index);
      my_array[index % 10] = my_struct(index % 2, write_data);
      index++;
    }
  }
  void read_oldvalue()
  {
    while (true)
    {
      wait(clk.posedge_event());
      for (int idx = 0; idx < 10; idx++)
      {
        cout << my_array[idx];
      }
      cout << endl;
    }
  }
  void read_newvalue()
  {
    while (true)
    {
      wait(clk.posedge_event());
      wait(SC_ZERO_TIME); // wait for read_oldvalue() to execute
      wait(SC_ZERO_TIME); // wait for write() to execute
      for (int idx = 0; idx < 10; idx++)
      {
        cout << my_array[idx];
      }
      cout << endl;
    }
  }
  SC_CTOR(timing)
  {
    SC_THREAD(write);
    SC_THREAD(read_oldvalue);
    SC_THREAD(read_newvalue);
  }
};


I think frequent use of wait(SC_ZERO_TIME) will cause performance degradation and violate the original intention of SystemC. In the two cases of read_oldvalue and read_newvalue, is there any way to avoid me using wait(SC_ZERO_TIME)?

Link to comment
Share on other sites

You try to make the invocation of functions sequential by abusing delta cycles. This usually leads to bad simulation performance.

To ensure deterministic simulation there is the concept of primitive channels or signals (which is an implementation of a primitive channel).

I would do an implementation similar to the following (untested):

SC_MODULE(timing)
{
  sc_in_clk clk;
  sc_core::sc_vector<sc_core::sc_signal<my_struct>>> my_array{"my_array", 10};
  int index = 0;
  void write()
  {
  	  std::array<int, 4> write_data;
      write_data.fill(index);
      my_array[index % 10].write(my_struct(index % 2, write_data));
      index++;
  }
  void read_oldvalue()
  {
      for (int idx = 0; idx < 10; idx++)
      {
        cout << my_array[idx];
      }
      cout << endl;
  }
  void read_newvalue()
  {
      for (int idx = 0; idx < 10; idx++)
      {
        cout << my_array[idx];
      }
      cout << endl;
  }
  SC_CTOR(timing)
  {
    SC_METHOD(write);
    dont_initialize();
    sensitive<<clk.pos();
    SC_MEHTOD(read_oldvalue);
    sensitive<<clk.pos();
    SC_METHOD(read_newvalue);
    for(auto& s: my_value)
      sensitive<<s;
  }
};

This simplifies your design and read_newvalue() is only called if something changes. Since you did not write about your intend I cannot judge I cannot judge if this makes sense.

Another option would be to instantiate a sc_event being triggered by the write function and make the read_newvalue() sensitive to this event.

 

Link to comment
Share on other sites

10 hours ago, Eyck said:

You try to make the invocation of functions sequential by abusing delta cycles. This usually leads to bad simulation performance.

To ensure deterministic simulation there is the concept of primitive channels or signals (which is an implementation of a primitive channel).

I would do an implementation similar to the following (untested):

SC_MODULE(timing)
{
  sc_in_clk clk;
  sc_core::sc_vector<sc_core::sc_signal<my_struct>>> my_array{"my_array", 10};
  int index = 0;
  void write()
  {
  	  std::array<int, 4> write_data;
      write_data.fill(index);
      my_array[index % 10].write(my_struct(index % 2, write_data));
      index++;
  }
  void read_oldvalue()
  {
      for (int idx = 0; idx < 10; idx++)
      {
        cout << my_array[idx];
      }
      cout << endl;
  }
  void read_newvalue()
  {
      for (int idx = 0; idx < 10; idx++)
      {
        cout << my_array[idx];
      }
      cout << endl;
  }
  SC_CTOR(timing)
  {
    SC_METHOD(write);
    dont_initialize();
    sensitive<<clk.pos();
    SC_MEHTOD(read_oldvalue);
    sensitive<<clk.pos();
    SC_METHOD(read_newvalue);
    for(auto& s: my_value)
      sensitive<<s;
  }
};

This simplifies your design and read_newvalue() is only called if something changes. Since you did not write about your intend I cannot judge I cannot judge if this makes sense.

Another option would be to instantiate a sc_event being triggered by the write function and make the read_newvalue() sensitive to this event.

 

Thank you for your answer, this solved my problem and allowed me to connect to the systemc syntax I learned in books before.
Now I have a few questions:
1. If I want to define a matrix, should I use sc_vector<sc_vector>, or sc_vector<std::array>? At this time, what should be changed to the combinatorial logic sensitive list "for(auto& s: my_array) sensitive<<s;"?
2. My functional requirement is actually to complete the write function (it may contain many checks and write operations) on the rising edge of each clock cycle, and then run read_newvalue (also every cycle). I think what you said about using sc_event is more suitable for this situation. I can write like this:

void write(){
     //.....
     my_event.notify();
}
void read_newvalue(){
     while(true){
     wait(my_event);
     // ....
}
}


But this will cause a problem, that is, when my_event is notified, the read_newvalue function will be added to the running list of the current time stamp. At this point, is the value in sc_vector still the old value (Due to the characteristics of systemc, sc_vector will be updated to a new value at the next time stamp)? If so, maybe I should use my_event.notify(SC_ZERO_TIME), which seems the same as wait(SC_ZERO_TIME).
Thanks again for your help.

Link to comment
Share on other sites

Wrt. to you first question there is  a clear 'it depends'. If you have an array of sc_objects (basically SystemC named things like modules, ports, signals, channels, and alike as well as sc_vector) you should use sc_vector. Otherwise stick with STL container types.

Wrt. your second question: your example is correct but your interpretation not: SystemC executes as mayn as needed delta cycles at the same time stamp. So the read_oldvalue() and the write() function are evaluated in the first delta cycle of the current time stamp. Then the signals are written and the event is notified so that read_newvalue() is evaluated in the second delta cycle at the current time stamp. Between the 2 delta cycles the SystemC kernel runs the update phase so that signals get their new values. Therefore read_newvalues() will read the new values as it is scheduled after the update phase.

I seriously recommend to read the LRM, some text book (e.g. @David Blacks 'SystemC from the ground up') or take some (online) tutorials like https://fpgatutorial.com/systemc-scheduler/ or https://learnsystemc.com/

Link to comment
Share on other sites

On 3/15/2023 at 7:11 PM, Eyck said:

Wrt. to you first question there is  a clear 'it depends'. If you have an array of sc_objects (basically SystemC named things like modules, ports, signals, channels, and alike as well as sc_vector) you should use sc_vector. Otherwise stick with STL container types.

Wrt. your second question: your example is correct but your interpretation not: SystemC executes as mayn as needed delta cycles at the same time stamp. So the read_oldvalue() and the write() function are evaluated in the first delta cycle of the current time stamp. Then the signals are written and the event is notified so that read_newvalue() is evaluated in the second delta cycle at the current time stamp. Between the 2 delta cycles the SystemC kernel runs the update phase so that signals get their new values. Therefore read_newvalues() will read the new values as it is scheduled after the update phase.

I seriously recommend to read the LRM, some text book (e.g. @David Blacks 'SystemC from the ground up') or take some (online) tutorials like https://fpgatutorial.com/systemc-scheduler/ or https://learnsystemc.com/

Thank you for your answer. I think if I use systemc type, such as sc_signal, I have to go to the next delta cycle. If I don't use the systemc type, I can directly use sc_event to sort the order of function execution. 

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...