require "csim" # This represents a compound of gates. It has an interface similar enough # to Gate to be used in place of one, though it isn't derived from Gate. # Ruby thinks this is just fine. # To build compound circuits, you must create a Blueprint. To do so: # 1. Create the Blueprint object. # 2. Create the objects, and connect them to each other. Don't make # any external connections. # 3. As needed specify gates you create as input or output devices for # the compund. # 4. Call lock(). This completes the Bluprint and makes it ready to # generate compounds. # The resulting Blueprint is also a Compound. It can be connected and used # like a Gate (though it is not derived from Gate). The another method # of Blueprint creates a Compound, which can be used in a circuit, but # lacks the building infrastructure. The Compound remembers its Blueprint, # and its another method simply calls the one in Blueprint. # # You should not add the same component to multiple compounds. class Compound protected # This is called only by Blueprint. Clients don't get to create # Compounds themselves. def initialize(ingates, outgates, blueprint) @ingates = ingates # Initially array of input gates, replaced # on each connect with a connection to it. @conndex = -1 # Index for connecting to it. @outgates = outgates # Array of output gates. @joindex = -1 # Index for joining. @blueprint = blueprint # Our blueprint object. end public # Connect to a specific input port. def portconn(port, v) c = @ingates[port] c.signal(v) return Gate::LinkHandle.new(self,port) end # Connect to the next input port. def connect(v) return portconn(@conndex += 1, v) end # Join an output to another device. Outputs are joined in the order # specified by outputs, or out(n) may be used to join to a specific output. def out(n) return @outgates[n] end def join(gate) @outgates[@joindex += 1].join(gate) end # Handle inbound signals. def signal(port, val) Gate.activate @ingates[port].signal(val) Gate.deactivate end # All the links on the output list, except for internal connections. def outlinks ret = [ ] for i in (0...@outgates.length) outs = @outgates[i].outlinks for j in (0...outs.length) ret.push(outs[j]) if outs[j] && @blueprint.internmap[i][j] != 1 end end return ret end # This creates another one of us. This runs in the blueprint, which # has more information. def another return @blueprint.another end # Create several others of us in an array. def manyothers(n) ret = [] n.times { ret.push(self.another) } return ret end # For prettier printing. def to_s ret = self.class.to_s st = @blueprint.subtype ret += " [" + st + "]" if st return ret + " " + self.object_id.to_s end end # Class Blueprint is used to describe a component made of other components. class Blueprint < Compound # Note: Initialization is not really complete until the lock method is # called. def initialize(subtype = nil) super([], [], self) @allgates = [ ] # This is a list of connections to internal gates # which represent the device inputs. @allcons = [ ] # List of [src, sink] in proper connect order. @internmap = [ ] # For each output gate, a mask of connections which # are internal. @subtype = subtype end # Specify gates as inputs to the circuits. You may specify any number of # Gates or Compounds, or arrays thereof. Each input makes a connection to # the specified gate in the order given; gates may be repeated when the # they form more than one input. If input order matters, be careful to # mix the specification of gates as inputs, and the joining of internal # connections, in the correct order. def inputs(*gates) for g in flatten(gates) @ingates.push(g.connect(false)) end end # This specifies some gates which are outputs. Gates, Compounds, or arrays # thereof may be specified. Output connections are made to these gates # in the order given. Gates may be repeated to supply multiple ouputs. # It is also possible to use a specific output connection number to # join multiple devices to the same port. def outputs(*gates) @outgates += flatten(gates) end # This closes the definition. It finds all the objects in the collection, # and makes a list of pairs that can be used to reproduce all the connections # preserving relative order at each end. This involves a topological sort. # Yecccch. def lock # Create the initial pending list of all input gates, plus all the # output gates (which should be redundant, but ...) gset = { } ((@ingates.map { |c| c.sinkg }) + @outgates).each { |g| gset[g] = true } pend = gset.keys # Process pending gates for outlinks. Find all devices which are # connected downstream from an input or output. while g = pend.shift # Scan the receiving gates. We take the list of outbound connections # and extract the sink gates therein, and run through that. for t in g.outlinks.map { |lnk| lnk.sinkg } if ! gset.has_key?(t) then # If not already in the set, add it to the set and the pending list. pend.push(t) gset[t] = true end end end # These are all the reachable gates; all the gates in the device. @allgates = gset.keys # Allocate graph nodes for each connection. This allocates a node for # each connection between two contained gates. Each node is an array # of the form # [ srcnext, sinknext, predcnt, src, sink ] # Where the first two point to nodes that come later in the order at # the source or destination (respectively), predcnt is an integer number # of nodes which must come before this one, and src and sink are the # gates at the start and end of the connection. This loop allocates # such nodes for each connection, adds links (only) for the source # ordering, and places them in a hash so we can find them by destination. nmap = { } n = 0 for g in @allgates lnode = nil # Previous node in source ordering. for c in g.outlinks # Create the node and store it in the hash. key = [ c.sinkg, c.sinkp ] node = [ nil, nil, (if lnode then 1 else 0 end), g, c.sinkg ] nmap[key] = node # Add a link here to the previous node. lnode[0] = node if lnode lnode = node end n += 1 end # This adds more nodes to represent the array of input links. These # nodes are like the above, except source position is nil. lnode = nil for c in @ingates do key = [ c.sinkg, c.sinkp ] node = [ nil, nil, (if lnode then 1 else 0 end), nil, c.sinkg ] nmap[key] = node lnode[0] = node if lnode lnode = node end # Now we go through and add links representing the ordering at the # destination. for k in nmap.keys # Scan all node keys. gate, port = k # Get the destination information targ = [gate, port+1] # Get key of next node in destination order if nmap.has_key?(targ) # See if such a node exists. tnode = nmap[targ] # Get the sink successor from the hash. tnode[2] += 1 # Increment its pred. count for source node. nmap[k][1] = tnode # Add a link from source to sink node. end end # Find all the roots (nodes without predecessors). This is the initial # value of links which may be established, since all links which must # appear earlier have been created. ready = nmap.values.select { |n| n[2] == 0 } # Traverse the graph obeyong all the constratints and produce a list # of connections in an order which will preserve the order at each end. # That preserves the creation order of the links in case it matters. @allcons = [ ] while n = ready.shift # Extract the contents of the ready node and add the relevant information # to the output order list. srcnext, sinknext, count, source, sink, sinkp = n @allcons.push([source, sink]) # Reduce the count of each successor, and add it each to the ready list # if it has no more predecessors. if srcnext then srcnext[2] -= 1; ready.push(srcnext) if srcnext[2] == 0 end if sinknext then sinknext[2] -= 1; ready.push(sinknext) if sinknext[2] == 0 end end # This makes a record of all internal links outbound from output # devices. These are not to be reported as connections out from the # compound. They are recorded in an array parallel to @outgates. # For each output gate, they contain a bitmap showing ones for each # position occupied at this time. @internmap = @outgates.map do |g| ret = 0 bit = 1 g.outlinks.each { |c| if c then ret &= bit end; bit <<= 1; } ret end # Whew! end # Make a Compound like this one. def another # Make copies of all the objects, and keep a map from the original to the # copy, so we can copy the connections. copymap = { } @allgates.each { |g| copymap[g] = g.another } # Reproduce all the connections on the new gates. Use the order computed # by close which preserves order of connections at each end. ingates = [ ] for c in @allcons if c[0] then copymap[c[0]].join(copymap[c[1]]) else # nil source indicates an input connection ingates.push(copymap[c[1]].connect(false)) end end # Construct the new compound using the new gates. return Compound.new(ingates, @outgates.map { |g| copymap[g] }, self) end attr_reader :internmap, :subtype private # This flattens an list by opening component arrays. def flatten(a) ret = [] a.each { |x| if x.is_a?(Array) then ret += x else ret.push(x) end } return ret end end