#ifndef __BS_PRIM_MOD_SYNCHRONIZERS_H__ #define __BS_PRIM_MOD_SYNCHRONIZERS_H__ #include "bluesim_kernel_api.h" #include "bs_module.h" #include "bs_vcd.h" // This is a helper class we use manage race conditions at // clock domain crossings. A SyncVar allows us to read a // variable in one domain that was written from a different // domain at the same time, and see the previous value. // This mimics the Verilog behavior of a non-blocking write // that updates at the of the simulation cycle. template class SyncVar { public: SyncVar(tSimStateHdl simHdl, unsigned int width) : sim_hdl(simHdl), bits(width) { init_val(prev_value, bits); write_undet(&prev_value, bits); init_val(value, bits); write_undet(&value, bits); written_at = ~bk_now(sim_hdl); } public: const T& read() const { if (bk_is_same_time(sim_hdl, written_at)) return prev_value; else return value; } const T& probe() const { return value; } void write(const T& x) { prev_value = value; value = x; written_at = bk_now(sim_hdl); } void force(const T& x) { prev_value = x; value = x; } public: SyncVar& operator=(const SyncVar& sv) { prev_value = sv.prev_value; value = sv.value; written_at = sv.written_at; return (*this); } private: tSimStateHdl sim_hdl; const unsigned int bits; T prev_value; T value; tTime written_at; }; // Simple uni-directional synchronizers // This is the definition used for 1-bit synchronizer primitives // with a 2-destination-clock-cycle delay. class MOD_Sync2 : public Module { public: MOD_Sync2(tSimStateHdl simHdl, const char* name, Module* parent, tUInt8 v) : Module(simHdl, name, parent), sSyncReg(simHdl, 1), reset_value(v) { init_val(dSyncReg1, 1); write_undet(&dSyncReg1, 1); init_val(dSyncReg2, 1); write_undet(&dSyncReg2, 1); in_reset = false; } public: const tUInt8 METH_read() const { return dSyncReg2; } void METH_send(tUInt8 x) { if (!in_reset) sSyncReg.write(x); } void clk_dst(tUInt8 /* clock_value */, tUInt8 gate_value = 1) { if (gate_value == 0) return; dSyncReg2 = dSyncReg1; dSyncReg1 = sSyncReg.read(); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); if (in_reset) { dSyncReg1 = dSyncReg2 = reset_value; sSyncReg.force(reset_value); } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdSyncReg1 = ", indent+2, ""); dump_val(dSyncReg1, 1); putchar('\n'); printf("%*sdSyncReg2 = ", indent+2, ""); dump_val(dSyncReg2, 1); putchar('\n'); printf("%*ssSyncReg = ", indent+2, ""); dump_val(sSyncReg.read(), 1); putchar('\n'); } unsigned int dump_VCD_defs(unsigned int /* num */) { vcd_num = vcd_reserve_ids(sim_hdl, 3); vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, vcd_num, "dSyncReg1", 1); vcd_write_def(sim_hdl, vcd_num+1, "dSyncReg2", 1); vcd_write_def(sim_hdl, vcd_num+2, "sSyncReg", 1); vcd_write_scope_end(sim_hdl); return (vcd_num + 3); } void dump_VCD(tVCDDumpType dt, MOD_Sync2& backing) { if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, vcd_num, 1); vcd_write_x(sim_hdl, vcd_num+1, 1); vcd_write_x(sim_hdl, vcd_num+2, 1); } else { if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg1 != dSyncReg1)) { vcd_write_val(sim_hdl, vcd_num, dSyncReg1, 1); backing.dSyncReg1 = dSyncReg1; } if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg2 != dSyncReg2)) { vcd_write_val(sim_hdl, vcd_num+1, dSyncReg2, 1); backing.dSyncReg2 = dSyncReg2; } if ((dt != VCD_DUMP_CHANGES) || (backing.sSyncReg.read() != sSyncReg.read())) { vcd_write_val(sim_hdl, vcd_num+2, sSyncReg.read(), 1); backing.sSyncReg = sSyncReg; } } } private: tUInt8 dSyncReg1; tUInt8 dSyncReg2; SyncVar sSyncReg; tUInt8 reset_value; bool in_reset; }; // This is the definition used for synchronizer primitives // with a 1.5-destination-clock-cycle delay. class MOD_Sync15 : public Module { public: MOD_Sync15(tSimStateHdl simHdl, const char* name, Module* parent, tUInt8 v) : Module(simHdl, name, parent), sSyncReg(simHdl, 1), reset_value(v) { init_val(dSyncReg1, 1); write_undet(&dSyncReg1, 1); init_val(dSyncReg2, 1); write_undet(&dSyncReg2, 1); in_reset = false; } public: const tUInt8 METH_read() const { return dSyncReg2; } void METH_send(tUInt8 x) { if (!in_reset) sSyncReg.write(x); } void clk_dst(tUInt8 clock_value, tUInt8 gate_value = 1) { if (gate_value == 0) return; if (clock_value != 0) dSyncReg2 = dSyncReg1; else dSyncReg1 = sSyncReg.read(); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); if (in_reset) { dSyncReg2 = dSyncReg1 = reset_value; sSyncReg.force(reset_value); } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdSyncReg1 = ", indent+2, ""); dump_val(dSyncReg1, 1); putchar('\n'); printf("%*sdSyncReg2 = ", indent+2, ""); dump_val(dSyncReg2, 1); putchar('\n'); printf("%*ssSyncReg = ", indent+2, ""); dump_val(sSyncReg.read(), 1); putchar('\n'); } unsigned int dump_VCD_defs(unsigned int /* num */) { vcd_num = vcd_reserve_ids(sim_hdl, 3); vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, vcd_num, "dSyncReg1", 1); vcd_write_def(sim_hdl, vcd_num+1, "dSyncReg2", 1); vcd_write_def(sim_hdl, vcd_num+2, "sSyncReg", 1); vcd_write_scope_end(sim_hdl); return (vcd_num + 3); } void dump_VCD(tVCDDumpType dt, MOD_Sync15& backing) { if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, vcd_num, 1); vcd_write_x(sim_hdl, vcd_num+1, 1); vcd_write_x(sim_hdl, vcd_num+2, 1); } else { if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg1 != dSyncReg1)) { vcd_write_val(sim_hdl, vcd_num, dSyncReg1, 1); backing.dSyncReg1 = dSyncReg1; } if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg2 != dSyncReg2)) { vcd_write_val(sim_hdl, vcd_num+1, dSyncReg2, 1); backing.dSyncReg2 = dSyncReg2; } if ((dt != VCD_DUMP_CHANGES) || (backing.sSyncReg.read() != sSyncReg.read())) { vcd_write_val(sim_hdl, vcd_num+2, sSyncReg.read(), 1); backing.sSyncReg = sSyncReg; } } } private: tUInt8 dSyncReg1; tUInt8 dSyncReg2; SyncVar sSyncReg; tUInt8 reset_value; bool in_reset; }; // This is the definition used for synchronizer primitives // with a 1-destination-clock-cycle delay. It is also used // for 0.5-destination-clock-cycle delay, since the only difference // is whether clk_dst() is called on posedge or negedge. class MOD_Sync1 : public Module { public: MOD_Sync1(tSimStateHdl simHdl, const char* name, Module* parent, tUInt8 v) : Module(simHdl, name, parent), sSyncReg(simHdl, 1), reset_value(v) { init_val(dSyncReg1, 1); write_undet(&dSyncReg1, 1); in_reset = false; } public: const tUInt8 METH_read() const { return dSyncReg1; } void METH_send(tUInt8 x) { if (!in_reset) sSyncReg.write(x); } void clk_dst(tUInt8 /* clock_value */, tUInt8 gate_value = 1) { if (gate_value == 0) return; dSyncReg1 = sSyncReg.read(); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); if (in_reset) { dSyncReg1 = reset_value; sSyncReg.force(reset_value); } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdSyncReg1 = ", indent+2, ""); dump_val(dSyncReg1, 1); putchar('\n'); printf("%*ssSyncReg = ", indent+2, ""); dump_val(sSyncReg.read(), 1); putchar('\n'); } unsigned int dump_VCD_defs(unsigned int /* num */) { vcd_num = vcd_reserve_ids(sim_hdl, 2); vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, vcd_num, "dSyncReg1", 1); vcd_write_def(sim_hdl, vcd_num+1, "sSyncReg", 1); vcd_write_scope_end(sim_hdl); return (vcd_num + 2); } void dump_VCD(tVCDDumpType dt, MOD_Sync1& backing) { if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, vcd_num, 1); vcd_write_x(sim_hdl, vcd_num+1, 1); } else { if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg1 != dSyncReg1)) { vcd_write_val(sim_hdl, vcd_num, dSyncReg1, 1); backing.dSyncReg1 = dSyncReg1; } if ((dt != VCD_DUMP_CHANGES) || (backing.sSyncReg.read() != sSyncReg.read())) { vcd_write_val(sim_hdl, vcd_num+1, sSyncReg.read(), 1); backing.sSyncReg = sSyncReg; } } } private: tUInt8 dSyncReg1; SyncVar sSyncReg; tUInt8 reset_value; bool in_reset; }; // Pulse synchronizers // A pulse synchronizer is a uni-directional synchronizer // based on transmitting a pulse across clock domains. class MOD_SyncPulse : public Module { public: MOD_SyncPulse(tSimStateHdl simHdl, const char* name, Module* parent) : Module(simHdl, name, parent), sSyncReg(simHdl, 1) { init_val(dSyncReg1, 1); write_undet(&dSyncReg1, 1); init_val(dSyncReg2, 1); write_undet(&dSyncReg2, 1); init_val(dSyncPulse, 1); write_undet(&dSyncPulse, 1); in_reset = false; } public: tUInt8 METH_pulse() const { return (dSyncReg2 ^ dSyncPulse) ? 0x1 : 0x0; } void METH_send() { if (!in_reset) sSyncReg.write((sSyncReg.read() == 0) ? 0x1 : 0x0); } void clk_dst(tUInt8 /* clock_value */, tUInt8 gate_value = 1) { if (gate_value == 0) return; dSyncPulse = dSyncReg2; dSyncReg2 = dSyncReg1; dSyncReg1 = sSyncReg.read(); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); if (in_reset) { dSyncPulse = dSyncReg2 = dSyncReg1 = 0; sSyncReg.force(0); } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdSyncReg1 = ", indent+2, ""); dump_val(dSyncReg1, 1); putchar('\n'); printf("%*sdSyncReg2 = ", indent+2, ""); dump_val(dSyncReg2, 1); putchar('\n'); printf("%*sdSyncPulse = ", indent+2, ""); dump_val(dSyncPulse, 1); putchar('\n'); printf("%*ssSyncReg = ", indent+2, ""); dump_val(sSyncReg.read(), 1); putchar('\n'); } unsigned int dump_VCD_defs(unsigned int /* num */) { vcd_num = vcd_reserve_ids(sim_hdl, 4); vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, vcd_num, "dSyncReg1", 1); vcd_write_def(sim_hdl, vcd_num+1, "dSyncReg2", 1); vcd_write_def(sim_hdl, vcd_num+2, "dSyncPulse", 1); vcd_write_def(sim_hdl, vcd_num+3, "sSyncReg", 1); vcd_write_scope_end(sim_hdl); return (vcd_num + 4); } void dump_VCD(tVCDDumpType dt, MOD_SyncPulse& backing) { if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, vcd_num, 1); vcd_write_x(sim_hdl, vcd_num+1, 1); vcd_write_x(sim_hdl, vcd_num+2, 1); vcd_write_x(sim_hdl, vcd_num+3, 1); } else { if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg1 != dSyncReg1)) { vcd_write_val(sim_hdl, vcd_num, dSyncReg1, 1); backing.dSyncReg1 = dSyncReg1; } if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncReg2 != dSyncReg2)) { vcd_write_val(sim_hdl, vcd_num+1, dSyncReg2, 1); backing.dSyncReg2 = dSyncReg2; } if ((dt != VCD_DUMP_CHANGES) || (backing.dSyncPulse != dSyncPulse)) { vcd_write_val(sim_hdl, vcd_num+2, dSyncPulse, 1); backing.dSyncPulse = dSyncPulse; } if ((dt != VCD_DUMP_CHANGES) || (backing.sSyncReg.read() != sSyncReg.read())) { vcd_write_val(sim_hdl, vcd_num+3, sSyncReg.read(), 1); backing.sSyncReg = sSyncReg; } } } private: tUInt8 dSyncReg1; tUInt8 dSyncReg2; tUInt8 dSyncPulse; SyncVar sSyncReg; bool in_reset; }; // Handshaking synchronizers // A handshake synchronizer is a pulse synchronizer which feeds // synchronization information back from the destination domain // to ensure that the next pulse cannot be transmitted until the // previous pulse has become visible in the destination domain. class MOD_SyncHandshake : public Module { public: MOD_SyncHandshake(tSimStateHdl simHdl, const char* name, Module* parent ,bool init = 0, bool delayreturn = false) : Module(simHdl, name, parent), dSyncReg2(simHdl, 1) , dLastState(simHdl, 1) , sToggleReg(simHdl, 1) , param_init(init) , param_delayreturn(delayreturn) , __clk_handle_0(BAD_CLOCK_HANDLE) , __clk_handle_1(BAD_CLOCK_HANDLE) { init_val(sSyncReg1, 1); write_undet(&sSyncReg1, 1); init_val(sSyncReg2, 1); write_undet(&sSyncReg2, 1); init_val(sRDY, 0); sRDY = 0; init_val(dSyncReg1, 1); write_undet(&dSyncReg1, 1); sSyncReg1 = 1; sSyncReg2 = 1; en = false; did_send = false; pulsing = false; in_reset = false; } public: tUInt8 METH_pulse() const { return (dSyncReg2.read() != dLastState.read()) ? 0x1 : 0x0; } void METH_send() { en = true; } bool METH_RDY_send() const { return (!in_reset && (sRDY != 0)); } void clk_src(tUInt8 /* clock_value */, tUInt8 gate_value = 1) { if (gate_value == 0) return; if (!in_reset) { sSyncReg2 = sSyncReg1; sSyncReg1 = param_delayreturn ? dLastState.read() : dSyncReg2.read(); } if (en) { sToggleReg.write((sToggleReg.read() == 0) ? 0x1 : 0x0); sRDY = 0; } else sRDY = (sSyncReg2 == sToggleReg.read()); did_send = en; en = false; } void clk_dst(tUInt8 /* clock_value */, tUInt8 gate_value = 1) { if (gate_value == 0) return; dLastState.write (dSyncReg2.read()); dSyncReg2.write(dSyncReg1); dSyncReg1 = sToggleReg.read(); pulsing = dLastState.probe() != dSyncReg2.probe(); } void set_clk_0(const char* s) { __clk_handle_0 = bk_get_or_define_clock(sim_hdl, s); } void set_clk_1(const char* s) { __clk_handle_1 = bk_get_or_define_clock(sim_hdl, s); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); if (in_reset) { dSyncReg2.force(param_init ? 1 : 0); sToggleReg.force(param_init ? 1 : 0); dSyncReg1 = param_init ? 1 : 0; dLastState.force (param_init ? 1 : 0); sSyncReg1 = param_init ? 0 : 1; /* ! init */ sSyncReg2 = param_init ? 0 : 1; /* ! init */ sRDY = 0; en = false; pulsing = false; } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdSyncReg1 = ", indent+2, ""); dump_val(dSyncReg1, 1); putchar('\n'); printf("%*sdSyncReg2 = ", indent+2, ""); dump_val(dSyncReg2.read(), 1); putchar('\n'); printf("%*sdLastState = ", indent+2, ""); dump_val(dLastState.read(), 1); putchar('\n'); printf("%*ssToggleReg = ", indent+2, ""); dump_val(sToggleReg.read(), 1); putchar('\n'); printf("%*ssSyncReg1 = ", indent+2, ""); dump_val(sSyncReg1, 1); putchar('\n'); printf("%*ssSyncReg2 = ", indent+2, ""); dump_val(sSyncReg2, 1); putchar('\n'); printf("%*ssRDY = ", indent+2, ""); dump_val(sRDY, 1); putchar('\n'); } unsigned int dump_VCD_defs(unsigned int /* num */) { vcd_num = vcd_reserve_ids(sim_hdl, 12); unsigned int n = vcd_num; vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, n++, "dSyncReg1", 1); vcd_write_def(sim_hdl, n++, "dSyncReg2", 1); vcd_write_def(sim_hdl, n++, "dLastState", 1); vcd_write_def(sim_hdl, n++, "sToggleReg", 1); vcd_write_def(sim_hdl, n++, "sSyncReg1", 1); vcd_write_def(sim_hdl, n++, "sSyncReg2", 1); vcd_write_def(sim_hdl, n++, "sRDY", 1); vcd_set_clock(sim_hdl, n, __clk_handle_0); vcd_write_def(sim_hdl, n++, "sEN", 1); vcd_write_def(sim_hdl, bk_clock_vcd_num(sim_hdl, __clk_handle_0), "sCLK", 1); vcd_write_def(sim_hdl, bk_clock_vcd_num(sim_hdl, __clk_handle_1), "dCLK", 1); vcd_write_def(sim_hdl, n++, "sRST", 1); vcd_write_def(sim_hdl, n++, "dPulse", 1); vcd_write_scope_end(sim_hdl); return (n); } void dump_VCD(tVCDDumpType dt, MOD_SyncHandshake& backing) { unsigned int num = vcd_num; if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); } else if (dt == VCD_DUMP_CHANGES) { if (backing.dSyncReg1 != dSyncReg1) vcd_write_val(sim_hdl, num++, dSyncReg1, 1); else num++; if (backing.dSyncReg2.probe() != dSyncReg2.probe()) vcd_write_val(sim_hdl, num++, dSyncReg2.probe(), 1); else num++; if (backing.dLastState.probe() != dLastState.probe()) vcd_write_val(sim_hdl, num++, dLastState.probe(), 1); else num++; if (backing.sToggleReg.probe() != sToggleReg.probe()) vcd_write_val(sim_hdl, num++, sToggleReg.probe(), 1); else num++; if (backing.sSyncReg1 != sSyncReg1) vcd_write_val(sim_hdl, num++, sSyncReg1, 1); else num++; if (backing.sSyncReg2 != sSyncReg2) vcd_write_val(sim_hdl, num++, sSyncReg2, 1); else num++; if (backing.sRDY != sRDY) vcd_write_val(sim_hdl, num++, sRDY, 1); else num++; if (backing.did_send != did_send) { vcd_write_val(sim_hdl, num++, did_send, 1); backing.did_send = did_send; } else num++; if (backing.in_reset != in_reset) vcd_write_val(sim_hdl, num++, !in_reset, 1); else num++; if (backing.pulsing != pulsing) vcd_write_val(sim_hdl, num++, pulsing, 1); else num++; } else { vcd_write_val(sim_hdl, num++, dSyncReg1, 1); vcd_write_val(sim_hdl, num++, dSyncReg2.probe(), 1); vcd_write_val(sim_hdl, num++, dLastState.probe(), 1); vcd_write_val(sim_hdl, num++, sToggleReg.probe(), 1); vcd_write_val(sim_hdl, num++, sSyncReg1, 1); vcd_write_val(sim_hdl, num++, sSyncReg2, 1); vcd_write_val(sim_hdl, num++, sRDY, 1); vcd_write_val(sim_hdl, num++, did_send, 1); backing.did_send = did_send; vcd_write_val(sim_hdl, num++, !in_reset, 1); vcd_write_val(sim_hdl, num++, pulsing, 1); } backing.dSyncReg1 = dSyncReg1; backing.dSyncReg2 = dSyncReg2; backing.dLastState = dLastState; backing.sToggleReg = sToggleReg; backing.sSyncReg1 = sSyncReg1; backing.sSyncReg2 = sSyncReg2; backing.sRDY = sRDY; backing.pulsing = pulsing; } private: tUInt8 dSyncReg1; SyncVar dSyncReg2; SyncVar dLastState; SyncVar sToggleReg; tUInt8 sSyncReg1; tUInt8 sSyncReg2; tUInt8 sRDY; bool en; bool in_reset; bool pulsing; bool param_init; bool param_delayreturn; // used for VCD dumping tClock __clk_handle_0; // sCLK tClock __clk_handle_1; // dCLK bool did_send; }; // Synchronized registers template class MOD_SyncReg : public Module { public: MOD_SyncReg(tSimStateHdl simHdl, const char* name, Module* parent, unsigned int width, const T& v) : Module(simHdl, name, parent), sDataSyncIn(simHdl, width), reset_value(v), sync(simHdl, "sync", this, false, true), bits(width) { init_val(dD_OUT, bits); write_undet(&dD_OUT, bits); in_reset = false; } MOD_SyncReg(tSimStateHdl simHdl, const char* name, Module* parent, unsigned int width) : Module(simHdl, name, parent), sync(simHdl, "sync", this), sDataSyncIn(simHdl, width), bits(width) { init_val(dD_OUT, bits); write_undet(&dD_OUT, bits); init_val(reset_value, bits); write_undet(reset_value, bits); in_reset = false; } public: const T& METH_read() const { return dD_OUT; } bool METH_RDY_write() const { return sync.METH_RDY_send(); } void METH_write(const T& x) { sDataSyncIn.write(x); sync.METH_send(); } void clk_src(tUInt8 clock_value, tUInt8 gate_value = 1) { if (gate_value == 0) return; sync.clk_src(clock_value, gate_value); } void clk_dst(tUInt8 clock_value, tUInt8 gate_value = 1) { if (gate_value == 0) return; if (sync.METH_pulse()) dD_OUT = sDataSyncIn.read(); sync.clk_dst(clock_value, gate_value); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); sync.reset_sRST(rst_in); if (in_reset) { sDataSyncIn.force(reset_value); dD_OUT = reset_value; } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdD_OUT = ", indent+2, ""); dump_val(dD_OUT, bits); putchar('\n'); printf("%*ssDataSyncIn = ", indent+2, ""); dump_val(sDataSyncIn.read(), bits); putchar('\n'); sync.dump_state(indent + 2); } unsigned int dump_VCD_defs(unsigned int /* num */) { vcd_num = vcd_reserve_ids(sim_hdl, 2); vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, vcd_num, "dD_OUT", bits); vcd_write_def(sim_hdl, vcd_num+1, "sDataSyncIn", bits); unsigned int n = sync.dump_VCD_defs(vcd_num + 2); vcd_write_scope_end(sim_hdl); return (n); } void dump_VCD(tVCDDumpType dt, MOD_SyncReg& backing) { if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, vcd_num, bits); vcd_write_x(sim_hdl, vcd_num+1, bits); } else { if ((dt != VCD_DUMP_CHANGES) || (backing.dD_OUT != dD_OUT)) { vcd_write_val(sim_hdl, vcd_num, dD_OUT, bits); backing.dD_OUT = dD_OUT; } if ((dt != VCD_DUMP_CHANGES) || (backing.sDataSyncIn.read() != sDataSyncIn.read())) { vcd_write_val(sim_hdl, vcd_num+1, sDataSyncIn.read(), bits); backing.sDataSyncIn = sDataSyncIn; } } sync.dump_VCD(dt, backing.sync); } private: SyncVar sDataSyncIn; T dD_OUT; T reset_value; MOD_SyncHandshake sync; bool in_reset; const unsigned int bits; }; // Synchronized FIFOs template const unsigned int* index_fn_syncfifo(void* base, tUInt64 addr); static unsigned int index_size(unsigned int d) { unsigned int sz = 0; while (d != 0) { ++sz; d = d >> 1; } return sz; } template class MOD_SyncFIFO : public Module { public: MOD_SyncFIFO(tSimStateHdl simHdl, const char* name, Module* parent, unsigned int width, unsigned int depth, unsigned int hasClr) : Module(simHdl, name, parent), width(width), depth(depth), src_hi(simHdl, index_size(depth)+1), dst_lo(simHdl, index_size(depth)+1), hasClear(hasClr), sClrSync(simHdl, "sClrSync", this), dClrSync(simHdl, "dClrSync", this) { data = new T[depth]; for (unsigned int n = 0; n < depth; ++n) init_val(data[n], width); init_val(dDoutReg, width); idx_bits = index_size(depth); mask = (1 << idx_bits) - 1; src_lo = 0; dst_hi = 0; src_hi_plus_1 = 1; dst_lo_plus_1 = 1; dSyncReg1 = 0; sSyncReg1 = 0; init_val(sCountReg, idx_bits); sCountReg = 0; init_val(dCountReg, idx_bits); dCountReg = 0; not_empty = false; not_full = true; in_reset = false; s_reset = false; d_reset = false; did_enq = false; did_deq = false; did_sclear = false; did_dclear = false; symbol_count = 3; symbols = new tSym[symbol_count]; range.lo = 0; range.hi = depth - 1; range.base = (void*) this; range.fetch = index_fn_syncfifo; symbols[0].key = ""; symbols[0].info = SYM_RANGE | width << 4; symbols[0].value = (void*)(&range); symbols[1].key = "depth"; symbols[1].info = SYM_PARAM | (8*sizeof(unsigned int)) << 4; symbols[1].value = (void*)(&depth); symbols[2].key = "level"; symbols[2].info = SYM_DEF | idx_bits << 4; symbols[2].value = (void*)(&dCountReg); } ~MOD_SyncFIFO() { delete[] data; } public: bool METH_notEmpty() { // true when non-empty and not in reset return (!d_reset && ((depth != 1) ? not_empty : dst_hi != dst_lo.probe())); // return (!d_reset && not_empty); } // support the alternate naming used by the SyncFIFOLevel import-BVI bool METH_dNotEmpty() { return METH_notEmpty(); } bool METH_RDY_first() { return METH_notEmpty(); } T METH_first() { return dDoutReg; } bool METH_RDY_deq() { return METH_notEmpty(); } void METH_deq() { did_deq = true; } bool METH_notFull() { // true when non-full and not in reset // (note: depth assumed to be power of 2) return (!s_reset && not_full); } // support the alternate naming used by the SyncFIFOLevel import-BVI bool METH_sNotFull() { return METH_notFull(); } bool METH_RDY_enq() { return METH_notFull(); } void METH_enq(const T& x) { if (depth == 1) dDoutReg = x; data[src_hi.read() % depth] = x; did_enq = true; } // For zero-width variants void METH_enq() { did_enq = true; } I METH_sCount() { return sCountReg; } I METH_dCount() { return dCountReg; } bool METH_RDY_sClear() { return sClrSync.METH_RDY_send(); } void METH_sClear() { sClrSync.METH_send(); did_sclear = true; } bool METH_RDY_dClear() { return dClrSync.METH_RDY_send(); } void METH_dClear() { dClrSync.METH_send(); did_dclear = true; } void clk_src(tUInt8 clock_value, tUInt8 gate_value = 1) { if (gate_value == 0) return; // update source-side reset s_reset = in_reset; // update not full reg and count if (s_reset || (hasClear && (did_sclear || !sClrSync.METH_RDY_send() || dClrSync.METH_pulse()))) { src_hi.force(0); src_hi_plus_1 = 1; not_full = false; sCountReg = 0; } else if (did_enq) { not_full = ((src_hi_plus_1 ^ depth) != src_lo); if (src_hi_plus_1 > src_lo) sCountReg = src_hi_plus_1 - src_lo; else sCountReg = (src_hi_plus_1 + (2*depth) - src_lo) & mask; src_hi.write(src_hi_plus_1); src_hi_plus_1 = (src_hi_plus_1 + 1) % (2*depth); } else { not_full = ((src_hi.read() ^ depth) != src_lo); if (src_hi.read() > src_lo) sCountReg = src_hi.read() - src_lo; else sCountReg = (src_hi.read() + (2*depth) - src_lo) & mask; } did_sclear = false; did_enq = false; // synchronize index from destination side src_lo = sSyncReg1; sSyncReg1 = dst_lo.read(); if (depth == 1) { not_full = src_hi.probe() == src_lo; sCountReg = not_full ? 0 : 1; } sClrSync.clk_src(clock_value, gate_value); dClrSync.clk_dst(clock_value, gate_value); } void clk_dst(tUInt8 clock_value, tUInt8 gate_value = 1) { if (gate_value == 0) return; // update destination-side reset d_reset = in_reset; // update not empty reg and count if (d_reset || (hasClear && (did_dclear || !dClrSync.METH_RDY_send() || sClrSync.METH_pulse()))) { dst_lo.force(0); dst_lo_plus_1 = 1; not_empty = false; dCountReg = 0; } else if (did_deq) { not_empty = (dst_hi != dst_lo.read()); if (dst_hi > dst_lo_plus_1) dCountReg = dst_hi - dst_lo.read(); else dCountReg = (dst_hi + (2*depth) - dst_lo.read()) & mask; //not_empty = dCountReg != 0; if (not_empty) { if (depth != 1) dDoutReg = data[dst_lo.read() % depth]; dst_lo.write(dst_lo_plus_1); dst_lo_plus_1 = (dst_lo_plus_1 + 1) % (2*depth); } } else { if (dst_hi > dst_lo.read()) dCountReg = dst_hi - dst_lo.read(); else dCountReg = (dst_hi + (2*depth) - dst_lo.read()) & mask; if ((depth != 1) && !not_empty && (dst_hi != dst_lo.read())) { dDoutReg = data[dst_lo.read() % depth]; dst_lo.write(dst_lo_plus_1); dst_lo_plus_1 = (dst_lo_plus_1 + 1) % (2*depth); not_empty = true; } } did_dclear = false; did_deq = false; // synchronize index from source side dst_hi = dSyncReg1; dSyncReg1 = src_hi.read(); if (depth == 1) { not_empty = dst_lo.probe() == dst_hi; dCountReg = not_empty ? 1 : 0; } sClrSync.clk_dst(clock_value, gate_value); dClrSync.clk_src(clock_value, gate_value); } void reset_sRST(tUInt8 rst_in) { in_reset = (rst_in == 0); sClrSync.reset_sRST(rst_in); dClrSync.reset_sRST(rst_in); if (in_reset) { src_lo = dst_hi = 0; src_hi.force(0); dst_lo.force(0); src_hi_plus_1 = dst_lo_plus_1 = 1; dSyncReg1 = sSyncReg1 = 0; sCountReg = dCountReg = 0; s_reset = d_reset = true; did_enq = did_deq = false; did_sclear = did_dclear = false; } } void rst_tick_clk_src(tUInt8 /* clock_gate */) { /* unused */ } private: bool occupied(unsigned int idx) { unsigned int l = dst_lo.probe(); unsigned int h = src_hi.probe(); // when l == h the FIFO is empty if (l == h) return false; // map l and h onto data array indexes l = l % depth; h = h % depth; return (l < h) ? (idx >= l && idx < h) : (idx >= l || idx < h); } public: const unsigned int* data_index(tUInt64 addr) const { if (addr < ((tUInt64) dCountReg)) return symbol_value(&(data[(dst_lo.read() + addr) % depth]),width); else return NULL; } void dump_state(unsigned int indent) { printf("%*s%s = ", indent, "", inst_name); if (src_hi.read() == dst_lo.read()) printf("EMPTY"); else { printf("{ "); for (unsigned int n = 0; (dst_lo.read() + n) % (2*depth) != src_hi.read(); ++n) { if (n > 0) printf(", "); dump_val(data[(dst_lo.read() + n) % depth], width); } printf(" }"); } putchar('\n'); if (hasClear) { sClrSync.dump_state(indent + 2); dClrSync.dump_state(indent + 2); } } unsigned int dump_VCD_defs(unsigned int num) { vcd_num = vcd_reserve_ids(sim_hdl, depth + 13); unsigned int n = vcd_num; char buf[16]; vcd_write_scope_start(sim_hdl, inst_name); vcd_write_def(sim_hdl, n++, "FULL_N", 1); vcd_write_def(sim_hdl, n++, "EMPTY_N", 1); vcd_write_def(sim_hdl, n++, "dEnqPtr", idx_bits+1); vcd_write_def(sim_hdl, n++, "dGDeqPtr", idx_bits+1); vcd_write_def(sim_hdl, n++, "dGDeqPtr1", idx_bits+1); vcd_write_def(sim_hdl, n++, "dSyncReg1", idx_bits+1); vcd_write_def(sim_hdl, n++, "sDeqPtr", idx_bits+1); vcd_write_def(sim_hdl, n++, "sGEnqPtr", idx_bits+1); vcd_write_def(sim_hdl, n++, "sGEnqPtr1", idx_bits+1); vcd_write_def(sim_hdl, n++, "sSyncReg1", idx_bits+1); vcd_write_def(sim_hdl, n++, "sCount", idx_bits); vcd_write_def(sim_hdl, n++, "dCount", idx_bits); vcd_write_def(sim_hdl, n++, "dDoutReg", width); for (unsigned int i = 0; i < depth; ++i) { snprintf(buf, 16, "arr_%d", i); vcd_write_def(sim_hdl, n++, buf, width); } unsigned int n2 = sClrSync.dump_VCD_defs(n); unsigned int n3 = dClrSync.dump_VCD_defs(n2); vcd_write_scope_end(sim_hdl); return n3; } void dump_VCD(tVCDDumpType dt, MOD_SyncFIFO& backing) { unsigned int num = vcd_num; if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, 1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits+1); vcd_write_x(sim_hdl, num++, idx_bits); vcd_write_x(sim_hdl, num++, idx_bits); vcd_write_x(sim_hdl, num++, width); for (unsigned int i = 0; i < depth; ++i) vcd_write_x(sim_hdl, num++, width); } else if (dt == VCD_DUMP_CHANGES) { if (not_full != backing.not_full) vcd_write_val(sim_hdl, num++, not_full, 1); else ++num; if (METH_RDY_first() != backing.METH_RDY_first()) vcd_write_val(sim_hdl, num++, METH_RDY_first(), 1); else ++num; if (dst_hi != backing.dst_hi) vcd_write_val(sim_hdl, num++, dst_hi, idx_bits+1); else ++num; if (dst_lo.probe() != backing.dst_lo.read()) vcd_write_val(sim_hdl, num++, dst_lo.probe(), idx_bits+1); else ++num; if (dst_lo_plus_1 != backing.dst_lo_plus_1) vcd_write_val(sim_hdl, num++, dst_lo_plus_1, idx_bits+1); else ++num; if (dSyncReg1 != backing.dSyncReg1) vcd_write_val(sim_hdl, num++, dSyncReg1, idx_bits+1); else ++num; if (src_lo != backing.src_lo) vcd_write_val(sim_hdl, num++, src_lo, idx_bits+1); else ++num; if (src_hi.probe() != backing.src_hi.read()) vcd_write_val(sim_hdl, num++, src_hi.probe(), idx_bits+1); else ++num; if (src_hi_plus_1 != backing.src_hi_plus_1) vcd_write_val(sim_hdl, num++, src_hi_plus_1, idx_bits+1); else ++num; if (sSyncReg1 != backing.sSyncReg1) vcd_write_val(sim_hdl, num++, sSyncReg1, idx_bits+1); else ++num; if (sCountReg != backing.sCountReg) vcd_write_val(sim_hdl, num++, sCountReg, idx_bits); else ++num; if (dCountReg != backing.dCountReg) vcd_write_val(sim_hdl, num++, dCountReg, idx_bits); else ++num; if (METH_RDY_first() && !backing.METH_RDY_first()) vcd_write_val(sim_hdl, num++, dDoutReg, width); else if (!METH_RDY_first() && backing.METH_RDY_first()) vcd_write_x(sim_hdl, num++, width); else if (METH_RDY_first() && (dDoutReg != backing.dDoutReg)) vcd_write_val(sim_hdl, num++, dDoutReg, width); else ++num; for (unsigned int i = 0; i < depth; ++i) { unsigned int idx = (dst_lo.probe() + i) % depth; unsigned int b_idx = (backing.dst_lo.read() + i) % depth; // handle value which has been added if (occupied(idx) && !backing.occupied(b_idx)) vcd_write_val(sim_hdl, num++, data[idx], width); // handle value which is removed else if (!occupied(idx) && backing.occupied(b_idx)) vcd_write_x(sim_hdl, num++, width); // handle value which is changed else if (occupied(idx) && backing.occupied(b_idx) && (data[idx] != backing.data[b_idx])) vcd_write_val(sim_hdl, num++, data[idx], width); // handle value which is unchanged else ++num; } } else { vcd_write_val(sim_hdl, num++, METH_RDY_enq(), 1); vcd_write_val(sim_hdl, num++, METH_RDY_first(), 1); vcd_write_val(sim_hdl, num++, dst_hi, idx_bits+1); vcd_write_val(sim_hdl, num++, dst_lo.probe(), idx_bits+1); vcd_write_val(sim_hdl, num++, dst_lo_plus_1, idx_bits+1); vcd_write_val(sim_hdl, num++, dSyncReg1, idx_bits+1); vcd_write_val(sim_hdl, num++, src_lo, idx_bits+1); vcd_write_val(sim_hdl, num++, src_hi.probe(), idx_bits+1); vcd_write_val(sim_hdl, num++, src_hi_plus_1, idx_bits+1); vcd_write_val(sim_hdl, num++, sSyncReg1, idx_bits+1); vcd_write_val(sim_hdl, num++, sCountReg, idx_bits); vcd_write_val(sim_hdl, num++, dCountReg, idx_bits); if (METH_RDY_first()) vcd_write_val(sim_hdl, num++, dDoutReg, width); else vcd_write_x(sim_hdl, num++, width); for (unsigned int i = 0; i < depth; ++i) { unsigned int idx = (dst_lo.read() + i) % depth; if (occupied(idx)) vcd_write_val(sim_hdl, num++, data[idx], width); else vcd_write_x(sim_hdl, num++, width); } } backing.src_lo = src_lo; backing.src_hi = src_hi; backing.dst_lo = dst_lo; backing.dst_hi = dst_hi; backing.src_hi_plus_1 = src_hi_plus_1; backing.dst_lo_plus_1 = dst_lo_plus_1; backing.sSyncReg1 = sSyncReg1; backing.dSyncReg1 = dSyncReg1; backing.sCountReg = sCountReg; backing.dCountReg = dCountReg; backing.not_empty = not_empty; backing.not_full = not_full; backing.s_reset = s_reset; backing.d_reset = d_reset; backing.dDoutReg = dDoutReg; for (unsigned int i = 0; i < depth; ++i) backing.data[i] = data[i]; sClrSync.dump_VCD(dt, backing.sClrSync); dClrSync.dump_VCD(dt, backing.dClrSync); } private: const unsigned int width; const unsigned int depth; unsigned int idx_bits; unsigned int mask; T* data; T dDoutReg; unsigned int src_lo; SyncVar src_hi; SyncVar dst_lo; unsigned int dst_hi; unsigned int dSyncReg1; unsigned int sSyncReg1; unsigned int src_hi_plus_1; unsigned int dst_lo_plus_1; I sCountReg; I dCountReg; bool not_empty; bool not_full; bool in_reset; bool s_reset; bool d_reset; bool did_enq; bool did_deq; bool did_sclear; bool did_dclear; bool hasClear; MOD_SyncHandshake sClrSync; MOD_SyncHandshake dClrSync; // range structure for symbolic access to FIFO data Range range; }; // Function to index into FIFO data array template const unsigned int* index_fn_syncfifo(void* base, tUInt64 addr) { MOD_SyncFIFO* fifo = (MOD_SyncFIFO*) base; return fifo->data_index(addr); } // Synchronized RAM Model template class MOD_DualPortRam : public Module { public: MOD_DualPortRam(tSimStateHdl simHdl, const char* name, Module* parent, unsigned int addr_width, unsigned int data_width) : Module(simHdl, name, parent), addr_bits(addr_width), data_bits(data_width), written_at(~bk_now(sim_hdl)) { if (vcd_is_backing_instance(sim_hdl)) return; // do not allocate storage for backing instance nWords = 1llu << addr_width; data = new DT[nWords]; for (unsigned long long n = 0llu; n < nWords; ++n) { init_val(data[n], data_bits); write_undet(&(data[n]), data_bits); } init_val(write_addr, addr_bits); init_val(prev_value, data_bits); } ~MOD_DualPortRam() { delete[] data; } public: // Note: the read and write methods of a DualPortRam are // conflict free. When the edges coincide and a simultaneous // read and write occur the same address, we want the read to // return the value from the beginning of the cycle. const DT METH_read(const AT& addr) const { if ((write_addr == addr) && bk_is_same_time(sim_hdl, written_at)) return prev_value; else return data[addr]; } void METH_write(const AT& addr, const DT& val) { written_at = bk_now(sim_hdl); write_addr = addr; prev_value = data[addr]; data[addr] = val; } public: void dump_state(unsigned int indent) { // Memory contents are not dumped } unsigned int dump_VCD_defs(unsigned int num) { // Memory contents are not dumped return (num); } void dump_VCD(tVCDDumpType /* unused */, MOD_DualPortRam& /* unused */) { // Memory contents are not dumped } private: DT* data; unsigned int addr_bits; unsigned int data_bits; unsigned long long nWords; tTime written_at; AT write_addr; DT prev_value; }; // This is a synchronization primitive that combines a register // with a latch. The register is written and read in the source // domain, and its output is latched to be read in the destination // domain. The purpose of this primitive is to align data to a shifted // clock domain. template class MOD_LatchCrossingReg : public Module { public: MOD_LatchCrossingReg(tSimStateHdl simHdl, const char* name, Module* parent, unsigned int width, const T& v) : Module(simHdl, name, parent), reset_value(v), bits(width) { init_val(dLatch, width); write_undet(&dLatch, width); init_val(sFlop, width); write_undet(&sFlop, width); init_val(prev_value, width); write_undet(&prev_value, width); writtenAt = ~bk_now(sim_hdl); in_reset = false; prev_transparent = false; transparent = false; } public: const T& METH__read() const { return sFlop; } void METH__write(const T& x) { if (!in_reset) { prev_value = sFlop; sFlop = x; writtenAt = bk_now(sim_hdl); if (transparent) dLatch = x; } } const T& METH_crossed() const { if (transparent) { if (writtenAt == bk_now(sim_hdl)) return prev_value; else return sFlop; } else { return dLatch; } } void dstClk(tUInt8 clock_value, tUInt8 gate_value = 1) { prev_transparent = transparent; transparent = (gate_value != 0 && clock_value != 0); if (transparent) { dLatch = sFlop; } else if (prev_transparent) { if (writtenAt == bk_now(sim_hdl)) dLatch = prev_value; else dLatch = sFlop; } } void reset_SRST(tUInt8 rst_in) { in_reset = (rst_in == 0); if (in_reset) { dLatch = reset_value; sFlop = reset_value; prev_value = reset_value; prev_transparent = false; transparent = false; } } void rst_tick_clk(tUInt8 /* clock_gate */) { /* unused */ } public: void dump_state(unsigned int indent) { printf("%*s%s:\n", indent, "", inst_name); printf("%*sdLatch = ", indent+2, ""); dump_val(dLatch, bits); putchar('\n'); printf("%*ssFlop = ", indent+2, ""); dump_val(sFlop, bits); putchar('\n'); } unsigned int dump_VCD_defs(unsigned int /* num */) { char buf[128]; vcd_num = vcd_reserve_ids(sim_hdl, 2); snprintf(buf,128,"%s$L_OUT",inst_name); vcd_write_def(sim_hdl, vcd_num, buf, bits); snprintf(buf,128,"%s$Q_OUT",inst_name); vcd_write_def(sim_hdl, vcd_num+1, buf, bits); return (vcd_num + 2); } void dump_VCD(tVCDDumpType dt, MOD_LatchCrossingReg& backing) { if (dt == VCD_DUMP_XS) { vcd_write_x(sim_hdl, vcd_num, bits); vcd_write_x(sim_hdl, vcd_num+1, bits); } else { if ((dt != VCD_DUMP_CHANGES) || (backing.dLatch != dLatch)) { vcd_write_val(sim_hdl, vcd_num, dLatch, bits); backing.dLatch = dLatch; } if ((dt != VCD_DUMP_CHANGES) || (backing.sFlop != sFlop)) { vcd_write_val(sim_hdl, vcd_num+1, sFlop, bits); backing.sFlop = sFlop; } } } private: T dLatch; T sFlop; T prev_value; T reset_value; unsigned int bits; tTime writtenAt; bool in_reset; bool prev_transparent; bool transparent; }; #endif /* __BS_PRIM_MOD_SYNCHRONIZERS_H__ */