################################################################################
### Generate checkoff files for ALU Lab 3
################################################################################

import sys
from checkoffgen import CGen, Signal

# Here's where we put out output files (a proxy for /mit/6.004/jsim):
MIT="6004lib/"

# Global variables to communicate the output mechanism to the ALU simulator:
OutputCG = None                         # the CGen instance to use

################################################################################
### Opcode (Fn) names:
################################################################################

# Give names to some of our opcodes.
# WARNING: These get copied into the global environment, for ease of use in
#   writing test cases!
OpcodeNames = {
    'ADD': 0,
    'SUB': 1,
    'MUL': 2,
    'CMPEQ': 5,
    'CMPLT': 7,
    'CMPLE': 0xD,
    'SHL': 8,
    'SHR': 9,
    'SRA': 0xB,
    'AND': 0x18,
    'OR': 0x1E,
    'XOR': 0x16,
    'XNOR': 0x19,
    'A': 0x1A
    }

### Copy them into globals:
for opn in OpcodeNames: globals()[opn] = OpcodeNames[opn]

# Get the name for a numeric opcode. This gives systematic
# names to otherwise unnamed bitwise Boolean ops...
def get_opcode_name(op):

    for n in OpcodeNames:
        if OpcodeNames[n] == op: return n

    # Is it an unnamed Boolean?
    if op & 0xFFFFFFF0 == 0x10:
        return "F%d%d%d%d" % ( 1&(op>>3), 1&(op>>2), 1&(op>>1), 1&op)

    return '???'

### Build the .plotdef string to tell jsim our opcode mapping:
OPCODE_DEFS = ".plotdef op\n+"
for op in range(16): OPCODE_DEFS += (' '+get_opcode_name(op))
OPCODE_DEFS += '\n+'
for op in range(16): OPCODE_DEFS += (' '+get_opcode_name(op+16))
OPCODE_DEFS += '\n\n'

################################################################################
### Python ALU emulator:
### Generates test vectors for Lab 3's ALU.
################################################################################

def ALU(Fn,                             # 5-bit Fn code
        A,                              # 32-bit A operand
        B,                              # 32-bit B operand
        comment=None):

    global OutputCG
    cg = OutputCG

    # Add a comment, specifying Fn and args:
    descr = " [%s 0x%x 0x%x]" % (get_opcode_name(Fn), A, B)

    if comment: comment += descr
    else: comment = descr

    # Here are our outputs:
    z, v, n, y = 0, 0, 0, 0

    # Adder always compute sum or difference, depending on Fn[0]:
    NB = B
    if Fn & 1:
        # Subtract:
        NB = 0xFFFFFFFF & (-B)
        S = 0xFFFFFFFF & (A-B)
        if S & 0x80000000: n = 1
        if (A & 0x80000000)==0 and (B & 0x80000000)!=0 and n==1: v = 1
        if (A & 0x80000000)!=0 and (B & 0x80000000)==0 and n==0: v = 1
    else:
        # Add:
        S = 0xFFFFFFFF & (A+B)
        if S & 0x80000000: n = 1
        if (A & 0x80000000)==0 and (B & 0x80000000)==0 and n==1: v = 1
        if (A & 0x80000000)!=0 and (B & 0x80000000)!=0 and n==0: v = 1
    if S == 0: z=1
    if S & 0x80000000: n=1

    # Handle Fn code cases:

    if Fn == ADD:
        cg.testcase(fn=Fn, a=A, b=B, y=S, z=z, n=n, v=v, comment=comment)

    elif Fn == SUB:
        cg.testcase(fn=Fn, a=A, b=B, y=S, z=z, n=n, v=v, comment=comment)

    elif Fn == MUL:
        y = 0xFFFFFFFF & (A*B)
        cg.testcase(fn=Fn, a=A, b=B, y=y, comment=comment)

    elif Fn == CMPEQ:
        cg.testcase(fn=Fn, a=A, b=B, y=z, z=z, n=n, v=v, comment=comment)

    elif Fn == CMPLT:
        cg.testcase(fn=Fn, a=A, b=B, y=n^v, z=z, n=n, v=v, comment=comment)

    elif Fn == CMPLE:
        cg.testcase(fn=Fn, a=A, b=B, y=n^v | z, z=z, n=n, v=v, comment=comment)

    elif Fn == SHL:
        y = 0xFFFFFFFF & (A << B)
        cg.testcase(fn=Fn, a=A, b=B, y=y, comment=comment)

    elif Fn == SHR:
        y = 0xFFFFFFFF & (A >> B)
        cg.testcase(fn=Fn, a=A, b=B, y=y, comment=comment)

    elif Fn == SRA:
        sext = 0
        if A & 0x80000000:
            sext = 0xFFFFFFFF & (0xFFFFFFFF << (32-B))
        y = 0xFFFFFFFF & ((A >> B) | sext)
        cg.testcase(fn=Fn, a=A, b=B, y=y, comment=comment)

    elif (Fn & 0xFFF0) == 0x10:
        # bitwise Boolean operation
        TT = { 0: Fn & 1,
               1: (Fn >> 1) & 1,
               2: (Fn >> 2) & 1,
               3: (Fn >> 3) & 1 }

        y = 0

        for i in range(32):
            abit = (A >> i) & 1
            bbit = (B >> i) & 1
            ybit = TT[abit+bbit+bbit]
            y |= ybit << i

        cg.testcase(fn=Fn, a=A, b=B, y=y, comment=comment)

    else:
        print "Bad Fn code: 0x%x" % Fn


