Jump to content

Leaderboard


Popular Content

Showing content with the highest reputation since 08/23/2018 in all areas

  1. 2 points
    The problem is, when you integrate RTL IP into Loosely-Timed VP that way, the whole simulator will have a performance of cycle-accurate model. Because clock generator will be always on, and Verilated model will be triggered even if it is idle. So don't try to boot Linux with such a simulator. If your RTL IP supports power gating or clock gating, it is a good idea to disable clock generation when RTL IP is turned off. In that case you don't pay for what you don't use: you can boot your OS quickly and then enable clock generator when you start to debug IP-specific driver code.
  2. 2 points
    Hello @kallooran, What version of SystemC library are you using? This issue has been fixed in the release of SystemC-2.3.2. You can find the latest release of SystemC-2.3.3 here: http://accellera.org/downloads/standards/systemc Hope it helps. Regards, Ameya Vikram Singh
  3. 2 points
    David Black

    Seeking Feedback on Datatypes

    Actually, it adds a lot of value. std::array can be passed by reference in a function call and the function can then determine the proper size of the array. This is much better than passing pointers, the C standard. You can also copy an array, which should be synthesizable, which reduces coding and greatly improves readability. It should be possible to implement some #include <algorithm>s on std::array too. Also, you can have bounds checking for additional safety; although, that aspect is probably not synthesizable. Additionally, constexpr should be quite helpful for the synthesis aspect.
  4. 2 points
    Hi, I'm not an implementer of the reference simulator but as far as I can judge the re-throw is used to find a more specific type of exception (since sc_elab_and_sim() just uses a catch-all) and uses sc_handle_exception() to convert it into an sc_report so it can be handled by the SystemC reproting system. Actually I agree it would be better to handle it directly in sc_elab_and_sim() but this would duplicate code. A side note rgd. debugging: if you use gdb there is a command 'catch throw' which stops execution right at the point where the (original) exception is thrown. This comes pretty handy in such cases. Best regards
  5. 1 point
    Lukas Jakober

    Seeking Feedback on Datatypes

    Some feedback on AC datatypes: We have found AC datatypes to be an invaluable tool for large DSP ASIC designs. My experience is mostly on the simulation front where speed, bit- (but not necessarily cycle-) accuracy, and seamless integration with standard C++ code is important. AC datatypes provide all the relevant digital system design features (clipping/rounding/bit-slicing), as well as support for complex data types, with very little overhead. The ability to customize/expand AC datatype functionality via private headers (for simulation only) is another great benefit that minimizes the need for instrumentation code around HLS code during system simulations.
  6. 1 point
    Eyck

    Read in customized structs

    Where did you check the values of p1 and p2? write() only schedules the values to be written, you will see the actual value in the next delta cycle. Best regards
  7. 1 point
    Philipp A Hartmann

    tlm_socket_base_if

    Hi Guillaume, I agree, that the new pure-virtual functions in tlm_base_(initiator/target)_socket are not compliant to IEEE 1666-2011. However, I'm curious what use case you have to use these classes directly instead of inheriting from tlm_(initiator/target)_socket, where the functions are implemented? Regarding the implementation on the base socket level, I suggest to add a typedef to the fw/bw interface classes, and use these typedefs in the socket base class then. Something like: template <typename TYPES = tlm_base_protocol_types> class tlm_fw_transport_if // ... { public: typedef TYPES protocol_types; }; // tlm_base_target_socket virtual sc_core::sc_type_index get_protocol_types() const { return typeid(typename FW_IF::protocol_types); } Theoretically, these types could mismatch between FW_IF and BW_IF in manual socket implementations. Therefore, I'd suggest to refer to the FW_IF in the target and BW_IF in the initiator socket. Greetings from Duisburg, Philipp
  8. 1 point
    maehne

    Direct Digital Synthesis

    As you can see from the DDS block diagram, which you posted, the structure of DDS block is quite simple. I therefore would model it as a single TDF module containing the accumulator register as a state variable. As input, you will have your frequency control and phase offset control signals. The frequency control signal basically is the value, which you continuously add to the value in your accumulator register to generate a periodic sawtooth signal (due to the overflow of the accumulator). The phase offset control signal gets added to the accumulator register to enable shifting the phase of the sawtooth signal. This sawtooth signal is interpreted as the angle operand of a sine function. The period of the sawtooth signal represents a full revolution on the phase circle, i.e., the 2^L possible sawtooth signal values are evenly mapped to phase angles in the range of 0 rad to 2 pi rad (0° to 360°). To avoid repeated calculations of the sine function, the sine amplitudes for all possible angles are pre-calculated and stored in a look-up table (LUT). The current value of the sawtooth signal is then used as the index into that look-up table to find the corresponding output amplitude. To implement this in a generic DDS TDF module, I would choose M, L, and K (bit widths) as generic parameters, which can be passed upon DDS module instantiation to the DDS module's constructor. From M and L, you can calculate the value for the modulo operation, which models in software your overflowing of a M-bits-wide and L-bits-wide addition operation. The difference of M and L gives you the shift distance needed to implement your accumulator output quantisation. The sine LUT needs to have 2^L entries. The sine function should be scaled by 2^(k-1)-1 to use the full range of a k bit wide signed output. All these preparatory calculations, you can do in your DDS module's constructor. The processing function then only needs to only implement the two additions and the amplitude look-up based on the current values of the frequency and phase control inputs and the current value stored in the accumulator register. If you plan to use this kind of module in a model of communication system, you might consider having a look to the Vienna SystemC AMS Building Block Library, which is available for download from systems-ams.org. The latter web site contains also other useful SystemC AMS resources.
  9. 1 point
    Roman Popov

    using gtkwave

    I don't work on Windows. But as far as I remember executable should be somewhere in project sub-directory called "Release" or "Debug". Sorry, can't help you more here.
  10. 1 point
    Roman Popov

    using gtkwave

    In the working directory (active directory when you launch the executable). Search for "and_gate.vcd"
  11. 1 point
    Eyck

    using gtkwave

    Actually SystemC provided sc_trace(...) functions where you register your signals and variables for tracing. Running the simulation yields a .vcd file which you can open in gtkwave. You may have a look into https://github.com/Minres/SystemC-Components-Test/blob/master/examples/transaction_recording/scv_tr_recording_example.cpp In sc_main() you will find sc_trace_file *tf = sc_create_vcd_trace_file("my_db"); sc_trace_file *tf = sc_create_vcd_trace_file("my_db"); This opens the waveform database. At the end you have to call sc_close_vcd_trace_file(tf); to properly close the database. 'tf' is a handle to the database, if you follow the code you will see how to trace signals (or variables), Best
  12. 1 point
    AmeyaVS

    Behavioral XOR Gate with Delay

    Hello @re1418ma, You can look at this example: http://forums.accellera.org/topic/5678-clock-to-q-propagation-delay/?do=findComment&comment=13657 Or this one which shows how to add delay in full adder: http://forums.accellera.org/topic/5715-delaying-simulated-execution/?do=findComment&comment=13844 Hope it helps. Regards, Ameya Vikram Singh
  13. 1 point
    Hello everyone, first of all I apologize if the post is too big and I know sometimes people get discouraged to read big posts. On the other hand I spent quite some time trying to make the post as clear as possible for the reader. So please do not get discouraged :). My SystemC version: SystemC 2.3.1 My operating System: Ubuntu 16.04 I am trying to understand how SystemC simulator works, and for that I ran the following code: SC_MODULE(EventNotifications) { sc_event evt1; void p1() { evt1.notify(10, SC_NS); evt1.notify(5, SC_NS); evt1.notify(); evt1.notify(0, SC_NS); wait(10, SC_NS); evt1.notify(5, SC_NS); wait(10, SC_NS); } void p2() { wait(10, SC_NS); evt1.cancel(); evt1.notify(); wait(10,SC_NS); } void p3() { cout << "evt1 is activated at " << sc_time_stamp() << endl; } SC_CTOR(EventNotifications){ SC_THREAD(p1); SC_THREAD(p2); SC_METHOD(p3); sensitive << evt1; dont_initialize(); } }; I referred to the SystemC Language Reference Manual 2.0.1: http://homes.di.unimi.it/~pedersini/AD/SystemC_v201_LRM.pdf and in my explanation down below, I used the following abbreviations: R = {} - list of runnable processes, D = {} - list of processes that have been notified using delta notifications, T = {} - list of processes where timed notification has been used e.g. event.notify( >0, SC_NS) 2.4.1 Scheduler Steps at page 6 from the SystemC Language Reference Manual 2.0.1 was used for the following reasoning of how this code works: Initialize phase: We initialize all the processes that are ready to run. Process p3 at the beginning will not be runnable due to dont_initialize command. So only p1 and p2 processes runnable, as a result R = {p1, p2} at the end of initialize phase. Now we go to the evaluation phase. We start with simulation time equal to 0ns. Simulation time = 0 ns Evaluation phase (delta cycle = 0): We have at the beginning R = {p1, p2}, D = {} (empty list) and T = {}. Let's say scheduler decides to execute p1 first, so p1 gets removed from R, effectively R = {p2}. Then we execute 1st timed notifications evt1.notify(10, SC_NS), after that we have evt1.notify(5, SC_NS), since 5ns is earlier than 10ns, only 5ns will be remembered so we have T = {p3}. Next statement is evt1.notify() which is immediate notification, and will overwrite the previous notification evt1.notify(5, SC_NS). Immediate notification is put into the list p3, R ={p3}, and T = {}. Next statement is evt1.notify(0, SC_NS), so p3 will be put in the list D. So now we have R = {p3}, D = {p3}, T ={}. Question 1: if I swapped two statements evt1.notify(0, SC_NS) and evt1.notify() here, will the delta notification will be removed? In my opinion only evt1.nofity() will be remembered: From page 128 of the manual: "A given sc_event object can have at most one pending notification at any point. If multiple notifications are made to an event that would violate this rule, the “earliest notification wins” rule is applied to determine which notification is discarded." As a result I would have R = {p3, p2}, D = {}, T = {}. Now we encounter the wait(10, SC_NS) and p1 is put to wait. Question 2: Since I we have wait(10, SC_NS), does that mean that the process p3 will be put in separate list/queue of sleep processes? Let's call it list S, so we would have S = {p1} effectively? Next let's say scheduler decides to run p2, so we remove p2 from the R list and we have R = {p3}. There we encounter wait(10, SC_NS), and p2 gets into S list, S = {p1, p2}. Now we have R = {p3} and p3 gets executed, so immediate notification gets executed at simulation time 0 ns as 1st console output indicates. Now method p3 exits, and list R is empty, R = {}, so we go to update phase. Update phase (delta cycle = 0): Nothing to be updated. We just go to the next delta cycle phase. Next delta cycle phase(delta cycle = 0): We increment delta cycle, and check all the contents of list D and put them in the list R. In our case D = {p3}, thus R = {p3}. Now we go back to evaluation phase. Evaluation phase(delta cycle = 1): We run only p3, here so the delta notification happens at simulation time 0 ns + 1 delta cycle. R = {}, we go to the update phase. Update phase (delta cycle = 1): Nothing to be updated. Go to next delta cycle phase. Next delta cycle phase(delta cycle = 1): We increment delta cycle, but since D = {}, we go to the increase simulation time phase. Increase simulation time phase (delta cycle = 2): From the page 6: "If there are no more timed event notifications, the simulation is finished. Else, advance the current simulation time to the time of the earliest (next) pending timed event notification." Now back to my Question 2, since we T = {}, that would mean that we have no timed event notifications, and based on reference manual simulation should be finished, which is not the case here if you run the code. So from my understanding, the processes that were called with wait operation, will not be put in the "list of sleep processes" but instead they will be put to either list T or D. I think in case wait(>0, SC_NS), process gets put into the T list, and if wait(0, SC_NS) is called process should be put into the D list. So in our case T = {p1, p2}? We increase simulation time to the earliest 10 ns, and contents of list T = {p1, p2} are put into the list R = {p1, p2}. and we go to the next evaluation phase. Simulation time = 10 ns: Evaluation phase (delta cycle = 0): Here we can either run p1 or p2. Let's say p2 is run first, and we encounter evt1.cancel(), since there are no pending events nothing will happen. Then we execute evt1.notify(), and p3 gets into the list R, so R = {p1, p3}. Then wait encountered, so T = {p2}. Now let's say scheduler decides to execute p3, and then immediate notification happens at simulation time of 10 ns. Now R = {p1}, so p1 gets executed and there we have evt1.notify(5, SC_NS), so p3 gets into the list T = {p2, p3}. Then we execute wait(10, SC_NS), and p1 sleeps again. So T = {p2, p3, p1}. Since R = {0}, we go to update phase. Update phase (delta cycle = 0): Nothing to be updated, so we go to the next phase. Next delta cycle phase (delta cycle = 0): We increment delta cycle. Nothing in list D, so we go to the next phase. Increase simulation time phase (delta cycle = 1): We put contents of T into R, thus R = {p2, p3, p1}, and we increment time to the earliest pending notification, since we had evt1.notify(5, SC_NS) and threads slept for 10 ns, we chose 5 ns. So simulated time is increased to 15 ns. We again go to the evaluation phase. Simulation time = 15 ns: Evaluation phase (delta cycle = 0): Here R = {p2, p3, p1}, so let's say we execute p3 first, as result timed notification evt1.notify(5, SC_NS), happens at simulated time 15 ns. Now R = {p2, p1}, and p1 executes, since nothing after last wait statement thread terminates. Same situation for p2, so p2 terminates. R ={} go to next phase. Update phase (delta cycle = 0): Go to next phase. Next delta cycle phase (delta cycle = 0): Delta cycle updates, since D = {}, we go to the next phase Increase simulation time phase (delta cycle = 1): Since T = {}, nothing to be simulated and simulation ends. So this would explain the following result I got outputted on the console: evt1 is activated at 0 s evt1 is activated at 0 s evt1 is activated at 5 ns evt1 is activated at 10 ns I tried to check my assumption that when wait(0, SC_NS) gets called in some process, the process will be put in the D list. So I ran this code: SC_MODULE(DeltaTimeWait) { void p1(void) { while (true) { cout << "p1: " << sc_time_stamp() << endl; wait(0, SC_NS); } } void p2(void) { while (true) { cout << "p2: " << sc_time_stamp() << endl; wait(0, SC_NS); } } SC_CTOR(DeltaTimeWait) { SC_THREAD(p1); SC_THREAD(p2); } }; There is also one thing I noticed. For example if I change the order of the thread registration in the constructor of the 1st code, and having SC_THREAD(p2) before the SC_THREAD(p1), I get different result. SC_CTOR(Task5_d){ SC_THREAD(p2); SC_THREAD(p1); SC_METHOD(p3); sensitive << evt1; dont_initialize(); } I get the following result: evt1 is activated at 0 s evt1 is activated at 0 s evt1 is activated at 10 ns I am not sure if my reasoning for this result is correct. So I think that we get his output due to reason that at the point where simulation time was 10 ns, we had two choices, we could either schedule p1 or p2 first. Simulation time = 10 ns: Evaluation phase (delta cycle = 0): At this point as I have mentioned earlier, we can either run p1 or p2 first. And in the first case we assumed p2 was run first. But if we assume now p1 will be run 1st instead of p2. So now p1 gets executed and statement evt1.notfiy(5, SC_NS) is encountered. As a result, process p1 gets into the list T and then we sleep the process. Now the process p2 gets scheduled, and the 1st line we encounter is evt1.cancel(), which as result would cancel pending notification evt1.notfiy(5, SC_NS) from process p1. After that evt1.notify() is executed which results p3 getting into R list. So p3 being only in the list R, we execute process p3, and evt1 is notified at simulation of time 10 ns. Question 3: How come that order of thread registration actually affects the order of the process being scheduled? I am not sure if my reasoning is correct, so I would appreciate your feedback, as I am only a beginner in SystemC. Looking forward to your feedback. Ivan.
  14. 1 point
    Hi Ivan, instead of referring to the very old 2.01. LRM, I suggest to check the IEEE Std. 1666-2011 for SystemC, which could can download at no cost (sponsored by Accellera) via https://standards.ieee.org/findstds/standard/1666-2011.html. This document includes the normative answers to all of your questions. Yes, see section 5.10.8 of the aforementioned standard. Kind of, yes. This is called "time out", see section 4.2(.1) of the standard. The order to execution of processes in the runnable queue is entirely implementation-defined. See section 4.2.1.2. Hope that helps, Philipp Disclaimer: I haven't checked all of your post for correctness and focused on the questions instead. .
  15. 1 point
    Martin Barnasconi

    Error: System not scheduable

    It looks like you have a multi-rate system, i.e. somewhere you defined a <port>.set_rate(..) in a set_attributes callback. Now you try to access the n-th sample at this port, like <port>.read(<sample>), but the nth sample is higher than the rate specified. This means you have either the wrong rate, or reading a sample outside the range defined by the rate.
  16. 1 point
    Uwghiello

    Install SystemC on Visual Studio 2017

    I am trying to install systemC on visual studio 2017 but it seems that I cannot find the guide for it. So I had to follow the guide of installing systemC on VS2010 :( In fact, I have got systemC.lib but when I configured the project for systemC, i.e, the project->property->C/C++->Code Generation->Runtime Library->balabalabala... finished all of them and build solutions, there are some errors. So...what should I do? Totally confused because I know nothing about VS =_= PS: the guide is here
  17. 1 point
    David Black

    Scope of SC_HAS_PROCESS

    Personally, I prefer to put SC_HAS_PROCESS directly into the constructor body, which works very reliably. All that SC_HAS_PROCESS does is to create a typedef called SC_CURRENT_USER_MODULE which refers to .... the current module name. It is only used in the three macros SC_THREAD, SC_METHOD and SC_CTHREAD. Syntactically, C++ allows a typedef to be created within a function (in this case the constructor function). Advantages of this approach: It's defined very close to the point of usage. The user of a header file, does not need to see it. /.h #import <systemc> // I never use systemc.h ... class Blah: sc_module { Blah(sc_module_name); ~Blah(); } //.cpp file #include "Blah.h" using namespace sc_core; Blah::Blah(sc_module_name nm): sc_module(nm) { SC_HAS_PROCESS(Blah); SC_THREAD(blah_thread); ... } Blah::~Blah(){ }
  18. 1 point
    David Black

    approximately timed

    IEEE 1666-2011 section 10.2 states: IEEE 1666-2011 section 10.3.4 states:
  19. 1 point
    HI Can any one provide some example for how to get virtual interface in sequence? I need to use the clk in sequece . Thanks praneeth
  20. 1 point
    Hi. This is because an unbound port cannot be read. A port forwards all read and write calls to the actual interface (signal) it is bound to. In you module constructor, you are still in the model set up and elaboration phase. The port is not yet bound to any signal. Hence, you cannot read from it. Accessing ports should not be done befor end-of-elaboration. Greetings Ralph
  21. 1 point
    There have been some improvements to the performance of the field automation macros, but I still do not believe their benefit is worth the cost. You should be able to prove it to yourself by creating a simple testbench with and without the macros.
  22. 1 point
    Actually, you can start a sequence in any phase. It is more important to understand the domain/scheduling relationships between the task based (i.e. runtime) phases. UVM undergoes a number of pre-simulation phases (build, connect, end_of_elaboration, start_of_simulation) that are all implemented with functions. Once those are completed, the task based phases begin. The standard includes two schedules. One is simply the run_phase, which starts executing at time zero and continues until all components have dropped their objections within the run_phase. The other schedule contains twelve phases that execute parallel to the run phase. They are: pre_reset, reset, post_reset, pre_config, config, post_config, pre_main, main, post_main, pre_shutdown, shutdown, and post_shutdown. They execute in sequence. Every component has the opportunity to define or not define tasks to execute these phases. A phase starts only when all components in the previous phase have dropped their objections. A phase continues to execute until all components have dropped their objections in the current phase. Many companies use the run_phase for everything because there are some interesting issues to consider when crossing phase boundaries. In some respects it may be easier to use uvm_barriers for synchronization. Drivers and monitors (things that touch the hardware) are usally run exclusively in the run_phase, but there is nothing to prevent them also having reset_phase, main_phase, etc...
  23. 1 point
    An other way to understand this is to look at SystemC/TLM as pure C++ code and see what is going on.. Let us assume that you have two classes, "Initiator" and "Target", that need to communicate with each other. Target implements a function called "transport" to be called by Initiator, and Initiator implements a function called "response" - that is called by target. For the sake of the explanation here, the payload itself is not important, which we will assume is plain "int" in both directions. You would normally do this as following: // Interface classes: class initiator_if { public: void response(int i) = 0; }; class target_if { public: void transport(int i) = 0; }; class Initiator : public initiator_if { public: // Implement the interface void response(int i) { cout << "Got: " << i << " from target" << endl; } void bind(target_if &_if) { // Store the pointer locally m_target = &_if; } }; class Target : public target_if { public: // Implement the interface void transport(int i) { cout << "Got: " << i << " from initiator " << endl; } void bind(initiator_if &_if) { // Store the pointer locally m_initiator = &_if; } }; Next we instantiate the objects and "bind" them: Initiator initiator; Target target; initiator.bind(target); target.bind(initiator); I hope you are seeing where we are going with this. What has been done above is a crude equivalent of sc_port binding. There is one problem however with the approach above. What if the class "Target" doesn't implement the target_if directly? Like so: class TargetGrandChild : public target_if { public: void transport(int i) { // Implement the interface.. cout << "Got " << i << " from initiator (in grand child)" << std::endl; } }; class TargetChild { public: TargetGrandChild gch; /* Other stuff.. */ }; class Target { public: TargetChild ch; /* Other stuff.. */ }; One way to deal with this is to change the way bind function is called: initiator.bind(target.ch.gch); This is ugly. Firstly, it makes the client of bind function dependent on an internal design aspect of the "Target" class. For example, if the Target class changes tomorrow (to let the TargetChild implement the "target_if" directly), the bind function call needs to change. Also, to allow the client to call the bind function as above, the elements "ch" and "gch" must be made public, which may not be necessarily a good thing. A way out is to have an additional indirection. Let us call this as simple_export (a very simplified version of sc_export): class simple_export : public target_if { public: void transport(int i) { // One level of indirection p_real_target->transport(i); } void bind(target_if &_if) { p_real_target = &_if; } private: target_if *p_real_target; }; The new version of the Target class now looks like the following: class TargetGrandChild : public target_if { public: void transport(int i) { // Implement the interface.. cout << "Got " << i << " from initiator (in grand child)" << std::endl; } }; class TargetChild { public: TargetGrandChild gch; /* Other stuff.. */ }; class Target { public: simple_export port; private: // private is ok TargetChild ch; /* Other stuff.. */ Target() { // Tell "export" where the real implementation is port.bind(ch.gch); } }; The initiator will be "bound" to the target like so: initiator.bind(target.port); So for the simple_export to work, you need two binds: First is where the initiator is bound to the simple_export instance Second is when the "real" implementation is bound to the simple_export instance The simple_export class acts like a bridge. It forward the calls from the initiator to the "real" implementation. In case the "Target" itself implements the interface, the bind would look like: class Target : public target_if { public: simple_export port; private: // private is ok TargetChild ch; /* Other stuff.. */ Target() { // Tell "export" where the real implementation is port.bind(*this); } }; I hope this explains the line you pointed out in the code. The TLM socket has both a port and an export. Please note that the code snips above does not correspond to how OSCI simulator actually implements sc_port/sc_export!
  24. 1 point
    David Long

    static and dynamic sensitivity

    Hi Amit, A process has static sensitivity if it contains one or more calls to wait(). The sensitivity is set before the simulation starts running, usually be using "sensitive" in its parent module's constructor. Dynamic sensitivity is where a process contains one or more calls to wait(a_time_value) or wait(a_named_event). It is dynamic because the conditions that cause a thread process to wake up change as each wait statement is executed when the simulation runs. Here is a very brief example (not tested): SC_MODULE(mod) { sc_in<bool> clk; sc_event e; void proc1() { wait(); //static sensitivity e.notify(); } void proc2() { while(1) { wait(e); //wait for event (dynamic) //do something wait(1,SC_NS); //wait for time (dynamic) //do something } SC_CTOR(mod) { SC_METHOD(proc1); sensitive << clk.pos(); //static sensitivity SC_THREAD(proc2); //no static sensitivity, runs during initialization until 1st wait reached } }; You can find further details in section 4.2 of the SystemC LRM (1066.2011). Regards, Dave
  25. 1 point
    Assuming you're using plain signal ports, you can use the event member function to check, whether a specific port has been triggered in the current delta cycle: sc_vector< sc_in< int> > in_vec; // ... SC_METHOD(proc); for( unsigned i= 0; i<in_vec.size(); ++i ) sensitive << in_vec[i]; // ... void proc() { for( unsigned i= 0; i<in_vec.size(); ++i ) if( in_vec[i]->event() ) std::cout << "in_vec[" << i << "] triggered." << std::endl; } Greetings from Oldenburg, Philipp
×