#!/usr/bin/python -O """ # GAL/PAL chip emulator # # Copyright (C) 2008 Michael Buesch # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ import sys import re VERSION_STRING = "0.4" class GalemuEx(Exception): "Galemu Exception" class LogicEx(GalemuEx): "GAL logics exception" class OlmcInput: "The OR input logic to an OLMC" class AndRange: "Fuse range for the AND array" def __init__(self, start, nrFuses): self.start = start self.end = start + nrFuses - 1 self.nrFuses = nrFuses def __init__(self, andArrayRanges): "andArrayRanges = list of class AndRange to specify the input arrays" self.andArrayRanges = andArrayRanges self.olmc = None def setOlmcRef(self, olmc): self.olmc = olmc def getState(self): "Get the state of the OR-output" gal = self.olmc.gal # Walk the AND terms and apply an OR operation to the results orState = False for r in self.andArrayRanges: andState = gal.getProductTermState(r.start, self) if andState: orState = True if orState: break return orState class OlmcOutput: "The output pin of an OLMC" def __init__(self, outputPin): "outputPin = Pin number of the output" self.olmc = None self.pin = outputPin def setOlmcRef(self, olmc): self.olmc = olmc def getState(self): "Get the boolean state of this output" return self.olmc.getOutputState() def getPin(self): "Get the pin number" return self.pin class OlmcOE: "OutputEnable of an OLMC" def __init__(self, andArrayStart, nrFuses): """andArrayStart = Startfuse of the AND array for the OE nrFuses = Number of fuses for the OE AND array""" self.olmc = None self.start = andArrayStart self.end = andArrayStart + nrFuses - 1 self.nrFuses = nrFuses def setOlmcRef(self, olmc): self.olmc = olmc def getState(self): "Returns True, if the output is enabled or False otherwise." gal = self.olmc.gal andState = gal.getProductTermState(self.start, self) # The OE state is connected active-low to the AND-array oeState = not andState return oeState class OlmcBackpropagation: "Defines the backpropagation mechanism of an OLMC" def __init__(self, column, invColumn): """column = AND-array column of the non-inverting connection invColumn = AND-array column of the inverting connection""" self.olmc = None self.column = column self.invColumn = invColumn def setOlmcRef(self, olmc): self.olmc = olmc def getState(self): "Get the state of this backpropagation" if self.olmc.getMode() == Olmc.MODE_REGISTERED: Q_inv = not self.olmc.getRegisterState() return Q_inv else: return self.olmc.getOutputState() def getInvState(self): "Get the inverted state of this backpropagation" return not self.getState() def getColumn(self): "Get the Fusearray column for the Non-inverted backpropagation" return self.column def getInvColumn(self): "Get the Fusearray column for the inverted backpropagation" return self.invColumn class Olmc: "An Output-Logic-Macrocell" MODE_REGISTERED = 0 MODE_COMBINATORIAL = 1 def __init__(self, gal, input, output, bp, oe, mode = MODE_COMBINATORIAL, activelow = True): """gal = GalXXXX(...), input = OlmcInput(...), output = OlmcOutput(...), bp = OlmeBackpropagation(...), oe = OlmeOE(...), mode = MODE_***, activelow = True or False""" self.gal = gal self.input = input self.input.setOlmcRef(self) self.output = output self.output.setOlmcRef(self) self.bp = bp self.bp.setOlmcRef(self) self.oe = oe self.oe.setOlmcRef(self) self.mode = mode self.activelow = activelow self.state = False self.newState = None self.previousClkState = None # Unknown def AR(self): "Asynchronous reset" if self.mode != Olmc.MODE_REGISTERED: raise LogicEx("OLMC: Asynchronous reset on combinatorial OLMC") self.state = False def SP(self): "Synchronous preset" if self.mode != Olmc.MODE_REGISTERED: raise LogicEx("OLMC: Synchronous preset on combinatorial OLMC") self.newState = True def CLK(self, clkpinState): "Clock signal" if self.mode != Olmc.MODE_REGISTERED: return # Only trigger a clock on a _rising_ edge. if clkpinState == self.previousClkState: return # No edge self.previousClkState = clkpinState if not clkpinState: return # Not rising # First check if we have a synchronous preset. if self.gal.getSyncPreset().getState(): self.SP() else: # No preset. # Load the product term state into the register. self.newState = self.input.getState() def commitCLK(self): if self.newState is not None: self.state = self.newState self.newState = None def __getOutputState(self): if not self.oe.getState(): # This output is not enabled return False if self.mode == Olmc.MODE_REGISTERED: return self.getRegisterState() else: return self.input.getState() def getOutputState(self): state = self.__getOutputState() if self.activelow: return not state return state def getOutputPin(self): "Get the output pin object" return self.output def getMode(self): "Returns the current Olmc.MODE_***" return self.mode def getRegisterState(self): "Returns the raw flipflop register state" if self.mode != Olmc.MODE_REGISTERED: raise GalemuEx("OLMC: Called getRegisterState() on combinatorial OLMC") return self.state def getBackpropagation(self): "Returns the OlmcBackpropagation() object" return self.bp class AsyncReset: "Asynchronous reset trigger" def __init__(self, gal, row): """gal = GalXXXX(...) row = AND-array row of the AR trigger""" self.gal = gal self.row = row def getState(self): "Returns the logical state of the Asynchronous Reset trigger" fuseIdx = self.row * self.gal.nrArrayColumns() andState = self.gal.getProductTermState(fuseIdx, self) return andState class SyncPreset: "synchronous preset trigger" def __init__(self, gal, row): """gal = GalXXXX(...) row = AND-array row of the SP trigger""" self.gal = gal self.row = row def getState(self): "Returns the logical state of the Synchronous Preset trigger" fuseIdx = self.row * self.gal.nrArrayColumns() andState = self.gal.getProductTermState(fuseIdx, self) return andState class GalInput: "Input pins to the GAL chip" def __init__(self, gal, inputPin, column, invColumn, isCLK = False): """gal = GalXXXX(...) inputPin = Pin number of the input column = AND-array column of the non-inverting input invColumn = AND-array column of the inverting input isCLK = is the pin connected to the flipflop CLKs""" self.gal = gal self.pin = inputPin self.column = column self.invColumn = invColumn self.isCLK = isCLK self.state = False def getIsCLK(self): "Returns a boolean whether this is the CLK signal" return self.isCLK def setState(self, state): "Set the state of this input" self.state = state if self.isCLK: for olmc in self.gal.getOlmcList(): olmc.CLK(state) for olmc in self.gal.getOlmcList(): olmc.commitCLK() def getState(self): "Get the state of this input" return self.state def getInvState(self): "Get the inverted state of this input" return not self.state def getColumn(self): "Get the Fusearray column for the Non-inverted input" return self.column def getInvColumn(self): "Get the Fusearray column for the inverted input" return self.invColumn def getPin(self): "Get the pin number" return self.pin class Gal: "Generic-Array-Logic chip" def __init__(self, array): self.array = array self.prodTermRecursionDetect = [] # The implementation must define the following objects self.olmcs = None self.inputs = None def reset(self): "Reset all state information in the chip" for olmc in self.getOlmcList(): if olmc.getMode() == Olmc.MODE_REGISTERED: olmc.AR() def getArray(self): "Returns the Fusearray" return self.array def arrayIndexToArrayColumn(self, index): "Convert a Fusearray index into a Fusearray column" return index % self.nrArrayColumns() def getInputList(self): "Get a list of GAL chip inputs" return self.inputs def __getInputAtColumn(self, column): """Returns a tuple (GalInput(), True|False) where [0] is the input at the column and [1] is True for inverted and False for Non-Inverted.""" for input in self.getInputList(): if input.getColumn() == column: return (input, False) if input.getInvColumn() == column: return (input, True) return (None, None) def getOlmcList(self): "Get a list of OLMCs on this GAL chip" return self.olmcs def __getBackpropagationAtColumn(self, column): """Returns a tuple (OlmcBackpropagation(), True|False) where [0] is the OLMC-backpropagation at the column and [1] is True for inverted and False for Non-Inverted.""" for olmc in self.getOlmcList(): bp = olmc.getBackpropagation() if bp.getColumn() == column: return (bp, False) if bp.getInvColumn() == column: return (bp, True) return (None, None) def __getLogicStateOfArrayIntersection(self, fuseArrayIndex): "Get the logical state at a fuse" if self.getArray().isBlown(fuseArrayIndex): raise GalemuEx("Intersection resolve: Cannot resolve a blown fuse") column = self.arrayIndexToArrayColumn(fuseArrayIndex) (input, inverted) = self.__getInputAtColumn(column) if input: if inverted: v = input.getInvState() else: v = input.getState() else: (bp, inverted) = self.__getBackpropagationAtColumn(column) if not bp: raise GalemuEx("Intersection resolve: Neither Input nor " +\ "Backpropagation found at column %d" % column) if inverted: v = bp.getInvState() else: v = bp.getState() return v def getProductTermState(self, startFuseIndex, caller): "Get the state of an AND-product-term" # First check if we have a recursion if caller in self.prodTermRecursionDetect: raise LogicEx("Product term state recursion detected at row %d" % (startFuseIndex / self.nrArrayColumns())) self.prodTermRecursionDetect.append(caller) andState = False for fuseIdx in range(startFuseIndex, startFuseIndex + self.nrArrayColumns()): if self.getArray().isBlown(fuseIdx): # This fuse is blown, so it doesn't affect # the state of the AND logic continue v = self.__getLogicStateOfArrayIntersection(fuseIdx) if v: andState = True else: # This part of the AND equation is false. # Set the AND equation state to false and terminate # the evaluation of the AND equation. andState = False break self.prodTermRecursionDetect.remove(caller) return andState def getAsyncReset(self): "Returns the AsyncReset(...) object" return self.asyncReset def getSyncPreset(self): "Returns the SyncPreset(...) object" return self.syncPreset class Gal22v10(Gal): "GAL/PAL 22V10 Generic-Array-Logic chip" idStrings = ("PAL22V10", "GAL22V10") ARRAY_SIZE = 5828 @staticmethod def getInputPinNumbers(): return (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13) @staticmethod def getOutputPinNumbers(): return (14, 15, 16, 17, 18, 19, 20, 21, 22, 23) @staticmethod def getVccPinNumber(): return 24 @staticmethod def getGndPinNumber(): return 12 @staticmethod def getClkPinNumber(): return 1 def f2m(self, fuse): "Convert a fuse state to an OLMC mode value" if fuse: return Olmc.MODE_COMBINATORIAL return Olmc.MODE_REGISTERED def __init__(self, array): if len(array) != self.ARRAY_SIZE: raise GalemuEx("Fusearray size is wrong for the GAL22V10. " +\ "Is %d, but should be %d." % (len(array), self.ARRAY_SIZE)) Gal.__init__(self, array) # Create the GAL-input pins self.inputs = ( GalInput(self, 1, 0, 1, isCLK = True), GalInput(self, 2, 4, 5), GalInput(self, 3, 8, 9), GalInput(self, 4, 12, 13), GalInput(self, 5, 16, 17), GalInput(self, 6, 20, 21), GalInput(self, 7, 24, 25), GalInput(self, 8, 28, 29), GalInput(self, 9, 32, 33), GalInput(self, 10, 36, 37), GalInput(self, 11, 40, 41), GalInput(self, 13, 42, 43), ) # Create the OLMC-input AND array fabric inAndRange0 = [] for i in range(88, 396+1, 44): inAndRange0.append(OlmcInput.AndRange(i, 44)) inAndRange1 = [] for i in range(484, 880+1, 44): inAndRange1.append(OlmcInput.AndRange(i, 44)) inAndRange2 = [] for i in range(968, 1452+1, 44): inAndRange2.append(OlmcInput.AndRange(i, 44)) inAndRange3 = [] for i in range(1540, 2112+1, 44): inAndRange3.append(OlmcInput.AndRange(i, 44)) inAndRange4 = [] for i in range(2200, 2860+1, 44): inAndRange4.append(OlmcInput.AndRange(i, 44)) inAndRange5 = [] for i in range(2948, 3608+1, 44): inAndRange5.append(OlmcInput.AndRange(i, 44)) inAndRange6 = [] for i in range(3696, 4268+1, 44): inAndRange6.append(OlmcInput.AndRange(i, 44)) inAndRange7 = [] for i in range(4356, 4840+1, 44): inAndRange7.append(OlmcInput.AndRange(i, 44)) inAndRange8 = [] for i in range(4928, 5324+1, 44): inAndRange8.append(OlmcInput.AndRange(i, 44)) inAndRange9 = [] for i in range(5412, 5720+1, 44): inAndRange9.append(OlmcInput.AndRange(i, 44)) # Create the OLMC fabric self.olmcs = ( Olmc(gal = self, input = OlmcInput(inAndRange0), output = OlmcOutput(23), bp = OlmcBackpropagation(2, 3), oe = OlmcOE(44, 44), mode = self.f2m(array[5809]), activelow = not array[5808]), Olmc(gal = self, input = OlmcInput(inAndRange1), output = OlmcOutput(22), bp = OlmcBackpropagation(6, 7), oe = OlmcOE(440, 44), mode = self.f2m(array[5811]), activelow = not array[5810]), Olmc(gal = self, input = OlmcInput(inAndRange2), output = OlmcOutput(21), bp = OlmcBackpropagation(10, 11), oe = OlmcOE(924, 44), mode = self.f2m(array[5813]), activelow = not array[5812]), Olmc(gal = self, input = OlmcInput(inAndRange3), output = OlmcOutput(20), bp = OlmcBackpropagation(14, 15), oe = OlmcOE(1496, 44), mode = self.f2m(array[5815]), activelow = not array[5814]), Olmc(gal = self, input = OlmcInput(inAndRange4), output = OlmcOutput(19), bp = OlmcBackpropagation(18, 19), oe = OlmcOE(2156, 44), mode = self.f2m(array[5817]), activelow = not array[5816]), Olmc(gal = self, input = OlmcInput(inAndRange5), output = OlmcOutput(18), bp = OlmcBackpropagation(22, 23), oe = OlmcOE(2904, 44), mode = self.f2m(array[5819]), activelow = not array[5818]), Olmc(gal = self, input = OlmcInput(inAndRange6), output = OlmcOutput(17), bp = OlmcBackpropagation(26, 27), oe = OlmcOE(3652, 44), mode = self.f2m(array[5821]), activelow = not array[5820]), Olmc(gal = self, input = OlmcInput(inAndRange7), output = OlmcOutput(16), bp = OlmcBackpropagation(30, 31), oe = OlmcOE(4312, 44), mode = self.f2m(array[5823]), activelow = not array[5822]), Olmc(gal = self, input = OlmcInput(inAndRange8), output = OlmcOutput(15), bp = OlmcBackpropagation(34, 35), oe = OlmcOE(4884, 44), mode = self.f2m(array[5825]), activelow = not array[5824]), Olmc(gal = self, input = OlmcInput(inAndRange9), output = OlmcOutput(14), bp = OlmcBackpropagation(38, 39), oe = OlmcOE(5368, 44), mode = self.f2m(array[5827]), activelow = not array[5826]), ) # Create the Reset and Preset terms self.asyncReset = AsyncReset(self, row=0) self.syncPreset = SyncPreset(self, row=5764/44) def nrArrayColumns(self): "Number of columns in the AND fuse-array" return 44 class FuseArray: """A representation of the fuse array The array contains boolean values for the blown fuses. So an array entry of True indicates a blown fuse.""" def __init__(self): self.reset() def getGalType(self): return self.type def getArray(self): return self.array def __getitem__(self, index): return self.getArray()[index] def isBlown(self, index): "Returns whether a fuse is blown" return self.getArray()[index] == True def __len__(self): return len(self.getArray()) def reset(self): self.array = [] self.type = "" def readJedecFile(self, filename): try: data = file(filename, "r").read() except IOError, e: raise GalemuEx("Could not open JEDEC file %s: %s" %\ (filename, e.strerror)) self.readJedecData(data) def readJedecData(self, data): lines = data.splitlines() statemachine = 0 reBitmap = re.compile(r"^L(\d+) ([01]+)*") self.reset() for line in lines: if not line: continue if statemachine == 0: if line[0] != '\2': # \2 = STX = StartOfText marks the start continue statemachine = 1 continue line = line.strip() if statemachine == 1: if len(line): self.type = line statemachine = 2 continue if statemachine == 2: m = reBitmap.match(line) if m: bits = m.group(2) self.__parseBitmapString(bits) if not self.type: raise GalemuEx("JEDEC data does not contain the GAL type") if not self.array: raise GalemuEx("JEDEC data does not contain a Fusemap") self.type = self.type.upper() def __parseBitmapString(self, str): for bit in str: if bit == "0": self.array.append(False) elif bit == "1": self.array.append(True) else: raise GalemuEx("Invalid character in JEDEC bitmap: %s" % bit) class Emulator: "This is the emulator that runs the GAL chip" def __init__(self, gal): "gal = The GalXXXX(...) chip object" self.gal = gal def setInput(self, input): "input = List or tuple of input pin states" gal = self.gal galInputList = gal.getInputList() if len(input) != len(galInputList): raise GalemuEx("Emulator: setInput() wrong count of input signals. "+\ "Was: %d, Expected: %d" % (len(input), len(galInputList))) clk = None clkIndex = -1 for i in range(0, len(input)): gi = galInputList[i] if gi.getIsCLK(): # Clock last! clk = gi clkIndex = i continue gi.setState(not not input[i]) if clk: galInputList[clkIndex].setState(not not input[clkIndex]) def getOutput(self): "Returns a list of output pin states" gal = self.gal out = [] for olmc in gal.getOlmcList(): out.append(olmc.getOutputState()) # If we have an asynchronous reset, trigger it and re-evaluate # the output states. if gal.getAsyncReset().getState(): # Trigger for olmc in gal.getOlmcList(): if olmc.getMode() == Olmc.MODE_REGISTERED: olmc.AR() # re-evaluate out = [] for olmc in gal.getOlmcList(): out.append(olmc.getOutputState()) return out def reset(self): "Reset the emulator and the GAL state" self.gal.reset() def getGal(self): "Returns the Gal(...) object" return self.gal 'New "class GalXXXX" implementations need to be added to this list' __implementedGalTypes = ( Gal22v10, ) def selectGalClassObject(galTypeString): "Get the GAL/PAL class object by the type string." type = galTypeString.upper() for g in __implementedGalTypes: if type in g.idStrings: return g raise GalemuEx("GAL type %s is not supported" % galTypeString) def main(argv): def usage(argv): print "Usage: %s FILE.JED INPUT_STATES..." % argv[0] if (len(argv) < 3): usage(argv) return 1 jedecFile = argv[1] inputStates = [] for i in range(2, len(argv)): if argv[i] == "0" or argv[i].lower() == "false": inputStates.append(False) else: inputStates.append(True) a = FuseArray() a.readJedecFile(jedecFile) gal = selectGalClassObject(a.getGalType()) (a) e = Emulator(gal) e.setInput(inputStates) out = e.getOutput() for i in range(len(out) - 1, -1, -1): outpin = gal.getOlmcList()[i].getOutputPin() print "Output pin %d = %d" % (outpin.getPin(), out[i]) return 0 if __name__ == "__main__": try: sys.exit(main(sys.argv)) except GalemuEx, e: print "[EXCEPTION] %s" % e.message