2017-09-17 15:06:10 +02:00
|
|
|
// -*- C++ -*- <this line is for emacs to recognize it as C++ code>
|
|
|
|
/*****************************************************************************
|
|
|
|
|
|
|
|
The following code is derived, directly or indirectly, from the SystemC
|
|
|
|
source code Copyright (c) 1996-2014 by all Contributors.
|
|
|
|
All Rights reserved.
|
|
|
|
|
|
|
|
The contents of this file are subject to the restrictions and limitations
|
|
|
|
set forth in the SystemC Open Source License (the "License");
|
|
|
|
You may not use this file except in compliance with such restrictions and
|
|
|
|
limitations. You may obtain instructions on how to receive a copy of the
|
|
|
|
License at http://www.accellera.org/. Software distributed by Contributors
|
|
|
|
under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
|
|
|
ANY KIND, either express or implied. See the License for the specific
|
|
|
|
language governing rights and limitations under the License.
|
|
|
|
|
|
|
|
*****************************************************************************/
|
|
|
|
#include "scv.h"
|
2018-09-29 20:01:11 +02:00
|
|
|
#include "scc/scv_tr_db.h"
|
|
|
|
#include "scc/report.h"
|
|
|
|
|
2018-11-04 13:43:58 +01:00
|
|
|
// text 11308µs/11602µs
|
|
|
|
// compressed 10365µs/ 9860µs
|
|
|
|
// binary 13233µs/10698µs
|
|
|
|
// SQLite 30363µs/30018µs
|
|
|
|
// LeveDB 23898µs/22367µs
|
2017-09-17 15:06:10 +02:00
|
|
|
|
|
|
|
// hack to fake a true fifo_mutex
|
|
|
|
#define fifo_mutex sc_mutex
|
|
|
|
|
|
|
|
const unsigned ram_size = 256;
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
class rw_task_if : virtual public sc_interface {
|
2017-09-17 15:06:10 +02:00
|
|
|
public:
|
|
|
|
typedef sc_uint<8> addr_t;
|
|
|
|
typedef sc_uint<8> data_t;
|
|
|
|
struct write_t {
|
|
|
|
addr_t addr;
|
|
|
|
data_t data;
|
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
virtual data_t read(const addr_t *) = 0;
|
|
|
|
virtual void write(const write_t *) = 0;
|
2017-09-17 15:06:10 +02:00
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
SCV_EXTENSIONS(rw_task_if::write_t) {
|
2017-09-17 15:06:10 +02:00
|
|
|
public:
|
2017-09-25 22:00:46 +02:00
|
|
|
scv_extensions<rw_task_if::addr_t> addr;
|
|
|
|
scv_extensions<rw_task_if::data_t> data;
|
|
|
|
SCV_EXTENSIONS_CTOR(rw_task_if::write_t) {
|
|
|
|
SCV_FIELD(addr);
|
|
|
|
SCV_FIELD(data);
|
|
|
|
}
|
2017-09-17 15:06:10 +02:00
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
class pipelined_bus_ports : public sc_module {
|
2017-09-17 15:06:10 +02:00
|
|
|
public:
|
|
|
|
sc_in<bool> clk;
|
|
|
|
sc_inout<bool> rw;
|
|
|
|
sc_inout<bool> addr_req;
|
|
|
|
sc_inout<bool> addr_ack;
|
2017-09-25 22:00:46 +02:00
|
|
|
sc_inout<sc_uint<8>> bus_addr;
|
2017-09-17 15:06:10 +02:00
|
|
|
sc_inout<bool> data_rdy;
|
2017-09-25 22:00:46 +02:00
|
|
|
sc_inout<sc_uint<8>> bus_data;
|
|
|
|
|
|
|
|
SC_CTOR(pipelined_bus_ports)
|
|
|
|
: clk("clk")
|
|
|
|
, rw("rw")
|
|
|
|
, addr_req("addr_req")
|
|
|
|
, addr_ack("addr_ack")
|
|
|
|
, bus_addr("bus_addr")
|
|
|
|
, data_rdy("data_rdy")
|
|
|
|
, bus_data("bus_data") {}
|
|
|
|
virtual void trace(sc_trace_file *tf) const;
|
2017-09-17 15:06:10 +02:00
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
void pipelined_bus_ports::trace(sc_trace_file *tf) const {
|
2017-09-17 15:06:10 +02:00
|
|
|
sc_trace(tf, clk, clk.name());
|
|
|
|
sc_trace(tf, rw, rw.name());
|
|
|
|
sc_trace(tf, addr_req, addr_req.name());
|
|
|
|
sc_trace(tf, addr_ack, addr_ack.name());
|
|
|
|
sc_trace(tf, bus_addr, bus_addr.name());
|
|
|
|
sc_trace(tf, data_rdy, data_rdy.name());
|
|
|
|
sc_trace(tf, bus_data, bus_data.name());
|
|
|
|
}
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
class rw_pipelined_transactor : public rw_task_if, public pipelined_bus_ports {
|
2017-09-17 15:06:10 +02:00
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
fifo_mutex addr_phase;
|
|
|
|
fifo_mutex data_phase;
|
2017-09-17 15:06:10 +02:00
|
|
|
|
|
|
|
scv_tr_stream pipelined_stream;
|
|
|
|
scv_tr_stream addr_stream;
|
|
|
|
scv_tr_stream data_stream;
|
2017-09-25 22:00:46 +02:00
|
|
|
scv_tr_generator<sc_uint<8>, sc_uint<8>> read_gen;
|
|
|
|
scv_tr_generator<sc_uint<8>, sc_uint<8>> write_gen;
|
|
|
|
scv_tr_generator<sc_uint<8>> addr_gen;
|
|
|
|
scv_tr_generator<_scv_tr_generator_default_data, sc_uint<8>> rdata_gen;
|
|
|
|
scv_tr_generator<sc_uint<8>> wdata_gen;
|
2017-09-17 15:06:10 +02:00
|
|
|
|
|
|
|
public:
|
|
|
|
rw_pipelined_transactor(sc_module_name nm)
|
2017-09-25 22:00:46 +02:00
|
|
|
: pipelined_bus_ports(nm)
|
|
|
|
, addr_phase("addr_phase")
|
|
|
|
, data_phase("data_phase")
|
|
|
|
, pipelined_stream((std::string(name()) + ".pipelined_stream").c_str(), "transactor")
|
|
|
|
, addr_stream((std::string(name()) + ".addr_stream").c_str(), "transactor")
|
|
|
|
, data_stream((std::string(name()) + ".data_stream").c_str(), "transactor")
|
|
|
|
, read_gen("read", pipelined_stream, "addr", "data")
|
|
|
|
, write_gen("write", pipelined_stream, "addr", "data")
|
|
|
|
, addr_gen("addr", addr_stream, "addr")
|
|
|
|
, rdata_gen("rdata", data_stream, NULL, "data")
|
|
|
|
, wdata_gen("wdata", data_stream, "data") {}
|
|
|
|
virtual data_t read(const addr_t *p_addr);
|
|
|
|
virtual void write(const write_t *req);
|
2017-09-17 15:06:10 +02:00
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
rw_task_if::data_t rw_pipelined_transactor::read(const addr_t *addr) {
|
2017-09-17 15:06:10 +02:00
|
|
|
addr_phase.lock();
|
|
|
|
scv_tr_handle h = read_gen.begin_transaction(*addr);
|
|
|
|
h.record_attribute("data_size", sizeof(data_t));
|
|
|
|
scv_tr_handle h1 = addr_gen.begin_transaction(*addr, "addr_phase", h);
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
bus_addr = *addr;
|
2017-09-25 22:00:46 +02:00
|
|
|
rw = false;
|
2017-09-17 15:06:10 +02:00
|
|
|
addr_req = 1;
|
|
|
|
wait(addr_ack->posedge_event());
|
|
|
|
wait(clk->negedge_event());
|
|
|
|
addr_req = 0;
|
|
|
|
wait(addr_ack->negedge_event());
|
|
|
|
addr_gen.end_transaction(h1);
|
|
|
|
addr_phase.unlock();
|
|
|
|
|
|
|
|
data_phase.lock();
|
|
|
|
scv_tr_handle h2 = rdata_gen.begin_transaction("data_phase", h);
|
|
|
|
wait(data_rdy->posedge_event());
|
|
|
|
data_t data = bus_data.read();
|
|
|
|
wait(data_rdy->negedge_event());
|
|
|
|
rdata_gen.end_transaction(h2, data);
|
|
|
|
read_gen.end_transaction(h, data);
|
|
|
|
data_phase.unlock();
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
void rw_pipelined_transactor::write(const write_t *req) {
|
2017-09-17 15:06:10 +02:00
|
|
|
addr_phase.lock();
|
|
|
|
scv_tr_handle h = write_gen.begin_transaction(req->addr);
|
|
|
|
h.record_attribute("data_size", sizeof(data_t));
|
|
|
|
scv_tr_handle h1 = addr_gen.begin_transaction(req->addr, "addr_phase", h);
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
bus_addr = req->addr;
|
2017-09-25 22:00:46 +02:00
|
|
|
rw = true;
|
2017-09-17 15:06:10 +02:00
|
|
|
addr_req = 1;
|
|
|
|
wait(addr_ack->posedge_event());
|
|
|
|
wait(clk->negedge_event());
|
|
|
|
addr_req = 0;
|
|
|
|
wait(addr_ack->negedge_event());
|
|
|
|
addr_gen.end_transaction(h1);
|
|
|
|
addr_phase.unlock();
|
|
|
|
|
|
|
|
data_phase.lock();
|
|
|
|
scv_tr_handle h2 = wdata_gen.begin_transaction(req->data, "data_phase", h);
|
2017-09-25 22:00:46 +02:00
|
|
|
bus_data = req->data;
|
2017-09-17 15:06:10 +02:00
|
|
|
wait(data_rdy->posedge_event());
|
|
|
|
wait(data_rdy->negedge_event());
|
|
|
|
wdata_gen.end_transaction(h2);
|
|
|
|
write_gen.end_transaction(h, req->data);
|
|
|
|
data_phase.unlock();
|
|
|
|
}
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
class test : public sc_module {
|
2017-09-17 15:06:10 +02:00
|
|
|
public:
|
|
|
|
sc_port<rw_task_if> transactor;
|
|
|
|
SC_HAS_PROCESS(test);
|
2017-09-25 22:00:46 +02:00
|
|
|
test(::sc_core::sc_module_name) {
|
2017-09-17 15:06:10 +02:00
|
|
|
SC_THREAD(main1);
|
|
|
|
SC_THREAD(main2);
|
|
|
|
}
|
|
|
|
void main1();
|
|
|
|
void main2();
|
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
class write_constraint : virtual public scv_constraint_base {
|
2017-09-17 15:06:10 +02:00
|
|
|
public:
|
2017-09-25 22:00:46 +02:00
|
|
|
scv_smart_ptr<rw_task_if::write_t> write;
|
|
|
|
SCV_CONSTRAINT_CTOR(write_constraint) {
|
2017-09-17 15:06:10 +02:00
|
|
|
SCV_CONSTRAINT(write->addr() <= ram_size);
|
|
|
|
SCV_CONSTRAINT(write->addr() != write->data());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-09-25 22:00:46 +02:00
|
|
|
inline void process(scv_smart_ptr<int> data) {}
|
2017-09-17 15:06:10 +02:00
|
|
|
|
|
|
|
inline void test::main1() {
|
|
|
|
// simple sequential tests
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
rw_task_if::addr_t addr = i;
|
|
|
|
rw_task_if::data_t data = transactor->read(&addr);
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "received data : " << data;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scv_smart_ptr<rw_task_if::addr_t> addr;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
|
|
|
addr->next();
|
|
|
|
rw_task_if::data_t data = transactor->read(addr->get_instance());
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "data for address " << *addr << " is " << data;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scv_smart_ptr<rw_task_if::write_t> write;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
write->next();
|
|
|
|
transactor->write(write->get_instance());
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "send data : " << write->data;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scv_smart_ptr<int> data;
|
|
|
|
scv_bag<int> distribution;
|
|
|
|
distribution.push(1, 40);
|
|
|
|
distribution.push(2, 60);
|
|
|
|
data->set_mode(distribution);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
data->next();
|
|
|
|
process(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void test::main2() {
|
|
|
|
// simple sequential tests
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
rw_task_if::addr_t addr = i;
|
|
|
|
rw_task_if::data_t data = transactor->read(&addr);
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "received data : " << data;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scv_smart_ptr<rw_task_if::addr_t> addr;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
|
|
|
addr->next();
|
|
|
|
rw_task_if::data_t data = transactor->read(addr->get_instance());
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "data for address " << *addr << " is " << data;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scv_smart_ptr<rw_task_if::write_t> write;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
write->next();
|
|
|
|
transactor->write(write->get_instance());
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "send data : " << write->data;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
scv_smart_ptr<int> data;
|
|
|
|
scv_bag<int> distribution;
|
|
|
|
distribution.push(1, 140);
|
|
|
|
distribution.push(2, 160);
|
|
|
|
data->set_mode(distribution);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
data->next();
|
|
|
|
process(data);
|
|
|
|
}
|
|
|
|
}
|
2017-09-25 22:00:46 +02:00
|
|
|
class design : public pipelined_bus_ports {
|
|
|
|
std::list<sc_uint<8>> outstandingAddresses;
|
2017-09-17 15:06:10 +02:00
|
|
|
std::list<bool> outstandingType;
|
|
|
|
sc_uint<8> memory[ram_size];
|
|
|
|
|
|
|
|
public:
|
|
|
|
SC_HAS_PROCESS(design);
|
2017-09-25 22:00:46 +02:00
|
|
|
design(sc_module_name nm)
|
|
|
|
: pipelined_bus_ports(nm) {
|
2017-09-17 15:06:10 +02:00
|
|
|
for (unsigned i = 0; i < ram_size; ++i) {
|
|
|
|
memory[i] = i;
|
|
|
|
}
|
|
|
|
SC_THREAD(addr_phase);
|
|
|
|
SC_THREAD(data_phase);
|
|
|
|
}
|
|
|
|
void addr_phase();
|
|
|
|
void data_phase();
|
|
|
|
};
|
|
|
|
|
|
|
|
inline void design::addr_phase() {
|
|
|
|
while (1) {
|
|
|
|
while (addr_req.read() != 1) {
|
|
|
|
wait(addr_req->value_changed_event());
|
|
|
|
}
|
|
|
|
sc_uint<8> _addr = bus_addr.read();
|
|
|
|
bool _rw = rw.read();
|
|
|
|
|
|
|
|
int cycle = rand() % 10 + 1;
|
|
|
|
while (cycle-- > 0) {
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
}
|
|
|
|
|
|
|
|
addr_ack = 1;
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
addr_ack = 0;
|
|
|
|
|
|
|
|
outstandingAddresses.push_back(_addr);
|
|
|
|
outstandingType.push_back(_rw);
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "received request for memory address " << _addr;
|
2017-09-17 15:06:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void design::data_phase() {
|
|
|
|
while (1) {
|
|
|
|
while (outstandingAddresses.empty()) {
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
}
|
|
|
|
int cycle = rand() % 10 + 1;
|
|
|
|
while (cycle-- > 0) {
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
}
|
|
|
|
if (outstandingType.front() == false) {
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "reading memory address " << outstandingAddresses.front() << " with value "
|
|
|
|
<< memory[outstandingAddresses.front().to_ulong()];
|
2017-09-17 15:06:10 +02:00
|
|
|
bus_data = memory[outstandingAddresses.front().to_ulong()];
|
|
|
|
data_rdy = 1;
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
data_rdy = 0;
|
|
|
|
|
|
|
|
} else {
|
2018-09-29 20:01:11 +02:00
|
|
|
SCINFO(sc_get_current_object()->name()) << "writing memory address " << outstandingAddresses.front() << " with value " << bus_data;
|
2017-09-25 22:00:46 +02:00
|
|
|
memory[outstandingAddresses.front().to_ulong()] = bus_data;
|
2017-09-17 15:06:10 +02:00
|
|
|
data_rdy = 1;
|
|
|
|
wait(clk->posedge_event());
|
|
|
|
data_rdy = 0;
|
|
|
|
}
|
|
|
|
outstandingAddresses.pop_front();
|
|
|
|
outstandingType.pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-04 13:43:58 +01:00
|
|
|
inline const char* init_db(char type){
|
|
|
|
switch(type){
|
|
|
|
case '2':
|
|
|
|
scv_tr_compressed_init();
|
|
|
|
return "my_db.txlog";
|
|
|
|
break;
|
|
|
|
case '3':
|
|
|
|
scv_tr_binary_init();
|
|
|
|
return "my_db.txb";
|
|
|
|
break;
|
|
|
|
case '4':
|
|
|
|
scv_tr_sqlite_init();
|
|
|
|
return "my_db.txdb";
|
|
|
|
break;
|
|
|
|
case '5':
|
|
|
|
scv_tr_ldb_init();
|
|
|
|
return "my_db.txldb";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
scv_tr_text_init();
|
|
|
|
return "my_db.txlog";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-17 15:06:10 +02:00
|
|
|
int sc_main(int argc, char *argv[]) {
|
2018-09-29 20:01:11 +02:00
|
|
|
auto start = std::chrono::system_clock::now();
|
2017-09-17 15:06:10 +02:00
|
|
|
scv_startup();
|
2018-09-29 20:01:11 +02:00
|
|
|
scc::init_logging(logging::INFO);
|
|
|
|
LOGGER(SystemC)::print_time() = false;
|
2018-11-04 13:43:58 +01:00
|
|
|
const char *fileName = argc==2? init_db(argv[1][0]): "my_db.txlog";
|
|
|
|
if(argc<2) scv_tr_text_init();
|
2017-09-17 15:06:10 +02:00
|
|
|
scv_tr_db db(fileName);
|
|
|
|
scv_tr_db::set_default_db(&db);
|
2017-09-25 22:00:46 +02:00
|
|
|
sc_trace_file *tf = sc_create_vcd_trace_file("my_db");
|
2017-09-17 15:06:10 +02:00
|
|
|
// create signals
|
|
|
|
sc_clock clk("clk", 20.0, SC_NS, 0.5, 0.0, SC_NS, true);
|
|
|
|
sc_signal<bool> rw;
|
|
|
|
sc_signal<bool> addr_req;
|
|
|
|
sc_signal<bool> addr_ack;
|
2017-09-25 22:00:46 +02:00
|
|
|
sc_signal<sc_uint<8>> bus_addr;
|
2017-09-17 15:06:10 +02:00
|
|
|
sc_signal<bool> data_rdy;
|
2017-09-25 22:00:46 +02:00
|
|
|
sc_signal<sc_uint<8>> bus_data;
|
2017-09-17 15:06:10 +02:00
|
|
|
|
|
|
|
// create modules/channels
|
|
|
|
test t("t");
|
|
|
|
rw_pipelined_transactor tr("tr");
|
|
|
|
design duv("duv");
|
|
|
|
|
|
|
|
// connect them up
|
|
|
|
t.transactor(tr);
|
|
|
|
|
|
|
|
tr.clk(clk);
|
|
|
|
tr.rw(rw);
|
|
|
|
tr.addr_req(addr_req);
|
|
|
|
tr.addr_ack(addr_ack);
|
|
|
|
tr.bus_addr(bus_addr);
|
|
|
|
tr.data_rdy(data_rdy);
|
|
|
|
tr.bus_data(bus_data);
|
|
|
|
tr.trace(tf);
|
|
|
|
|
|
|
|
duv.clk(clk);
|
|
|
|
duv.rw(rw);
|
|
|
|
duv.addr_req(addr_req);
|
|
|
|
duv.addr_ack(addr_ack);
|
|
|
|
duv.bus_addr(bus_addr);
|
|
|
|
duv.data_rdy(data_rdy);
|
|
|
|
duv.bus_data(bus_data);
|
|
|
|
duv.trace(tf);
|
|
|
|
|
|
|
|
// Accellera SystemC >=2.2 got picky about multiple drivers.
|
|
|
|
// Disable check for bus simulation.
|
|
|
|
sc_report_handler::set_actions(SC_ID_MORE_THAN_ONE_SIGNAL_DRIVER_, SC_DO_NOTHING);
|
|
|
|
// run the simulation
|
2018-09-29 20:01:11 +02:00
|
|
|
sc_start(10.0, SC_US);
|
2017-09-17 15:06:10 +02:00
|
|
|
sc_close_vcd_trace_file(tf);
|
2018-09-29 20:01:11 +02:00
|
|
|
auto int_us = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now()-start);
|
|
|
|
SCINFO() << "simulation duration "<<int_us.count()<<"µs";
|
2017-09-17 15:06:10 +02:00
|
|
|
return 0;
|
|
|
|
}
|