# # Ruby circuit simulation classes. This file contains a base class Gate, # and several derived classes describing digital logic gates. There are # also classes for input and display. There's also a flip-flop. # class Gate # This is a count of the "active" gates, which are ones which have received # a signal but not resolved it. @@active = 0 # This is a list of gates which have registered that they want to be # notified when the circuit is quiet. They give an integer priority, # and are notified in increasing priority order. @@needquiet = { } def quiet_register(pri) if ! @@needquiet.key?(pri) then @@needquiet[pri] = [ ] end @@needquiet[pri].push(self) end # Here's how we set stuff. There are static and object versions of # each, since I may want to activate from other spots. def Gate.activate @@active += 1 end def activate Gate.activate end def Gate.deactivate @@active -= 1 if @@active == 0 then @@needquiet.keys.sort.each \ { |p| @@needquiet[p].each { |g| g.onquiet } } end end def deactivate Gate.deactivate end # This is the default quiet action (nothing). def onquiet end # A signal is directed to a particular port on a particular gate. This # encapsulates those two data. When a gate connects to us, we send back # one of these to direct its later signal changes. class LinkHandle def initialize(sink_gate, sink_port) @sinkg = sink_gate @sinkp = sink_port end # The sending gate uses this method to forward the signal to the # downstream gate. def signal(value) @sinkg.signal(@sinkp,value) end attr_reader :sinkg, :sinkp end def initialize(ival = false) @inputs = [ ] # Array of inputs (boolean values) @outputs = [ ] # Array of LinkHandle objects where to send output @outval = ival # Present output value. end # This is called when a input gate sends us a signal on a particular input. # We recompute our output value, and, if it changes, we send it on to all # of our outbound connections. def signal(port, val) # The derived class needs to implement the value method. self.activate @inputs[port] = val newval = self.value if newval != @outval then @outval = newval @outputs.each { | c | c.signal(newval) } end self.deactivate end # Call this to connect your output to the next one of our inputs. def connect(v) port = @inputs.length @inputs.push(v) c = LinkHandle.new(self, port) self.signal(port, v) return c end # Join me to another gate. def join(g) @outputs.push(g.connect(@outval)) end def joinmany(*p) p.each { |i| self.join(i); } end attr_reader :outval # Some printing help def name return self.class.to_s end def insstr return (if @inputs.length == 0 then "-" else @inputs.join('.') end) end def to_s return name + " " + insstr + " => " + @outval.to_s end # Create another object of the same type. def another return self.class.new end def manyothers(n) ret = [] n.times { ret.push(self.another) } return ret end # This manufactures any number of objects. It is a static method, and # inherited by the real gates. The expression self.new, then, runs the # new method on the actual object, which the inheriting class. Therefore, # it will create any gate. def Gate.many(n) ret = [ ] n.times { ret.push(self.new) } return ret end # Dump a whole circuit. Yecch. def outlinks return @outputs end def Gate.dump(*roots) ct = -1 gatemap = { } for g in roots gatemap[g] = (ct += 1) unless gatemap.has_key?(g) end printed = { } while roots.length > 0 g = roots.shift next if printed.has_key?(g) print "[", gatemap[g], "] ", g, ":" for c in g.outlinks og = c.sinkg gatemap[og] = (ct += 1) unless gatemap.has_key?(og) print " ", gatemap[og], "@", c.sinkp roots.push(og) end print " [none]" if g.outlinks.length <= 0 print "\n" printed[g] = true end end end # Standard and gate class AndGate < Gate def initialize super(true) end def value for i in @inputs return false if !i end return true end end class NandGate < AndGate def value return ! super end end # Standard or gate class OrGate < Gate def value for i in @inputs return true if i end return false end end class NorGate < OrGate def value return ! super end end # Standard xor gate class XorGate < Gate def value ret = false for i in @inputs ret ^= i end return ret end end # Gates with a limited number of input connections. class LimitedGate < Gate def initialize(max=1,i=false) super(i) @max = max end # Enforce connect limit. def connect(v) if @inputs.length >= @max then raise TypeError.new("Too many input connections.") end super(v) end end # Not gate. class NotGate < LimitedGate def initialize super(1,true) end def value return ! @inputs[0] end end # This is a "yes gate" or amplifier. It just forwards its input to all its # outputs class Connector < LimitedGate def value return @inputs[0] end # We can also use it as a one-bit input device. def send(v) self.signal(0,v) end end # D Flip-Flop. Level-triggered. First input is D, second is clock. class FlipFlop < LimitedGate def initialize super(2) end def value return (if @inputs[1] then @inputs[0] else @outval end) end end # D Flip-Flop. Edge-triggered. First input is D, second is clock. # I think the level-triggered might make a lot more sense with this # simulation, though these are better in circuits. class FlipFlopET < FlipFlop def initialize super @newval = false end def value return @newval end def signal(port, val) # Need to stick our fingers in this thing to find the rising edge. self.activate @newval = if port == 1 && !@inputs[1] && val then @inputs[0] else @outval end super(port,val) self.deactivate end end # Simple test point class Tester < LimitedGate def initialize(name="Tester") super(1) @name = name end attr_writer :name def value print @name, ": ", if @inputs[0] then "on" else "off" end, "\n"; return @inputs[0] end end # Numeric output device. Connect lines starting with LSB. class NumberOut < Gate @@quiet = false def NumberOut.shush(q=true) @@quiet = q end def initialize(name="Value", pri = 1) @name = name quiet_register(pri) super() end attr_writer :name # Print the value on quiet. def onquiet return if @@quiet; val = 0 @inputs.reverse_each { |i| val <<= 1 if i then val |= 1 end } print @name, ": ", val, "\n" end def value return false end end # LED which prints when circuit becomes quiet. class LED < NumberOut def initialize(name="LED", pri = 1) super(name, pri) end def onquiet if @inputs.length > 0 && ! @@quiet then print @name, ": ", if @inputs[0] then "on" else "off" end, "\n" end end def connect(v) if @inputs.length >= 1 then raise TypeError.new("Too many input connections.") end super(v) end end # Base for input devices. Mostly deals will collecting connections. class InputDevice def initialize @targs = [] end # Add a connection def join(g) @targs.push(g.connect(false)) end def joinmany(*p) p.each { |i| self.join(i); } end def outlinks return @targs end end # Switch bank. Connects to any number of gates, and will feed them a # binary number (as a string). Connections start with LSB. Initially, # all the switches are off. class SwitchBank < InputDevice # Send a number. Can take an integer or a string. def set(n) if n.is_a?(TrueClass) || n.is_a?(FalseClass) then @targs.each { | x | x.signal(n) } elsif n.is_a?(Integer) then @targs.each { | x | x.signal(n&1 == 1); n >>= 1 } else # Assume n is an ascii string of 1's and 0's. if n.length < @targs.length then n = ('0' * (@targs.length - n.length)) + n end sub = n.length - 1 @targs.each { | x | x.signal(n[sub].chr != "0"); sub -= 1 } end end # This is like switch, but it keeps the circuit active during each # sending. def value=(n) Gate.activate self.set(n) Gate.deactivate end end # Send a pulse (clock tick?) class Pulser < InputDevice def pulse Gate.activate @targs.each { |t| t.signal(true); } @targs.each { |t| t.signal(false); } Gate.deactivate end end