""" Hyperrealistic voltage-based logic gates for digital simulation. Each gate operates on analog voltages, with digital 1/0 determined by thresholding. Gate switching speed is parameterized to match target transistor switching rates. """ import random # Constants for voltage logic VDD = 0.7 # High voltage (V) VSS = 0.0 # Low voltage (V) VTH = 0.35 # Threshold voltage (V) # Gate switching delay (in seconds) to match fastest possible switching # This should be the minimum possible, based on electron_speed.py calculation from electron_speed import max_switch_freq GATE_DELAY = 1 / max_switch_freq # seconds per switch (theoretical limit) class LogicGate: def __init__(self, vdd=VDD, vss=VSS, vth=VTH, delay=GATE_DELAY): self.vdd = vdd self.vss = vss self.vth = vth self.delay = delay def interpret(self, voltage): """Return digital 1 if voltage > Vth, else 0.""" return 1 if voltage > self.vth else 0 def voltage(self, bit): """Return voltage for digital bit.""" return self.vdd if bit else self.vss class NANDGate(LogicGate): def output(self, vin1, vin2): # Interpret inputs as digital in1 = self.interpret(vin1) in2 = self.interpret(vin2) # NAND logic: output is high unless both inputs are high out_bit = 0 if (in1 and in2) else 1 # Add random noise for realism noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise class ANDGate(LogicGate): def output(self, vin1, vin2): in1 = self.interpret(vin1) in2 = self.interpret(vin2) out_bit = 1 if (in1 and in2) else 0 noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise class ORGate(LogicGate): def output(self, vin1, vin2): in1 = self.interpret(vin1) in2 = self.interpret(vin2) out_bit = 1 if (in1 or in2) else 0 noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise class NOTGate(LogicGate): def output(self, vin): in_bit = self.interpret(vin) out_bit = 0 if in_bit else 1 noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise # Example usage and test if __name__ == "__main__": nand = NANDGate() andg = ANDGate() org = ORGate() notg = NOTGate() print("NAND(0.7, 0.7):", nand.output(0.7, 0.7)) print("AND(0.7, 0.7):", andg.output(0.7, 0.7)) print("OR(0.0, 0.7):", org.output(0.0, 0.7)) print("NOT(0.7):", notg.output(0.7)) print(f"Gate delay (s): {GATE_DELAY:.2e}") # --- Combinational Logic --- class XORGate(LogicGate): def output(self, vin1, vin2): in1 = self.interpret(vin1) in2 = self.interpret(vin2) out_bit = 1 if (in1 != in2) else 0 noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise class NORGate(LogicGate): def output(self, vin1, vin2): in1 = self.interpret(vin1) in2 = self.interpret(vin2) out_bit = 0 if (in1 or in2) else 1 noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise class XNORGate(LogicGate): def output(self, vin1, vin2): in1 = self.interpret(vin1) in2 = self.interpret(vin2) out_bit = 1 if (in1 == in2) else 0 noise = random.gauss(0, 0.01 * self.vdd) return self.voltage(out_bit) + noise # Example: 1-bit Full Adder (combinational logic) class FullAdder: def __init__(self): self.xor1 = XORGate() self.xor2 = XORGate() self.and1 = ANDGate() self.and2 = ANDGate() self.or1 = ORGate() def output(self, a, b, cin): sum1 = self.xor1.output(a, b) sum_bit = self.xor2.output(sum1, cin) carry1 = self.and1.output(a, b) carry2 = self.and2.output(sum1, cin) cout = self.or1.output(carry1, carry2) return sum_bit, cout # --- Sequential Logic --- # SR, D, JK, T Flip-Flops (voltage-based, using gates) class SRFlipFlop: def __init__(self): self.q = VSS self.nand1 = NANDGate() self.nand2 = NANDGate() def output(self, s, r): # s, r: voltages q_bar = self.nand1.output(s, self.q) self.q = self.nand2.output(r, q_bar) return self.q class DFlipFlop: def __init__(self): self.sr = SRFlipFlop() def output(self, d, clk): # On rising clock, sample d s = d if clk > VTH else VSS r = NOTGate().output(d) if clk > VTH else VSS return self.sr.output(s, r) class JKFlipFlop: def __init__(self): self.q = VSS self.j = None self.k = None self.nand1 = NANDGate() self.nand2 = NANDGate() self.nand3 = NANDGate() self.nand4 = NANDGate() def output(self, j, k, clk): # Simple JK: toggle on J=K=1, set/reset otherwise if clk > VTH: if j > VTH and k > VTH: self.q = VDD if self.q == VSS else VSS elif j > VTH: self.q = VDD elif k > VTH: self.q = VSS return self.q class TFlipFlop: def __init__(self): self.q = VSS def output(self, t, clk): if clk > VTH and t > VTH: self.q = VDD if self.q == VSS else VSS return self.q # Example: 2-bit Register (sequential logic) class Register2Bit: def __init__(self): self.dff0 = DFlipFlop() self.dff1 = DFlipFlop() def output(self, d0, d1, clk): q0 = self.dff0.output(d0, clk) q1 = self.dff1.output(d1, clk) return q0, q1 # Example usage if __name__ == "__main__": # ...existing code... xor = XORGate() print("XOR(0.7, 0.0):", xor.output(0.7, 0.0)) fa = FullAdder() s, c = fa.output(0.7, 0.7, 0.0) print("FullAdder(1,1,0): sum=", s, "carry=", c) sr = SRFlipFlop() print("SRFlipFlop S=1, R=0:", sr.output(0.7, 0.0)) dff = DFlipFlop() print("DFlipFlop D=1, CLK=1:", dff.output(0.7, 0.7)) jk = JKFlipFlop() print("JKFlipFlop J=1, K=1, CLK=1:", jk.output(0.7, 0.7, 0.7)) tff = TFlipFlop() print("TFlipFlop T=1, CLK=1:", tff.output(0.7, 0.7)) reg = Register2Bit() print("Register2Bit D0=1, D1=0, CLK=1:", reg.output(0.7, 0.0, 0.7)) # --- Functional Units and Modules --- # Arithmetic Logic Unit (ALU) - 1-bit (can be extended to n-bit) class ALU1Bit: def __init__(self): self.andg = ANDGate() self.org = ORGate() self.xorg = XORGate() self.fadd = FullAdder() def operate(self, a, b, cin, op): """ op: 2-bit operation selector 00 = AND, 01 = OR, 10 = ADD, 11 = XOR Returns (result, carry_out) """ if op == 0b00: return self.andg.output(a, b), 0.0 elif op == 0b01: return self.org.output(a, b), 0.0 elif op == 0b10: s, c = self.fadd.output(a, b, cin) return s, c elif op == 0b11: return self.xorg.output(a, b), 0.0 else: raise ValueError("Invalid ALU op") # 2-bit ALU (example of module composition) class ALU2Bit: def __init__(self): self.alu0 = ALU1Bit() self.alu1 = ALU1Bit() def operate(self, a0, a1, b0, b1, cin, op): # Least significant bit r0, c0 = self.alu0.operate(a0, b0, cin, op) # Most significant bit r1, c1 = self.alu1.operate(a1, b1, c0, op) return (r0, r1), c1 # 2-bit Counter (using T flip-flops) class Counter2Bit: def __init__(self): self.tff0 = TFlipFlop() self.tff1 = TFlipFlop() def tick(self, clk): q0 = self.tff0.output(VDD, clk) q1 = self.tff1.output(q0, clk) return self.tff0.q, self.tff1.q # 2x2-bit Register File (2 registers, 2 bits each) class RegisterFile2x2: def __init__(self): self.reg0 = Register2Bit() self.reg1 = Register2Bit() self.sel = 0 # select register 0 or 1 def write(self, d0, d1, clk, sel): if sel == 0: self.reg0.output(d0, d1, clk) else: self.reg1.output(d0, d1, clk) def read(self, sel): if sel == 0: return self.reg0.dff0.sr.q, self.reg0.dff1.sr.q else: return self.reg1.dff0.sr.q, self.reg1.dff1.sr.q # Example usage of functional units if __name__ == "__main__": # ...existing code... alu = ALU1Bit() res, cout = alu.operate(0.7, 0.0, 0.0, 0b10) print("ALU1Bit ADD 1+0: result=", res, "carry=", cout) alu2 = ALU2Bit() (r0, r1), c = alu2.operate(0.7, 0.0, 0.7, 0.7, 0.0, 0b10) print("ALU2Bit ADD (10)+(11): result=", (r0, r1), "carry=", c) counter = Counter2Bit() print("Counter2Bit tick 1:", counter.tick(0.7)) print("Counter2Bit tick 2:", counter.tick(0.7)) regfile = RegisterFile2x2() regfile.write(0.7, 0.0, 0.7, 0) regfile.write(0.0, 0.7, 0.7, 1) print("RegisterFile2x2 read reg0:", regfile.read(0)) print("RegisterFile2x2 read reg1:", regfile.read(1)) # --- Control Unit, Registers, and Memory Management Units --- # Simple Control Unit (Finite State Machine for ALU operations) class ControlUnit: def __init__(self): self.state = 0 self.opcode = 0b00 # default operation def set_opcode(self, opcode): self.opcode = opcode def next_state(self): self.state = (self.state + 1) % 4 return self.state def get_control_signals(self): # Example: output ALU op and register select reg_sel = self.state % 2 return {'alu_op': self.opcode, 'reg_sel': reg_sel} # General Purpose Register (n-bit, here 2-bit for demo) class GeneralPurposeRegister: def __init__(self, bits=2): self.bits = bits self.dffs = [DFlipFlop() for _ in range(bits)] def write(self, data, clk): for i in range(self.bits): self.dffs[i].output(data[i], clk) def read(self): return tuple(self.dffs[i].sr.q for i in range(self.bits)) # Simple Memory Management Unit (MMU) - address decode and register file access class SimpleMMU: def __init__(self, num_registers=2, bits=2): self.registers = [GeneralPurposeRegister(bits) for _ in range(num_registers)] def write(self, addr, data, clk): if 0 <= addr < len(self.registers): self.registers[addr].write(data, clk) def read(self, addr): if 0 <= addr < len(self.registers): return self.registers[addr].read() return None # Example usage of control and memory units if __name__ == "__main__": # ...existing code... cu = ControlUnit() cu.set_opcode(0b10) # ADD print("ControlUnit state:", cu.next_state(), cu.get_control_signals()) gpr = GeneralPurposeRegister(bits=2) gpr.write([0.7, 0.0], 0.7) print("GeneralPurposeRegister read:", gpr.read()) mmu = SimpleMMU(num_registers=2, bits=2) mmu.write(0, [0.7, 0.0], 0.7) mmu.write(1, [0.0, 0.7], 0.7) print("SimpleMMU read reg0:", mmu.read(0)) print("SimpleMMU read reg1:", mmu.read(1))