Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions src/trs/crates/trs-interp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1421,18 +1421,39 @@ impl Interp {
return v;
}
// JIT mode: fire signals and schedule-position defs live
// in arena slots kept current by the native scheds
// in arena slots kept current by the native scheds. In
// the combo (PG_FINAL early-rule) pass, DATA defs must
// recompute at post-edge state — Bluesim's after-edge
// schedule computes its def cone as fresh locals — so
// only rule-fire defs (edge-schedule state the combo fn
// reads as ports) still serve from their slots; a data
// slot holds the EDGE-time value, where a crossed read
// was still backdated
if !self.jit_arena_ptr.is_null() {
if let Some(&(base, w)) = self.jit_eager_slots.get(&(inst, *name)) {
let words = ((w.max(1) as usize) + 63) / 64;
let limbs = unsafe {
std::slice::from_raw_parts(
self.jit_arena_ptr.add(base as usize),
words,
)
let use_slot = if crate::prim::in_combo_sched() {
let module = self.module_of(inst);
let mir = self.mods[module].ir;
// a name with no def-table entry has no expr
// to recompute — its slot stays authoritative
self.mods[module].defs.get(name).map_or(true, |&di| {
let p = &self.d.modules[mir].defs[di].props;
p.can_fire || p.will_fire
})
} else {
true
};
if use_slot {
let words = ((w.max(1) as usize) + 63) / 64;
let limbs = unsafe {
std::slice::from_raw_parts(
self.jit_arena_ptr.add(base as usize),
words,
)
}
.to_vec();
return Value::from_limbs64(w.max(1), limbs);
}
.to_vec();
return Value::from_limbs64(w.max(1), limbs);
}
}
let module = self.module_of(inst);
Expand Down Expand Up @@ -4855,6 +4876,11 @@ impl Interp {
if self.vcd.is_active() {
self.vcd_event(t);
}
// kernel combinational schedule (bk_is_combo_sched):
// crossed reads inside this pass see POST-edge state —
// the same-instant backdate applies to destination-
// domain logic only (MOD_Reg::METH_crossed)
crate::prim::set_combo_sched(true);
for rci in std::mem::take(&mut fired_this_slice) {
let rc = &rcomps[rci];
if rc.early.is_empty() {
Expand Down Expand Up @@ -4892,6 +4918,7 @@ impl Interp {
}
}
}
crate::prim::set_combo_sched(false);
}

// steady state may only begin here (fusion compiles after
Expand Down
38 changes: 35 additions & 3 deletions src/trs/crates/trs-interp/src/prim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ pub(crate) fn quiet_engine() -> bool {
QUIET_ENGINE.with(|c| c.get())
}

thread_local! {
/// Kernel combinational-schedule flag (bk_is_combo_sched): true
/// while the after-edge PG_FINAL pass runs clock-crossing "early"
/// rules. The owning Interp stamps it around that pass; early
/// rules always execute on the interp tier (compiled edges skip
/// them), so one flag covers every artifact tier.
pub(crate) static IN_COMBO_SCHED: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) };
}

/// See `IN_COMBO_SCHED`: the advance loop stamps this around the
/// after-edge early-rule pass.
pub(crate) fn set_combo_sched(on: bool) {
IN_COMBO_SCHED.with(|c| c.set(on));
}

pub(crate) fn in_combo_sched() -> bool {
IN_COMBO_SCHED.with(|c| c.get())
}

