diff --git a/src/kyupy/circuit.py b/src/kyupy/circuit.py index 5801f0c..559ae5d 100644 --- a/src/kyupy/circuit.py +++ b/src/kyupy/circuit.py @@ -256,14 +256,14 @@ class Circuit: yielded first. """ visit_count = [0] * len(self.nodes) - queue = deque(n for n in self.nodes if len(n.ins) == 0 or 'DFF' in n.kind) + queue = deque(n for n in self.nodes if len(n.ins) == 0 or 'dff' in n.kind.lower()) while len(queue) > 0: n = queue.popleft() for line in n.outs: if line is None: continue succ = line.reader visit_count[succ] += 1 - if visit_count[succ] == len(succ.ins) and 'DFF' not in succ.kind: + if visit_count[succ] == len(succ.ins) and 'dff' not in succ.kind.lower(): queue.append(succ) yield n @@ -282,13 +282,13 @@ class Circuit: yielded first. """ visit_count = [0] * len(self.nodes) - queue = deque(n for n in self.nodes if len(n.outs) == 0 or 'DFF' in n.kind) + queue = deque(n for n in self.nodes if len(n.outs) == 0 or 'dff' in n.kind.lower()) while len(queue) > 0: n = queue.popleft() for line in n.ins: pred = line.driver visit_count[pred] += 1 - if visit_count[pred] == len(pred.outs) and 'DFF' not in pred.kind: + if visit_count[pred] == len(pred.outs) and 'dff' not in pred.kind.lower(): queue.append(pred) yield n @@ -310,21 +310,21 @@ class Circuit: def fanout_free_regions(self): for stem in self.reversed_topological_order(): - if len(stem.outs) == 1 and 'DFF' not in stem.kind: continue + if len(stem.outs) == 1 and 'dff' not in stem.kind.lower(): continue region = [] - if 'DFF' in stem.kind: + if 'dff' in stem.kind.lower(): n = stem.ins[0] - if len(n.driver.outs) == 1 and 'DFF' not in n.driver.kind: + if len(n.driver.outs) == 1 and 'dff' not in n.driver.kind.lower(): queue = deque([n.driver]) else: queue = deque() else: queue = deque(n.driver for n in stem.ins - if len(n.driver.outs) == 1 and 'DFF' not in n.driver.kind) + if len(n.driver.outs) == 1 and 'dff' not in n.driver.kind.lower()) while len(queue) > 0: n = queue.popleft() preds = [pred.driver for pred in n.ins - if len(pred.driver.outs) == 1 and 'DFF' not in pred.driver.kind] + if len(pred.driver.outs) == 1 and 'dff' not in pred.driver.kind.lower()] queue.extend(preds) region.append(n) yield stem, region diff --git a/src/kyupy/logic.py b/src/kyupy/logic.py index 7b0c149..e78357e 100644 --- a/src/kyupy/logic.py +++ b/src/kyupy/logic.py @@ -291,6 +291,23 @@ def mv_xor(x1, x2, out=None): return out +def mv_latch(d, t, q_prev, out=None): + """A latch that is transparent if `t` is high. `q_prev` has to be the output value from the previous clock cycle. + """ + m = mv_getm(d, t, q_prev) + d, t, q_prev = mv_cast(d, t, q_prev, m=m) + out = out or MVArray(np.broadcast(d.data, t.data, q_prev).shape, m=m) + out.data[...] = t.data & d.data & 0b011 + out.data[...] |= ~t.data & 0b010 & (q_prev.data << 1) + out.data[...] |= ~t.data & 0b001 & (out.data >> 1) + out.data[...] |= ((out.data << 1) ^ (out.data << 2)) & 0b100 + unknown = (t.data == UNKNOWN) \ + | (t.data == UNASSIGNED) \ + | (((d.data == UNKNOWN) | (d.data == UNASSIGNED)) & (t.data != ZERO)) + np.putmask(out.data, unknown, UNKNOWN) + return out + + def mv_transition(init, final, out=None): """Computes the logic transitions from the initial values of ``init`` to the final values of ``final``. Pulses in the input data are ignored. If any of the inputs are ``UNKNOWN``, the result is ``UNKNOWN``. @@ -460,3 +477,27 @@ def bp_xor(out, *ins): out[..., 0, :] |= any_unknown out[..., 1, :] &= ~any_unknown out[..., 2, :] &= ~any_unknown + + +def bp_latch(out, d, t, q_prev): + md = out.shape[-2] + assert md == d.shape[-2] + assert md == t.shape[-2] + assert md == q_prev.shape[-2] + if md == 1: + out[...] = (d & t) | (q_prev & ~t) + elif md == 2: + any_unknown = t[..., 0, :] ^ t[..., 1, :] + any_unknown |= (d[..., 0, :] ^ d[..., 1, :]) & (t[..., 0, :] | t[..., 1, :]) + out[...] = (d & t) | (q_prev & ~t) + out[..., 0, :] |= any_unknown + out[..., 1, :] &= ~any_unknown + else: + any_unknown = (t[..., 0, :] ^ t[..., 1, :]) & ~t[..., 2, :] + any_unknown |= ((d[..., 0, :] ^ d[..., 1, :]) & ~d[..., 2, :]) & (t[..., 0, :] | t[..., 1, :] | t[..., 2, :]) + out[..., 1, :] = (d[..., 1, :] & t[..., 1, :]) | (q_prev[..., 0, :] & ~t[..., 1, :]) + out[..., 0, :] = (d[..., 0, :] & t[..., 0, :]) | (out[..., 1, :] & ~t[..., 0, :]) + out[..., 2, :] = out[..., 1, :] ^ out[..., 0, :] + out[..., 0, :] |= any_unknown + out[..., 1, :] &= ~any_unknown + out[..., 2, :] &= ~any_unknown diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index 92641f2..fd227fc 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -30,15 +30,22 @@ class LogicSim: self.circuit = circuit self.sims = sims nbytes = (sims - 1) // 8 + 1 - self.interface = list(circuit.interface) + [n for n in circuit.nodes if 'dff' in n.kind.lower()] + dffs = [n for n in circuit.nodes if 'dff' in n.kind.lower()] + latches = [n for n in circuit.nodes if 'latch' in n.kind.lower()] + self.interface = list(circuit.interface) + dffs + latches + self.width = len(self.interface) """The number of bits in the circuit state (number of ports + number of state-elements).""" + self.state = np.zeros((len(circuit.lines), mdim, nbytes), dtype='uint8') self.state_epoch = np.zeros(len(circuit.nodes), dtype='int8') - 1 self.tmp = np.zeros((5, mdim, nbytes), dtype='uint8') self.zero = np.zeros((mdim, nbytes), dtype='uint8') self.epoch = 0 + self.latch_dict = dict((n.index, i) for i, n in enumerate(latches)) + self.latch_state = np.zeros((len(latches), mdim, nbytes), dtype='uint8') + known_fct = [(f[:-4], getattr(self, f)) for f in dir(self) if f.endswith('_fct')] self.node_fct = [] for n in circuit.nodes: @@ -69,8 +76,11 @@ class LogicSim: """ for node, stim in zip(self.interface, stimuli.data if hasattr(stimuli, 'data') else stimuli): if len(node.outs) == 0: continue - outputs = [self.state[line] if line else self.tmp[3] for line in node.outs] - self.node_fct[node]([stim], outputs) + if node.index in self.latch_dict: + self.latch_state[self.latch_dict[node.index]] = stim + else: + outputs = [self.state[line] if line else self.tmp[3] for line in node.outs] + self.node_fct[node]([stim], outputs) for line in node.outs: if line is not None: self.state_epoch[line.reader] = self.epoch for n in self.circuit.nodes: @@ -83,13 +93,27 @@ class LogicSim: def capture(self, responses): """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 constructed from the previous state and the new state. :param responses: A bit-parallel storage target for the responses in a compatible shape. :type responses: :py:class:`~kyupy.logic.BPArray` :returns: The given responses object. """ for node, resp in zip(self.interface, responses.data if hasattr(responses, 'data') else responses): - if len(node.ins) > 0: resp[...] = self.state[node.ins[0]] + if len(node.ins) == 0: continue + if node.index in self.latch_dict: + resp[...] = self.state[node.outs[0]] + else: + resp[...] = self.state[node.ins[0]] + if self.m > 2 and 'dff' in node.kind.lower() and len(node.outs) > 0: + if node.outs[0] is None: + resp[1, :] = ~self.state[node.outs[1], 0, :] # assume QN is connected, take inverse of that. + else: + resp[1, :] = self.state[node.outs[0], 0, :] + resp[..., 2, :] = resp[..., 0, :] ^ resp[..., 1, :] + # We don't handle X or - correctly. + return responses def propagate(self, inject_cb=None): @@ -116,7 +140,8 @@ class LogicSim: if self.state_epoch[node] != self.epoch: continue inputs = [self.state[line] if line else self.zero for line in node.ins] outputs = [self.state[line] if line else self.tmp[3] for line in node.outs] - # print('sim', node) + if node.index in self.latch_dict: + inputs.append(self.latch_state[self.latch_dict[node.index]]) self.node_fct[node](inputs, outputs) for line in node.outs: if inject_cb is not None: inject_cb(line, self.state[line]) @@ -179,6 +204,12 @@ class LogicSim: if len(outputs) > 1: logic.bp_not(outputs[1], inputs[0]) + @staticmethod + def latch_fct(inputs, outputs): + logic.bp_latch(outputs[0], inputs[0], inputs[1], inputs[2]) + if len(outputs) > 1: + logic.bp_not(outputs[1], inputs[0]) + @staticmethod def nand_fct(inputs, outputs): logic.bp_and(outputs[0], *inputs) diff --git a/src/kyupy/techlib.py b/src/kyupy/techlib.py index e4c4955..4a97691 100644 --- a/src/kyupy/techlib.py +++ b/src/kyupy/techlib.py @@ -33,12 +33,14 @@ class TechLib: if pin[0] == 'A': return int(pin[1]) if pin[0] == 'B': return int(pin[1]) + int(kind[4]) for prefix, pins, index in [('HADD', ('B0', 'SO'), 1), - ('MUX21', ('S',), 2), + ('MUX21', ('S', 'S0'), 2), ('MX2', ('S0',), 2), ('TBUF', ('OE',), 1), ('TINV', ('OE',), 1), - ('DFF', ('QN',), 1), + ('LATCH', ('D',), 0), + ('LATCH', ('QN',), 1), ('DFF', ('D',), 0), + ('DFF', ('QN',), 1), ('SDFF', ('D',), 0), ('SDFF', ('QN',), 1), ('SDFF', ('CLK',), 3), diff --git a/tests/test_logic.py b/tests/test_logic.py index 8fb933a..27b61ae 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -145,6 +145,20 @@ def test_mv_operations(): assert lg.mv_xor(x1_4v, x2_4v)[0] == '0XX1XXXXXXXX1XX0' assert lg.mv_xor(x1_8v, x2_8v)[0] == '0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP' + x30_2v = lg.MVArray("0000", m=2) + x31_2v = lg.MVArray("1111", m=2) + x30_4v = lg.MVArray("0000000000000000", m=4) + x31_4v = lg.MVArray("1111111111111111", m=4) + x30_8v = lg.MVArray("0000000000000000000000000000000000000000000000000000000000000000", m=8) + x31_8v = lg.MVArray("1111111111111111111111111111111111111111111111111111111111111111", m=8) + + assert lg.mv_latch(x1_2v, x2_2v, x30_2v)[0] == '0001' + assert lg.mv_latch(x1_2v, x2_2v, x31_2v)[0] == '1011' + assert lg.mv_latch(x1_4v, x2_4v, x30_4v)[0] == '0XX00XXX0XXX0XX1' + assert lg.mv_latch(x1_4v, x2_4v, x31_4v)[0] == '1XX01XXX1XXX1XX1' + assert lg.mv_latch(x1_8v, x2_8v, x30_8v)[0] == '0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11' + assert lg.mv_latch(x1_8v, x2_8v, x31_8v)[0] == '1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111' + def test_bparray(): @@ -212,3 +226,27 @@ def test_bparray(): assert lg.MVArray(out_2v)[0] == '0110' assert lg.MVArray(out_4v)[0] == '0XX1XXXXXXXX1XX0' assert lg.MVArray(out_8v)[0] == '0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP' + + x30_2v = lg.BPArray("0000", m=2) + x30_4v = lg.BPArray("0000000000000000", m=4) + x30_8v = lg.BPArray("0000000000000000000000000000000000000000000000000000000000000000", m=8) + + lg.bp_latch(out_2v.data, x1_2v.data, x2_2v.data, x30_2v.data) + lg.bp_latch(out_4v.data, x1_4v.data, x2_4v.data, x30_4v.data) + lg.bp_latch(out_8v.data, x1_8v.data, x2_8v.data, x30_8v.data) + + assert lg.MVArray(out_2v)[0] == '0001' + assert lg.MVArray(out_4v)[0] == '0XX00XXX0XXX0XX1' + assert lg.MVArray(out_8v)[0] == '0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11' + + x31_2v = lg.BPArray("1111", m=2) + x31_4v = lg.BPArray("1111111111111111", m=4) + x31_8v = lg.BPArray("1111111111111111111111111111111111111111111111111111111111111111", m=8) + + lg.bp_latch(out_2v.data, x1_2v.data, x2_2v.data, x31_2v.data) + lg.bp_latch(out_4v.data, x1_4v.data, x2_4v.data, x31_4v.data) + lg.bp_latch(out_8v.data, x1_8v.data, x2_8v.data, x31_8v.data) + + assert lg.MVArray(out_2v)[0] == '1011' + assert lg.MVArray(out_4v)[0] == '1XX01XXX1XXX1XX1' + assert lg.MVArray(out_8v)[0] == '1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111' diff --git a/tests/test_logic_sim.py b/tests/test_logic_sim.py index 76edb95..e2369da 100644 --- a/tests/test_logic_sim.py +++ b/tests/test_logic_sim.py @@ -73,6 +73,41 @@ def test_8v(): assert resp[i] == mva[i] +def test_loop(): + c = bench.parse('q=dff(d) d=not(q)') + s = LogicSim(c, 2, m=8) + assert len(s.interface) == 1 + mva = MVArray([['0'], ['1']], m=8) + + s.assign(BPArray(mva)) + s.propagate() + resp_bp = BPArray((len(s.interface), s.sims)) + s.capture(resp_bp) + resp = MVArray(resp_bp) + + assert resp[0] == 'R' + assert resp[1] == 'F' + + resp_bp = s.cycle(resp_bp) + resp = MVArray(resp_bp) + + assert resp[0] == 'F' + assert resp[1] == 'R' + + +def test_latch(): + c = bench.parse('input(d, t) output(q) q=latch(d, t)') + s = LogicSim(c, 8, m=8) + assert len(s.interface) == 4 + mva = MVArray(['00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1'], m=8) + exp = MVArray(['0000', '0011', '0100', '0100', '1000', '1011', '1111', '1111'], m=8) + + resp = MVArray(s.cycle(BPArray(mva))) + + for i in range(len(mva)): + assert resp[i] == exp[i] + + def test_b01(mydir): c = bench.load(mydir / 'b01.bench')