Free Online Training Events
Free Technical Resources
The following examples of SystemC coding may be downloaded for use by readers of the Doulos SystemC Golden Reference Guide. You may freely use them in your projects subject to the Apache 2.0 License both privately and commercially. Please do not publish or re-distribute to others except by recommending they get a copy of our SystemVerilog Golden Reference Guide. If you wish to use some fragment in a presentation or article you are writing, please request permission from Doulos.
Click here to download the examples below as a zip file, or use the links below to jump to a particular example.
Click on the links under each section to run prepared examples in a live simulation environment using EDA Playground.
Click here to use EDA Playground to run your own sample code.
A simple example showing hierarchical SystemC modules with SC_METHOD and SC_THREAD processes
Run this example in EDA Playground
#ifndef NAND2_H #define NAND2_H #include "systemc" using namespace sc_core; SC_MODULE(nand2) // declare nand2 sc_module { sc_in<bool> A, B; // input signal ports sc_out<bool> F; // output signal ports void do_nand2() // a C++ function { F.write( !(A.read() && B.read()) ); } SC_CTOR(nand2) // constructor for nand2 { SC_METHOD(do_nand2); // register do_nand2 with kernel sensitive << A << B; // static sensitivity } }; #endif
#ifndef EXOR2_H #define EXOR2_H #include "systemc" using namespace sc_core; #include "nand2.h" SC_MODULE(exor2) { sc_in<bool> A, B; sc_out<bool> F; nand2 n1, n2, n3, n4; sc_signal<bool> S1, S2, S3; SC_CTOR(exor2) : n1("N1"), n2("N2"), n3("N3"), n4("N4") { n1.A(A); n1.B(B); n1.F(S1); n2.A(A); n2.B(S1); n2.F(S2); n3(S1,B,S3); n4.A(S2); n4.B(S3); n4.F(F); } }; #endif
#ifndef STIM_H #define STIM_H #include "systemc" using namespace sc_core; SC_MODULE(stim) { sc_out<bool> A, B; sc_in<bool> Clk; void StimGen() { A.write(false); B.write(false); wait(); A.write(false); B.write(true); wait(); A.write(true); B.write(false); wait(); A.write(true); B.write(true); wait(); sc_stop(); } SC_CTOR(stim) { SC_THREAD(StimGen); sensitive << Clk.pos(); } }; #endif
#ifndef MON_H #define MON_H #include #include "systemc" using namespace sc_core; SC_MODULE(mon) { sc_in<bool> A, B, F; sc_in<bool> Clk; void run() { std::cout << " Time A B F\n"; while (1) { wait(); std::cout << std::setw(5) << sc_time_stamp() << " " << A << " " << B << " " << F << "\n"; } } SC_CTOR(mon) { SC_THREAD(run); sensitive << Clk.pos(); } }; #endif
#include "systemc" using namespace sc_core; #include "stim.h" #include "exor2.h" #include "mon.h" int sc_main(int argc, char* argv[]) { sc_signal<bool> ASig, BSig, FSig; sc_clock TestClk("TestClock", 10, SC_NS); stim Stim1("Stimulus"); Stim1.A(ASig); Stim1.B(BSig); Stim1.Clk(TestClk); exor2 DUT("exor2"); DUT.A(ASig); DUT.B(BSig); DUT.F(FSig); mon Monitor1("Monitor"); Monitor1.A(ASig); Monitor1.B(BSig); Monitor1.F(FSig); Monitor1.Clk(TestClk); sc_start(); // run forever return 0; }
A simple hierarchical channel with a register_port method
Run this example in EDA Playground
#ifndef STACK_IF_H #define STACK_IF_H #include "systemc" using namespace sc_core; class stack_write_if: virtual public sc_interface { public: virtual bool nb_write(char) = 0; // write a character virtual void reset() = 0; // empty the stack }; class stack_read_if: virtual public sc_interface { public: virtual bool nb_read(char&) = 0; // read a character }; class stack_if: virtual public stack_write_if, virtual public stack_read_if { }; #endif
#ifndef STACK_H #define STACK_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; #include "stack_if.h" // this class implements the virtual functions // in the interfaces class stack : public sc_module, public stack_if { private: char data[20]; int top; // top of stack public: // constructor stack(sc_module_name nm) : sc_module(nm), top(0) {} bool nb_write(char c) { if (top < 20) { data[top++] = c; return true; } return false; } void reset() { top = 0; } bool nb_read(char& c) { if (top > 0) { c = data[--top]; return true; } return false; } void register_port(sc_port_base& port_, const char* if_typename_) { cout << "binding " << port_.name() << " to " << "interface: " << if_typename_ << endl; } }; #endif
#ifndef PRODUCER_H #define PRODUCER_H #include "systemc" using namespace sc_core; #include "stack_if.h" class producer : public sc_module { public: sc_port<stack_write_if> out; sc_in<bool> Clock; void do_writes() { int i = 0; const char* TestString = "This Will Be Reversed"; while (true) { wait(); // for clock edge if (out->nb_write(TestString[i])) std::cout << "Write " << TestString[i] << " at " << sc_time_stamp() << std::endl; i = (i+1) % 21; } } SC_CTOR(producer) { SC_THREAD(do_writes); sensitive << Clock.pos(); } }; #endif
#ifndef CONSUMER_H #define CONSUMER_H #include "systemc" using namespace sc_core; #include "stack_if.h" class consumer : public sc_module { public: sc_port<stack_read_if> in; sc_in<bool> Clock; void do_reads() { char ch; while (true) { wait(); // for clock edge if (in->nb_read(ch)) std::cout << "Read " << ch << " at " << sc_time_stamp() << std::endl; } } SC_CTOR(consumer) { SC_THREAD(do_reads); sensitive << Clock.pos(); } }; #endif
#include "systemc" #include "producer.h" #include "consumer.h" #include "stack.h" using namespace sc_core; int sc_main(int argc, char* argv[]) { sc_clock ClkFast("ClkFast", 100, SC_NS); sc_clock ClkSlow("ClkSlow", 50, SC_NS); stack Stack1("S1"); producer P1("P1"); P1.out(Stack1); P1.Clock(ClkFast); consumer C1("C1"); C1.in(Stack1); C1.Clock(ClkSlow); sc_start(5000, SC_NS); return 0; }
Shows non-deterministic execution order
Run this example in EDA Playground
#ifndef NONDET_H #define NONDET_H #include <systemc> using namespace sc_core; using std::cout; using std::endl; SC_MODULE(nondet) { sc_in<bool> Trig; int SharedVariable; // The execution order of the following two processes // is non-deterministic void proc_1() { SharedVariable = 1; cout << SharedVariable << endl; } void proc_2() { SharedVariable = 2; cout << SharedVariable << endl; } SC_CTOR(nondet) { SC_THREAD(proc_1); sensitive << Trig.pos(); SC_THREAD(proc_2); sensitive << Trig.pos(); } }; #endif
#include "nondet.h" int sc_main(int argc, char* argv[]) { sc_clock clock; // Default period is 10ns nondet instance("instance"); instance.Trig.bind(clock); sc_start(20, SC_NS); return 0; }
A user-defined primitive channel with its own interfaces accessed through ports
Run this example in EDA Playground
#ifndef FIFO_IF_H #define FIFO_IF_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; class fifo_out_if : virtual public sc_interface { public: virtual void write(char) = 0; // blocking write virtual int num_free() const = 0; // free entries protected: fifo_out_if() { }; private: fifo_out_if (const fifo_out_if&); // disable copy fifo_out_if& operator= (const fifo_out_if&); // disable }; class fifo_in_if : virtual public sc_interface { public: virtual void read(char&) = 0; // blocking read virtual char read() = 0; virtual int num_available() const = 0; // available // entries protected: fifo_in_if() { }; private: fifo_in_if(const fifo_in_if&); // disable copy fifo_in_if& operator= (const fifo_in_if&); // disable = }; #endif
#ifndef FIFO_H #define FIFO_H #include "fifo_if.h" class fifo : public sc_prim_channel, public fifo_out_if, public fifo_in_if { protected: int size; // size char* buf; // fifo buffer int free; // free space int ri; // read index int wi; // write index int num_readable, num_read, num_written; sc_event data_read_event; sc_event data_written_event; public: // constructor explicit fifo(int _size = 16) : sc_prim_channel(sc_gen_unique_name("myfifo")) { size = _size; buf = new char[size]; reset(); } ~fifo() //destructor { delete [] buf; } int num_available() const { return num_readable - num_read; } int num_free() const { return size - num_readable - num_written; } void write(char c) // blocking write { if (num_free() == 0) wait(data_read_event); num_written++; buf[wi] = c; wi = (wi + 1) % size; free--; request_update(); } void reset() { free = size; ri = 0; wi = 0; } void read(char& c) // blocking read { if (num_available() == 0) wait(data_written_event); num_read++; c = buf[ri]; ri = (ri + 1) % size; free++; request_update(); } char read() // shortcut read function { char c; read(c); return c; } void update() // updates the state of the channel { if (num_read > 0) data_read_event.notify(SC_ZERO_TIME); if (num_written > 0) data_written_event.notify(SC_ZERO_TIME); num_readable = size - free; num_read = 0; num_written = 0; } }; #endif
#ifndef PRODUCER_H #define PRODUCER_H #include "fifo_if.h" SC_MODULE(producer) { sc_in<bool> Clock; sc_port<fifo_out_if> out; SC_CTOR(producer) { SC_THREAD(run); } void run() { std::string text = "Message"; for (int i = 0; i < text.size(); i++) { wait(Clock.posedge_event()); out->write(text[i]); } } }; #endif
#ifndef CONSUMER_H #define CONSUMER_H #include "fifo_if.h" SC_MODULE(consumer) { sc_in<bool> Clock; sc_port<fifo_in_if> in; SC_CTOR(consumer) { SC_THREAD(run); } void run() { while (true) { wait(Clock.posedge_event()); cout << in->read() << endl; } } }; #endif
#include "producer.h" #include "consumer.h" #include "fifo.h" int sc_main(int argc, char* argv[]) { sc_clock ClkFast("ClkFast", 1, SC_NS); sc_clock ClkSlow("ClkSlow", 500, SC_NS); fifo fifo1; producer P1("P1"); P1.out .bind(fifo1); P1.Clock.bind(ClkFast); consumer C1("C1"); C1.in .bind(fifo1); C1.Clock.bind(ClkSlow); sc_start(5000, SC_NS); return 0; }
Shows SystemC interface, channel, ports, and exports. The channel is accessed 1) standalone 2) through port-to-channel bindings 3) through port-to-export bindings
Run this example in EDA Playground
#ifndef QUEUE_IF_H #define QUEUE_IF_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; class queue_write_if : virtual public sc_core::sc_interface { public: virtual void write(char c) = 0; }; class queue_read_if : virtual public sc_core::sc_interface { public: virtual char read() = 0; }; class queue_if : public queue_write_if, public queue_read_if {}; #endif
#ifndef QUEUE_H #define QUEUE_H #include "queue_if.h" class Queue : public queue_if, public sc_object { public: Queue(const char* nm, int _sz) : sc_object(nm), sz(_sz) { data = new char[sz]; w = r = n = 0; } void write(char c); char read(); private: char* data; int sz, w, r, n; sc_event read_event, write_event; }; void Queue::write(char c) { if (n < sz) { n++; data[w++] = c; if (w == sz) w = 0; } else { sc_core::wait(read_event); write(c); } write_event.notify(); } char Queue::read() { char c = 0; if (n > 0) { n--; c = data[r++]; if (r == sz) r = 0; } else { sc_core::wait(write_event); c = read(); } read_event.notify(); return c; } #endif
#ifndef TEST_H #define TEST_H #include "queue.h" SC_MODULE(Test) { // Standalone instantiation of queue channel // accessed without any ports or exports Queue queue; SC_CTOR(Test) : queue("queue", 100) { SC_THREAD(T); } void T() { std::string txt = "Hallo World."; cout << "Test program: "; for (int i = 0; i < txt.size(); i++) queue.write(txt[i]); for (int i = 0; i < txt.size(); i++) cout << queue.read(); cout << endl; } }; #endif
#ifndef PRODUCER_H #define PRODUCER_H #include "queue.h" class Producer : public sc_module { public: sc_port<queue_write_if> out; void do_writes(); sc_time m_delay; // A user-defined constructor argument delays executing of the producer Producer(sc_module_name name, const sc_time& delay) : sc_module(name), m_delay(delay) { SC_HAS_PROCESS(Producer); SC_THREAD(do_writes); } }; void Producer::do_writes() { wait(m_delay); cout << "Producer running at time " << sc_time_stamp() << endl; std::string txt = "Hallo World."; for (int i = 0; i < txt.size(); i++) { out->write(txt[i]); } } #endif
#ifndef CONSUMER_H #define CONSUMER_H #include "queue.h" class Consumer : public sc_module { public: sc_port<queue_read_if> in; void do_reads(); SC_CTOR(Consumer) { SC_THREAD(do_reads); } }; void Consumer::do_reads() { for (;;) { wait(SC_ZERO_TIME); cout << in->read() << endl; } } #endif
#ifndef TOP2_H #define TOP2_H #include "producer.h" #include "consumer.h" SC_MODULE(Top2) { Queue queue; Producer *producer; Consumer *consumer; SC_CTOR(Top2) : queue("queue", 100) { // Bind exports of both producer and consumer to queue channel producer = new Producer("producer", sc_time(100, SC_NS)); producer->out.bind(queue); consumer = new Consumer("consumer"); consumer->in.bind(queue); } }; #endif
#ifndef CONTAINER_H #define CONTAINER_H #include "queue.h" class Container : public sc_module { public: // The queue channel is encapsulated within a container class, // which makes the queue methods available through two exports sc_export<queue_write_if> producer_export; sc_export<queue_read_if> consumer_export; Queue queue; Container(sc_module_name name) : sc_module(name), queue("queue", 100) { // Bind both exports to the local queue channel producer_export.bind(queue); consumer_export.bind(queue); } }; #endif
#ifndef TOP3_H #define TOP3_H #include "producer.h" #include "consumer.h" #include "container.h" SC_MODULE(Top3) { Producer *producer; Consumer *consumer; Container *container; SC_CTOR(Top3) { container = new Container("container"); // Bind exports of both producer and consumer to exports of container producer = new Producer("producer", sc_time(200, SC_NS)); producer->out.bind( container->producer_export ); consumer = new Consumer("consumer"); consumer->in.bind ( container->consumer_export ); } }; #endif
#include "test.h" #include "top2.h" #include "top3.h" int sc_main(int argc, char* argv[]) { // Three top-level modules: // the first a test program for the queue channel Test test_inst("test_inst"); // the second shows port-to-channel binding Top2 top2_inst("top2_inst"); // the third shows export-to-export binding Top3 top3_inst("top3_inst"); sc_start(); return 0; }
A hierarchical channel representing a cycle-accurate bus model with multiple masters and slaves
Run this example in EDA Playground
#ifndef DEF_H #define DEF_H #include "systemc" using namespace sc_core; using namespace sc_dt; using std::cout; using std::endl; namespace BusT { enum MEM_STATUS { ALL_READ_DONE, READ_DONE, WRITE_DONE }; typedef sc_uint<8> addressT; typedef sc_uint<8> dataT; const addressT MemStatusReg = 0xFF; //conversion functions for MEM_STATUS/dataT inline MEM_STATUS to_mem_status(const dataT& d) { return static_cast(ALL_READ_DONE + d.to_int()); } inline dataT to_dataT(const MEM_STATUS& m) { return dataT(m); } const char MSGTYPE[] = "DOULOS_BUS/"; }; #endif
#ifndef MASTER_IF_H #define MASTER_IF_H #include "defs.h" class master_if : virtual public sc_interface { public: virtual void write(BusT::addressT address, BusT::dataT data, int id) = 0; virtual void read (BusT::addressT address, BusT::dataT &data, int id) = 0; }; #endif
#ifndef SLAVE_IF_H #define SLAVE_IF_H #include "defs.h" class slave_if : virtual public sc_interface { public: virtual void slave_write(BusT::addressT address, BusT::dataT data) = 0; virtual void slave_read (BusT::addressT address, BusT::dataT &data) = 0; virtual void get_map(unsigned int &start, unsigned int &size) = 0; }; #endif
#ifndef BUS_H #define BUS_H #include "master_if.h" #include "slave_if.h" struct reqT { bool request; sc_event proceed; //default constructor reqT():request(false) {} }; // Hierarchical channel class Bus : public sc_module, public master_if { public: // clock port - bus will operate on negative edge sc_in<bool> clock; // a port to connect slave(s) sc_port<slave_if> slave_port; SC_HAS_PROCESS(Bus); // constructor Bus(sc_module_name name) : sc_module(name) , n_masters(0) , request_array(0) , request_count(0) , mem_status(0) { SC_THREAD(control_bus); sensitive << clock.neg(); } // destructor ~Bus() { if (request_array) delete [] request_array; if (request_count) delete [] request_count; if (mem_status) delete [] mem_status; } // bus public methods void write(BusT::addressT address, BusT::dataT data, int id); void read (BusT::addressT address, BusT::dataT &data, int id); private: // data fields unsigned n_masters; reqT* request_array; int* request_count; unsigned int *starts, *sizes; bool* mem_status; // private methods void control_bus(); int find_interface(BusT::addressT address); void end_of_elaboration(); void register_port(sc_port_base &port_, const char* if_typename_); BusT::dataT get_mem_status(int id); void set_mem_status(BusT::MEM_STATUS, int id=0); int n_pending(); }; using namespace BusT; void Bus::write(BusT::addressT address, BusT::dataT data, int id) { request_array[id].request = true; // set request wait(request_array[id].proceed); // wait to proceed if (address == MemStatusReg) { set_mem_status(to_mem_status(data),id); //cout << "Master " << id << " has set mem status reg to " << data // << " at " << sc_time_stamp() << endl; } else { int s = find_interface(address); // address decoding // slave write slave_port[s]->slave_write(address - starts[s], data); cout << "Master " << id << " has written " << "to address " << address << " data " << data << " at " << sc_time_stamp() << endl; } request_array[id].request = false; // clear request } void Bus::read(BusT::addressT address, BusT::dataT &data, int id) { request_array[id].request = true; // set request wait(request_array[id].proceed); // wait to proceed if (address == MemStatusReg) { data = get_mem_status(id); if (n_pending() <= 1) set_mem_status(ALL_READ_DONE); //cout << "Master " << id << " has got " << data // << " from mem status reg at " << sc_time_stamp() << endl; } else { int s = find_interface(address); // address decoding // slave read slave_port[s]->slave_read(address- starts[s], data); cout << "Master " << id << " has read " << "from address " << address << " data " << data << " at " << sc_time_stamp() << endl; } request_array[id].request = false; // clear request } void Bus::control_bus() { int highest, max_request_count; // clear counters for (unsigned i = 0; i < n_masters; i++) request_count[i] = 0; for (;;) { wait(); for (unsigned i = 0; i < n_masters; i++) { if (request_array[i].request == true) request_count[i]++; } highest = -1; max_request_count = 0; for (unsigned i = 0; i < n_masters; i++ ) { if (request_count[i] >= max_request_count) { max_request_count = request_count[i]; highest = i; } } // If highest is > -1, then give the bus to the master with a // request pending for the greatest number of clock cycles // Reset the pending request count for that master. if (highest > -1) { request_array[highest].proceed.notify(); request_count[highest] = 0; } } } int Bus::find_interface(BusT::addressT address) { // check address range of each slave in turn for (int i = 0; i < slave_port.size(); i++) if (address >= starts[i] && address < (starts[i] + sizes[i])) return i; SC_REPORT_ERROR(BusT::MSGTYPE, "address out-of-range"); return 1; } void Bus::end_of_elaboration() { // create the request array request_array = new reqT[n_masters]; request_count = new int[n_masters]; // create the array for MemStatusReg mem_status = new bool[n_masters]; // clear mem_status for (unsigned i = 0; i < n_masters; i++) mem_status[i] = false; const int n = slave_port.size(); starts = new unsigned[n]; sizes = new unsigned[n]; bool ok = true; for (int i = 0; i < n; i++) { slave_port[i]->get_map(starts[i], sizes[i]); // Check for overlapping address ranges for (int j = 0; j < i; j++) if (starts[i] < (starts[j] + sizes[j]) && starts[j] < (starts[i] + sizes[i])) { cout << "Error: overlapping regions in Bus address map: " << starts[i] << ".." << starts[i] + sizes[i] - 1 << " and " << starts[j] << ".." << starts[j] + sizes[j] - 1 << endl; ok = false; } } sc_assert(ok); } // counts how many masters are bound to the bus channel void Bus::register_port(sc_port_base &port_, const char* if_typename_) { std::string nm(if_typename_); if ( nm == typeid(master_if).name() ) n_masters++; } BusT::dataT Bus::get_mem_status(int id) { //return 1 if flag set if (mem_status[id]) return 1; else return 0; } void Bus::set_mem_status(MEM_STATUS status, int id) { switch (status) { case ALL_READ_DONE: for (unsigned i=0; i < n_masters; i++) mem_status[i] = false; break; case READ_DONE: mem_status[id] = false; break; case WRITE_DONE: for (unsigned i=0; i < n_masters; i++) mem_status[i] = true; } } int Bus::n_pending() { int count=0; for (unsigned i=0; i < n_masters; i++) if (mem_status[i]) count++; return count; } #endif
#ifndef SOURCE_H #define SOURCE_H #include "master_if.h" class Source: public sc_module { public: sc_in<bool> clock; sc_port<master_if> system_bus; SC_HAS_PROCESS(Source); // constructor Source( sc_module_name _name, unsigned _src, unsigned _num_items, int _id) : sc_module(_name), src(_src), num_items(_num_items), id(_id) { SC_THREAD(do_source); sensitive << clock.pos(); } // method declarations void do_source(); private: const unsigned src, num_items; const int id; }; using namespace BusT; void Source::do_source() { BusT::dataT wrdata = 0, rddata = 0; const int EOD = 255; for (;;) { // write random numbers between 0 and EOD-1 to the bus for (unsigned int i = 0; i < num_items; i++) { wait(); wrdata = rand() % EOD; system_bus->write(src + i, wrdata, id); } // write to memory status register wait(); system_bus->write(MemStatusReg, to_dataT(WRITE_DONE), id); // wait for data to be processed, memory status register cleared for (;;) { wait(); system_bus->read(MemStatusReg, rddata, id); if (!rddata) break; } } } #endif
#ifndef PROC_H #define PROC_H #include "master_if.h" // Processor to read data values from the bus // and write them back to some output locations class Proc: public sc_module { public: sc_in<bool> clock; sc_port<master_if> system_bus; SC_HAS_PROCESS(Proc); // Constructor Proc(sc_module_name _nm, unsigned _src, unsigned _dest, unsigned _num_items, int _id) : sc_module(_nm), src(_src), dest(_dest), num_items(_num_items), id(_id) { SC_THREAD(do_proc); sensitive << clock.pos(); sc_assert (_src < _dest); } // Method declarations void do_proc(); private: const unsigned int src, dest, num_items; const int id; }; using namespace BusT; void Proc::do_proc() { BusT::dataT data = 0; for (;;) { // Scan the address range looking memory status register to clear do { wait(); system_bus->read(MemStatusReg, data, id); } while (!data); // Copy all values which have the LSB set (odd values) up to the block // of memory starting at address dest unsigned int copyto = dest; for (unsigned int i = src; i < num_items; i++) { wait(); system_bus->read(i, data, id); if (data[0] == 1) { wait(); system_bus->write(copyto++, data, id); } } // Finally, clear the memory status register for this master wait(); system_bus->write(MemStatusReg, to_dataT(READ_DONE), id); } } #endif
#ifndef MEM_H #define MEM_H #include "slave_if.h" class Mem : public sc_module, public slave_if { public: Mem(sc_module_name name, unsigned int start, unsigned int size, sc_trace_file *trace_file) : sc_module(name), start(start), size(size), trace_file(trace_file) { if (size < 1) { cout << "Mem size must be at least 1" << endl; sc_assert(false); } init(); } // interface methods void slave_write(BusT::addressT address, BusT::dataT data); void slave_read (BusT::addressT address, BusT::dataT &data); void get_map(unsigned int &start, unsigned int &size); // support methods void status(); void dump(unsigned int start_addr = 0 , unsigned int end_addr = ~0); void add_trace(unsigned int address); void init(); private: // data items unsigned int start; unsigned int size; sc_trace_file *trace_file; BusT::dataT *buf; }; void Mem::slave_write(BusT::addressT address, BusT::dataT data) { if ((address < 0) || (address >= size)) { SC_REPORT_ERROR(BusT::MSGTYPE, "write address out of range "); } else { buf[address] = data; } } void Mem::slave_read(BusT::addressT address, BusT::dataT &data) { if ((address < 0) || (address >= size)) { SC_REPORT_ERROR(BusT::MSGTYPE, "read address out of range "); } else { data = buf[address]; } } void Mem::get_map(unsigned int &mem_start, unsigned int &mem_size) { // return memory address map mem_start = start; mem_size = size; } // ------------------------------------------------------- // Initialisation routine void Mem::init() { buf = new BusT::dataT[size]; for (unsigned int i = 0; i < size; i++) buf[i] = 0; } // ------------------------------------------------------- // Debug routines void Mem::status() { cout << "Slave status" << endl; cout << "------------" << endl; cout << name() << " "; cout << " Size = " << size; cout << " Start Address = " << start; cout << endl << endl; } void Mem::dump(unsigned int start_addr, unsigned int end_addr) { if (start_addr < 0) start_addr = 0; if (end_addr >= (size - 1)) end_addr = size - 1; cout << name() << " contents:" << endl; cout << "--------------" << endl; for (unsigned int i = start_addr; i <= end_addr; i++) { cout << start+i << " " << buf[i] << endl; } cout << endl; } void Mem::add_trace(unsigned int address) { std::string nm( name() ); nm = nm + "(" + sc_uint<32>(address+start).to_string().c_str() + ")"; if ((address >= 0) && (address < size) ) sc_trace(trace_file, buf[address], nm.c_str()); else SC_REPORT_WARNING(BusT::MSGTYPE, "address to trace out of range"); } #endif
#ifndef TOP_H #define TOP_H #include "bus.h" #include "source.h" #include "proc.h" #include "mem.h" SC_MODULE(Top) { sc_clock clock; sc_trace_file *trace; // Module Instantiations Mem ram0; // Add another ram here and initialize it to start 32, size 32 // Bus Bus bus1; // Data source ID 0 Source source0; // Processor, ID = 1 // destination address starts at 16 Proc proc1; SC_CTOR(Top) : clock( "clock", 100, SC_NS), trace(sc_create_vcd_trace_file("bus_system")), // Memory ram0, start address 0 , size 32 ram0("ram0", 0, 32, trace), // Bus bus1("bus1"), // Data source ID 0 source0("source0", 0, 12, 0), // Processor, ID = 1 // Destination address starts at 16 proc1("proc1", 0, 16, 12, 1) { bus1.clock(clock); bus1.slave_port(ram0); source0.clock(clock); source0.system_bus(bus1); proc1.clock(clock); proc1.system_bus(bus1); // Trace external pins sc_trace(trace, clock, "clock"); // Trace ram memory locations int i; for (i = 0; i < 32; i++) ram0.add_trace(i); // Display ram status ram0.status(); // Dump ram contents ram0.dump(); } void end_of_simulation() { // Dump slave contents ram0.dump(); } ~Top() { sc_close_vcd_trace_file(trace); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top1("top1"); sc_start(25000, SC_NS); sc_stop(); // triggers end_of_simulation() callback return 0; }
Shows a SystemC interface and channel with default_event and register_port methods and specialized ports that define event finders
Run this example in EDA Playground
#ifndef ZQ_IF_H #define ZQ_IF_H // A SystemC interface for a zero-length queue // separated into a write interface and a read interface // such that register_port() can be used for checking // the number of writers and readers #include "systemc" using namespace sc_core; template<class T> class zq_write_if: virtual public sc_interface { public: virtual void write(T) = 0; virtual const sc_event& read_event() const = 0; }; template<class T> class zq_read_if: virtual public sc_interface { public: virtual void read(T&) = 0; virtual const sc_event& write_event() const = 0; }; template<class T> class zq_if: virtual public zq_write_if<T>, virtual public zq_read_if<T> { public: virtual const sc_event& default_event() const = 0; }; #endif
#ifndef ZQ_CHANNEL_H #define ZQ_CHANNEL_H #include "zq_if.h" // A SystemC channel that implements the zero-length queue interface template<class T> class zq_channel: public zq_if<T>, public sc_object { public: zq_channel(const char* name) : sc_object(name), m_written(false), m_writer(0), m_reader(0) {} virtual void write(T arg) { m_value = arg; m_written = true; m_write_event.notify(); sc_core::wait(m_read_event); m_written = false; } virtual void read(T& arg) { if (!m_written) sc_core::wait(m_write_event); arg = m_value; m_read_event.notify(); sc_core::wait(SC_ZERO_TIME); } virtual const sc_event& write_event() const { return m_write_event; } virtual const sc_event& read_event() const { return m_write_event; } virtual const sc_event& default_event() const { return m_read_event; } // register_port checks there is no more than one reader or writer void register_port( sc_port_base& port_ , const char* if_typename_) { if(std::string(if_typename_) == typeid(zq_write_if<T>).name()) { if( m_writer != 0 ) SC_REPORT_ERROR("zq_channel", "More than one writer"); m_writer = &port_; } if(std::string(if_typename_) == typeid(zq_read_if<T>).name()) { if( m_reader != 0 ) SC_REPORT_ERROR("zq_channel", "More than one reader"); m_reader = &port_; } } void end_of_elaboration() { if( m_writer == 0 || m_reader == 0 ) SC_REPORT_ERROR("zq_channel", "Must have exactly one writer and one reader"); } private: T m_value; bool m_written; sc_event m_write_event; sc_event m_read_event; sc_port_base *m_writer; sc_port_base *m_reader; }; #endif
#ifndef ZQ_PORT_H #define ZQ_PORT_H #include "zq_if.h" // Specialized read and write ports, each with their own event finder template<class T> class zq_write_port: public sc_port<zq_write_if<T> > { public: sc_event_finder& find_read_event() const { return *new sc_event_finder_t<zq_write_if<T> >( *this , &zq_write_if<T>::read_event ); } }; template<class T> class zq_read_port: public sc_port<zq_read_if<T> > { public: sc_event_finder& find_write_event() const { return *new sc_event_finder_t<zq_read_if<T> >( *this , &zq_read_if<T>::write_event ); } }; #endif
#ifndef PRODUCER_H #define PRODUCER_H #include "zq_port.h" template<class T> class Producer: public sc_module { public: zq_write_port<int> out; SC_CTOR(Producer) { SC_THREAD(run); SC_METHOD(monitor); sensitive << out.find_read_event(); } void run() { const sc_time tick(10, SC_NS); wait(tick); out->write(1); wait(tick); wait(tick); out->write(2); wait(tick); out->write(3); wait(tick); out->write(4); out->write(5); } void monitor() { std::cout << "Producer sees read_event at " << sc_time_stamp() << std::endl; } }; #endif
#ifndef CONSUMER_H #define CONSUMER_H #include "zq_port.h" using std::cout; using std::endl; template<class T> class Consumer: public sc_module { public: zq_read_port<int> in; SC_CTOR(Consumer) { SC_THREAD(run); SC_METHOD(monitor); sensitive << in.find_write_event(); } void run() { const sc_time tick(10, SC_NS); int data; wait(tick); wait(tick); in->read(data); cout << "data = " << data << " at " << sc_time_stamp() << endl; wait(tick); in->read(data); cout << "data = " << data << " at " << sc_time_stamp() << endl; wait(tick); wait(tick); in->read(data); cout << "data = " << data << " at " << sc_time_stamp() << endl; in->read(data); cout << "data = " << data << " at " << sc_time_stamp() << endl; wait(tick); wait(tick); in->read(data); cout << "data = " << data << " at " << sc_time_stamp() << endl; } void monitor() { cout << "Consumer sees write_event at " << sc_time_stamp() << endl; } }; #endif
#ifndef TOP_H #define TOP_H #include "systemc" using namespace sc_core; #include "producer.h" #include "consumer.h" #include "zq_channel.h" class Top: public sc_module { public: Producer<int> *m_producer; Consumer<int> *m_consumer; zq_channel<int> m_channel; SC_CTOR(Top) : m_channel("m_channel") { m_producer = new Producer<int>("m_producer"); m_producer->out.bind(m_channel); m_consumer = new Consumer<int>("m_consumer"); m_consumer->in.bind(m_channel); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); return 0; }
Uses a SystemC interface, channel, multiport, and sc_vector to show the next_trigger method being used with an explicit event list object
Run this example in EDA Playground
#ifndef INTERFACE_H #define INTERFACE_H // A simple SystemC interface #include "systemc" using namespace sc_core; using std::cout; using std::endl; class i_f: virtual public sc_interface { public: virtual void write(int) = 0; virtual void read(int&) = 0; virtual bool written() = 0; }; #endif
#ifndef CHANNEL_H #define CHANNEL_H #include "interface.h" // A simple SystemC channel that implements the // default_event method of class sc_interface class channel: public i_f, public sc_object { public: channel(const char* name): sc_object(name) {} int m_value; bool m_written; sc_event m_write_event; const sc_event& default_event() const { return m_write_event; } virtual void write(int arg) { m_value = arg; m_written = true; m_write_event.notify(); } virtual void read(int& arg) { arg = m_value; m_written = false; } virtual bool written() { return m_written; } }; #endif
#ifndef MODULE_H #define MODULE_H #include "interface.h" SC_MODULE(module) { sc_port<i_f, 0, SC_ZERO_OR_MORE_BOUND> p; SC_CTOR(module) : p("p") { SC_METHOD(method); } sc_event_or_list all_events() const { sc_event_or_list list; for (int i = 0; i < p.size(); i++) list = list | p[i]->default_event(); return list; } // An explicit event list object sc_event_or_list event_list; void method() { // The method is made sensitive to the default event of // all the channels that are bound to the multiport p event_list = all_events(); next_trigger( event_list ); for (int i = 0; i < p.size(); i++) { if ( p[i]->written() ) { int value; p[i]->read(value); cout << "Input " << i << ", value = " << value << endl; } } } }; #endif
#ifndef TOP_H #define TOP_H #include "module.h" #include "channel.h" SC_MODULE(top) { module *inst; sc_vector<channel> channels; // The number of channels to be bound to the multiport const int N = 4; SC_CTOR(top) : channels("channels") { channels.init(N); inst = new module("inst"); // Bind the multiport p to N channels for (int i = 0; i < channels.size(); i++) inst->p.bind(channels[i]); SC_THREAD(run); } void run() { // Test program to exercise the target module for (int i = 0; i < channels.size(); i++) { wait(10, SC_NS); channels[i].write(100 + i); } for (int i = 0; i < channels.size(); i++) { wait(10, SC_NS); channels[N-i-1].write(100 + N-i-1); } } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { top top_inst("top_inst"); sc_start(); return 0; }
Shows an sc_clock object being constructed explicitly with the clock waveform defined by the constructor arguments
Run this example in EDA Playground
#ifndef CHILD_H #define CHILD_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; SC_MODULE(child) { sc_in<bool> clk; SC_CTOR(child) { SC_THREAD(run); // Process sensitive to clock sensitive << clk.pos(); // Event finder gets event from clock } void run() { while (true) { wait(); cout << "child::run awoke at " << sc_time_stamp() << endl; } } }; #endif
#ifndef MODULE_H #define MODULE_H #include "child.h" SC_MODULE(module) { sc_export<sc_signal_in_if<bool> > clock_export; const sc_time period; // Clock parameters const double duty; const sc_time start_at; const bool pos; sc_clock clock; // Clock instance SC_CTOR(module) : period (10, SC_NS), // Initialize members duty (0.5), start_at(200, SC_NS), pos (true), clock ("clock", period, duty, start_at, pos) { clock_export.bind(clock); // Export the clock inst = new child("inst"); inst->clk(clock); // Connect clock to child port SC_THREAD(run); // Process sensitive to clock sensitive << clock.posedge_event(); } child* inst; void run() { while (true) { wait(); cout << "module::run awoke at " << sc_time_stamp() << endl; } } }; #endif
#include "module.h" int sc_main(int argc, char* argv[]) { module inst("module"); sc_start(250, SC_NS); return 0; }
Shows several clocked thread processes, one of which has a synchronous reset
Run this example in EDA Playground
#ifndef INTERLEAVE_H #define INTERLEAVE_H #include "systemc" using namespace sc_core; SC_MODULE(Interleave) { sc_in<bool> clock, reset, d0, d1; sc_out<bool> q; SC_CTOR(Interleave) { SC_CTHREAD(run, clock.pos()); reset_signal_is(reset, true); } void run() { q = 0; // Reset wait(); q = d0; // Initialize after reset while (true) { wait(); q = d1; wait(); q = d0; } } }; #endif
#ifndef STIMULUS_H #define STIMULUS_H #include "systemc" using namespace sc_core; SC_MODULE(Stimulus) { sc_in<bool> clock; sc_out<bool> reset, d0, d1; SC_CTOR(Stimulus) { SC_CTHREAD(run, clock.neg()); } void run() { reset = 1; d0 = 0; d1 = 1; wait(); reset = 0; while (true) { wait(); } } }; #endif
#ifndef MONITOR_H #define MONITOR_H #include "systemc" #include "iomanip" using namespace sc_core; using std::cout; using std::endl; SC_MODULE(Monitor) { sc_in<bool> clock, reset, d0, d1, q; SC_CTOR(Monitor) { SC_CTHREAD(run, clock.neg()); } void run() { cout << "reset d0 d1 q" << endl; wait(); while (true) { wait(); cout << " " << reset << " " << d0 << " " << d1 << " " << q << endl; } } }; #endif
#include "stimulus.h" #include "interleave.h" #include "monitor.h" int sc_main(int argc, char* argv[]) { sc_clock clock("clock"); sc_signal<bool> reset, d0, d1, q; Stimulus stimulus("stimulus"); stimulus.clock.bind(clock); stimulus.reset.bind(reset); stimulus.d0.bind(d0); stimulus.d1.bind(d1); Interleave interleave("interleave"); interleave.clock.bind(clock); interleave.reset.bind(reset); interleave.d0.bind(d0); interleave.d1.bind(d1); interleave.q.bind(q); Monitor monitor("monitor"); monitor.clock.bind(clock); monitor.reset.bind(reset); monitor.d0.bind(d0); monitor.d1.bind(d1); monitor.q.bind(q); sc_start(10, SC_NS); return 0; }
Uses an immediate event notification to synchronize two thread processes
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; SC_MODULE(Test) { int data; sc_event e; SC_CTOR(Test) { SC_THREAD(producer); SC_THREAD(consumer); } void producer() { wait(1, SC_NS); for (data = 0; data < 10; data++) { e.notify(); wait(1, SC_NS); } } void consumer() { for (;;) { wait(e); cout << "Received " << data << endl; } } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst"); sc_start(); return 0; }
Simple example of an event queue
Run this example in EDA Playground
#ifndef SOURCE_H #define SOURCE_H #include "systemc" using namespace sc_core; SC_MODULE(Source) { // A port is not mandatory – just an example sc_port<sc_event_queue_if> eqport; SC_CTOR(Source) { SC_THREAD(run); } void run() { while(true) { // Queue a burst of 4 events then wait for them to occur for (int i = 0; i < 3; i++) eqport->notify( sc_time(i * 10, SC_NS) ); wait(100, SC_NS); } } }; #endif
#ifndef TOP_H #define TOP_H #include "source.h" SC_MODULE(Top) { sc_event_queue eq; Source* source; SC_CTOR(Top) { source = new Source("source"); source->eqport(eq); SC_METHOD(run); dont_initialize(); sensitive << eq; } void run() // Called once per event on the queue { std::cout << "Event at " << sc_time_stamp() << std::endl; } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_start(500, SC_NS); return 0; }
Shows a SystemC interface and channel with examples of port-to-export binding (module Parent) and export-to-channel binding (module Child2)
Run this example in EDA Playground
#ifndef CHILDREN_H #define CHILDREN_H #include "systemc" using namespace sc_core; class IF: virtual public sc_interface { public: virtual void method() = 0; }; class Channel: public IF, public sc_object { public: Channel(): sc_object(sc_gen_unique_name("channel")) {} virtual void method() {} }; SC_MODULE(Child1) { sc_port<IF> port; SC_CTOR(Child1) { SC_THREAD(T); } void T() { port->method(); // Interface Method Call } }; SC_MODULE(Child2) { sc_export<IF> xport; Channel chan; // Local channel SC_CTOR(Child2) { xport.bind(chan); // Bind export to channel } }; #endif
#ifndef PARENT_H #define PARENT_H #include "children.h" SC_MODULE(Parent) { Child1* child1; Child2* child2; SC_CTOR(Parent) { child1 = new Child1("child1"); child2 = new Child2("child2"); // Top level port-to-export binding // Method bind() is equivalent to operator() child1->port.bind( child2->xport ); } }; #endif
#include "parent.h" int sc_main(int argc, char* argv[]) { Parent parent_inst("parent_inst"); sc_start(); return 0; }
Shows an sc_fifo<int> channel accessed through sc_fifo_in and sc_fifo_out ports
Run this example in EDA Playground
#ifndef PRODUCER_H #define PRODUCER_H #include "systemc" using namespace sc_core; SC_MODULE(Producer) { sc_fifo_out<int> fifo_out; SC_CTOR(Producer) : fifo_out("fifo_out") { SC_THREAD(run); } void run() { int data = 1; for (int i = 0; i < 4; i++) fifo_out->write(data++); } }; #endif
#ifndef CONSUMER_H #define CONSUMER_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; SC_MODULE(Consumer) { sc_fifo_in<int> fifo_in;; SC_CTOR(Consumer) : fifo_in("fifo_in") { SC_THREAD(run); } void run() { wait(10, SC_NS); for (int i = 0; i < 4; i++) { int data; data = fifo_in->read(); cout << "Consumer received data = " << data << endl; } } }; #endif
#ifndef TOP_H #define TOP_H #include "producer.h" #include "consumer.h" SC_MODULE(Top) { Producer *producer; Consumer *consumer; sc_fifo<int> fifo; SC_CTOR(Top) : fifo("fifo", 5) { producer = new Producer("producer"); producer->fifo_out.bind(fifo); consumer = new Consumer("consumer"); consumer->fifo_in.bind(fifo); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_start(); return 0; }
Shows the SC_HAS_PROCESS macro used both outside the constructor (module counter) and inside the constructor (module Top)
Run this example in EDA Playground
#ifndef COUNTER_H #define COUNTER_H #include "systemc" using namespace sc_core; SC_MODULE(counter) { sc_out<int> count; sc_in<bool> clock; counter(sc_module_name _name, int _size) : sc_module(_name), m_size(_size) { SC_THREAD(run); sensitive << clock.pos(); } void run(); SC_HAS_PROCESS(counter); int m_size; }; // Parameterized counter counts up from 0 to m_size-1 void counter::run() { count.write(0); wait(); while (true) { count.write( (count.read() + 1) % m_size ); wait(); } } #endif
#ifndef TOP_H #define TOP_H #include "counter.h" SC_MODULE(Top) { counter *m_counter; sc_signal<int> count; sc_clock clock; Top(sc_module_name _name) : sc_module(_name), count("count"), clock("clock") { // Instantiate parameterized counter module m_counter = new counter("m_counter", 16); m_counter->clock.bind(clock); m_counter->count.bind(count); // SC_HAS_PROCESS can be placed in a module class or its constructor SC_HAS_PROCESS(Top); SC_THREAD(run); sensitive << clock.posedge_event(); } void run() { wait(); while (true) { wait(); std::cout << "count = " << count.read() << std::endl; } } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_start(20, SC_NS); return 0; }
Example showing basic operations on the sc_int data type
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #include "systemc" using namespace sc_core; SC_MODULE(Test) { SC_CTOR(Test) { SC_THREAD(run); } void run() { // A regular C++ int int i; i = 0x1234; sc_assert( i == 0x1234 ); // An 8-bit sc_int sc_dt::sc_int<8> i8; // sc_int<8> value truncated to 8 bits i8 = 0x1234; sc_assert( i8 == 0x34 ); // Assignment from int to sc_int<8> i8 = i; sc_assert( i8 == 0x34 ); // Assignment from sc_int<8> to int i = i8; sc_assert( i == 0x34 ); // Picking one bit from an sc_int sc_assert( i8[0] == 0 ); // Picking a range of bits from an sc_int sc_assert( i8.range(7,4) == 0x3 ); // Initialization to an int value sc_dt::sc_int<16> i16(1); sc_assert( i16 == 0x0001 ); // Addition of two sc_ints sc_assert( i8 + i16 == 0x0035 ); // Concatenation of two sc_ints sc_assert( concat(i8,i16) == 0x340001 ); // Conversion to string sc_assert( i8.to_string(sc_dt::SC_BIN) == "0b00110100" ); sc_assert( i8.to_string(sc_dt::SC_OCT) == "0o064" ); sc_assert( i8.to_string(sc_dt::SC_DEC) == "52" ); sc_assert( i8.to_string(sc_dt::SC_HEX) == "0x34" ); } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst"); sc_start(); std::cout << "Reached the end of sc_main" << std::endl; return 0; }
Example showing basic operations on the sc_logic and sc_lv data types
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #include "systemc" using namespace sc_core; SC_MODULE(Test) { SC_CTOR(Test) { SC_THREAD(run); } void run() { sc_dt::sc_logic a; sc_dt::sc_logic b; sc_dt::sc_logic c; sc_dt::sc_logic d; // Implicit conversions from char to sc_logic a = '0'; b = '1'; c = 'X'; d = 'Z'; // Built-in constants sc_assert( a == sc_dt::SC_LOGIC_0 ); sc_assert( b == sc_dt::SC_LOGIC_1 ); sc_assert( c == sc_dt::SC_LOGIC_X ); sc_assert( d == sc_dt::SC_LOGIC_Z ); // Operators sc_assert( (a & b) == sc_dt::SC_LOGIC_0 ); sc_assert( (a | b) == '1' ); // Logic vectors sc_dt::sc_lv<8> p; sc_dt::sc_lv<8> q; // Implicit conversion from string to sc_lv p = "111101XZ"; sc_assert( p[7] == '1' ); // Bit select sc_assert( p[6] == '1' ); sc_assert( p[5] == '1' ); sc_assert( p[4] == '1' ); sc_assert( p[3] == '0' ); sc_assert( p[2] == '1' ); sc_assert( p[1] == 'X' ); sc_assert( p[0] == 'Z' ); // Implicit conversion from string to sc_lv q = "0X34"; sc_assert( q == "00110100" ); // Bitwise operations sc_assert( (p & q) == "00110100" ); // AND sc_assert( (p | q) == "111101XX" ); // OR sc_assert( ~p == "000010XX" ); // NOT sc_assert( p.range(3,0) == "01XZ" ); // Part select sc_assert( p.range(0,3) == "ZX10" ); // Reverse bits sc_assert( concat(p,q) == "111101XZ00110100" ); // Concatentation // Conversion to string sc_assert( q.to_string() == "00110100" ); // Conversion to numerical representation sc_assert( q.to_string(sc_dt::SC_BIN) == "0b000110100" ); sc_assert( q.to_string(sc_dt::SC_OCT) == "0o064" ); sc_assert( q.to_string(sc_dt::SC_DEC) == "0d52" ); sc_assert( q.to_string(sc_dt::SC_HEX) == "0x034" ); // Conversion to int int i = q.to_int(); sc_assert( i == 52 ); // Various methods sc_assert( p.is_01() == 0 ); sc_assert( q.is_01() == 1 ); sc_assert( q.length() == 8 ); sc_assert( q.reverse() == "00101100" ); } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst"); sc_start(); std::cout << "Reached the end of sc_main" << std::endl; return 0; }
Shows calls to sc_pause and sc_get_status
Run this example in EDA Playground
#ifndef TOP_H #define TOP_H #include "systemc" using namespace sc_core; class Top: public sc_module { public: Top(sc_module_name name) { SC_HAS_PROCESS(Top); SC_THREAD(run); } void end_of_elaboration() { sc_assert( sc_get_status() == SC_END_OF_ELABORATION ); } void run() { sc_assert( sc_get_status() == SC_RUNNING ); wait(100, SC_NS); sc_pause(); wait(10, SC_NS); sc_pause(); wait(90, SC_NS); sc_stop(); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_assert( sc_get_status() == SC_ELABORATION ); sc_start(); sc_assert( sc_get_status() == SC_PAUSED ); sc_assert( sc_time_stamp() == sc_time(100, SC_NS) ); sc_start(); sc_assert( sc_get_status() == SC_PAUSED ); sc_assert( sc_time_stamp() == sc_time(110, SC_NS) ); sc_start(); sc_assert( sc_get_status() == SC_STOPPED ); sc_assert( sc_time_stamp() == sc_time(200, SC_NS) ); std::cout << "Reached the end of sc_main" << std::endl; }
Shows the use of the methods set_verbosity_level, set_actions, set_log_file_name, and stop_after of class sc_report_handler, and an example of catching the exception thrown by an error report
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #include "systemc" using namespace sc_core; SC_MODULE(Test) { SC_CTOR(Test) { SC_THREAD(run); // By default, an error report throws an exception try { SC_REPORT_ERROR("/EDAplayground", "The first ERROR report"); } catch (sc_exception& e) { SC_REPORT_INFO("/EDAplayground", "Caught exception from first ERROR report"); } // Suppress info messages with a verbosity above SC_HIGH (default is SC_MEDIUM) sc_report_handler::set_verbosity_level(SC_HIGH); // Save all warning reports to a log file irrespective of their message type sc_report_handler::set_actions(SC_WARNING, SC_DISPLAY | SC_LOG); sc_report_handler::set_log_file_name("warnings.txt"); // Suppress the default error actions, which are SC_LOG|SC_CACHE_REPORT|SC_THROW sc_report_handler::set_actions("/EDAplayground", SC_ERROR, SC_DO_NOTHING); // Set the error action to write the message to standard output sc_report_handler::set_actions("/EDAplayground", SC_ERROR, SC_DISPLAY); // Call sc_stop() after the fourth error report sc_report_handler::stop_after(SC_ERROR, 4); // Suppress the default fatal actions, which include SC_ABORT sc_report_handler::set_actions("/EDAplayground", SC_FATAL, SC_DO_NOTHING); } void run() { wait(1, SC_NS); SC_REPORT_INFO ("/EDAplayground", "This is an INFO report"); SC_REPORT_INFO_VERB("/EDAplayground", "This is a LOW INFO report", SC_LOW); SC_REPORT_INFO_VERB("/EDAplayground", "This is a HIGH INFO report", SC_HIGH); SC_REPORT_INFO_VERB("/EDAplayground", "This is a DEBUG INFO report", SC_DEBUG); SC_REPORT_WARNING ("/EDAplayground", "This is a WARNING report"); SC_REPORT_ERROR ("/EDAplayground", "This is an ERROR report"); SC_REPORT_FATAL ("/EDAplayground", "This is a FATAL report"); for (int i = 0; i < 10; i++) { wait(1, SC_NS); SC_REPORT_ERROR ("/EDAplayground", "This is another ERROR report"); } } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst"); sc_start(); return 0; }
An example of setting the simulation time resolution
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; SC_MODULE(Test) { SC_CTOR(Test) { SC_THREAD(thread); } void thread() { const sc_time period(10, SC_NS); for (;;) { wait(period); cout << "time = " << sc_time_stamp() << endl; cout << "deltas = " << sc_delta_count() << endl; } } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { sc_set_time_resolution(100, SC_PS); Test test("test"); sc_start(100, SC_NS); return 0; }
Shows the effect of the sc_signal writer policies SC_ONE_WRITER and SC_MANY_WRITERS
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #include "systemc" using namespace sc_core; struct Test: sc_module { // The default is SC_ONE_WRITER, the signal is only ever allowed one writer sc_signal<int> sig1; // This signal is allowed to have many writers provided the writes occur // in different delta cycles sc_signal<int, SC_MANY_WRITERS> sig_many; Test(sc_module_name n) : sig1("sig1"), sig_many("sig_many") { SC_HAS_PROCESS(Test); SC_THREAD(proc1); SC_THREAD(proc2); // Avoid aborting simulation on the first error sc_report_handler::set_actions(SC_ERROR, SC_DO_NOTHING); sc_report_handler::set_actions(SC_ERROR, SC_DISPLAY); } void proc1() { sig1.write(1); // Okay wait(1, SC_NS); sig_many.write(3); // Okay wait(1, SC_NS); sig_many.write(4); // Error at 2ns } void proc2() { sig_many.write(2); // Okay wait(1, SC_NS); sig1.write(4); // Error at 1ns wait(1, SC_NS); sig_many.write(6); // Error at 2ns } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst"); sc_start(); return 0; }
Shows sc_spawn being used to create two concurrent thread processes, each of which has one argument
Run this example in EDA Playground
#ifndef TOP_H #define TOP_H #define SC_INCLUDE_DYNAMIC_PROCESSES #include <systemc> using namespace sc_core; SC_MODULE(Top) { SC_CTOR(Top) { // Thread process f1 takes a reference argument sc_spawn( sc_bind(&Top::f1, this, sc_ref (n)), "f1"); // Thread process f2 takes a const reference argument sc_spawn(&final, sc_bind(&Top::f2, this, sc_cref(n)), "f2"); } int n, final; void f1(int &arg) { for (arg = 0; arg < 10; arg++) wait(10, SC_NS); } int f2(const int &arg) { wait(5, SC_NS); for (int i = 0; i < 10; i++) { wait(10, SC_NS); std::cout << "arg = " << arg << "\n"; } return arg; } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_start(); return 0; }
Shows the use of sc_spawn_options to spawn a method process with static sensitivity
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #define SC_INCLUDE_DYNAMIC_PROCESSES #include "systemc" using namespace sc_core; struct Test: sc_module { SC_CTOR(Test) { // Spawn a method process sensitive to 'a' and 'b' sc_spawn_options opt; opt.spawn_method(); opt.set_sensitivity( &a ); opt.set_sensitivity( &b ); opt.dont_initialize(); sc_spawn( sc_bind( &Test::method, this), "method", &opt ); // Spawn a thread process with a single pass-by-value argument sc_spawn( sc_bind( &Test::thread, this, 5), "thread"); } sc_signal<int> a, b; void method() { std::cout << "a = " << a << ", b = " << b << " at " << sc_time_stamp() << std:: endl; } void thread(int count) { int n = 0; for (int i = 0; i < count; i++) { wait(1, SC_NS); a.write(++n); wait(1, SC_NS); b.write(++n); } } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst"); sc_start(); return 0; }
Shows a vector-of-ports, a vector-of-signals, and vector-to-vector binding
Run this example in EDA Playground
#ifndef CHILD_H #define CHILD_H #include "systemc" using namespace sc_core; struct Child: sc_module { sc_vector< sc_in<int> > port_vec; // Vector of ports Child(sc_module_name n) : port_vec("port_vec", 4) { SC_HAS_PROCESS(Child); SC_METHOD(run); sensitive << port_vec[0] << port_vec[1] << port_vec[2] << port_vec[3]; dont_initialize(); } void run() { for (int i = 0; i < port_vec.size(); i++) std::cout << " " << port_vec[i]; std::cout << " at " << sc_time_stamp() << std::endl; } }; #endif
#ifndef TOP_H #define TOP_H #include "child.h" struct Top: sc_module { sc_vector< sc_signal<int> > sig_vec; // Vector of signals Child* c; Top(sc_module_name n) : sig_vec("sig_vec", 4) { c = new Child("c"); c->port_vec.bind(sig_vec); // Bind vector to vector SC_HAS_PROCESS(Top); SC_THREAD(run); } void run() { for (int base = 0; base < 4; base++) { for (int i = 0; i < sig_vec.size(); i++) sig_vec[i].write(base * sig_vec.size() + i); wait(1, SC_NS); } } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_start(); return 0; }
Shows the partial binding of an sc_vector and the use of sc_assemble_vector to assemble a vector-of-exports from a vector-of-modules where each module has one export
Run this example in EDA Playground
#ifndef CHANNEL_H #define CHANNEL_H #include "systemc" using namespace sc_core; using std::cout; using std::endl; class i_f: virtual public sc_interface { public: virtual void print(const sc_object& from) const = 0; }; class channel: virtual public i_f, public sc_object { public: channel(const char* name) : sc_object(name) {} virtual void print(const sc_object& from) const { cout << name() << " print called from " << from.name() << endl; } }; #endif
#ifndef MODULES_H #define MODULES_H #include "channel.h" typedef sc_vector< sc_port<i_f> > port_type; SC_MODULE(M) { port_type ports; SC_CTOR(M) { ports.init(8); SC_THREAD(run); } void run() { for (int i = 0; i < ports.size(); i++) { ports[i]->print(*this); } } }; class Target: public sc_module, public i_f { public: sc_export<i_f> xport; SC_CTOR(Target) : xport("xport") { xport.bind(*this); } virtual void print(const sc_object& from) const { cout << name() << " print called from " << from.name() << endl; } }; SC_MODULE(Top) { M *m1, *m2; sc_vector<channel> vec1; sc_vector<channel> vec2; sc_vector<Target> targets; SC_CTOR(Top) : vec1("vec1"), vec2("vec2") { vec1.init(4); vec2.init(4); m1 = new M("m1"); m2 = new M("m2"); port_type::iterator it; // Bind elements 0 to 3 of ports to vec1 it = m1->ports.bind(vec1); // Bind elements 4 to 7 of ports to vec2 it = m1->ports.bind(vec2.begin(), vec2.end(), it); targets.init(8); // Bind elements 0 to 7 of ports to xport in target 0 to 7 m2->ports.bind( sc_assemble_vector(targets, &Target::xport)); } }; #endif
#include "modules.h" int sc_main(int argc, char* argv[]) { Top top_inst("top_inst"); sc_start(); return 0; }
Shows an abstract task scheduler implemented using the methods suspend and resume of class sc_process_handle
Run this example in EDA Playground
#ifndef TEST_H #define TEST_H #define SC_INCLUDE_DYNAMIC_PROCESSES #include "systemc" using namespace sc_core; using std::cout; using std::endl; struct Test: sc_module { const int n; const sc_time timeslot; sc_process_handle* task_handle; // User-defined constructor argument n_threads Test(sc_module_name name, const int n_threads) : n(n_threads), timeslot(1, SC_US) { SC_HAS_PROCESS(Test); SC_THREAD(scheduler); // Spawn a set of thread processes task_handle = new sc_process_handle[n]; for (int i = 0; i < n; i++) task_handle[i] = sc_spawn(sc_bind(&Test::task, this , i)); } void scheduler() { for (int i = 0; i < n; i++) task_handle[i].suspend(); while (true) { int count_dead_processes = 0; // Switch between the running tasks for (int i = 0; i < n; i++) { if ( task_handle[i].terminated() ) count_dead_processes++; else { task_handle[i].resume(); wait(timeslot); task_handle[i].suspend(); } } // Quit once all the spawned processes are dead if (count_dead_processes == n) { SC_REPORT_INFO("/EDAplayground", "Scheduler has nothing left to do"); break; } } } void task(int number) { for (int i = 0; i < 6; i++) { cout << "Thread " << number << " busy at " << sc_time_stamp() << endl; sc_time busy_for; busy_for = sc_time(rand() % 800, SC_NS); wait(busy_for); cout << "Thread " << number << " done at " << sc_time_stamp() << endl; } } }; #endif
#include "test.h" int sc_main(int argc, char* argv[]) { Test test_inst("test_inst", 4); sc_start(); return 0; }
Shows the generic payload, sockets, and blocking transport interface. Shows the responsibilities of the initiator and target with respect to the generic payload
Run this example in EDA Playground
//---------------------------------------------------------------------- // Copyright (c) 2007-2008 by Doulos Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //---------------------------------------------------------------------- // Version 2 16-June-2008 - updated for TLM-2.0 // Getting Started with TLM-2.0, Tutorial Example 1 // For a full description, see https://www.doulos.com/knowhow/systemc/tlm2 // Shows the generic payload, sockets, and blocking transport interface. // Shows the responsibilities of initiator and target with respect to the generic payload. // Has only dummy implementations of the direct memory and debug transaction interfaces. // Does not show the non-blocking transport interface.
#ifndef INITIATOR_H #define INITIATOR_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_initiator_socket.h" // Initiator module generating generic payload transactions struct Initiator: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_initiator_socket<Initiator> socket; SC_CTOR(Initiator) : socket("socket") // Construct and name socket { SC_THREAD(thread_process); } void thread_process() { // TLM-2 generic payload transaction, reused across calls to b_transport tlm::tlm_generic_payload* trans = new tlm::tlm_generic_payload; sc_time delay = sc_time(10, SC_NS); // Generate a random sequence of reads and writes for (int i = 32; i < 96; i += 4) { tlm::tlm_command cmd = static_cast(rand() % 2); if (cmd == tlm::TLM_WRITE_COMMAND) data = 0xFF000000 | i; // Initialize 8 out of the 10 attributes, byte_enable_length and extensions being unused trans->set_command( cmd ); trans->set_address( i ); trans->set_data_ptr( reinterpret_cast(&data) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value socket->b_transport( *trans, delay ); // Blocking transport call // Initiator obliged to check response status and delay if ( trans->is_response_error() ) SC_REPORT_ERROR("TLM-2", "Response error from b_transport"); cout << "trans = { " << (cmd ? 'W' : 'R') << ", " << hex << i << " } , data = " << hex << data << " at time " << sc_time_stamp() << " delay = " << delay << endl; // Realize the delay annotated onto the transport call wait(delay); } } // Internal data buffer used by initiator with generic payload int data; }; #endif
#ifndef TARGET_H #define TARGET_H // Needed for the simple_target_socket #define SC_INCLUDE_DYNAMIC_PROCESSES #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_target_socket.h" // Target module representing a simple memory struct Memory: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_target_socket<Memory> socket; enum { SIZE = 256 }; SC_CTOR(Memory) : socket("socket") { // Register callback for incoming b_transport interface method call socket.register_b_transport(this, &Memory::b_transport); // Initialize memory with random data for (int i = 0; i < SIZE; i++) mem[i] = 0xAA000000 | (rand() % 256); } // TLM-2 blocking transport method virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay ) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); // Obliged to check address range and check for unsupported features, // i.e. byte enables, streaming, and bursts // Can ignore DMI hint and extensions // Using the SystemC report handler is an acceptable way of signalling an error if (adr >= sc_dt::uint64(SIZE) || byt != 0 || len > 4 || wid < len) SC_REPORT_ERROR("TLM-2", "Target does not support given generic payload transaction"); // Obliged to implement read and write commands if ( cmd == tlm::TLM_READ_COMMAND ) memcpy(ptr, &mem[adr], len); else if ( cmd == tlm::TLM_WRITE_COMMAND ) memcpy(&mem[adr], ptr, len); // Obliged to set response status to indicate successful completion trans.set_response_status( tlm::TLM_OK_RESPONSE ); } int mem[SIZE]; }; #endif
#ifndef TOP_H #define TOP_H #include "initiator.h" #include "target.h" SC_MODULE(Top) { Initiator *initiator; Memory *memory; SC_CTOR(Top) { // Instantiate components initiator = new Initiator("initiator"); memory = new Memory ("memory"); // One initiator is bound directly to one target with no intervening bus // Bind initiator socket to target socket initiator->socket.bind( memory->socket ); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); return 0; }
Shows the direct memory interfaces and the DMI hint. Shows the debug transaction interface. Shows the proper use of the response status
Run this example in EDA Playground
//---------------------------------------------------------------------- // Copyright (c) 2007-2008 by Doulos Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //---------------------------------------------------------------------- // Version 2 18-June-2008 - updated for TLM-2.0 // Version 3 3-July-2008 - bug fix: call dmi_data.init() // Version 4 12-Jan-2009 - fix bug in transport_dbg // Version 5 26-Sep-2009 - fix bug with set_end_address // Getting Started with TLM-2.0, Tutorial Example 2 // For a full description, see https://www.doulos.com/knowhow/systemc/tlm2 // Shows the direct memory interfaces and the DMI hint. // Shows the debug transaction interface // Shows the proper use of response status // Define the following macro to invoke an error response from the target // #define INJECT_ERROR
#ifndef INITIATOR_H #define INITIATOR_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_initiator_socket.h" // Initiator module generating generic payload transactions struct Initiator: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_initiator_socket<Initiator> socket; SC_CTOR(Initiator) : socket("socket"), // Construct and name socket dmi_ptr_valid(false) { // Register callbacks for incoming interface method calls socket.register_invalidate_direct_mem_ptr(this, &Initiator::invalidate_direct_mem_ptr); SC_THREAD(thread_process); } void thread_process() { // TLM-2 generic payload transaction, reused across calls to b_transport, DMI and debug tlm::tlm_generic_payload* trans = new tlm::tlm_generic_payload; sc_time delay = sc_time(10, SC_NS); // Generate a random sequence of reads and writes for (int i = 0; i < 128; i += 4) { int data; tlm::tlm_command cmd = static_cast(rand() % 2); if (cmd == tlm::TLM_WRITE_COMMAND) data = 0xFF000000 | i; // ********************************************* // Use DMI if it is available, reusing same transaction object // ********************************************* if (dmi_ptr_valid) { // Bypass transport interface and use direct memory interface // Implement target latency if ( cmd == tlm::TLM_READ_COMMAND ) { assert( dmi_data.is_read_allowed() ); memcpy(&data, dmi_data.get_dmi_ptr() + i, 4); wait( dmi_data.get_read_latency() ); } else if ( cmd == tlm::TLM_WRITE_COMMAND ) { assert( dmi_data.is_write_allowed() ); memcpy(dmi_data.get_dmi_ptr() + i, &data, 4); wait( dmi_data.get_write_latency() ); } cout << "DMI = { " << (cmd ? 'W' : 'R') << ", " << hex << i << " } , data = " << hex << data << " at time " << sc_time_stamp() << endl; } else { trans->set_command( cmd ); trans->set_address( i ); trans->set_data_ptr( reinterpret_cast(&data) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value #ifdef INJECT_ERROR if (i > 90) trans->set_streaming_width(2); #endif // Other fields default: byte enable = 0, streaming width = 0, DMI_hint = false, no extensions socket->b_transport( *trans, delay ); // Blocking transport call // Initiator obliged to check response status if ( trans->is_response_error() ) { // ********************************************* // Print response string // ********************************************* char txt[100]; sprintf(txt, "Error from b_transport, response status = %s", trans->get_response_string().c_str()); SC_REPORT_ERROR("TLM-2", txt); } // ********************************************* // Check DMI hint // ********************************************* if ( trans->is_dmi_allowed() ) { // Re-user transaction object for DMI dmi_data.init(); dmi_ptr_valid = socket->get_direct_mem_ptr( *trans, dmi_data ); } cout << "trans = { " << (cmd ? 'W' : 'R') << ", " << hex << i << " } , data = " << hex << data << " at time " << sc_time_stamp() << " delay = " << delay << endl; } } // ********************************************* // Use debug transaction interface to dump memory contents, reusing same transaction object // ********************************************* trans->set_address(0); trans->set_read(); trans->set_data_length(128); unsigned char* data = new unsigned char[128]; trans->set_data_ptr(data); unsigned int n_bytes = socket->transport_dbg( *trans ); for (unsigned int i = 0; i < n_bytes; i += 4) { cout << "mem[" << i << "] = " << *(reinterpret_cast( &data[i] )) << endl; } } // ********************************************* // TLM-2 backward DMI method // ********************************************* virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range, sc_dt::uint64 end_range) { // Ignore range and invalidate all DMI pointers regardless dmi_ptr_valid = false; } bool dmi_ptr_valid; tlm::tlm_dmi dmi_data; }; #endif
#ifndef TARGET_H #define TARGET_H // Needed for the simple_target_socket #define SC_INCLUDE_DYNAMIC_PROCESSES #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_target_socket.h" // Target module representing a simple memory struct Memory: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_target_socket<Memory> socket; enum { SIZE = 256 }; const sc_time LATENCY; SC_CTOR(Memory) : socket("socket"), LATENCY(10, SC_NS) { // Register callbacks for incoming interface method calls socket.register_b_transport( this, &Memory::b_transport); socket.register_get_direct_mem_ptr(this, &Memory::get_direct_mem_ptr); socket.register_transport_dbg( this, &Memory::transport_dbg); // Initialize memory with random data for (int i = 0; i < SIZE; i++) mem[i] = 0xAA000000 | (rand() % 256); SC_THREAD(invalidation_process); } // TLM-2 blocking transport method virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay ) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); // Obliged to check address range and check for unsupported features, // i.e. byte enables, streaming, and bursts // Can ignore extensions // ********************************************* // Generate the appropriate error response // ********************************************* if (adr >= sc_dt::uint64(SIZE)) { trans.set_response_status( tlm::TLM_ADDRESS_ERROR_RESPONSE ); return; } if (byt != 0) { trans.set_response_status( tlm::TLM_BYTE_ENABLE_ERROR_RESPONSE ); return; } if (len > 4 || wid < len) { trans.set_response_status( tlm::TLM_BURST_ERROR_RESPONSE ); return; } // Obliged to implement read and write commands if ( cmd == tlm::TLM_READ_COMMAND ) memcpy(ptr, &mem[adr], len); else if ( cmd == tlm::TLM_WRITE_COMMAND ) memcpy(&mem[adr], ptr, len); // Illustrates that b_transport may block wait(delay); // Reset timing annotation after waiting delay = SC_ZERO_TIME; // ********************************************* // Set DMI hint to indicated that DMI is supported // ********************************************* trans.set_dmi_allowed(true); // Obliged to set response status to indicate successful completion trans.set_response_status( tlm::TLM_OK_RESPONSE ); } // ********************************************* // TLM-2 forward DMI method // ********************************************* virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { // Permit read and write access dmi_data.allow_read_write(); // Set other details of DMI region dmi_data.set_dmi_ptr( reinterpret_cast( &mem[0] ) ); dmi_data.set_start_address( 0 ); dmi_data.set_end_address( SIZE*4-1 ); dmi_data.set_read_latency( LATENCY ); dmi_data.set_write_latency( LATENCY ); return true; } void invalidation_process() { // Invalidate DMI pointers periodically for (int i = 0; i < 4; i++) { wait(LATENCY*8); socket->invalidate_direct_mem_ptr(0, SIZE-1); } } // ********************************************* // TLM-2 debug transport method // ********************************************* virtual unsigned int transport_dbg(tlm::tlm_generic_payload& trans) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); // Calculate the number of bytes to be actually copied unsigned int num_bytes = (len < (SIZE - adr) * 4) ? len : (SIZE - adr) * 4; if ( cmd == tlm::TLM_READ_COMMAND ) memcpy(ptr, &mem[adr], num_bytes); else if ( cmd == tlm::TLM_WRITE_COMMAND ) memcpy(&mem[adr], ptr, num_bytes); return num_bytes; } int mem[SIZE]; }; #endif
#ifndef TOP_H #define TOP_H #include "initiator.h" #include "target.h" SC_MODULE(Top) { Initiator *initiator; Memory *memory; SC_CTOR(Top) { // Instantiate components initiator = new Initiator("initiator"); memory = new Memory ("memory"); // One initiator is bound directly to one target with no intervening bus // Bind initiator socket to target socket initiator->socket.bind(memory->socket); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); return 0; }
Shows a router modeled as an interconnect component between the initiator and the target. The router decodes the address to select a target, and masks the address in the transaction. Shows the router passing transport, DMI and debug transactions along forward and backward paths and doing address translation in both directions
Run this example in EDA Playground
//---------------------------------------------------------------------- // Copyright (c) 2007-2008 by Doulos Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //---------------------------------------------------------------------- // Version 2 - fix warnings that only showed up using g++ // Version 3 18-June-2008 - updated for TLM-2.0 // Version 4 12-Jan-2009 - fix bug in transport_dbg // Version 5 26-Sep-2009 - fix bug with set_end_address // Getting Started with TLM-2.0, Tutorial Example 3 // For a full description, see https://www.doulos.com/knowhow/systemc/tlm2 // Shows a router modeled as an interconnect component between the initiator and the target // The router decodes the address to select a target, and masks the address in the transaction // Shows the router passing transport, DMI and debug transactions along forward and backward paths // and doing address translation in both directions // Define the following macro to invoke an error response from the target // #define INJECT_ERROR
#ifndef INITIATOR_H #define INITIATOR_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_initiator_socket.h" // Initiator module generating generic payload transactions struct Initiator: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_initiator_socket<Initiator> socket; SC_CTOR(Initiator) : socket("socket"), // Construct and name socket dmi_ptr_valid(false) { // Register callbacks for incoming interface method calls socket.register_invalidate_direct_mem_ptr(this, &Initiator::invalidate_direct_mem_ptr); SC_THREAD(thread_process); } void thread_process() { // TLM-2 generic payload transaction, reused across calls to b_transport, DMI and debug tlm::tlm_generic_payload* trans = new tlm::tlm_generic_payload; sc_time delay = sc_time(10, SC_NS); // Generate a random sequence of reads and writes for (int i = 256-64; i < 256+64; i += 4) { int data; tlm::tlm_command cmd = static_cast(rand() % 2); if (cmd == tlm::TLM_WRITE_COMMAND) data = 0xFF000000 | i; // Use DMI if it is available if (dmi_ptr_valid && sc_dt::uint64(i) >= dmi_data.get_start_address() && sc_dt::uint64(i) <= dmi_data.get_end_address()) { // Bypass transport interface and use direct memory interface // Implement target latency if ( cmd == tlm::TLM_READ_COMMAND ) { assert( dmi_data.is_read_allowed() ); memcpy(&data, dmi_data.get_dmi_ptr() + i - dmi_data.get_start_address(), 4); wait( dmi_data.get_read_latency() ); } else if ( cmd == tlm::TLM_WRITE_COMMAND ) { assert( dmi_data.is_write_allowed() ); memcpy(dmi_data.get_dmi_ptr() + i - dmi_data.get_start_address(), &data, 4); wait( dmi_data.get_write_latency() ); } cout << "DMI = { " << (cmd ? 'W' : 'R') << ", " << hex << i << " } , data = " << hex << data << " at time " << sc_time_stamp() << endl; } else { trans->set_command( cmd ); trans->set_address( i ); trans->set_data_ptr( reinterpret_cast(&data) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value #ifdef INJECT_ERROR if (i > 90) trans->set_streaming_width(2); #endif // Other fields default: byte enable = 0, streaming width = 0, DMI_hint = false, no extensions socket->b_transport( *trans, delay ); // Blocking transport call // Initiator obliged to check response status if ( trans->is_response_error() ) { // Print response string char txt[100]; sprintf(txt, "Error from b_transport, response status = %s", trans->get_response_string().c_str()); SC_REPORT_ERROR("TLM-2", txt); } // Check DMI hint if ( trans->is_dmi_allowed() ) { // ********************************************* // Re-use transaction object for DMI. Reset the address because it could // have been modified by the interconnect on the previous transport call // ********************************************* trans->set_address( i ); dmi_ptr_valid = socket->get_direct_mem_ptr( *trans, dmi_data ); } cout << "trans = { " << (cmd ? 'W' : 'R') << ", " << hex << i << " } , data = " << hex << data << " at time " << sc_time_stamp() << endl; } } // Use debug transaction interface to dump memory contents, reusing same transaction object sc_dt::uint64 A = 128; trans->set_address(A); trans->set_read(); trans->set_data_length(256); unsigned char* data = new unsigned char[256]; trans->set_data_ptr(data); unsigned int n_bytes = socket->transport_dbg( *trans ); for (unsigned int i = 0; i < n_bytes; i += 4) { cout << "mem[" << (A + i) << "] = " << *(reinterpret_cast( &data[i] )) << endl; } A = 256; trans->set_address(A); trans->set_data_length(128); n_bytes = socket->transport_dbg( *trans ); for (unsigned int i = 0; i < n_bytes; i += 4) { cout << "mem[" << (A + i) << "] = " << *(reinterpret_cast( &data[i] )) << endl; } } // TLM-2 backward DMI method virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range, sc_dt::uint64 end_range) { // Ignore range and invalidate all DMI pointers regardless dmi_ptr_valid = false; } bool dmi_ptr_valid; tlm::tlm_dmi dmi_data; }; #endif
#ifndef ROUTER_H #define ROUTER_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/simple_target_socket.h" // ********************************************* // Generic payload blocking transport router // ********************************************* template<unsigned int N_TARGETS> struct Router: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_target_socket<Router> target_socket; // ********************************************* // Use tagged sockets to be able to distinguish incoming backward path calls // ********************************************* tlm_utils::simple_initiator_socket_tagged<Router>* initiator_socket[N_TARGETS]; SC_CTOR(Router) : target_socket("target_socket") { // Register callbacks for incoming interface method calls target_socket.register_b_transport( this, &Router::b_transport); target_socket.register_get_direct_mem_ptr(this, &Router::get_direct_mem_ptr); target_socket.register_transport_dbg( this, &Router::transport_dbg); for (unsigned int i = 0; i < N_TARGETS; i++) { char txt[20]; sprintf(txt, "socket_%d", i); initiator_socket[i] = new tlm_utils::simple_initiator_socket_tagged<Router>(txt); // ********************************************* // Register callbacks for incoming interface method calls, including tags // ********************************************* initiator_socket[i]->register_invalidate_direct_mem_ptr(this, &Router::invalidate_direct_mem_ptr, i); } } // **************** // FORWARD PATH // **************** // TLM-2 blocking transport method virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay ) { sc_dt::uint64 address = trans.get_address(); sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( address, masked_address); // Modify address within transaction trans.set_address( masked_address ); // Forward transaction to appropriate target ( *initiator_socket[target_nr] )->b_transport( trans, delay ); } // TLM-2 forward DMI method virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); trans.set_address( masked_address ); bool status = ( *initiator_socket[target_nr] )->get_direct_mem_ptr( trans, dmi_data ); // Calculate DMI address of target in system address space dmi_data.set_start_address( compose_address( target_nr, dmi_data.get_start_address() )); dmi_data.set_end_address ( compose_address( target_nr, dmi_data.get_end_address() )); return status; } // TLM-2 debug transaction method virtual unsigned int transport_dbg(tlm::tlm_generic_payload& trans) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); trans.set_address( masked_address ); // Forward debug transaction to appropriate target return ( *initiator_socket[target_nr] )->transport_dbg( trans ); } // **************** // BACKWARD PATH // **************** // ************************** // Tagged backward DMI method // ************************** virtual void invalidate_direct_mem_ptr(int id, sc_dt::uint64 start_range, sc_dt::uint64 end_range) { // Reconstruct address range in system memory map sc_dt::uint64 bw_start_range = compose_address( id, start_range ); sc_dt::uint64 bw_end_range = compose_address( id, end_range ); target_socket->invalidate_direct_mem_ptr(bw_start_range, bw_end_range); } // **************** // ROUTER INTERNALS // **************** // Simple fixed address decoding inline unsigned int decode_address( sc_dt::uint64 address, sc_dt::uint64& masked_address ) { unsigned int target_nr = static_cast( (address >> 8) & 0x3 ); masked_address = address & 0xFF; return target_nr; } inline sc_dt::uint64 compose_address( unsigned int target_nr, sc_dt::uint64 address) { return (target_nr << 8) | (address & 0xFF); } }; #endif
#ifndef TARGET_H #define TARGET_H // Needed for the simple_target_socket #define SC_INCLUDE_DYNAMIC_PROCESSES #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" #include "tlm_utils/simple_target_socket.h" // Target module representing a simple memory struct Memory: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_target_socket<Memory> socket; enum { SIZE = 256 }; const sc_time LATENCY; SC_CTOR(Memory) : socket("socket"), LATENCY(10, SC_NS) { // Register callbacks for incoming interface method calls socket.register_b_transport( this, &Memory::b_transport); socket.register_get_direct_mem_ptr(this, &Memory::get_direct_mem_ptr); socket.register_transport_dbg( this, &Memory::transport_dbg); // Initialize memory with random data for (int i = 0; i < SIZE; i++) mem[i] = 0xAA000000 | (mem_nr << 20) | (rand() % 256); ++mem_nr; } // TLM-2 blocking transport method virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay ) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); // Obliged to check address range and check for unsupported features, // i.e. byte enables, streaming, and bursts // Can ignore extensions // Generate the appropriate error response if (adr >= SIZE) { trans.set_response_status( tlm::TLM_ADDRESS_ERROR_RESPONSE ); return; } if (byt != 0) { trans.set_response_status( tlm::TLM_BYTE_ENABLE_ERROR_RESPONSE ); return; } if (len > 4 || wid < len) { trans.set_response_status( tlm::TLM_BURST_ERROR_RESPONSE ); return; } wait(delay); delay = SC_ZERO_TIME; // Obliged to implement read and write commands if ( cmd == tlm::TLM_READ_COMMAND ) memcpy(ptr, &mem[adr], len); else if ( cmd == tlm::TLM_WRITE_COMMAND ) memcpy(&mem[adr], ptr, len); // Set DMI hint to indicated that DMI is supported trans.set_dmi_allowed(true); // Obliged to set response status to indicate successful completion trans.set_response_status( tlm::TLM_OK_RESPONSE ); } // TLM-2 forward DMI method virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { // Permit read and write access dmi_data.allow_read_write(); // Set other details of DMI region dmi_data.set_dmi_ptr( reinterpret_cast( &mem[0] ) ); dmi_data.set_start_address( 0 ); dmi_data.set_end_address( SIZE*4-1 ); dmi_data.set_read_latency( LATENCY ); dmi_data.set_write_latency( LATENCY ); return true; } // TLM-2 debug transaction method virtual unsigned int transport_dbg(tlm::tlm_generic_payload& trans) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); // Calculate the number of bytes to be actually copied unsigned int num_bytes = (len < (SIZE - adr) * 4) ? len : (SIZE - adr) * 4; if ( cmd == tlm::TLM_READ_COMMAND ) memcpy(ptr, &mem[adr], num_bytes); else if ( cmd == tlm::TLM_WRITE_COMMAND ) memcpy(&mem[adr], ptr, num_bytes); return num_bytes; } int mem[SIZE]; static unsigned int mem_nr; }; unsigned int Memory::mem_nr = 0; #endif
#ifndef TOP_H #define TOP_H #include "initiator.h" #include "target.h" #include "router.h" SC_MODULE(Top) { Initiator* initiator; Router<4>* router; Memory* memory[4]; SC_CTOR(Top) { // Instantiate components initiator = new Initiator("initiator"); router = new Router<4>("router"); for (int i = 0; i < 4; i++) { char txt[20]; sprintf(txt, "memory_%d", i); memory[i] = new Memory(txt); } // Bind sockets initiator->socket.bind( router->target_socket ); for (int i = 0; i < 4; i++) router->initiator_socket[i]->bind( memory[i]->socket ); } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); return 0; }
Shows the non-blocking transport interface with the generic payload and simple sockets
Run this example in EDA Playground
//---------------------------------------------------------------------- // Copyright (c) 2007-2008 by Doulos Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //---------------------------------------------------------------------- // Version 2 19-June-2008 - updated for TLM-2.0 // Getting Started with TLM-2.0, Example 4 // Shows the non-blocking transport interface with the generic payload and simple sockets // Shows nb_transport used with the forward and backward paths // Both components are able to accept transactions on the return path, // although neither component actually uses the return path (TLM_UPDATED) // Shows the Approximately Timed coding style // Models processing delay of initiator, latency of target, and request and response accept delays // Uses payload event queues to manage both timing annotations and internal delays // Shows the BEGIN_REQ exclusion rule at the initiator and BEGIN_RESP exclusion rule at the target // In this example, the target allows two pipelined transactions in-flight // Shows an explicit memory manager and reference counting // No use of temporal decoupling, DMI or debug transport // Nominal use of the blocking transport interface just to show the simple socket b/nb adapter
#ifndef UTILITIES_H #define UTILITIES_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" static ofstream fout("output.txt"); // ************************************************************************************** // User-defined memory manager, which maintains a pool of transactions // ************************************************************************************** class mm: public tlm::tlm_mm_interface { typedef tlm::tlm_generic_payload gp_t; public: mm() : free_list(0), empties(0) #ifdef DEBUG , count(0) #endif {} gp_t* allocate(); void free(gp_t* trans); private: struct access { gp_t* trans; access* next; access* prev; }; access* free_list; access* empties; #ifdef DEBUG int count; #endif }; mm::gp_t* mm::allocate() { #ifdef DEBUG fout << "----------------------------- Called allocate(), #trans = " << ++count << endl; #endif gp_t* ptr; if (free_list) { ptr = free_list->trans; empties = free_list; free_list = free_list->next; } else { ptr = new gp_t(this); } return ptr; } void mm::free(gp_t* trans) { #ifdef DEBUG fout << "----------------------------- Called free(), #trans = " << --count << endl; #endif if (!empties) { empties = new access; empties->next = free_list; empties->prev = 0; if (free_list) free_list->prev = empties; } free_list = empties; free_list->trans = trans; empties = free_list->prev; } // Generate a random delay (with power-law distribution) to aid testing and stress the protocol int rand_ps() { int n = rand() % 100; n = n * n * n; return n / 100; } #endif
#ifndef INITIATOR_H #define INITIATOR_H #include "utilities.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/peq_with_cb_and_phase.h" // ************************************************************************************** // Initiator module generating multiple pipelined generic payload transactions // ************************************************************************************** struct Initiator: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_initiator_socket<Initiator> socket; SC_CTOR(Initiator) : socket("socket") // Construct and name socket , request_in_progress(0) , m_peq(this, &Initiator::peq_cb) { // Register callbacks for incoming interface method calls socket.register_nb_transport_bw(this, &Initiator::nb_transport_bw); SC_THREAD(thread_process); } void thread_process() { tlm::tlm_generic_payload* trans; tlm::tlm_phase phase; sc_time delay; // Generate a sequence of random transactions for (int i = 0; i < 1000; i++) { int adr = rand(); tlm::tlm_command cmd = static_cast(rand() % 2); if (cmd == tlm::TLM_WRITE_COMMAND) data[i % 16] = rand(); // Grab a new transaction from the memory manager trans = m_mm.allocate(); trans->acquire(); // Set all attributes except byte_enable_length and extensions (unused) trans->set_command( cmd ); trans->set_address( adr ); trans->set_data_ptr( reinterpret_cast(&data[i % 16]) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value // Initiator must honor BEGIN_REQ/END_REQ exclusion rule if (request_in_progress) wait(end_request_event); request_in_progress = trans; phase = tlm::BEGIN_REQ; // Timing annotation models processing time of initiator prior to call delay = sc_time(rand_ps(), SC_PS); fout << hex << adr << " new, cmd=" << (cmd ? 'W' : 'R') << ", data=" << hex << data[i % 16] << " at time " << sc_time_stamp() << endl; // Non-blocking transport call on the forward path tlm::tlm_sync_enum status; status = socket->nb_transport_fw( *trans, phase, delay ); // Check value returned from nb_transport_fw if (status == tlm::TLM_UPDATED) { // The timing annotation must be honored m_peq.notify( *trans, phase, delay ); } else if (status == tlm::TLM_COMPLETED) { // The completion of the transaction necessarily ends the BEGIN_REQ phase request_in_progress = 0; // The target has terminated the transaction check_transaction( *trans ); } wait( sc_time(rand_ps(), SC_PS) ); } wait(100, SC_NS); // Allocate a transaction for one final, nominal call to b_transport trans = m_mm.allocate(); trans->acquire(); trans->set_command( tlm::TLM_WRITE_COMMAND ); trans->set_address( 0 ); trans->set_data_ptr( reinterpret_cast(&data[0]) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value delay = sc_time(rand_ps(), SC_PS); fout << "Calling b_transport at " << sc_time_stamp() << " with delay = " << delay << endl; // Call b_transport to demonstrate the b/nb conversion by the simple_target_socket socket->b_transport( *trans, delay ); check_transaction( *trans ); } // TLM-2 backward non-blocking transport method virtual tlm::tlm_sync_enum nb_transport_bw( tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay ) { // The timing annotation must be honored m_peq.notify( trans, phase, delay ); return tlm::TLM_ACCEPTED; } // Payload event queue callback to handle transactions from target // Transaction could have arrived through return path or backward path void peq_cb(tlm::tlm_generic_payload& trans, const tlm::tlm_phase& phase) { #ifdef DEBUG if (phase == tlm::END_REQ) fout << hex << trans.get_address() << " END_REQ at " << sc_time_stamp() << endl; else if (phase == tlm::BEGIN_RESP) fout << hex << trans.get_address() << " BEGIN_RESP at " << sc_time_stamp() << endl; #endif if (phase == tlm::END_REQ || (&trans == request_in_progress && phase == tlm::BEGIN_RESP)) { // The end of the BEGIN_REQ phase request_in_progress = 0; end_request_event.notify(); } else if (phase == tlm::BEGIN_REQ || phase == tlm::END_RESP) SC_REPORT_FATAL("TLM-2", "Illegal transaction phase received by initiator"); if (phase == tlm::BEGIN_RESP) { check_transaction( trans ); // Send final phase transition to target tlm::tlm_phase fw_phase = tlm::END_RESP; sc_time delay = sc_time(rand_ps(), SC_PS); socket->nb_transport_fw( trans, fw_phase, delay ); // Ignore return value } } // Called on receiving BEGIN_RESP or TLM_COMPLETED void check_transaction(tlm::tlm_generic_payload& trans) { if ( trans.is_response_error() ) { char txt[100]; sprintf(txt, "Transaction returned with error, response status = %s", trans.get_response_string().c_str()); SC_REPORT_ERROR("TLM-2", txt); } tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address(); int* ptr = reinterpret_cast<int*>( trans.get_data_ptr() ); fout<< hex << adr << " check, cmd=" << (cmd ? 'W' : 'R') << ", data=" << hex << *ptr << " at time " << sc_time_stamp() << endl; // Allow the memory manager to free the transaction object trans.release(); } mm m_mm; int data[16]; tlm::tlm_generic_payload* request_in_progress; sc_event end_request_event; tlm_utils::peq_with_cb_and_phase<Initiator> m_peq; }; #endif </int*>
#ifndef TARGET_H #define TARGET_H // Needed for the simple_target_socket #define SC_INCLUDE_DYNAMIC_PROCESSES #include "utilities.h" #include "tlm_utils/simple_target_socket.h" // ************************************************************************************** // Target module able to handle two pipelined transactions // ************************************************************************************** DECLARE_EXTENDED_PHASE(internal_ph); struct Target: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_target_socket<Target> socket; SC_CTOR(Target) : socket("socket") , n_trans(0) , response_in_progress(false) , next_response_pending(0) , end_req_pending(0) , m_peq(this, &Target::peq_cb) { // Register callbacks for incoming interface method calls socket.register_nb_transport_fw(this, &Target::nb_transport_fw); } // TLM-2 non-blocking transport method virtual tlm::tlm_sync_enum nb_transport_fw( tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay ) { sc_dt::uint64 adr = trans.get_address(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); // Obliged to check the transaction attributes for unsupported features // and to generate the appropriate error response if (byt != 0) { trans.set_response_status( tlm::TLM_BYTE_ENABLE_ERROR_RESPONSE ); return tlm::TLM_COMPLETED; } if (len > 4 || wid < len) { trans.set_response_status( tlm::TLM_BURST_ERROR_RESPONSE ); return tlm::TLM_COMPLETED; } // Now queue the transaction until the annotated time has elapsed m_peq.notify( trans, phase, delay); return tlm::TLM_ACCEPTED; } void peq_cb(tlm::tlm_generic_payload& trans, const tlm::tlm_phase& phase) { tlm::tlm_sync_enum status; sc_time delay; switch (phase) { case tlm::BEGIN_REQ: #ifdef DEBUG fout << hex << trans.get_address() << " BEGIN_REQ at " << sc_time_stamp() << endl; #endif // Increment the transaction reference count trans.acquire(); // Put back-pressure on initiator by deferring END_REQ until pipeline is clear if (n_trans == 2) end_req_pending = &trans; else { status = send_end_req(trans); if (status == tlm::TLM_COMPLETED) // It is questionable whether this is valid break; } break; case tlm::END_RESP: // On receiving END_RESP, the target can release the transaction // and allow other pending transactions to proceed #ifdef DEBUG fout << hex << trans.get_address() << " END_RESP at " << sc_time_stamp() << endl; #endif if (!response_in_progress) SC_REPORT_FATAL("TLM-2", "Illegal transaction phase END_RESP received by target"); trans.release(); n_trans--; // Target itself is now clear to issue the next BEGIN_RESP response_in_progress = false; if (next_response_pending) { send_response( *next_response_pending ); next_response_pending = 0; } // ... and to unblock the initiator by issuing END_REQ if (end_req_pending) { status = send_end_req( *end_req_pending ); end_req_pending = 0; } break; case tlm::END_REQ: case tlm::BEGIN_RESP: SC_REPORT_FATAL("TLM-2", "Illegal transaction phase received by target"); break; default: if (phase == internal_ph) { // Execute the read or write commands tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address(); unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); if ( cmd == tlm::TLM_READ_COMMAND ) { *reinterpret_cast<int*>(ptr) = rand(); fout << hex << adr << " Execute READ, data = " << *reinterpret_cast<int*>(ptr) << endl; } else if ( cmd == tlm::TLM_WRITE_COMMAND ) fout << hex << adr << " Execute WRITE, data = " << *reinterpret_cast<int*>(ptr) << endl; trans.set_response_status( tlm::TLM_OK_RESPONSE ); // Target must honor BEGIN_RESP/END_RESP exclusion rule // i.e. must not send BEGIN_RESP until receiving previous END_RESP or BEGIN_REQ if (response_in_progress) { // Target allows only two transactions in-flight if (next_response_pending) SC_REPORT_FATAL("TLM-2", "Attempt to have two pending responses in target"); next_response_pending = &trans; } else send_response(trans); break; } } } tlm::tlm_sync_enum send_end_req(tlm::tlm_generic_payload& trans) { tlm::tlm_sync_enum status; tlm::tlm_phase bw_phase; tlm::tlm_phase int_phase = internal_ph; sc_time delay; // Queue the acceptance and the response with the appropriate latency bw_phase = tlm::END_REQ; delay = sc_time(rand_ps(), SC_PS); // Accept delay status = socket->nb_transport_bw( trans, bw_phase, delay ); if (status == tlm::TLM_COMPLETED) { // Transaction aborted by the initiator // (TLM_UPDATED cannot occur at this point in the base protocol, so need not be checked) trans.release(); return status; } // Queue internal event to mark beginning of response delay = delay + sc_time(rand_ps(), SC_PS); // Latency m_peq.notify( trans, int_phase, delay ); n_trans++; return status; } void send_response(tlm::tlm_generic_payload& trans) { tlm::tlm_sync_enum status; tlm::tlm_phase bw_phase; sc_time delay; response_in_progress = true; bw_phase = tlm::BEGIN_RESP; delay = SC_ZERO_TIME; status = socket->nb_transport_bw( trans, bw_phase, delay ); if (status == tlm::TLM_UPDATED) { // The timing annotation must be honored m_peq.notify( trans, bw_phase, delay); } else if (status == tlm::TLM_COMPLETED) { // The initiator has terminated the transaction trans.release(); n_trans--; response_in_progress = false; } } int n_trans; bool response_in_progress; tlm::tlm_generic_payload* next_response_pending; tlm::tlm_generic_payload* end_req_pending; tlm_utils::peq_with_cb_and_phase<Target> m_peq; }; #endif </int*></int*></int*>
#ifndef TOP_H #define TOP_H #include "initiator.h" #include "target.h" SC_MODULE(Top) { Initiator *initiator; Target *target; SC_CTOR(Top) { // Instantiate components initiator = new Initiator("initiator"); target = new Target ("target"); // One initiator is bound directly to one target with no intervening bus // Bind initiator socket to target socket initiator->socket.bind(target->socket); } }; #endif
#define DEBUG #include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); cout << "\n***** Messages have been written to file output.txt *****\n"; cout << "***** Select 'Download files after run' to read file in EDA Playground *****\n\n"; return 0; }
Shows two loosely-timed initiators both of which use the quantum keeper to keep track of temporal decoupling
Run this example in EDA Playground
//---------------------------------------------------------------------- // Copyright (c) 2007-2008 by Doulos Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //---------------------------------------------------------------------- // Version 1, 26-June-2008 // Version 2, 3-July-2008 - fix bug: call dmi_data.init() // Version 3 12-Jan-2009 - fix bug in transport_dbg // Version 4 26-Sep-2009 - fix bug with set_end_address // Getting Started with TLM-2.0, Example 5 // Shows two loosely-timed initiators both with temporal decoupling and quantum keeper // Shows a bus with multiple initiators and multiple targets (four memories) // Routes transactions to target and back using address decoding built into the bus // Uses tagged interfaces and sockets to implement multiple fw/bw interfaces in a single module // Propagates DMI calls on both forward and backward paths, // with 'invalidate' being broadcast to every initiator // Shows transaction pooling using a memory manager
#ifndef UTILITIES_H #define UTILITIES_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" // ************************************************************************************** // User-defined memory manager, which maintains a pool of transactions // ************************************************************************************** class mm: public tlm::tlm_mm_interface { typedef tlm::tlm_generic_payload gp_t; public: mm() : free_list(0), empties(0) {} gp_t* allocate(); void free(gp_t* trans); private: struct access { gp_t* trans; access* next; access* prev; }; access* free_list; access* empties; }; mm::gp_t* mm::allocate() { gp_t* ptr; if (free_list) { ptr = free_list->trans; empties = free_list; free_list = free_list->next; } else { ptr = new gp_t(this); } return ptr; } void mm::free(gp_t* trans) { if (!empties) { empties = new access; empties->next = free_list; empties->prev = 0; if (free_list) free_list->prev = empties; } free_list = empties; free_list->trans = trans; empties = free_list->prev; } #endif
#ifndef INITIATOR1_H #define INITIATOR1_H #include "utilities.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/tlm_quantumkeeper.h" // ***************************************************************************************** // Initiator1 writes to all 4 memories, and demonstrates DMI and debug transport // Does not use an explicit memory manager // ***************************************************************************************** const int RUN_LENGTH = 256; struct Initiator1: sc_module { tlm_utils::simple_initiator_socket<Initiator1> socket; SC_CTOR(Initiator1) : socket("socket"), dmi_ptr_valid(false) { socket.register_invalidate_direct_mem_ptr(this, &Initiator1::invalidate_direct_mem_ptr); SC_THREAD(thread_process); // ************************************************************************* // All initiators use a quantum of 1us, that is, they synchronize themselves // to simulation time every 1us using the quantum keeper // ************************************************************************* m_qk.set_global_quantum( sc_time(1, SC_US) ); m_qk.reset(); } void thread_process() { // Use debug transaction interface to dump entire memory contents dump(); tlm::tlm_generic_payload* trans = new tlm::tlm_generic_payload; sc_time delay; for (int i = 0; i < RUN_LENGTH; i += 4) { data = i; delay = m_qk.get_local_time(); if (dmi_ptr_valid && sc_dt::uint64(i) >= dmi_data.get_start_address() && sc_dt::uint64(i) <= dmi_data.get_end_address()) { // Bypass transport interface and use direct memory interface assert( dmi_data.is_write_allowed() ); memcpy(dmi_data.get_dmi_ptr() + i - dmi_data.get_start_address(), &data, 4); // Accumulate memory latency into local time delay += dmi_data.get_write_latency(); cout << "WRITE/DMI addr = " << hex << i << ", data = " << data << " at " << sc_time_stamp() << " delay = " << delay << "\n"; } else { // No DMI, so use blocking transport interface trans->set_command( tlm::TLM_WRITE_COMMAND ); trans->set_address( i ); trans->set_data_ptr( reinterpret_cast(&data) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value socket->b_transport( *trans, delay ); cout << "WRITE addr = " << hex << i << ", data = " << data << " at " << sc_time_stamp() << " delay = " << delay << "\n"; // Initiator obliged to check response status if (trans->is_response_error()) SC_REPORT_ERROR("TLM-2", trans->get_response_string().c_str()); if ( trans->is_dmi_allowed() ) { dmi_data.init(); dmi_ptr_valid = socket->get_direct_mem_ptr( *trans, dmi_data ); } } // Accumulate local time and synchronize when quantum is reached m_qk.set( delay ); m_qk.inc( sc_time(100, SC_NS) ); // Model time used for additional processing if (m_qk.need_sync()) m_qk.sync(); } // Use debug transaction interface to dump entire memory contents dump(); } virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range, sc_dt::uint64 end_range) { cout << "INVALIDATE DMI (" << start_range << ".." << end_range << ") for Initiator1 at " << sc_time_stamp() << "\n"; // Ignore range and invalidate all DMI pointers regardless dmi_ptr_valid = false; } void dump() { unsigned char buffer[64]; // Use debug transaction interface to dump memory contents cout << "\nDump memories at time " << sc_time_stamp() << "\n"; for (unsigned int k = 0; k < 4; k++) { tlm::tlm_generic_payload dbg; sc_dt::uint64 A = 64 * k; dbg.set_address(A); dbg.set_read(); dbg.set_data_length(64); dbg.set_data_ptr(buffer); unsigned int n_bytes = socket->transport_dbg( dbg ); for (unsigned int i = 0; i < n_bytes; i += 4) { cout << "mem[" << hex << (A + i) << "] = " << *(reinterpret_cast( &buffer[i] )) << endl; } } cout << "\n"; } int data; // Internal data buffer used by initiator with generic payload tlm_utils::tlm_quantumkeeper m_qk; // Quantum keeper for temporal decoupling bool dmi_ptr_valid; tlm::tlm_dmi dmi_data; }; #endif
#ifndef INITIATOR2_H #define INITIATOR2_H #include "utilities.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/tlm_quantumkeeper.h" // ***************************************************************************************** // Initiator2 reads from all 4 memories, but does not use DMI or debug transport // Uses an explicit memory manager and transaction pool // ***************************************************************************************** struct Initiator2: sc_module { tlm_utils::simple_initiator_socket<Initiator2> socket; SC_CTOR(Initiator2) : socket("socket") { // No callback methods registered with socket SC_THREAD(thread_process); } void thread_process() { tlm::tlm_generic_payload* trans; sc_time delay; // Reset the local quantum keeper m_qk.reset(); wait(1, SC_US); for (int i = 0; i < RUN_LENGTH; i += 4) { // Grab a new transaction from the memory manager trans = m_mm.allocate(); trans->acquire(); data = i; trans->set_command( tlm::TLM_READ_COMMAND ); trans->set_address( i ); trans->set_data_ptr( reinterpret_cast(&data) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value delay = m_qk.get_local_time(); socket->b_transport( *trans, delay ); // Initiator obliged to check response status if (trans->is_response_error()) SC_REPORT_ERROR("TLM-2", trans->get_response_string().c_str()); if (data != i) SC_REPORT_ERROR("TLM-2", "Mismatch in initiator when reading back data"); cout << "READ addr = " << hex << i << ", data = " << data << " at " << sc_time_stamp() << " delay = " << delay << "\n"; trans->release(); // Accumulate local time and synchronize when quantum is reached m_qk.set( delay ); m_qk.inc( sc_time(100, SC_NS) );// Model time used for additional processing if (m_qk.need_sync()) m_qk.sync(); } } int data; // Internal data buffer used by initiator with generic payload mm m_mm; // Memory manager tlm_utils::tlm_quantumkeeper m_qk; // Quantum keeper for temporal decoupling }; #endif
#ifndef BUS_H #define BUS_H #include "utilities.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/simple_target_socket.h" // ************************************************************************************ // Bus model supports multiple initiators and multiple targets // Supports b_ and nb_ transport interfaces, although only b_transport is actually used // It does no arbitration, but routes all transactions from initiators without blocking // It uses a simple built-in routing algorithm // ************************************************************************************ template<unsigned int N_INITIATORS, unsigned int N_TARGETS> struct Bus: sc_module { // Tagged sockets allow incoming transactions to be identified tlm_utils::simple_target_socket_tagged<Bus>* targ_socket[N_INITIATORS]; tlm_utils::simple_initiator_socket_tagged<Bus>* init_socket[N_TARGETS]; SC_CTOR(Bus) { for (unsigned int i = 0; i < N_INITIATORS; i++) { char txt[20]; sprintf(txt, "targ_socket_%d", i); targ_socket[i] = new tlm_utils::simple_target_socket_tagged<Bus>(txt); targ_socket[i]->register_nb_transport_fw( this, &Bus::nb_transport_fw, i); targ_socket[i]->register_b_transport( this, &Bus::b_transport, i); targ_socket[i]->register_get_direct_mem_ptr(this, &Bus::get_direct_mem_ptr, i); targ_socket[i]->register_transport_dbg( this, &Bus::transport_dbg, i); } for (unsigned int i = 0; i < N_TARGETS; i++) { char txt[20]; sprintf(txt, "init_socket_%d", i); init_socket[i] = new tlm_utils::simple_initiator_socket_tagged<Bus>(txt); init_socket[i]->register_nb_transport_bw( this, &Bus::nb_transport_bw, i); init_socket[i]->register_invalidate_direct_mem_ptr(this, &Bus::invalidate_direct_mem_ptr, i); } } // Tagged non-blocking transport forward method virtual tlm::tlm_sync_enum nb_transport_fw(int id, tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay) { if (id < N_INITIATORS) { // Forward path m_id_map[ &trans ] = id; sc_dt::uint64 address = trans.get_address(); sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( address, masked_address); if (target_nr < N_TARGETS) { // Modify address within transaction trans.set_address( masked_address ); // Forward transaction to appropriate target tlm::tlm_sync_enum status = (*init_socket[target_nr])->nb_transport_fw(trans, phase, delay); if (status == tlm::TLM_COMPLETED) // Put back original address trans.set_address( address ); return status; } else return tlm::TLM_COMPLETED; } else { SC_REPORT_FATAL("TLM-2", "Invalid tagged socket id in bus"); return tlm::TLM_COMPLETED; } } // Tagged non-blocking transport backward method virtual tlm::tlm_sync_enum nb_transport_bw(int id, tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay) { if (id < N_TARGETS) { // Backward path // Replace original address sc_dt::uint64 address = trans.get_address(); trans.set_address( compose_address( id, address ) ); return ( *(targ_socket[ m_id_map[ &trans ] ]) )->nb_transport_bw(trans, phase, delay); } else { SC_REPORT_FATAL("TLM-2", "Invalid tagged socket id in bus"); return tlm::TLM_COMPLETED; } } // Tagged TLM-2 blocking transport method virtual void b_transport( int id, tlm::tlm_generic_payload& trans, sc_time& delay ) { if (id < N_INITIATORS) { // Forward path sc_dt::uint64 address = trans.get_address(); sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( address, masked_address); if (target_nr < N_TARGETS) { // Modify address within transaction trans.set_address( masked_address ); // Forward transaction to appropriate target (*init_socket[target_nr])->b_transport(trans, delay); // Replace original address trans.set_address( address ); } } else SC_REPORT_FATAL("TLM-2", "Invalid tagged socket id in bus"); } // Tagged TLM-2 forward DMI method virtual bool get_direct_mem_ptr(int id, tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); if (target_nr >= N_TARGETS) return false; trans.set_address( masked_address ); bool status = ( *init_socket[target_nr] )->get_direct_mem_ptr( trans, dmi_data ); // Calculate DMI address of target in system address space dmi_data.set_start_address( compose_address( target_nr, dmi_data.get_start_address() )); dmi_data.set_end_address ( compose_address( target_nr, dmi_data.get_end_address() )); return status; } // Tagged debug transaction method virtual unsigned int transport_dbg(int id, tlm::tlm_generic_payload& trans) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); if (target_nr >= N_TARGETS) return 0; trans.set_address( masked_address ); // Forward debug transaction to appropriate target return ( *init_socket[target_nr] )->transport_dbg( trans ); } // Tagged backward DMI method virtual void invalidate_direct_mem_ptr(int id, sc_dt::uint64 start_range, sc_dt::uint64 end_range) { // Reconstruct address range in system memory map sc_dt::uint64 bw_start_range = compose_address( id, start_range ); sc_dt::uint64 bw_end_range = compose_address( id, end_range ); // Propagate call backward to all initiators for (unsigned int i = 0; i < N_INITIATORS; i++) (*targ_socket[i])->invalidate_direct_mem_ptr(bw_start_range, bw_end_range); } // Simple fixed address decoding inline unsigned int decode_address( sc_dt::uint64 address, sc_dt::uint64& masked_address ) { unsigned int target_nr = static_cast( (address >> 6) & 0x3 ); masked_address = address & 0x3F; return target_nr; } inline sc_dt::uint64 compose_address( unsigned int target_nr, sc_dt::uint64 address) { return (target_nr << 6) | (address & 0x3F); } std::map <tlm::tlm_generic_payload*, unsigned="" int=""> m_id_map; }; #endif </tlm::tlm_generic_payload*,>
#ifndef TARGET_H #define TARGET_H // ***************************************************************************************** // Target memory implements b_transport, DMI and debug // ***************************************************************************************** struct Memory: sc_module { tlm_utils::simple_target_socket<Memory> socket; enum { SIZE = 64 }; const sc_time LATENCY; SC_CTOR(Memory) : socket("socket"), LATENCY(10, SC_NS) { socket.register_b_transport( this, &Memory::b_transport); socket.register_get_direct_mem_ptr(this, &Memory::get_direct_mem_ptr); socket.register_transport_dbg( this, &Memory::transport_dbg); // Initialize memory with random data for (int i = 0; i < SIZE; i++) mem[i] = 0xAA000000 | (mem_nr << 20) | (rand() % 256); // Each instance is given identifiable contents to help debug ++mem_nr; SC_THREAD(invalidation_process); } virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay ) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); if (adr > sc_dt::uint64(SIZE)) { trans.set_response_status( tlm::TLM_ADDRESS_ERROR_RESPONSE ); return; } if (byt != 0) { trans.set_response_status( tlm::TLM_BYTE_ENABLE_ERROR_RESPONSE ); return; } if (len > 4 || wid < len) { trans.set_response_status( tlm::TLM_BURST_ERROR_RESPONSE ); return; } if (trans.get_command() == tlm::TLM_READ_COMMAND) memcpy(ptr, &mem[adr], len); else if (cmd == tlm::TLM_WRITE_COMMAND) memcpy(&mem[adr], ptr, len); // Use temporal decoupling: add memory latency to delay argument delay += LATENCY; trans.set_dmi_allowed(true); trans.set_response_status( tlm::TLM_OK_RESPONSE ); } // TLM-2 DMI method virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { // Permit read and write access dmi_data.allow_read_write(); // Set other details of DMI region dmi_data.set_dmi_ptr( reinterpret_cast( &mem[0] ) ); dmi_data.set_start_address( 0 ); dmi_data.set_end_address( SIZE*4-1 ); dmi_data.set_read_latency( LATENCY ); dmi_data.set_write_latency( LATENCY ); return true; } // TLM-2 debug transaction method virtual unsigned int transport_dbg(tlm::tlm_generic_payload& trans) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); // Calculate the number of bytes to be actually copied unsigned int num_bytes = (len < (SIZE - adr) * 4) ? len : (SIZE - adr) * 4; if ( cmd == tlm::TLM_READ_COMMAND ) memcpy(ptr, &mem[adr], num_bytes); else if ( cmd == tlm::TLM_WRITE_COMMAND ) memcpy(&mem[adr], ptr, num_bytes); return num_bytes; } void invalidation_process() { // Invalidate DMI pointers once just as an example of routing a call back to initiators wait(3, SC_US); socket->invalidate_direct_mem_ptr(0, SIZE-1); } int mem[SIZE]; static unsigned int mem_nr; // Unique memory number to help debug }; unsigned int Memory::mem_nr = 0; #endif
#ifndef TOP_H #define TOP_H #include "initiator1.h" #include "initiator2.h" #include "bus.h" #include "target.h" // ***************************************************************************************** // Top-level module instantiates 2 initiators, a bus, and 4 memories // ***************************************************************************************** SC_MODULE(Top) { Initiator1* init1; Initiator2* init2; Bus<2,4>* bus; Memory* memory[4]; SC_CTOR(Top) { init1 = new Initiator1("init1"); init2 = new Initiator2("init2"); bus = new Bus<2,4> ("bus"); init1->socket.bind( *(bus->targ_socket[0]) ); init2->socket.bind( *(bus->targ_socket[1]) ); for (int i = 0; i < 4; i++) { char txt[20]; sprintf(txt, "memory_%d", i); memory[i] = new Memory(txt); ( *(bus->init_socket[i]) ).bind( memory[i]->socket ); } } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); return 0; }
Shows the use of multi-sockets in an interconnect component
Run this example in EDA Playground
//---------------------------------------------------------------------- // Copyright (c) 2007-2008 by Doulos Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //---------------------------------------------------------------------- // Version 1, 26-June-2008 // Version 2, 7-July-2008 Remove N_INITIATORS, N_TARGETS template parameters from Bus // Version 3 8-March-2010 Replaced target end_req_pending pointer with a queue // Getting Started with TLM-2.0, Example 6 // Shows the use of multi-sockets in an interconnect component, // that is, multi_passthrough_initiator_socket and multi_passthrough_target_socket // This example combines the AT initiator and target from example 4 with the bus from // example 5, modified to use multi-sockets instead of tagged sockets // Uses the forward and backward non-blocking transport interfaces of the bus interconnect
#ifndef UTILITIES_H #define UTILITIES_H #include "systemc" using namespace sc_core; using namespace sc_dt; using namespace std; #include "tlm.h" static ofstream fout("output.txt"); // ************************************************************************************** // User-defined memory manager, which maintains a pool of transactions // ************************************************************************************** class mm: public tlm::tlm_mm_interface { typedef tlm::tlm_generic_payload gp_t; public: mm() : free_list(0), empties(0) {} gp_t* allocate(); void free(gp_t* trans); private: struct access { gp_t* trans; access* next; access* prev; }; access* free_list; access* empties; }; mm::gp_t* mm::allocate() { gp_t* ptr; if (free_list) { ptr = free_list->trans; empties = free_list; free_list = free_list->next; } else { ptr = new gp_t(this); } return ptr; } void mm::free(gp_t* trans) { if (!empties) { empties = new access; empties->next = free_list; empties->prev = 0; if (free_list) free_list->prev = empties; } free_list = empties; free_list->trans = trans; empties = free_list->prev; } // Generate a random delay (with power-law distribution) to aid testing and stress the protocol int rand_ps() { int n = rand() % 100; n = n * n * n; return n / 100; } #endif
#ifndef INITIATOR_H #define INITIATOR_H #include "utilities.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/peq_with_cb_and_phase.h" // ************************************************************************************** // Initiator module generating multiple pipelined generic payload transactions // ************************************************************************************** struct Initiator: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_initiator_socket<Initiator> socket; SC_CTOR(Initiator) : socket("socket") // Construct and name socket , request_in_progress(0) , m_peq(this, &Initiator::peq_cb) { // Register callbacks for incoming interface method calls socket.register_nb_transport_bw(this, &Initiator::nb_transport_bw); SC_THREAD(thread_process); } void thread_process() { tlm::tlm_generic_payload* trans; tlm::tlm_phase phase; sc_time delay; // Generate a sequence of random transactions for (int i = 0; i < 1000; i++) { int adr = rand(); tlm::tlm_command cmd = static_cast(rand() % 2); if (cmd == tlm::TLM_WRITE_COMMAND) data[i % 16] = rand(); // Grab a new transaction from the memory manager trans = m_mm.allocate(); trans->acquire(); // Set all attributes except byte_enable_length and extensions (unused) trans->set_command( cmd ); trans->set_address( adr ); trans->set_data_ptr( reinterpret_cast(&data[i % 16]) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value // Initiator must honor BEGIN_REQ/END_REQ exclusion rule if (request_in_progress) wait(end_request_event); request_in_progress = trans; phase = tlm::BEGIN_REQ; // Timing annotation models processing time of initiator prior to call delay = sc_time(rand_ps(), SC_PS); fout << hex << adr << " " << name() << " new, cmd=" << (cmd ? 'W' : 'R') << ", data=" << hex << data[i % 16] << " at time " << sc_time_stamp() << endl; // Non-blocking transport call on the forward path tlm::tlm_sync_enum status; status = socket->nb_transport_fw( *trans, phase, delay ); // Check value returned from nb_transport_fw if (status == tlm::TLM_UPDATED) { // The timing annotation must be honored m_peq.notify( *trans, phase, delay ); } else if (status == tlm::TLM_COMPLETED) { // The completion of the transaction necessarily ends the BEGIN_REQ phase request_in_progress = 0; // The target has terminated the transaction check_transaction( *trans ); } wait( sc_time(rand_ps(), SC_PS) ); } wait(100, SC_NS); // Allocate a transaction for one final, nominal call to b_transport trans = m_mm.allocate(); trans->acquire(); trans->set_command( tlm::TLM_WRITE_COMMAND ); trans->set_address( 0 ); trans->set_data_ptr( reinterpret_cast(&data[0]) ); trans->set_data_length( 4 ); trans->set_streaming_width( 4 ); // = data_length to indicate no streaming trans->set_byte_enable_ptr( 0 ); // 0 indicates unused trans->set_dmi_allowed( false ); // Mandatory initial value trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE ); // Mandatory initial value delay = sc_time(rand_ps(), SC_PS); fout << "Calling b_transport at " << sc_time_stamp() << " with delay = " << delay << endl; // Call b_transport to demonstrate the b/nb conversion by the simple_target_socket socket->b_transport( *trans, delay ); check_transaction( *trans ); } // TLM-2 backward non-blocking transport method virtual tlm::tlm_sync_enum nb_transport_bw( tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay ) { // The timing annotation must be honored m_peq.notify( trans, phase, delay ); return tlm::TLM_ACCEPTED; } // Payload event queue callback to handle transactions from target // Transaction could have arrived through return path or backward path void peq_cb(tlm::tlm_generic_payload& trans, const tlm::tlm_phase& phase) { #ifdef DEBUG if (phase == tlm::END_REQ) fout << hex << trans.get_address() << " " << name() << " END_REQ at " << sc_time_stamp() << endl; else if (phase == tlm::BEGIN_RESP) fout << hex << trans.get_address() << " " << name() << " BEGIN_RESP at " << sc_time_stamp() << endl; #endif if (phase == tlm::END_REQ || (&trans == request_in_progress && phase == tlm::BEGIN_RESP)) { // The end of the BEGIN_REQ phase request_in_progress = 0; end_request_event.notify(); } else if (phase == tlm::BEGIN_REQ || phase == tlm::END_RESP) SC_REPORT_FATAL("TLM-2", "Illegal transaction phase received by initiator"); if (phase == tlm::BEGIN_RESP) { check_transaction( trans ); // Send final phase transition to target tlm::tlm_phase fw_phase = tlm::END_RESP; sc_time delay = sc_time(rand_ps(), SC_PS); socket->nb_transport_fw( trans, fw_phase, delay ); // Ignore return value } } // Called on receiving BEGIN_RESP or TLM_COMPLETED void check_transaction(tlm::tlm_generic_payload& trans) { if ( trans.is_response_error() ) { char txt[100]; sprintf(txt, "Transaction returned with error, response status = %s", trans.get_response_string().c_str()); SC_REPORT_ERROR("TLM-2", txt); } tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address(); int* ptr = reinterpret_cast<int*>( trans.get_data_ptr() ); fout << hex << adr << " " << name() << " check, cmd=" << (cmd ? 'W' : 'R') << ", data=" << hex << *ptr << " at time " << sc_time_stamp() << endl; // Allow the memory manager to free the transaction object trans.release(); } mm m_mm; int data[16]; tlm::tlm_generic_payload* request_in_progress; sc_event end_request_event; tlm_utils::peq_with_cb_and_phase<Initiator> m_peq; }; #endif </int*>
#ifndef BUS_H #define BUS_H #include "utilities.h" #include "tlm_utils/multi_passthrough_initiator_socket.h" #include "tlm_utils/multi_passthrough_target_socket.h" // ************************************************************************************ // Bus model supports multiple initiators and multiple targets // Supports b_ and nb_ transport interfaces, DMI and debug // It does no arbitration, but routes all transactions from initiators without blocking // It uses a simple built-in routing algorithm // ************************************************************************************ struct Bus: sc_module { // *********************************************************** // Each multi-socket can be bound to multiple sockets // No need for an array-of-sockets // *********************************************************** tlm_utils::multi_passthrough_target_socket<Bus> targ_socket; tlm_utils::multi_passthrough_initiator_socket<Bus> init_socket; SC_CTOR(Bus) : targ_socket("targ_socket"), init_socket("init_socket") { targ_socket.register_nb_transport_fw( this, &Bus::nb_transport_fw); targ_socket.register_b_transport( this, &Bus::b_transport); targ_socket.register_get_direct_mem_ptr(this, &Bus::get_direct_mem_ptr); targ_socket.register_transport_dbg( this, &Bus::transport_dbg); init_socket.register_nb_transport_bw( this, &Bus::nb_transport_bw); init_socket.register_invalidate_direct_mem_ptr(this, &Bus::invalidate_direct_mem_ptr); } // Tagged non-blocking transport forward method virtual tlm::tlm_sync_enum nb_transport_fw(int id, tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay) { assert (id < targ_socket.size()); // Forward path m_id_map[ &trans ] = id; sc_dt::uint64 address = trans.get_address(); sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( address, masked_address); if (target_nr < init_socket.size()) { // Modify address within transaction trans.set_address( masked_address ); // Forward transaction to appropriate target tlm::tlm_sync_enum status = init_socket[target_nr]->nb_transport_fw(trans, phase, delay); if (status == tlm::TLM_COMPLETED) // Put back original address trans.set_address( address ); return status; } else return tlm::TLM_COMPLETED; } // Tagged non-blocking transport backward method virtual tlm::tlm_sync_enum nb_transport_bw(int id, tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay) { assert (id < init_socket.size()); // Backward path // Replace original address sc_dt::uint64 address = trans.get_address(); trans.set_address( compose_address( id, address ) ); return targ_socket[ m_id_map[ &trans ] ]->nb_transport_bw(trans, phase, delay); } // Tagged TLM-2 blocking transport method virtual void b_transport( int id, tlm::tlm_generic_payload& trans, sc_time& delay ) { assert (id < targ_socket.size()); // Forward path sc_dt::uint64 address = trans.get_address(); sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( address, masked_address); if (target_nr < init_socket.size()) { // Modify address within transaction trans.set_address( masked_address ); // Forward transaction to appropriate target init_socket[target_nr]->b_transport(trans, delay); // Replace original address trans.set_address( address ); } } // Tagged TLM-2 forward DMI method virtual bool get_direct_mem_ptr(int id, tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); if (target_nr >= init_socket.size()) return false; trans.set_address( masked_address ); bool status = init_socket[target_nr]->get_direct_mem_ptr( trans, dmi_data ); // Calculate DMI address of target in system address space dmi_data.set_start_address( compose_address( target_nr, dmi_data.get_start_address() )); dmi_data.set_end_address ( compose_address( target_nr, dmi_data.get_end_address() )); return status; } // Tagged debug transaction method virtual unsigned int transport_dbg(int id, tlm::tlm_generic_payload& trans) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); if (target_nr >= init_socket.size()) return 0; trans.set_address( masked_address ); // Forward debug transaction to appropriate target return init_socket[target_nr]->transport_dbg( trans ); } // Tagged backward DMI method virtual void invalidate_direct_mem_ptr(int id, sc_dt::uint64 start_range, sc_dt::uint64 end_range) { // Reconstruct address range in system memory map sc_dt::uint64 bw_start_range = compose_address( id, start_range ); sc_dt::uint64 bw_end_range = compose_address( id, end_range ); // Propagate call backward to all initiators for (unsigned int i = 0; i < targ_socket.size(); i++) targ_socket[i]->invalidate_direct_mem_ptr(bw_start_range, bw_end_range); } // Simple fixed address decoding // In this example, for clarity, the address is passed through unmodified to the target inline unsigned int decode_address( sc_dt::uint64 address, sc_dt::uint64& masked_address ) { unsigned int target_nr = static_cast( address & 0x3 ); masked_address = address; return target_nr; } inline sc_dt::uint64 compose_address( unsigned int target_nr, sc_dt::uint64 address) { return address; } std::map <tlm::tlm_generic_payload*, unsigned="" int=""> m_id_map; }; #endif </tlm::tlm_generic_payload*,>
#ifndef TARGET_H #define TARGET_H #include "utilities.h" #include "tlm_utils/simple_target_socket.h" #include // ************************************************************************************** // Target module able to handle two pipelined transactions // ************************************************************************************** DECLARE_EXTENDED_PHASE(internal_ph); struct Target: sc_module { // TLM-2 socket, defaults to 32-bits wide, base protocol tlm_utils::simple_target_socket<Target> socket; SC_CTOR(Target) : socket("socket") , n_trans(0) , response_in_progress(false) , next_response_pending(0) , end_req_pending() , m_peq(this, &Target::peq_cb) { // Register callbacks for incoming interface method calls socket.register_nb_transport_fw(this, &Target::nb_transport_fw); } // TLM-2 non-blocking transport method virtual tlm::tlm_sync_enum nb_transport_fw( tlm::tlm_generic_payload& trans, tlm::tlm_phase& phase, sc_time& delay ) { sc_dt::uint64 adr = trans.get_address(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); // Obliged to check the transaction attributes for unsupported features // and to generate the appropriate error response if (byt != 0) { trans.set_response_status( tlm::TLM_BYTE_ENABLE_ERROR_RESPONSE ); return tlm::TLM_COMPLETED; } if (len > 4 || wid < len) { trans.set_response_status( tlm::TLM_BURST_ERROR_RESPONSE ); return tlm::TLM_COMPLETED; } // Now queue the transaction until the annotated time has elapsed m_peq.notify( trans, phase, delay); return tlm::TLM_ACCEPTED; } void peq_cb(tlm::tlm_generic_payload& trans, const tlm::tlm_phase& phase) { tlm::tlm_sync_enum status; sc_time delay; switch (phase) { case tlm::BEGIN_REQ: #ifdef DEBUG fout << hex << trans.get_address() << " " << name() << " BEGIN_REQ at " << sc_time_stamp() << endl; #endif // Increment the transaction reference count trans.acquire(); // Put back-pressure on initiator by deferring END_REQ until pipeline is clear // (initiator is blocked until pending END_REQ comes out of queue) if (n_trans == 2) end_req_pending.push(&trans); else { status = send_end_req(trans); if (status == tlm::TLM_COMPLETED) // It is questionable whether this is valid break; } break; case tlm::END_RESP: // On receiving END_RESP, the target can release the transaction // and allow other pending transactions to proceed #ifdef DEBUG fout << hex << trans.get_address() << " " << name() << " END_RESP at " << sc_time_stamp() << endl; #endif if (!response_in_progress) SC_REPORT_FATAL("TLM-2", "Illegal transaction phase END_RESP received by target"); trans.release(); n_trans--; // Target itself is now clear to issue the next BEGIN_RESP response_in_progress = false; if (next_response_pending) { send_response( *next_response_pending ); next_response_pending = 0; } // ... and to unblock the initiator by issuing END_REQ if (!end_req_pending.empty()) { status = send_end_req( *end_req_pending.front() ); end_req_pending.pop(); } break; case tlm::END_REQ: case tlm::BEGIN_RESP: SC_REPORT_FATAL("TLM-2", "Illegal transaction phase received by target"); break; default: if (phase == internal_ph) { // Execute the read or write commands tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address(); unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); if ( cmd == tlm::TLM_READ_COMMAND ) { *reinterpret_cast<int*>(ptr) = rand(); fout << hex << adr << " " << name() << " Execute READ, target = " << name() << " data = " << *reinterpret_cast<int*>(ptr) << endl; } else if ( cmd == tlm::TLM_WRITE_COMMAND ) fout << hex << adr << " " << name() << " Execute WRITE, target = " << name() << " data = " << *reinterpret_cast<int*>(ptr) << endl; trans.set_response_status( tlm::TLM_OK_RESPONSE ); // Target must honor BEGIN_RESP/END_RESP exclusion rule // i.e. must not send BEGIN_RESP until receiving previous END_RESP or BEGIN_REQ if (response_in_progress) { // Target allows only two transactions in-flight if (next_response_pending) SC_REPORT_FATAL("TLM-2", "Attempt to have two pending responses in target"); next_response_pending = &trans; } else send_response(trans); break; } } } tlm::tlm_sync_enum send_end_req(tlm::tlm_generic_payload& trans) { tlm::tlm_sync_enum status; tlm::tlm_phase bw_phase; tlm::tlm_phase int_phase = internal_ph; sc_time delay; // Queue the acceptance and the response with the appropriate latency bw_phase = tlm::END_REQ; delay = sc_time(rand_ps(), SC_PS); // Accept delay status = socket->nb_transport_bw( trans, bw_phase, delay ); if (status == tlm::TLM_COMPLETED) { // Transaction aborted by the initiator // (TLM_UPDATED cannot occur at this point in the base protocol, so need not be checked) trans.release(); return status; } // Queue internal event to mark beginning of response delay = delay + sc_time(rand_ps(), SC_PS); // Latency m_peq.notify( trans, int_phase, delay ); n_trans++; return status; } void send_response(tlm::tlm_generic_payload& trans) { tlm::tlm_sync_enum status; tlm::tlm_phase bw_phase; sc_time delay; response_in_progress = true; bw_phase = tlm::BEGIN_RESP; delay = SC_ZERO_TIME; status = socket->nb_transport_bw( trans, bw_phase, delay ); if (status == tlm::TLM_UPDATED) { // The timing annotation must be honored m_peq.notify( trans, bw_phase, delay); } else if (status == tlm::TLM_COMPLETED) { // The initiator has terminated the transaction trans.release(); n_trans--; response_in_progress = false; } } int n_trans; bool response_in_progress; tlm::tlm_generic_payload* next_response_pending; std::queue<tlm::tlm_generic_payload*> end_req_pending; tlm_utils::peq_with_cb_and_phase<Target> m_peq; }; #endif </tlm::tlm_generic_payload*></int*></int*></int*>
#ifndef TOP_H #define TOP_H #include "initiator.h" #include "bus.h" #include "target.h" // ***************************************************************************************** // Top-level module instantiates 4 initiators, a bus, and 4 targets // ***************************************************************************************** SC_MODULE(Top) { Initiator* init[4]; Bus* bus; Target* target[4]; SC_CTOR(Top) { bus = new Bus("bus"); // *************************************************************************** // bus->init_socket and bus->targ_socket are multi-sockets, each bound 4 times // *************************************************************************** for (int i = 0; i < 4; i++) { char txt[20]; sprintf(txt, "init_%d", i); init[i] = new Initiator(txt); init[i]->socket.bind( bus->targ_socket ); } for (int i = 0; i < 4; i++) { char txt[20]; sprintf(txt, "target_%d", i); target[i] = new Target(txt); bus->init_socket.bind( target[i]->socket ); } } }; #endif
#include "top.h" int sc_main(int argc, char* argv[]) { Top top("top"); sc_start(); cout << "\n***** Messages have been written to file output.txt *****\n"; cout << "***** Select 'Download files after run' to read file in EDA Playground *****\n\n"; return 0; }