thread_local! {
/// Reference Bluesim reads a RegFile/BRAM load file when the model
/// object is constructed, which is run time; a `.mem` is an input to
Expand Down Expand Up @@ -2382,9 +2402,15 @@ impl Prim for Reg {
match method {
"read" | "get" | "_read" => self.load(),
// crossing read: a same-instant write is not yet visible
// to destination-domain LOGIC — but the after-edge combo
// pass (clock-crossing early rules) reads post-edge state,
// exactly like METH_crossed's !bk_is_combo_sched guard
// (bs_prim_mod_reg.h:92). Without the exemption a gate
// detector's export wire re-reads the pre-edge value and
// its coincident-edge sampler never observes a change.
// (crossing regs are never arena-backed)
"crossed" => {
if self.written_at == now {
if self.written_at == now && !in_combo_sched() {
self.prev.clone()
} else {
self.value.clone()
Expand Down Expand Up @@ -2823,7 +2849,11 @@ impl RWire {
let width = if zero_width { 0 } else { carg(consts, 0) as u32 };
RWire {
width,
value: Value::zero(width.max(1)),
// the unset value plane is OBSERVABLE: aDropUndet collapses
// `if whas then wget else _` to bare wget at compile, so an
// unwritten wire's data must match Bluesim's write_undet
// fill (MOD_Wire ctor), not zero
value: Value::undet(width.max(1)),
valid: false,
written: false,
slot: None,
Expand Down Expand Up @@ -2979,7 +3009,9 @@ impl BypassWire {
let width = if zero_width { 1 } else { carg(consts, 0) as u32 };
BypassWire {
width,
value: Value::zero(width.max(1)),
// same contract as RWire: pre-first-write reads must see
// Bluesim's write_undet fill (see RWire::new)
value: Value::undet(width.max(1)),
slot: None,
}
}
Expand Down
53 changes: 53 additions & 0 deletions src/trs/tests/regress/MakeClkCross.bsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Coincident MakeClock-domain edges + a CrossingReg sampled across them
// (the matx mkClkGate2Bool shape): a CrossingReg toggle in domain A
// (crossed-side = domain B), a domain-B register sampling .crossed, and
// the gate compare exported to the default domain via mkNullCrossingWire,
// whose write rule is a clock_crossing_rule in domain B's after-edge
// (combo-sched) pass. Pins two semantics: a crossed read backdates to
// the pre-edge value for destination-domain LOGIC only — the combo pass
// reads post-edge (MOD_Reg::METH_crossed's !bk_is_combo_sched arm) — and
// data defs consumed by early rules recompute at post-edge state rather
// than serving edge-time slot values.
import Clocks::*;

(* synthesize *)
module sysMakeClkCross(Empty);
MakeClockIfc#(Bool) aclkIfc <- mkUngatedClock(False);
Clock aclk = aclkIfc.new_clk;
Reset arst <- mkAsyncResetFromCR(0, aclk);

MakeClockIfc#(Bool) bclkIfc <- mkUngatedClock(False);
Clock bclk = bclkIfc.new_clk;
Reset brst <- mkAsyncResetFromCR(0, bclk);

// domain-A toggle in a CrossingReg whose crossed side is domain B
CrossingReg#(Bool) tog <- mkNullCrossingReg(bclk, False, clocked_by aclk, reset_by arst);
rule tog_r;
tog <= !tog;
endrule

// domain-B delayed sample of the crossed value (starts True, like
// mkClkGate2Bool's toggleDly)
Reg#(Bool) dly <- mkReg(True, clocked_by bclk, reset_by brst);
rule dly_r;
dly <= tog.crossed;
endrule

// the gate-detector output: a domain-B combinational value
Bool gate = (tog.crossed != dly);

Clock cc <- exposeCurrentClock;
ReadOnly#(Bool) gateV <- mkNullCrossingWire(cc, gate);
ReadOnly#(Bool) togV <- mkNullCrossingWire(cc, tog.crossed);
ReadOnly#(Bool) dlyV <- mkNullCrossingWire(cc, dly);

Reg#(UInt#(8)) cyc <- mkReg(0);
rule drive;
cyc <= cyc + 1;
Bool lvl = (pack(cyc)[0] == 1);
aclkIfc.setClockValue(lvl);
bclkIfc.setClockValue(lvl);
$display("cyc %0d lvl=%b tog=%b dly=%b gate=%b", cyc, lvl, togV, dlyV, gateV);
if (cyc == 12) $finish(0);
endrule
endmodule
4 changes: 4 additions & 0 deletions src/trs/tests/regress/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ check_dyn() { # name top errtag
if ! cmp -s "$SRC/$name.expected" gota.out; then echo "FAIL $name (art stdout)"; diff "$SRC/$name.expected" gota.out | head -3; fail=1; return; fi
echo "PASS $name"
}
# coincident MakeClock domains + CrossingReg crossed-read semantics:
# destination-domain logic backdates to pre-edge, the after-edge combo
# pass reads post-edge (gate detectors break as steady-0 otherwise)
check MakeClkCross sysMakeClkCross
check_dyn DynSched sysDynSched G0100
check_dyn DynSchedBoth sysDynSchedBoth G0101
check_dyn DynSchedSelf sysDynSchedSelf G0096
Expand Down