diff --git a/docs/Makefile b/docs/Makefile index d4bb2cb..0f632bf 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,3 +1,5 @@ +# pip install sphinx sphinx-rtd-theme +# # Minimal makefile for Sphinx documentation # diff --git a/src/kyupy/circuit.py b/src/kyupy/circuit.py index 5342a1a..3699d15 100644 --- a/src/kyupy/circuit.py +++ b/src/kyupy/circuit.py @@ -291,6 +291,15 @@ class Circuit: @property def stats(self): + """Generates a dictionary with the number of all different elements in the circuit. + + The dictionary contains the number of all different kinds of nodes, the number + of lines, as well various sums like number of combinational gates, number of + primary I/Os, number of sequential elements, and so on. + + The count of regular cells use their `Node.kind` as key, other statistics use + dunder-keys like: `__comb__`, `__io__`, `__seq__`, and so on. + """ stats = defaultdict(int) stats['__node__'] = len(self.nodes) stats['__cell__'] = len(self.cells) @@ -386,15 +395,31 @@ class Circuit: yield stem, region def io_locs(self, prefix): + """Returns the indices of primary I/Os that start with given name prefix. + + The returned values are used to index into the :py:attr:`io_nodes` array. + If only one I/O cell matches the given prefix, a single integer is returned. + If a bus matches the given prefix, a sorted list of indices is returned. + Busses are identified by integers in the cell names following the given prefix. + Lists for bus indices are sorted from LSB (e.g. `data[0]`) to MSB (e.g. `data[31]`). + If a prefix matches multiple different signals or busses, alphanumerically sorted + lists of lists are returned. Therefore, higher-dimensional busses + (e.g. `data0[0], data0[1], ...`, `data1[0], data1[1], ...`) are supported as well. + """ return self._locs(prefix, list(self.io_nodes)) def s_locs(self, prefix): + """Returns the indices of I/Os and sequential elements that start with given name prefix. + + The returned values are used to index into the :py:attr:`s_nodes` array. + It works the same as :py:attr:`io_locs`. See there for more details. + """ return self._locs(prefix, self.s_nodes) def _locs(self, prefix, nodes): d_top = dict() for i, n in enumerate(nodes): - if m := re.match(fr'({prefix}.*?)((?:\d+[_\[\]])*$)', n.name): + if m := re.match(fr'({prefix}.*?)((?:[\d_\[\]])*$)', n.name): path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0] d = d_top for j in path[:-1]: @@ -402,6 +427,7 @@ class Circuit: d = d[j] d[path[-1]] = i + # sort recursively for multi-dimensional lists. def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d l = sorted_values(d_top) while isinstance(l, list) and len(l) == 1: l = l[0] diff --git a/src/kyupy/logic.py b/src/kyupy/logic.py index 7baa503..85152fb 100644 --- a/src/kyupy/logic.py +++ b/src/kyupy/logic.py @@ -17,7 +17,7 @@ Except when bit0 differs from bit1, but bit2 (activity) is 0: * bit0 = 1, bit1 = 0, bit2 = 0 means ``UNKNOWN`` in 4-valued and 8-valued logic. * bit0 = 0, bit1 = 1, bit2 = 0 means ``UNASSIGNED`` in 4-valued and 8-valued logic. -2-valued logic only considers bit0, but should store logic one as ``ONE=0b011`` for interoerability. +2-valued logic only considers bit0, but should store logic one as ``ONE=0b011`` for interoperability. 4-valued logic only considers bit0 and bit1. 8-valued logic considers all 3 bits. diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index f4d9fb5..dc8c771 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -46,17 +46,12 @@ class LogicSim(sim.SimOps): """ self.c[self.pippi_c_locs] = self.s[0, self.pippi_s_locs, :self.mdim] - def c_to_s(self): #, responses, ff_transitions=False): + def c_to_s(self): """Capture the current values at the primary outputs and in the state-elements (flip-flops). + For primary outputs, the logic value is stored unmodified in the given target array. For flip-flops, the logic value is either stored unmodified (`ff_transitions=False`) or constructed from the previous state and the new state (`ff_transitions=True`). - - :param responses: A bit-parallel storage target for the responses in a compatible shape. - :type responses: :py:class:`~kyupy.logic.BPArray` - :param ff_transitions: If true, calculate and store the transitions between the previous state - (the currently assigned pattern) and the new state. - :returns: The given responses object. """ self.s[1, self.poppo_s_locs, :self.mdim] = self.c[self.poppo_c_locs] if self.mdim == 1: diff --git a/src/kyupy/stil.py b/src/kyupy/stil.py index 1a92c95..64c4920 100644 --- a/src/kyupy/stil.py +++ b/src/kyupy/stil.py @@ -95,12 +95,15 @@ class StilFile: tests = np.full((len(interface), len(self.patterns)), logic.UNASSIGNED) for i, p in enumerate(self.patterns): for si_port in self.si_ports.keys(): - pattern = logic.mv_xor(logic.mvarray(p.load[si_port]), scan_inversions[si_port]) + pattern = logic.mvarray(p.load[si_port]) + inversions = np.choose((pattern == logic.UNASSIGNED) | (pattern == logic.UNKNOWN), + [scan_inversions[si_port], logic.ZERO]).astype(np.uint8) + np.bitwise_xor(pattern, inversions, out=pattern) tests[scan_maps[si_port], i] = pattern tests[pi_map, i] = logic.mvarray(p.capture['_pi']) return tests - def tests_loc(self, circuit): + def tests_loc(self, circuit, init_filter=None, launch_filter=None): """Assembles and returns a LoC scan test pattern set for given circuit. This function assumes a launch-on-capture (LoC) delay test. @@ -112,9 +115,13 @@ class StilFile: for i, p in enumerate(self.patterns): # init.set_values(i, '0' * len(interface)) for si_port in self.si_ports.keys(): - pattern = logic.mv_xor(logic.mvarray(p.load[si_port]), scan_inversions[si_port]) + pattern = logic.mvarray(p.load[si_port]) + inversions = np.choose((pattern == logic.UNASSIGNED) | (pattern == logic.UNKNOWN), + [scan_inversions[si_port], logic.ZERO]).astype(np.uint8) + np.bitwise_xor(pattern, inversions, out=pattern) init[scan_maps[si_port], i] = pattern init[pi_map, i] = logic.mvarray(p.launch['_pi'] if '_pi' in p.launch else p.capture['_pi']) + if init_filter: init = init_filter(init) sim8v = LogicSim(circuit, init.shape[-1], m=8) sim8v.s[0] = logic.mv_to_bp(init) sim8v.s_to_c() @@ -130,6 +137,7 @@ class StilFile: if '_pi' in p.capture and 'P' in p.capture['_pi']: launch[pi_map, i] = logic.mvarray(p.capture['_pi']) launch[po_map, i] = logic.UNASSIGNED + if launch_filter: launch = launch_filter(launch) return logic.mv_transition(init, launch) diff --git a/src/kyupy/wave_sim.py b/src/kyupy/wave_sim.py index 878c49e..1e554c4 100644 --- a/src/kyupy/wave_sim.py +++ b/src/kyupy/wave_sim.py @@ -44,12 +44,13 @@ class WaveSim(sim.SimOps): :param strip_forks: If enabled, the simulator will not evaluate fork nodes explicitly. This saves simulation time by reducing the number of nodes to simulate, but (interconnect) delay annotations of lines read by fork nodes are ignored. - :param keep_waveforms: If disabled, memory of intermediate signal waveforms will be re-used. This greatly reduces + :param c_reuse: If enabled, memory of intermediate signal waveforms will be re-used. This greatly reduces memory footprint, but intermediate signal waveforms become unaccessible after a propagation. """ def __init__(self, circuit, delays, sims=8, c_caps=16, c_reuse=False, strip_forks=False): super().__init__(circuit, c_caps=c_caps, c_caps_min=4, c_reuse=c_reuse, strip_forks=strip_forks) self.sims = sims + if delays.ndim == 3: delays = np.expand_dims(delays, axis=0) self.delays = np.zeros((len(delays), self.c_locs_len, 2, 2), dtype=delays.dtype) self.delays[:, :delays.shape[1]] = delays @@ -72,8 +73,8 @@ class WaveSim(sim.SimOps): * ``s[8]`` (P)PO sampled capture value: decided by random sampling according to a given seed. * ``s[9]`` (P)PO sampled capture slack: (capture time - LST) - decided by random sampling according to a given seed. * ``s[10]`` Overflow indicator: If non-zero, some signals in the input cone of this output had more - transitions than specified in ``c_caps``. Some transitions have been discarded, the - final values in the waveforms are still valid. + transitions than specified in ``c_caps``. Some transitions have been discarded, the + final values in the waveforms are still valid. """ self.simctl_int = np.zeros((2, sims), dtype=np.int32) @@ -128,7 +129,7 @@ class WaveSim(sim.SimOps): self.s[3:, s_loc, vector] = wave_capture_cpu(self.c, c_loc, c_len, vector, time=time, sd=sd, seed=seed) def s_ppo_to_ppi(self, time=0.0): - """Re-assigns the last sampled capture to the appropriate pseudo-primary inputs (PPI). + """Re-assigns the last sampled capture to the appropriate pseudo-primary inputs (PPI). Each PPI transition is constructed from its previous final value, the given time, and the sampled captured value of its PPO. Reads and modifies ``self.s``.