################################################################################
### Some boiler-plate text to paste into checkoff files:
################################################################################

# FIXME: How do I compute the checksum, etc?
FILE_PREFIX1 = """

* This is a machine-generated file; do not edit.
*
* information on how to contact the on-line assignments server.  A checksum
* is included that is computed from the .verify statements, so don't change
* those or you won't be able to complete your checkoff!

"""

# Here's the checksum we use in the file, unless we have a better guess:
CHECKOFF_CHECKSUM="0"

FILE_PREFIX2 = '.checkoff "6004.csail.mit.edu/currentsemester/6004assignment.doit" "Lab #3" %s\n'

# FIXME:
#  1. Generate this from final CGen state (which knows the final cycle #)
#  2. Output op names from our defns.  Maybe put them in a dict...

FILE_SUFFIX = """

* instantiate alu
Xalu fn[4:0] a[31:0] b[31:0] y[31:0] z v n alu


* Run the simulation long enough to test all input values
.tran %sn

* Some useful plots... you can plot additional signals by specifying
* the appropriate .plot commands in your main netlist file.

""" + OPCODE_DEFS + """

.plot L(a[31:0])
.plot L(b[31:0])
.plot op(fn[4:0])
.plot L(y[31:0])
.plot z
.plot n
.plot v

"""

################################################################################
### Test sequences for each of our 4 subsystems:
################################################################################

def TestADDER(cg):

    # test all comginations of 3 inputs to each bit of the adder.')
    # Also tests N and both ways of producing V.
    ALU(ADD, 0x00000000, 0x00000000)
    ALU(ADD, 0x55555555, 0x00000000)
    ALU(ADD, 0x00000000, 0x55555555)
    ALU(ADD, 0x55555555, 0x55555555)

    ALU(ADD, 0xAAAAAAAA, 0x00000000)
    ALU(ADD, 0x00000000, 0xAAAAAAAA)
    ALU(ADD, 0xAAAAAAAA, 0xAAAAAAAA)
    ALU(ADD, 0xFFFFFFFF, 0xFFFFFFFF)
    ALU(ADD, 0x00000001, 0xFFFFFFFF)

    ALU(SUB, 0xFFFFFFFF, 0x00000000)

    # test each input to Z logic
    ALU(ADD, 0x00000001, 0x00000000)

    ALU(SUB, 0xFFFFFFF2, 0xFFFFFFF0)
    ALU(ADD, 0x00000001, 0x00000003)
    ALU(ADD, 0xAAAAAAAC, 0x5555555C)
    ALU(SUB, 0xFFFFFFFF, 0xFFFFFFEF)
    ALU(ADD, 0x00000002, 0x0000001E)
    ALU(SUB, 0x00000000, 0xFFFFFFC0)
    ALU(SUB, 0x0000007F, 0xFFFFFFFF)

    ALU(ADD, 0x00000080, 0x00000080)
    ALU(ADD, 0x00000180, 0x00000080)
    ALU(ADD, 0x00000380, 0x00000080)
    ALU(ADD, 0x00000780, 0x00000080)
    ALU(ADD, 0x00001000, 0x00000000)
    ALU(ADD, 0x00001000, 0x00001000)
    ALU(ADD, 0x00003000, 0x00001000)
    ALU(ADD, 0x00007000, 0x00001000)

    ALU(ADD, 0x0000F000, 0x00001000)
    ALU(SUB, 0x00001000, 0xFFFE1000)
    ALU(SUB, 0x00001000, 0xFFFC1000)
    ALU(ADD, 0x0007F800, 0x00000800)
    ALU(ADD, 0x000FFC00, 0x00000400)
    ALU(ADD, 0x001FFE00, 0x00000200)
    ALU(ADD, 0x003FFF00, 0x00000100)
    ALU(SUB, 0x00000080, 0xFF800080)

    ALU(ADD, 0xFF000000, 0x02000000)
    ALU(ADD, 0x04000000, 0xFE000000)
    ALU(ADD, 0x03000FFF, 0x00FFF001)
    ALU(ADD, 0x070007FF, 0x00FFF801)
    ALU(ADD, 0x0F0003FF, 0x00FFFC01)
    ALU(ADD, 0x1F0001FF, 0x00FFFE01)
    ALU(ADD, 0x3F0000FF, 0x00FFFF01)
    ALU(SUB, 0x80000001, 0x00000001)


