################################################################################
### Script to generate jsim checkoff files                  -- Steve Ward 9/2011
################################################################################

class Signal:
    """
    Wire or wire bundle, either produced or consumed by unit under test.
    """

    def __init__(self, name, width=1, inout='input'):
        self.name = name
        self.inout = inout
        self.width = width

    def jsim_name(self):
        s = self.name
        if self.width > 1: s += ('[%d:0]' % (self.width-1))
        return s

class TestCase:
    """
    Generate a 1-cycle test case.
    """
    def __init__(self, comment=None, **signals):
        self.signals = {}
        self.comment = comment
        for s in signals:
            self.signals[s] = signals[s]
        self.cycle_number = "unknown"


class CGen:
    """
    Class to generate a checkoff file for a given lab assignment.
    """

    def __init__(self, name,
                 period_ns = 100):

        self.name = name
        self.period_ns = period_ns
        self.cycle_number = 0

        # Dictionary of signals to generate or verify:
        self.signals = []

        # List of test cases/comments:
        self.sequence = []

    def add_signals(self, *signals):
        for s in signals:
            self.signals.append(s)

    def testcase(self, **kwargs):
        tc = TestCase(**kwargs)
        tc.cycle_number = self.cycle_number
        self.cycle_number += 1
        self.sequence.append(tc)

    def dump_output_signal(self, sig):
        s = "\n.verify %s tvpairs()\n" % sig.jsim_name()
        for tc in self.sequence:
            if sig.name in tc.signals:
                time = tc.cycle_number*self.period_ns +(self.period_ns-1)
                s += '+ %dns 0x%x' % (time, tc.signals[sig.name])
                s += ' // %dns' % time
                if tc.comment: s += ': %s' % tc.comment
                s += '\n'
        return s
        
    def dump_input_signal(self, sig):
        s = "\nW%s %s nrz(0, 3.3, 100n, 0n, .1n,.1n)\n" % (sig.name, sig.jsim_name())

        # Convert signals to width-bit ints, for hex printout:
        mask = (1<<sig.width) - 1

        for tc in self.sequence:
            if sig.name in tc.signals:
                val = mask & tc.signals[sig.name]
                time = tc.cycle_number*self.period_ns
                s += '+ 0x%x' % val
                s += ' // %dns' % time
                if tc.comment: s += ': %s' % tc.comment
                s += '\n'
        return s
        
    def dump(self):
        s = ""
        for sig in self.signals:
            if sig.inout == 'input':
                s += self.dump_input_signal(sig)
            else:
                s += self.dump_output_signal(sig)
        return s


if __name__ == "__main__":

    cg = CGen('ALU')
    # 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'))

    # Give names to some function codes:
    ADD=0
    SUB=1
    MUL=2
    CMPEQ=5
    CMPLT=7
    CMPLE=0xD
    SHL=8
    SHR=9
    SRA=0xB
    AND=0x18
    OR=0x1E
    XOR=0x6
    A=0xA

    def test_add(a, b, comment=None):
        y = 0xFFFFFFFF & (a+b)
        z, n, v = 0, 0, 0
        if y == 0: z = 1
        if (y & 0x80000000): n = 1
        if (a & 0x80000000)==0 and (b & 0x80000000)==0 and n==1: v = 1
        if (a & 0x80000000)==1 and (b & 0x80000000)==1 and n==0: v = 1
        cg.testcase(fn=ADD, a=a, b=b, y=y, z=z, n=n, v=v, comment=comment)

    def test_sub(a, b, comment=None):
        y = 0xFFFFFFFF & (a-b)
        z, n, v = 0, 0, 0
        if y == 0: z = 1
        if (y & 0x80000000): n = 1
        if (a & 0x80000000)==0 and (b & 0x80000000)==0 and n==1: v = 1
        if (a & 0x80000000)==1 and (b & 0x80000000)==1 and n==0: v = 1
        cg.testcase(fn=SUB, a=a, b=b, y=y, z=z, n=n, v=v, comment=comment)

    # Add some test cases:
    test_add(0x00000000, 0x00000000,
             comment='test all comginations of 3 inputs to each bit of the adder.')
    test_add(0x55555555, 0x00000000,
             comment='Also tests N and both ways of producing V.')
    test_add(0x00000000, 0x55555555)
    test_add(0x55555555, 0x55555555)

    test_add(0xAAAAAAAA, 0x00000000)
    test_add(0x00000000, 0xAAAAAAAA)
    test_add(0xAAAAAAAA, 0xAAAAAAAA)
    test_add(0xFFFFFFFF, 0xFFFFFFFF)
    test_add(0x00000001, 0xFFFFFFFF)

    test_sub(0xFFFFFFFF, 0x00000000)

    test_add(0x00000001, 0x00000000,
             comment='test each input to Z logic')
    test_sub(0xFFFFFFF2, 0xFFFFFFF0)
    test_add(0x00000001, 0x00000003)
    test_add(0xAAAAAAAC, 0x5555555C)
    test_sub(0xFFFFFFFF, 0xFFFFFFEF)
    test_add(0x00000002, 0x0000001E)
    test_sub(0x00000000, 0xFFFFFFC0)
    test_sub(0x0000007F, 0xFFFFFFFF)

    print cg.dump()
