Jump to content

David Black

Members
  • Posts

    690
  • Joined

  • Last visited

  • Days Won

    154

Everything posted by David Black

  1. Assumption: TLM means TLM 2.0 Each system has at least one initiator (presumably the CPU). Initiators cannot talk directly to one another because that would be like saying function f1() can somehow cause function f2() to call f1() and interchange information. It just doesn't work. However, f1() can deposit data in an agreed upon shared data location, and f2() can then interrogate that memory location. Furthermore, in SystemC, the call of f1() can also invoke an sc_event to wake up the other process from sleep. There is a danger to be aware of depending on how the information is transferred. If the information is exchanged atomically, there is not problem, and this is easy to accomplish in SystemC modeling since it is currently a cooperative multi-tasking environment. However, if your channels simulate timewise transfer of data, then you may need to include a SystemC mutex. A trivial way of ensuring synchronization would be to simply use an sc_fifo or a tlm_fifo in the target object. Suggestion: Learn SystemC core concepts before learning TLM-2.0.
  2. Simple answer: there really is no straightforward way to do this. Of course if you wanted to get into the internals of the SystemC implementation, it is possible; however, that completely violates the standard. As to why this is not allowed: performance. The desire to have efficiency overrides the desire for corner cases such as yours. How can you work around this? Several possible approaches, but here one I would use: Create a new event class that adds tracking and an API to access it. This is easy to do if you know your C++ and SystemC well enough. Trivial outline of concept:: static const sc_core::sc_process_handle NONEXISTANT; struct Tracked_Event { void notify( void ); // sets m_process_handle and notifies m_event void notify( sc_core::sc_time delay ); // same except waits for delay sc_process_handle get_trigger( void ) { return m_process_handle; } void clear( void ) { m_process_handle = NONEXISTANT; }; private: sc_core::sc_event m_event; sc_core::sc_processhandle m_process_handle; }; struct Event_array { Event_array( int depth ); void wait( void ); // clears all m_process on entry, then waits for an m_event Tracked_event operator[]( int ); sc_vector<sc_core::sc_process_handle> get_trigger_list( void ); private: sc_vector<Tracked_Event> m_event_array; sc_core::uint64 m_delta_count; };
  3. SC_THREADs are simply threads that are sensitive to a clock. Instead of wait(10ns), you wait(2), which means to "wait 2 clocks". Does not work for SC_METHOD though (no equivalent).
  4. In LT with Temporal Delay, the initiators maintain a local variable (sometimes with the aid of the Quantum Keeper) that represents time. Although time is not as important, we don't want to throw it completely away. So each initiator keeps a variable that represents how far ahead of the simulator's notion of time it has progressed. You can represent execution of instructions by simply adding a time value to that variable. Targets want to know the current simulated (not simulator) time so they can respond appropriately. For example, a timer definitely needs a notion of time in order to timeout. So initiators pass their local notion of the "current time" to the target. If a target wants to represent the notion of a time change, it can add to that delay, which will be reflected in the initiator since the b_transport call passes it by reference. LT models don't completely avoid sc_core::wait(), but they do try to minimize it because context switches affect performance. Adding to a local variable doesn't have as much of a hit.
  5. I think there are a few more options, but first, you need to acutely aware of the issue of OS thread safety. The SystemC kernel is not generally thread-safe unless you use async_request_update(), and a queue. I've done this several times. That said you have several options: Place software inside a SystemC SC_THREAD and provide convenience methods to initiate TLM transport calls. This is the simplest SystemC approach, but does not allow for modeling the actual CPU planned to be used and hence timing is not very accurate. This has the fastest performance from the SystemC point of view. Place the software on a development board that has the target CPU and some type of communications to the machine where you will run SystemC. I generally use TCP/IP sockets. Replace the driver with a special socket call passing packets to to a remote machine and receiving back the response. On the SystemC side, create an OS thread to receive the socket and inject the message into SystemC via the async_request_update call and an unbounded STL queue or FIFO of some type. A TLM 1.0 FIFO might do. A receiving thread can then pass the data into the SystemC simulation and return the result. This is somewhat more overhead, but allows for a more accurate target CPU. Obtain an ISS (Instruction Set Simulator) for the target processor and interface it to SystemC. This varies in complexity. You might look at existing models or talk with vendors such as Cadence or Mentor. Or perhaps your CPU vendor (e.g. Arm has some very nice models). As @Eyck suggested QEMU, DBT-RISE-RISCV. Also Gem5 (Google it).
  6. Use SystemC 2.3 is your only easy option. I would expect much help from others to recreate what has already been done. You could also recast threads into cthreads (which has always worked).
  7. Caveat: That is the number of statically allocated processes. It does not account for dynamic threads or processes (i.e. sc_spawn'ed), which would be more difficult to track. Mind you, it is pretty complete and most likely static is what you are after.
  8. Just a few short thoughts: 1. If using SystemC or UVM, you should use restrict messages to the respective SC_REPORT_* or `uvm_report_* macros. These have standardized output formats, which makes them easier to use. 2. If using SystemVerilog without UVM, then use $info, $error, etc. for similar reasons 3. At the end of a simulation, you can detect how many errors etc. and use a return code. If would also suggest always printing a summary of the run (e.g. how many warnings and erros), and ALWAYS show one of two messages at the end: PASSED or FAILED. Makes life easier. My reason for the original logscan tool was situations where you don't have control over the error messages (e.g. certain EDA tools output).
  9. @kilroy369 I had forgotten that one. Much better suggestion. Same basic idea.
  10. I would do something I've previously done with SystemC (where uvm_error hails from). Basically, replaced the report handler with my own intercept to recategorize errors. Then added a mechanism to register "expected" errors, warnings, fatals. Prior to a point where an error was going to be injected, I would issue something like: begin_expect_error( "packet failure", 2 ); inject_error( ... ); wait_for_appropriate_time_or_event(); end_expect_error( "packet failure" ); If the report handler sees up to two errors, I would change the message severity to INFO and count off the expected error. At the end_expect_error call, I would check that exactly the number of expected errors occurred or consider it an error. Also, have to consider the possibility of the end_ call never happening.
  11. If your question on UVM is whether the repository is open to modifications, the answer is no. The UVM proof of concept library is carefully managed by Accellera as part of the standard's development. I cannot answer the question about test suites.
  12. Surprisingly, to me at least, a perl script I wrote in 1997 still appears to be popular. You can find it here: <https://github.com/dcblack/logscan/blob/master/README.md>.
  13. SystemC's implementation code is not generally an area we discuss except in the extremely rare instance we find a bug. Please read IEEE-1666 standards document (freely available via accellera.org). The behavior is correct.
  14. [Sorry for delay. For some reason I was not notified of your reply.] The point of my challenge was that you would not initialize the FIFO beyond allowing to come up empty. At least I have never see a FIFO that would be pre-filled. At start_of_simulation, you can initialize all you want as long as you can do everything within a single delta cycle. Hence you can write signals and fffos. Effectively, start_of_simulation is when activity is allowed to start happening.
  15. Based on the question, it seems to me that you need to take a class in C++. Anyhow, of course you cannot execute an object file. You need a fully linked executable. Files ending in .o are not executable because they do not contain fully resolved code. Linking is part of the process of compilation. If your code compiled, I expect there is another file either with no extension or perhaps with .x or .exe. In any event, you definitely should not ever have to add the execute privilege (chmod +x). In fact, that was a bold and dangerous move on your part.
  16. dynamic_cast to sc_module* would only give you access to methods of sc_module that were overridden in A. fun() and function() are not part of sc_module.
  17. Read up on sc_spawn and sc_process_handle. Basically, you can do something like: // Example of fork-join any std::vector<sc_process_handle> process_handles; process_handles.push_back( sc_spawn( [&](){ ... } ); //< repeat as needed ... sc_event_or_list terminated_events; for( auto& ph : process_handles ) { terminated_events |= ph.terminated_event(); } wait( terminated_events ); //< wait for any process to exit for( auto& ph : process_handles ) { ph.kill(); } // Example of fork-join none (void)spawn( [&](){ ... } ); //< repeat as needed ...
  18. 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.
  19. 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.
  20. Actually, I was very close to the solution you provided, and it provides some of the solution. The hierarchical is not a problem for my needs. I am having problems getting my TLM sockets to report properly. I only need the initiator sockets. Thank you.
  21. Note: This is somewhat of an expert question. I am considering how best to probe a SystemC design to determine connectivity. My goal is to be able to automatically determine how ports are connected at end-of-elaboration or later. My first inclination was to do something like: std::cout << "Connection from " << myport.name() << " to " << myport->name() << std::endl; Just to be clear, myport.name() provides the path to the port itself; whereas, myport->name() should provide the path to the channel that the port is connected to. However the above requires all channels and interfaces to implement the method name(), which is not currently the case. The implementation would simply be: const char* Channel::name( void ) const { return sc_object::name(); } I am first looking to see if anybody has a current solution that does not require modification of the standard. I don't think it exists, but I could be overlooking something.
  22. I would suggest that those for-loops need a better bound and should be coded: sc_assert( objA->initiator_socket.size() >= objB->target_socket.size() && objB->target_socket.size() > 0 ); for( int i=0;i<objA->initiator_socket.size(); ++i ) { objA->initiator_socket[i].bind(objB->target_socket[i]); } Coding rule: NEVER use a literal constant when a reasonable alternative is possible. Even when "in a hurry", you will not be disappointed if you use this rule.
  23. I would suggest that putting anything other than instantiation of a top level module is inappropriate. You should create a top level module with a thread to contain your behavior. As you are interested in communicating with SystemC from outside the simulator (i.e. from the shell), then you need to use thread-safe techniques. SystemC is not thread-safe without special precautions. You have two choices in this regard: Use polling inside your thread and use appropriate interprocess communications mechanisms (don't forget to use a mutex). Create a primitive channel using async_request_update and appropriate interprocess communications mechanisms to capture the data from outside. Neither approach is trivial.
  24. The 'initialize(T)' method is a leftover from SystemC 1.0 circa 1999, when SystemC had not yet properly abstracted the port/channel concept. At that point in time, there was a stronger emphasis on making SystemC look like Verilog or VHDL. The 'initialize(T)' method is only present on the 'sc_out<T>' and 'sc_inout<T>' port classes, as part of their partial template specialization. The 'initialize(T)' method is not generally available to 'sc_port<>'. I usually don't mention it because then the reader gets the wrong impression that 'initialize(T)' should be present everywhere. In fact, it is only useful for RTL aspects. Certainly, this is not part of TLM. Since SystemC is more about abstraction and modeling, I avoid it. It is straightforward to override start_of_simulation. @TRANGIt is important for you to understand this distinction. I realize that the specification may say that "port is initialized to zero" or some such, but the concept of port in the specification is quite different than the concept of port in SystemC. If you don't understand this, you will hobble your understanding of SystemC. So there are three ways in SystemC of modeling what the specification says regarding an output pin on a hardware design. Depend on the underlying datatype's initial value to initialize the signal (not very flexible) If using the specialized ports (sc_out and sc_inout only), call the initialize(T) method. Write to the port during start_of_simulation, which is the most general and powerful approach. Challenge: How would you initialize an sc_fifo< float > connected to an sc_fifo< float > channel with four values? #include <systemc> #include <list> #include <iostream> using namespace sc_core; using namespace std; SC_MODULE( Source ) { sc_fifo_out< float > send_port; SC_CTOR( Source ) { SC_THREAD( source_thread ); } void source_thread( void ) { wait( 10, SC_NS ); send_port->write( 99.7 ); wait( 10, SC_NS ); std::cout << "Finished" << std::endl; sc_stop(); } // How to initialize output to contain following initial values? // { 4.2, -8.3e9, 0.0, 3.14 } // Do not add this to the thread. Instead, ensure that it happens before any thread executes. } }; SC_MODULE( Sink ) { sc_fifo_in< float > receive_port; SC_CTOR( Sink ) { SC_THREAD( sink_thread ); }; void sink_thread( void ) { for(;;) { std::cout << "Received " << setw(12) << receive_port->read() << " at " << sc_time_stamp() << std::endl; } } }; SC_MODULE( Top ) { // Constructor SC_CTOR( Top ) { m_source.send_port .bind( m_fifo ); m_sink.receive_port.bind( m_fifo ); } // Local modules Source m_source { "source" }; Sink m_sink { "sink" }; // Local channels sc_fifo< float > m_fifo; }; int sc_main( int argc, char* argv[] ) { Top top { "top" }; sc_start(); return 0; } Key concepts: SystemC is a modeling language mapped on top of C++. SystemC ports are not signals or pins. sc_in<T>, sc_out<T> and sc_inout<T> are partial template specializations of sc_port<T> on the respective sc_signal<T> interface classes. For historic reasons (SystemC 1.0), there are extra methods added to these specializations including initialize(T), read(), and write(T) that can later confuse novice SystemC programmers.
  25. Ports do not hold values. Ports are simply references to channels. Ports provide a means to access a channel. sc_signal is simply a type of channel. This particular channel supports several methods including read() and write(), which are used to obtain (get) and alter (set) the state (attribute) of the channel. You can use these methods signal directly: type value; //< local storage ... signal.write( value ); ... value = signal.read(); Or if the channel is located outside your module, you should access the methods via port connections thus: output->write( value ); ... value = input->read(); Because the connectivity is not fully established until after elaboration is completed, you generally cannot provide an initial value during construction of an external channel from inside a module. Also, the signal method takes advantage of the update phase of the simulator and should only be accessed at start of simulation. Therefore, the proper way to initialize a "port" is to write to the corresponding signal a the start of simulation by overriding the so-called callback method start_of_simulation(): SC_MODULE( MyModule ) { ... void start_of_simulation( void ) override; }; Implementation: void MyModule::start_of_simulation( void ) { output->write( initial_value ); }
×
×
  • Create New...