whmmy Posted March 14 Report Share Posted March 14 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)? Quote Link to comment Share on other sites More sharing options...
Eyck Posted March 14 Report Share Posted March 14 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. Quote Link to comment Share on other sites More sharing options...
whmmy Posted March 15 Author Report Share Posted March 15 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. Quote Link to comment Share on other sites More sharing options...
Eyck Posted March 15 Report Share Posted March 15 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/ Quote Link to comment Share on other sites More sharing options...
whmmy Posted March 15 Author Report Share Posted March 15 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. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.