#!/usr/bin/env python3 """ try to understand which transfer characteristics can be used to create stable logic. """ def fwd(offset: float, amp: float): return lambda x: min(1.0, offset + amp*x) def inv(offset: float, amp: float): return lambda x: max(0.0, 1.0 - offset - amp*x) def inv_from_fwd(inv): return lambda x: max(0.0, 1 - inv(x)) def test_stability(fwd, inv): low = 0.0 high = 1.0 mid = 0.5 for i in range(8): low, high, mid = inv(fwd(high)), inv(fwd(low)), inv(fwd(mid)) print(f"low {low:.2f} high {high:.2f} bistable {mid:.2f}") def map_stability(inv): s = [] for i in range(101): v = i/100.0 for _ in range(32): v = inv(v) s.append(v) logic_low = s[0] logic_high = s[100] logic_mean = 0.5*(logic_low + logic_high) print(f"low: {logic_low:.2f}, high: {logic_high:.2f}") for i, v in enumerate(s): if v >= logic_mean: print("logic cutoff: {:.2f}".format(i/100)) break def print_to_stable(inv, f): for _ in range(8): next = inv(f) print(f"{f} -> {next}") f = next def print_to_stable_noise(inv, f, noise=0.01): for i in range(8): if i%2: x = f - noise else: x = f + noise next = inv(x) print(f"{f:.3} ({x:.3}) -> {next:.3}") f = next print("stability: 0.2 + 2.0*x") test_stability(fwd(0.2, 2.0), inv(0.2, 2.0)) print("stability: 0.2 + 1.5*x") test_stability(fwd(0.2, 1.5), inv(0.2, 1.5)) print("stability: 0.2 + 1.1*x") test_stability(fwd(0.2, 1.1), inv(0.2, 1.1)) print("stability: 0.4 + 1.1*x") test_stability(fwd(0.4, 1.1), inv(0.4, 1.1)) print("stability: 0.5 + 2.0*x") test_stability(fwd(0.5, 2.0), inv(0.5, 2.0)) print("stability: 0.9*x") test_stability(fwd(0.0, 0.9), inv(0.0, 0.9)) print("stability: 0.2 + 1.5*x; 0.9 - 0.7*x") test_stability(fwd(0.2, 1.5), inv(0.1, 0.7)) print("stability: 0.1 + 1.3*x; 0.9 - 0.7*x") test_stability(fwd(0.1, 1.3), inv(0.1, 0.7)) print("""\ offset isn't a deal-breaker until it approaches 50%. for any offset < 0.5, amplification > 1.0, there is *some* stable pair of levels. as offset increases, the stable pairs become closer together """) def test_stability_inv(inv, mid=0.5): low = 0.0 high = 1.0 for i in range(32): low, high, mid = inv(high), inv(low), inv(mid) print(f"low {low:.2f} high {high:.2f} bistable {mid:.2f}") print("stability_inv: 0.2 + 2.0*x") test_stability_inv(inv(0.2, 2.0)) print("stability_inv: 0.2 + 1.1*x") test_stability_inv(inv(0.2, 1.1)) print("stability_inv: 0.5 + 1.1*x") test_stability_inv(inv(0.5, 1.1), 0.2) print("stability_inv: 0.7 + 1.1*x") test_stability_inv(inv(0.7, 1.1), 0.1) print("""\ inverter-only circuits can be stable, even if they ordinarily bias to just one direction... the same amp > 1.0 condition holds. importantly, offset > 0.5 becomes *fine* """) def piecewise(points: list, scale=20000.0): """ each element in points is a two-tuple (input, output), sorted by input value. the return value is a function which: - accepts f: [0..1] - maps that to [-scale..scale] - maps that through `points` with linear interpolation - scales back to [0..1] and return that """ def apply(f): x_in_scale = -scale + f * scale * 2.0 # locate the first point smaller than the input for first_lower in points[:-1][::-1]: if first_lower[0] < x_in_scale: break for first_higher in points[1:]: if first_higher[0] > x_in_scale: break # print(x_in_scale, first_lower, first_higher) tween = (x_in_scale - first_lower[0]) / (first_higher[0] - first_lower[0]) y_in_scale = tween * first_higher[1] + (1-tween) * first_lower[1] r = (y_in_scale + scale) / (2.0 * scale) return max(0.0, min(1.0, r)) return apply fwd_26 = piecewise( [ [ -14687, -7326 ], [ -13049, -6503 ], [ -11785, -5833 ], [ -4649, -1447 ], [ 4961, 7059 ], [ 11283, 11147 ], ], 17000 ) print("stability 26 7:1 windings (SUITABLY DIFFERENTIATED)") test_stability(fwd_26, lambda x: 1-x) map_stability(inv_from_fwd(fwd_26)) fwd_36 = piecewise( [ [ (-13430 + -14112 + -13935)/3, -8297], [ (-12796 + -13454 + -13293)/3, -7684], [ (-4872 + -5106 + -5091)/3, -282], [ (2322 + 2343 + 3705)/3, 7411], [ (4854 + 4840 + 7273)/3, 9318], [ (7324 + 7138 + 10608)/3, 10151], [ (10552 + 10509 + 14412)/3, 11398], [ (13418 + 13482 + 14760)/3, 13081], [ (14196 + 14528 + 14533)/3, 13580], ], 15000) print("stability 36 (3:1) cores (not suitably differentiated)") test_stability(fwd_36, lambda x: 1-x) fwd_38_2_0 = piecewise( [ [ (-13745 + -13012)/2, -6222 ], [ (-4969 + -4744)/2, 2373 ], [ (1772 + 2070)/2, 10467 ], [ (4472 + 4114)/2, 12921 ], [ (7221 + 6291)/2, 14530 ], [ (11159 + 10397)/2, 15865 ], [ (12430 + 15653)/2, 16202 ], ], 17000 ) print("stability 38 2:0 cores (SUITABLY DIFFERENTIATED)") test_stability(fwd_38_2_0, lambda x: 1-x) map_stability(inv_from_fwd(fwd_38_2_0)) # print_to_stable(inv_from_fwd(fwd_38_2_0), 0.0) # print_to_stable_noise(inv_from_fwd(fwd_38_2_0), 0.32, 0.01) fwd_38_3_0 = piecewise( [ [ (-13956 + -13890 + -13077)/3, -5203], [ (-4979 + -4885 + -4717)/3, 5051], [ (1531 + 503 + 1006)/3, 12509], [ (4180 + 1821 + 2239)/3, 14386], [ (6986 + 3436 + 3701)/3, 15451], [ (10482 + 6644 + 7735)/3, 16081], [ (11436 + 13343 + 14411)/3, 16380], ], 17000 ) print("stability 38 3:0 cores (SUITABLY DIFFERENTIATED)") test_stability(fwd_38_3_0, lambda x: 1-x) map_stability(inv_from_fwd(fwd_38_3_0)) # print_to_stable(inv_from_fwd(fwd_38_3_0), 0.0) # print_to_stable_noise(inv_from_fwd(fwd_38_3_0), 0.29, 0.01) # fwd_38_2_0 minus 8000 # de-biasing like this makes for a WORSE inverter # fwd_38_2_0_offset = piecewise( # [ # [ (-13745 + -13012)/2, -14222 ], # [ (-4969 + -4744)/2, -5627 ], # [ (1772 + 2070)/2, 2467 ], # [ (4472 + 4114)/2, 4921 ], # [ (7221 + 6291)/2, 6530 ], # [ (11159 + 10397)/2, 7865 ], # [ (12430 + 15653)/2, 8202 ], # ], 17000 # ) # print("stability 38 2:0 cores (offset -8000)") # print("pw(0) = ", fwd_38_2_0_offset(0)) # test_stability(fwd_38_2_0_offset, lambda x: 1-x) # map_stability(inv_from_fwd(fwd_38_2_0_offset)) # print_to_stable(inv_from_fwd(fwd_38_2_0_offset), 0.0) fwd_38_4_2 = piecewise([ [ (-14049 + -14191 + -14218 + -14161)/4, -10675], [ (-4993 + -4944 + -4985 + -5123)/4, -4640], [ (1948 + 1854 + 2633 + 2602)/4, 73], [ (4823 + 3914 + 5799 + 6177)/4, 1651], [ (7933 + 6731 + 10058 + 9404)/4, 2625], [ (11420 + 11947 + 15968 + 14039)/4, 6413], [ (13786 + 16465 + 16667 + 15144)/4, 8180], ], 17000) print("stability 38 4:2 cores (not suitably differentiated)") test_stability(fwd_38_4_2, lambda x: 1-x) fwd_38_5_2 = piecewise([ [ (-14056 + -14195 + -14234 + -14224 + -14162)/5, -8395], [ (-4990 + -4931 + -4935 + -4968 + -5080)/5, -1468], [ (1804 + 966 + 1564 + 1808 + 2237)/5, 3661], [ (4632 + 2427 + 3521 + 4500 + 5563)/5, 5238], [ (7629 + 4568 + 6648 + 8011 + 8527)/5, 6710], [ (10758 + 9552 + 12404 + 13748 + 12927)/5, 10892], [ (12335 + 15650 + 16603 + 16403 + 14635)/5, 13151], ], 17000) print("stability 38 5:2 cores (not suitably differentiated)") test_stability(fwd_38_5_2, lambda x: 1-x) # TODO: code 24, 26, 27, 28, 30 (asymmetric windings)