From 53309f9e597c91bf630886f7e125995bf48c6f53 Mon Sep 17 00:00:00 2001 From: stefan Date: Thu, 25 Jun 2026 14:28:54 +0900 Subject: [PATCH] simple incremental sim using dirty flags --- src/kyupy/__init__.py | 2 +- src/kyupy/logic_sim.py | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/kyupy/__init__.py b/src/kyupy/__init__.py index 83b6b4a..b4076e7 100644 --- a/src/kyupy/__init__.py +++ b/src/kyupy/__init__.py @@ -111,7 +111,7 @@ class Timer: class Timers: - def __init__(self, t={}): self.timers = defaultdict(Timer) | t + def __init__(self, t={}): self.timers: dict[str, Timer] = defaultdict(Timer) | t def __getitem__(self, name): return self.timers[name] def __repr__(self): return '{' + ', '.join([f'{k}: {v}' for k, v in self.timers.items()]) + '}' def __add__(self, t): diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index 4357401..0e6ec36 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -356,6 +356,19 @@ class LogicSim2V(sim.SimOps): Storage locations are indirectly addressed. Data for line `l` is in `self.c[self.c_locs[l]]`. """ + self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8) + """Marker for logic values that have changed recently for use in incremental simulation. + + A node n will be evaluated by `c_prop()` if and only if at least one of its inputs is marked dirty (`c_dirty[c_locs[n.ins[i]]] == 1`). + If a node is evaluated, its output is marked dirty. + By default, this array is all-1, therefore every node is evaluated every time by `c_prop()`. + Enable incremental simulation by setting this array to all-0, change signal values in `c` and set `c_dirty` to 1 for all changed signals. + Next call to `c_prop()` will only evaluate nodes that read from the changed signals. + To restore the original simulation state, revert the signal value changes in `c`, and call `c_prop()` again. + Caveats: + - Injected changes that are located downstream of other changes will have no effect. + - Only works with `c_reuse=False` (default). + """ self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8) """Logic values assigned to the ports and flip-flops of the circuit. @@ -390,7 +403,7 @@ class LogicSim2V(sim.SimOps): fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8) fault_mask2[:len(fault_mask)] = fault_mask fault_mask = fault_mask2 - c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model)) + c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model)) def c_to_s(self): """Captures the results of the combinational portion into ``self.s_result``. @@ -424,9 +437,11 @@ class LogicSim2V(sim.SimOps): @numba.njit -def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model): +def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model): for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]: o0, i0, i1, i2, i3 = [c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)] + if fault_line < 0 or o0l != fault_line: # fault injection forces node evaluation + if not (c_dirty[i0] | c_dirty[i1] | c_dirty[i2] | c_dirty[i3]): continue if op == sim.BUF1: c[o0]=c[i0] elif op == sim.INV1: c[o0] = ~c[i0] elif op == sim.AND2: c[o0] = c[i0] & c[i1] @@ -466,8 +481,9 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model): c[o0] = c[o0] & ~fault_mask elif fault_model == 1: c[o0] = c[o0] | fault_mask - else: + elif fault_model == 2: c[o0] = c[o0] ^ fault_mask + c_dirty[o0] = 1 class LogicSim4V(sim.SimOps):