Kelly Stevens Posted January 4, 2019 Report Share Posted January 4, 2019 Hello! My question is whether it is thread-safe to use multiple readers on a fifo. The "quick reference card" I found here indicates this is not allowed. However, I have a working code example with multiple readers that throws no errors and I haven't found any official documentation forbidding this. Here is my sample code that works. In a nutshell, all the consumers get a pointer directly to the fifo and no fifo port is used. #include <systemc.h> #include <iostream> class Consumer : public sc_module { public: sc_in<bool> clock_in; sc_fifo<int> *my_fifo; int id; SC_HAS_PROCESS(Consumer); Consumer(sc_module_name name) : sc_module(name) { // process registration SC_METHOD(readStuff); sensitive << clock_in; } void readStuff() { if (my_fifo->num_available() > 0) { int val = my_fifo->read(); cout << id << " reading val " << val << endl; } } }; class Producer : public sc_module { public: sc_clock clock; // submodules sc_vector<Consumer> consumers; // communications sc_fifo<int> my_fifo; // annotations SC_HAS_PROCESS(Producer); // constructor Producer(sc_module_name name) : sc_module(name), consumers("consumer", 5), clock("clock") { // process registration SC_THREAD(writeStuff); // submodules for (int i = 0; i < 5; i++) { consumers[i].my_fifo = &my_fifo; consumers[i].clock_in(clock); consumers[i].id = i; } } void writeStuff() { // write a bunch of numbers right away and then quit my_fifo.write(0); my_fifo.write(10); my_fifo.write(20); my_fifo.write(30); my_fifo.write(40); my_fifo.write(50); my_fifo.write(60); } }; int sc_main(int argc, char* argv[]) { Producer producer("producer"); srand(time(NULL)); sc_start(10, SC_NS); return 0; } I would like to know - is there a recommended design for a channel with multiple readers? Also, is there a recommended design for a channel with one reader and multiple writers? Quote Link to comment Share on other sites More sharing options...
David Black Posted January 4, 2019 Report Share Posted January 4, 2019 In general, your design would not work because reading a real hardware FIFO takes time and readers would get entangled. How would you manage that? Second, you are not using the correct methodology to connect up your FIFO, which is why you don't see the error. SystemC expects you to connect modules to channels using ports. Skipping the hierarchy and connecting consumers using pointers is incorrect. Instead, you consumer should replace your my_fifo pointer with a fifo port. You have two choices: sc_port< sc_fifo_in_if<int> my_fifo_port; or sc_fifo_in<int> my_fifo_port; The syntax to access the fifo will be my_fifo_port->read(). Next you will need to attempt to connect the port to the fifo during construction in your producer with: for( int i=0...4 ) consumer.port.bind( my_fifo ); When you run you will encounter an error during elaboration (construction) that you already have a connection and fifo ports don't allow more than one binding. swami-cst 1 Quote Link to comment Share on other sites More sharing options...
Roman Popov Posted January 5, 2019 Report Share Posted January 5, 2019 Quote Hello! My question is whether it is thread-safe to use multiple readers on a fifo. The "quick reference card" I found here indicates this is not allowed. However, I have a working code example with multiple readers that throws no errors and I haven't found any official documentation forbidding this. This is a common confusion, but in fact there is no real parallelism in SystemC. What is called SC_THREAD process in SystemC is commonly known as coroutine in software world (or fiber, if you are familiar with Windows API). So you can assume a SystemC model is a single-threaded application. Here is what SystemC standard says: 4.2.1.2 Evaluation phase Quote The scheduler is not pre-emptive. An application can assume that a method process will execute in its entirety without interruption, and a thread or clocked thread process will execute the code between two consecutive calls to function wait without interruption. Because the order in which processes are run within the evaluation phase is not under the control of the application, access to shared storage should be explicitly synchronized to avoid non-deterministic behavior. An implementation running on a machine that provides hardware support for concurrent processes may permit two or more processes to run concurrently, provided that the behavior appears identical to the coroutine semantics defined in this subclause. In other words, the implementation would be obliged to analyze any dependencies between processes and to constrain their execution to match the co-routine semantics. So: Co-routine semantics is guaranteed. "thread-safety" issues from parallel programming textbooks are not applicable in SystemC models, because they are single-threaded. Order of process evaluation in evaluation phase is not deterministic. In your case it means any of consumers can read from fifo first. So behavior of your model is non-deterministic. To make your model deterministic "access to shared storage should be explicitly synchronized ". Quote I would like to know - is there a recommended design for a channel with multiple readers? Also, is there a recommended design for a channel with one reader and multiple writers? There is no recommended way, it all depends on a concept you want to model. Do you want to broadcast a message to all readers? What do you want to do when a reader is not ready to accept a message? Or do you want only a single reader to receive a message? In that case, which one will have the priority? Quote Link to comment Share on other sites More sharing options...
David Black Posted January 7, 2019 Report Share Posted January 7, 2019 To answer the question of a fifo multiple writers and one reader, I would suggest either of two approaches: Create a module with a vector of inputs (1 per writer) and the sc_fifo output. A simple method process can then arbitrate and facilitate placing incoming data onto the output. This has the advantage of allowing a custom arbitration scheme Create an internal std::queue and limit access with a mutex (sc_mutex might work). Since the queue doesn't have a limitation, it will be up to you to manage maximum depth issues. Downside is that arbitration is managed by the mutex and may not be ideal. For a fifo with multiple readers, you probably need a manager to decide arbitrate requests, which is similar to 1 above. Kelly Stevens 1 Quote Link to comment Share on other sites More sharing options...
Kelly Stevens Posted January 7, 2019 Author Report Share Posted January 7, 2019 Roman, David, thank you for your responses and guidance. I'll add "coroutine" to my terminology stash. In answer to the question Roman asked at the end of his post, my use case involves a supervisor farming out job IDs to workers. It doesn't matter which worker does which job, so I was stuffing all the jobs into a fifo and letting chance decide who ends up with what. I agree with you all that switching to a deterministic approach is wise. I've rewritten my "write by one, read by many" example to respect ports and provide a manager that arbitrates requests (following along with David's suggestion #1). Here is that code. #include <systemc.h> #include <iostream> // define number of consumers #define N 5 class Consumer : public sc_module { public: sc_in<int> val_in; int id; SC_HAS_PROCESS(Consumer); Consumer(sc_module_name name) : sc_module(name) { // process registration SC_METHOD(readStuff); sensitive << val_in; dont_initialize(); } void readStuff() { int val = val_in; cout << "Consumer " << id << " reading val " << val_in << endl; } }; class Manager : public sc_module { public: sc_in<int> value_in; // one port for a writer to use sc_vector<sc_signal<int> > values_out_sig; // many signals for readers to use sc_fifo<int> fifo; SC_HAS_PROCESS(Manager); Manager(sc_module_name name) : sc_module(name), values_out_sig("values_out_sig", N) { SC_METHOD(manage); sensitive << value_in; curIdx = 0; } void manage() { int val = value_in; cout << "Manager received " << val << " and assigned to " << curIdx << endl; values_out_sig[curIdx%N] = val; curIdx++; // for now, just iterate through the consumers in order } private: int curIdx; }; class Producer : public sc_module { public: sc_vector<Consumer> consumers; Manager manager; sc_signal<int> signal; // for sending the int to the manager SC_HAS_PROCESS(Producer); Producer(sc_module_name name) : sc_module(name), consumers("consumers", N), manager("manager") { // process registration SC_THREAD(write); // submodules for (int i = 0; i < N; i++) { consumers[i].val_in(manager.values_out_sig[i]); consumers[i].id = i; } manager.value_in(signal); } void write() { for (int i = 0; i < 7; i++) { cout << "writing " << (i * 10) << endl; signal.write(i * 10); wait(SC_ZERO_TIME); } } }; int sc_main(int argc, char* argv[]) { Producer producer("producer"); srand(time(NULL)); sc_start(); return 0; } In the end I didn't need the fifo but that's okay. Thanks again everyone for your help. 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.