def TestCMP(cg):
    for ab in [(5,4), (5,5), (5,6), (-5,-4), (-5,-5), (-5,-6)]:
        ALU(CMPEQ, ab[0], ab[1])
        ALU(CMPLT, ab[0], ab[1])
        ALU(CMPLE, ab[0], ab[1])

def TestBOOL(cg):
    data = [0, 0xFFFFFFFF, 0x1234, 0xF0F, 0xAAAAAAAA, 0x55555555]
    for op in range(16):
        for a in data:
            for b in data:
                ALU(0x10+op, a, b)

def TestSHIFT(cg):
    for s in range(32):
        for w in [1, 3, 0x1234, 0xFFFF1234, 0xFFFFFFFF]:
            ALU(SHL, w, s, comment="SHL")
            ALU(SHR, w, s, comment="SHR")
            ALU(SRA, w, s, comment="SRA 0x%x >> %d" % (w, s))
    # from Eben
    ALU(SRA,0x8000000,0x10)
    # this one is captured above I think
    #ALU(SRA,0x0000001,0x10)

def TestMULT(cg):
    # Optional multiplier test:
    data = [0, 1, 0xFFFFFFFF]
    for a in data:
        for b in data:
            ALU(MUL, a, b)
    for i in range(32):
        ii = 1<<i
        iii = 3<<i
        ALU(MUL, ii, 1)
        ALU(MUL, -1, ii)
        ALU(MUL, iii, 3)

def GenALUTest(filename, tests='ACBSM'):
    """
    Generate an ALU test file called filename.jsim.
    Generates tests for devices indicated by letters
    in 'tests' argument.

    @param filename: Name of file to write.
    @type filename: string

    @param tests: String on alphabet 'ACBSM', selecting
    tests to run (Adder, Cmp, Bool, Shift, Mult).
    @type tests: string
    """

    global OutputCG

    # Make a CGen instance, to build our test cases:
    OutputCG = CGen('ALU')
    cg = OutputCG

    # ALU inputs include FN[5], A[32], B[32]:
    cg.add_signals(Signal('fn', width=5, inout='input'))
    cg.add_signals(Signal('a', width=32, inout='input'))
    cg.add_signals(Signal('b', width=32, inout='input'))

    # ALU outputs include Y[32], Z, N, V:
    cg.add_signals(Signal('y', width=32, inout='output'))
    cg.add_signals(Signal('z', inout='output'))
    cg.add_signals(Signal('n', inout='output'))
    cg.add_signals(Signal('v', inout='output'))

    # Then add test cases:

    if 'A' in tests: TestADDER(cg)
    if 'C' in tests: TestCMP(cg)
    if 'B' in tests: TestBOOL(cg)
    if 'S' in tests: TestSHIFT(cg)
    if 'M' in tests: TestMULT(cg)

    outstring = OutputCG.dump()

    if filename:
        f = open(MIT+filename+'.jsim', 'w')
        f.write(FILE_PREFIX1)
        f.write(FILE_PREFIX2 % CHECKOFF_CHECKSUM)
        f.write(outstring)
        f.write(FILE_SUFFIX % (cg.cycle_number*cg.period_ns))
        f.close()

    else:
        print outstring

if __name__ == "__main__":
    test_jig_name = None

    USAGE = """
    Usage:
      alu checkoff_file_name tests [test_jig_name]
         <tests> is a string of letters indicating units to test
         <test_file_name>, if supplied, means this is a test jig; changes .checkoff
    """

    import sys
    if len(sys.argv) < 3:
        print USAGE
        sys.exit(-1)

    filename = sys.argv[1]
    tests = sys.argv[2]

    # If we're given a test jig name, do dummy .checkoff line:
    if len(sys.argv) > 3:
        test_jig_name = sys.argv[3]
        FILE_PREFIX2 = ('.checkoff "" "Lab3: %s" ' % test_jig_name)+'%s'

    else:

        # If we have a cached checksum for this file, use it:
        try:
            f = open("cksms/%s.cksm" % filename, "r")
            data = f.read()
            f.close()
            i = data.find('=')
            if (i > 0):
                CHECKOFF_CHECKSUM = data[i+1:]
        except:
            pass
    
    GenALUTest(filename, tests)
