Jump to content

Eyck

Members
  • Content Count

    197
  • Joined

  • Last visited

  • Days Won

    61

Posts posted by Eyck

  1. No, you cannot run the SystemC simulator kernel as a Linux kernel module for sevberal reasons:

    • SystemC requires quite some libraries (libc, libqt, ...) that are not available in kernel space
    • You would taint the kernel and open up a security hole large as a gate. You would alos risk the system integrity. SystemC is simply not meant for such things.

    A proper design would make a distinction between (as little as possibel) code to run in kernel space providing interfaces (devices or shared memory) to interact with and a userspace application which could be the SystemC simulation e.g. running as a daemon. Even linux kernel hackers move everything into this direction...

    Yes, the simulation is a simple userspace process being constraint by the OS permissions. So it can use all resources a process is allowed. It can open socket connections and ports and whatever it needs. In the past I wrote some interface which allowed the SystemC simulation running on a Linux system to talk to another simulator running on a Windows host using network sockets (it also worked with UNIX domain sockets). And actually we are implementing a SystemC simulation talking to an FPGA on an accelerator card via device files.

    Unfortunately I'm not aware of freely available examples to do so. But if you have some C/C++ code to deal with the netlink sockets you can just embed this code into the simulator e.g. as part of a sc_module and tie it to the simulation phases (open the socket during start_of_simulation(), closing them during end_of_simulation()).

  2. One reason I've seen in the past quite often are uninitialized local variables being used in the control flow:

    int a;
    ...
    unsigned b = a;
    ...
    if(b>0){
      ...
    } else {
      ...
    }

    Although b seems to be initialize it gets its value uninitialized. Hwo would logging effect this? Depending on the configuration the stack being used has different residual values (from previous funcion executions). Therefore 'a' gets different -unitialized- values which are later used to steer the control flow. Therefore my mantra is always: initialize variables upon declaration. In C++11 this becomes quite easy:
     

    int a{0};
    // or better:
    auto a=0;

    The use of auto requires an initialization whioch is enforce by the compiler then which is quite handy.

    Aside of that: maybe you would want to have a look at https://github.com/VP-Vibes/SystemC-Components/blob/master/incl/scc/report.h and https://github.com/VP-Vibes/SystemC-Components/blob/master/src/report.cpp which provide a more convenient logging interface on top of the plain SystemC report infrastructure.

  3. If you can avoid global variables at all cost. The will bite you more you think. If you need to have static global variables (e.g. for loggers or memory managers) use the Meyers-SIngleton. Those will be initialized upone first use which usually gives you some control over the live cycle. Another example can be found here: https://github.com/Minres/SystemC-Components/blob/master/incl/tlm/tlm_mm.h

  4. Addresses and data should be modelled as sc_uint<WIDTH> since this forms one signal carrying events. Having a (sc_)vector creates n-signals with n events which adds simulation overhead. It should look like:

    sc_core::sc_signal<sc_dt::sc_uint<32>> data_i;

    The latency setting can either be set as a constructor argument or a SystemC CCI parameter.

  5. You need to built the SystemC lib on ubuntu. With the introduction of small string optimization in C++11 gcc decided to move this into a different (inline) namespace (it is called DualABI). The use of of this can be configured at build time of gcc. Basically this is a decision of the distribution and may differ even if the version is the same.

    You may try to compile your SystemC code with '-D_GLIBCXX_USE_CXX11_ABI=0' or '-D_GLIBCXX_USE_CXX11_ABI=1'. See also

    HTH

  6. Actually the usual bus protocols allow to send larger number of requests and the interconnect is allowed to answer them 'out of order' (e.g. AMBA AXI or CHI). But you are free to define you onw protocol and its own rules. The LRM states exactly how to do this. One example can be found at  https://github.com/Arteris-IP/tlm2-interfaces which defines the extensions and phases for the AXI/ACE and the CHI protocol.

  7. This part is wrong:

    for ( int i=0; i<N ; i++){
        for ( int j=0; j<NB_elements_trans ; j++){   
    
            i_adder = new adder("i_adder");
            i_adder->in[j](sig_data[i][j]);
        }
        i_adder->out(sig_add);
    }
    

    You create N x NB_elements_trans i_adder elements and on each of them you only connect 1 of 4 in ports. I guess you mean:

    for ( int i=0; i<N ; i++){
        i_adder = new adder("i_adder");
        for ( int j=0; j<NB_elements_trans ; j++){   
            i_adder->in[j](sig_data[i][j]);
        }
        i_adder->out(sig_add);
    }

    A few remarks:

    • you create a memory leak by assigning objects created with new to the same variable
    • you should not use raw pointer. Use C++11 unique_ptr instead
    • Name you SystemC objects, this helps debugging. C++11 makes it easy witn in-class constructors, e.g.:
      sc_signal<double> sig_add{"sig_add"}; 
    • don't use plain C-style arrays, uses either std::array or std::vector. These provide range checking capabilities and help finding out-of-bounds problems
    • don't use C  or C++arrays for SystemC objects. Use sc_core::sc_vector instead
    • use proper formatting, this eases reading (an IDe or clang-format is your friend). Keep in mind: code is a hundret times more often read than written!
  8. The quantum keeper is used to hold the local time of a time domain in loosly-timed models. E.g. in https://git.minres.com/VP/HIFIVE1-VP/src/branch/master/platform/src/sc_main.cpp#L119 the global quantum is set (the amount of time a time domain is allowed to run ahead). In https://git.minres.com/DBT-RISE/DBT-RISE-RISCV/src/branch/develop/incl/sysc/core_complex.h#L112 the local time is updated (within each instruction) and if the quantum is exceeded, the control is returned to the SystemC kernel (line 114).

  9. Describing particular protocols means extending the base protocol (see also IEEE 1666-2011, section 14.2). There are several ways to do this:

    1. ignorable phases (IEEE1666-2011, section 14.2.1, 15.2.5):
      here you add intermediate timepoints inbetween the base protocol phase timepoint. This allows: 'An ignorable phase may be ignored by its recipient.'
    2. define new protocol traits (IEEE1666-2011, section 14.2.2):
      you define new, non-ignorable phases so the implementation are base-protocol-compliant. This way you can only connect base-protocol-compliant models together

    The easiest way would be 1. as it allows to reuse a lot of the the tlm_utils for implementation. But 2. ist the recommended one. An example of the second apporach for AXI/ACE can be found at https://github.com/Arteris-IP/tlm2-interfaces

  10. In the constructor list you would provide a creator function. This is a functor(a function pointer, a std::function object, a Functor classinstance, a lambda function, ...) which accepts a char const* and a size_type and returns a constructed object. In your case it would look like (C++11):

    class example: public sc_core::sc_module {
    public:
      sc_core::sc_vector<sc_core::sc_fifo<unsigned>> fifos;
      
      example(sc_core::sc_module_name nm, unsigned outputs)
        : sc_core::sc_module(nm)
        , fifos("fifos", outputs, [](char const* name, unsigned i)->sc_core::sc_fifo<unsigned> {
            return new sc_core::sc_fifo<unsigned>(5000);
          }
      {
        // some construction code
      }
    }

     

  11. Your do_sum() is sensistive to A_val_in and B_val_in which means wati() finishes as soon as A_val_in or B_val_in gets data. Then you read the data using blocking read. This means the function waits until data in the fifo is available anyways. Your loop could be simplified as

     void sum::do_sum() {
        while(true) {
        	unsigned int Sint = A_val_in.read() + B_val_in.read();
          	S_val_out.write(Sint);
          	sum_finished.notify(SC_ZERO_TIME);
        }
    } 

    and you don't need a sensitivity list at all. This can be done also in a non-blocking way:

    void sum::do_sum() {
        unsigned int A_val=0, B_val=0;
        while(true) {
            wait(A_val_in.data_written_event() && B_val_in.data_written_event()); // this creates an sc_event_and_list
            if(A_val_in.nb_read(A_val) && B_val_in.nb_read(B_val)){
                unsigned int Sint = A_val + B_val;
                S_val_out.write(Sint);
                sum_finished.notify(SC_ZERO_TIME);
            }
        }
    } 

    You may also remove the sum_finished event since fifos provide a data_written event (part of the sc_fifo_nonblocking_in_if).

  12. A SC_METHOD being sensitive to a positive edge of a clock is not a latch rather a register. It is the same than in (System)Verilog or VHDL.

    Moreover a sc_signal is not a queue at all. Writes in the same delta cylce superseed earlier writes in the same delta cycle. A signal in SystemC is similar to a wire or reg in Verilog or a signal in VHDL. Maybe you should revisit your understanding of RTL description and its logics.

    There are many ways how to implement things. You may write a lot of different modules what brings a lot of overhead. Usually you implement a pipeline as a set of processes (SC_METHODS or even SC_THREADS) communicating via sc_signals or sc_fifos.

  13. Well, if Stage1 should hold the value of 2 until Stage2 is able to process it then Stage2 need to tell Stage1 that is busy with the value before. In your example Stage2 read (consumed) already the value 2 so that Stage1 can provide the next value (3).

    This is what you see in the simulation: Stage2 tells Stage1 that it is busy processing the value of 2 so Stage 1 holds the next value (3) until Stage2 is ready. Actually the implementation and behavior is correct.

  14. I'm not aware of any example so I will no be able to answer your question.

    But I do not see your problem. You would do it as it is done in hardware. Each stage is providing a ready signal indicating to the stage before that it can take inputs. And now the preceeding stage updates its outputs only if the ready signal of the next stage is active.

  15. Due to the METHOD your derived clock switches in the second delta cycle. One way I can think of is 'gating' the primary clock as well:

    #include "systemc.h"
    SC_MODULE(ClockPropagater) {
      sc_in<bool> clk{"clk"};
      sc_out<bool> p_clk1{"p_clk1"}, p_clk2{"p_clk2"};  
                                                   
      // Clock gating can be potentially added.
      void Propagate() { p_clk1 = clk; p_clk2 = clk;}
    
      SC_CTOR(ClockPropagater)  {
        SC_METHOD(Propagate);                                                                      
        sensitive << clk;
      }                  
    };
                                                   
    SC_MODULE(outputer) {              
      sc_in<bool> clk;
      sc_out<int> c_out;   
                                                   
      void writer() {
        for (int i = 0; i < 5; ++i) {
          cout << "Write " << this->name() << " " << i << endl;
          c_out.write(i);
          wait();
        }
        exit(0);                  
      }               
                                                   
      SC_CTOR(outputer) {
        SC_CTHREAD(writer, clk.pos());
      }
    };         
    
    SC_MODULE(reader) {
      sc_in<bool> clk;
      sc_in<int> c_in;
    
      void read() {
        while (true) {
          cout << "Run " << this->name() << " " << this->c_in.read() << endl;
          wait();
        }
      }
    
      SC_CTOR(reader) {
        SC_CTHREAD(read, clk.pos());
      }
    };
    
    
    int sc_main(int argc, char* argv[])
    {
      sc_clock TestClk("TestClock", 10, SC_NS,0.5);
      sc_signal<bool> ck1, ck2;
      sc_signal<int> sig;
    
      ClockPropagater cp{"clock_propagater"};
      cp.clk(TestClk);
      cp.p_clk1(ck1);
      cp.p_clk2(ck2);
    
      outputer o{"output"};
      o.clk(ck1);
      o.c_out(sig);
    
      reader r{"reader_propagated_clk"};
      r.clk(ck2);
      r.c_in(sig);
    
      reader r1{"reader_raw_clk"};
      r1.clk(ck1);
      r1.c_in(sig);
    
      sc_start();  // run forever
    
      return 0;
    }

     

×
×
  • Create New...