#  This file is part of the myhdl library, a Python package for using
#  Python as a Hardware Description Language.
#
#  Copyright (C) 2003-2008 Jan Decaluwe
#
#  The myhdl library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation; either version 2.1 of the
#  License, or (at your option) any later version.
#
#  This library 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
#  Lesser General Public License for more details.

#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

""" Run unit tests for Simulation """
import random
from random import randrange
from unittest import TestCase

from myhdl import (Signal, Simulation, SimulationError, StopSimulation, delay,
                   intbv, join, now)
from myhdl._Simulation import _error
from helpers import raises_kind

random.seed(1)  # random, but deterministic

QUIET = 1


class Shared:
    pass


class SimArgs(TestCase):
    """ Simulation arguments """

    def test1(self):
        with raises_kind(SimulationError, _error.ArgType):
            Simulation(None)

    def test2(self):

        def g():
            yield delay(10)

        i = g()
        with raises_kind(SimulationError, _error.DuplicatedArg):
            Simulation(i, i)


class YieldNone(TestCase):
    """ Basic test of yield None behavior """

    def test1(self):

        def stimulus():
            a = Signal(0)
            yield delay(10)
            a.next = 1
            yield None
            assert a.val == 0
            assert now() == 10
            yield delay(0)
            assert a.val == 1
            assert now() == 10

        Simulation(stimulus()).run(quiet=QUIET)

    def test2(self):

        def stimulus():
            a = Signal(0)
            yield delay(10)
            a.next = 1
            assert a.val == 0
            assert now() == 10
            yield None
            a.next = 0
            assert a.val == 0
            assert now() == 10
            yield None
            a.next = 1
            assert a.val == 0
            assert now() == 10
            yield delay(0)
            assert a.val == 1
            assert now() == 10

        Simulation(stimulus()).run(quiet=QUIET)

    def test3(self):

        def stimulus():
            a = Signal(0)
            yield delay(10)
            a.next = 1
            yield None, delay(10)
            assert a.val == 0
            assert now() == 10
            yield delay(0)
            assert a.val == 1
            assert now() == 10

        Simulation(stimulus()).run(quiet=QUIET)

    def test4(self):

        def stimulus():
            a = Signal(0)
            yield delay(10)

            def gen():
                yield delay(20)
                a.next = 1

            yield None, gen()
            assert a.val == 0
            assert now() == 10
            yield delay(25)
            assert a.val == 1

        Simulation(stimulus()).run(quiet=QUIET)


class JoinMix(TestCase):
    """ Test of joins mixed with other clauses """

    def test1(self):

        def stimulus():
            a = Signal(0)

            def gen():
                yield join(delay(10), delay(20))

            yield gen(), delay(5)
            assert now() == 5
            yield a
            raise AssertionError("Incorrect run")  # should not get here

        Simulation(stimulus()).run(quiet=QUIET)

    def test2(self):

        def stimulus():
            a = Signal(0)
            yield join(delay(10), delay(20)), delay(5)
            assert now() == 5
            yield a
            raise AssertionError("Incorrect run")  # should not get here

        Simulation(stimulus()).run(quiet=QUIET)

    def stimulus(self, a, b, c, d):
        yield delay(5)
        a.next = 1
        yield delay(5)
        a.next = 0
        b.next = 1
        yield delay(5)
        a.next = 1
        b.next = 0
        c.next = 1
        yield delay(5)
        a.next = 0
        b.next = 1
        c.next = 0
        d.next = 1

    def test3(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, b, c, d)
            assert now() == 20

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test4(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, b), join(c, d)
            assert now() == 10

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test5(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a), b, join(c, d)
            assert now() == 5

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test6(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, delay(20)), b, join(c, d)
            assert now() == 10

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test7(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, delay(30)), join(c, d)
            assert now() == 20

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test8(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, a.negedge)
            assert now() == 10

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test9(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, a.negedge, c.posedge)
            assert now() == 15

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test10(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, a)
            assert now() == 5

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)

    def test11(self):
        a, b, c, d = [Signal(0) for __ in range(4)]

        def response():
            yield join(a, b.posedge, b.negedge, a)
            assert now() == 15

        Simulation(self.stimulus(a, b, c, d), response()).run(quiet=QUIET)


class JoinedGen(TestCase):

    """ Basic test of yielding joined concurrent generators """

    def bench(self):
        sig1 = Signal(0)
        sig2 = Signal(0)
        td = 10

        def gen(s, n):
            for dummy in range(n - 1):
                yield delay(td)
            s.next = 1
            yield delay(td)

        for __ in range(10):
            offset = now()
            n0 = randrange(1, 50)
            n1 = randrange(1, 50)
            n2 = randrange(1, 50)
            sig1.next = 0
            sig2.next = 0
            yield join(delay(n0 * td), gen(sig1, n1), gen(sig2, n2))
            assert sig1.val == 1
            assert sig2.val == 1
            assert now() == offset + td * max(n0, n1, n2)

        raise StopSimulation("Joined concurrent generator yield")

    def testYieldJoinedGen(self):
        Simulation(self.bench()).run(quiet=QUIET)


class SignalUpdateFirst(TestCase):

    """ Check that signal updates are done first, as in VHDL """

    def bench(self):

        Q = Signal(0, delay=9)
        R = Signal(0, delay=10)
        S = Signal(0, delay=11)

        def process():
            Q.next = 0
            R.next = 0
            S.next = 0
            yield delay(50)
            Q.next = 1
            R.next = 1
            S.next = 1
            yield delay(10)
            assert Q.val == 1  # control
            assert R.val == 1  # actual check
            assert S.val == 0  # control
            yield delay(1)
            assert Q.val == 1  # control
            assert R.val == 1  # control
            assert S.val == 1  # control
            raise StopSimulation("Signal update test")

        return process()

    def testSignalUpdateFirst(self):
        Simulation(self.bench()).run(quiet=QUIET)


class YieldZeroDelay(TestCase):

    """ Basic test of yielding a zero delay """

    def bench(self):
        sig1 = Signal(0)
        sig2 = Signal(0)
        td = 10

        def gen(s, n):
            s.next = 0
            for dummy in range(n):
                yield delay(td)
            s.next = 1

        for __ in range(100):
            offset = now()
            n1 = randrange(2, 10)
            n2 = randrange(n1 + 1, 20)  # n2 > n1
            yield delay(0), gen(sig1, n1), gen(sig2, n2)
            assert sig1.val == 0
            assert sig2.val == 0
            assert now() == offset + 0
            yield sig1.posedge
            assert sig2.val == 0
            assert now() == offset + n1 * td
            yield sig2.posedge
            assert now() == offset + n2 * td

        raise StopSimulation("Zero delay yield")

    def testYieldZeroDelay(self):
        Simulation(self.bench()).run(quiet=QUIET)


class YieldConcurrentGen(TestCase):

    """ Basic test of yielding concurrent generators """

    def bench(self):
        sig1 = Signal(0)
        sig2 = Signal(0)
        td = 10

        def gen(s, n):
            s.next = 0
            for dummy in range(n):
                yield delay(td)
            s.next = 1

        for __ in range(100):
            offset = now()
            n1 = randrange(2, 10)
            n2 = randrange(n1 + 1, 20)  # n2 > n1
            yield delay(td), gen(sig1, n1), gen(sig2, n2)
            assert sig1.val == 0
            assert sig2.val == 0
            assert now() == offset + td
            yield sig1.posedge
            assert sig2.val == 0
            assert now() == offset + n1 * td
            yield sig2.posedge
            assert now() == offset + n2 * td

        raise StopSimulation("Concurrent generator yield")

    def testYieldConcurrentGen(self):
        Simulation(self.bench()).run(quiet=QUIET)


class YieldGen(TestCase):

    """ Basic test of yielding generators """

    def bench(self):

        clk = Signal(0)
        shared = Shared()
        shared.cnt = 0
        shared.i = 0
        expected = []
        nlists = []
        expectedCnt = 0
        for __ in range(300):
            l = []
            for __ in range(randrange(1, 6)):
                e = randrange(0, 5)
                l.append(e)
                expectedCnt += e
                expected.append(expectedCnt)
            nlists.append(l)

        def clkGen():
            while 1:
                yield delay(10)
                clk.next = 1
                yield delay(10)
                clk.next = 0

        def task(nlist):
            n = nlist.pop(0)
            for dummy in range(n):
                yield clk.posedge
                shared.cnt += 1
            assert shared.cnt == expected[shared.i]
            shared.i += 1
            if nlist:
                yield task(nlist)

        def module():
            for nlist in nlists:
                yield task(nlist)
            assert shared.cnt == expected[-1]
            raise StopSimulation("Generator yield")

        return(module(), clkGen())

    def testYieldGen(self):
        Simulation(self.bench()).run(quiet=QUIET)


class DeltaCycleOrder(TestCase):

    """ Check that delta cycle order does not matter """

    def bench(self, function):

        clk = Signal(0)
        a = Signal(0)
        b = Signal(0)
        c = Signal(0)
        d = Signal(0)
        z = Signal(0)
        delta = [Signal(0) for dummy in range(4)]
        inputs = Signal(intbv(0))
        s = [a, b, c, d]
        vectors = [intbv(j) for dummy in range(8) for j in range(16)]
        random.shuffle(vectors)
        index = list(range(4))

        def clkGen():
            while 1:
                yield delay(10)
                clk.next ^= 1

        def deltaGen():
            while 1:
                yield clk
                delta[0].next = clk.val
                yield delta[0]
                for i in range(1, 4):
                    delta[i].next = delta[i - 1].val
                    yield delta[i]

        def inGen(i):
            while 1:
                yield delta[i].posedge
                s[index[i]].next = inputs.val[index[i]]

        def logic():
            while 1:
                # yield a, b, c, d
                z.next = function(a.val, b.val, c.val, d.val)
                yield a, b, c, d

        def stimulus():
            for v in vectors:
                inputs.next = v
                random.shuffle(index)
                yield clk.posedge
                yield clk.posedge
                assert z.val == function(v[0], v[1], v[2], v[3])
            raise StopSimulation("Delta cycle order")

        inputGen = [inGen(i) for i in range(4)]
        instance = [clkGen(), deltaGen(), logic(), stimulus(), inputGen]
        return instance

    def testAnd(self):

        def andFunction(a, b, c, d):
            return a & b & c & d

        Simulation(self.bench(andFunction)).run(quiet=QUIET)

    def testOr(self):

        def orFunction(a, b, c, d):
            return a | b | c | d

        Simulation(self.bench(orFunction)).run(quiet=QUIET)

    def testXor(self):

        def xorFunction(a, b, c, d):
            return a ^ b ^ c ^ d

        Simulation(self.bench(xorFunction)).run(quiet=QUIET)

    def testMux(self):

        def muxFunction(a, b, c, d):
            if c:
                return a
            else:
                return b

        Simulation(self.bench(muxFunction)).run(quiet=QUIET)

    def testLogic(self):

        def function(a, b, c, d):
            return not (a & (not b)) | ((not c) & d)

        Simulation(self.bench(function)).run(quiet=QUIET)


class DeltaCycleRace(TestCase):

    """ Check that delta cycle races are like in VHDL """

    def bench(self):

        uprange = range(300)
        msig = Signal(uprange[0])
        ssig = [Signal(uprange[-1]) for __ in range(2)]
        dsig = [Signal(uprange[0]) for __ in range(2)]
        clk = Signal(0)
        deltaClk = Signal(0)
        shared = Shared()
        shared.t = now()

        def clkGen():
            for dummy in uprange[:-1]:
                yield delay(10)
                clk.next = 1
                yield delay(10)
                clk.next = 0

        def deltaClkGen():
            while 1:
                yield clk
                deltaClk.next = clk.val

        def master():
            i = 0
            while 1:
                yield clk.posedge
                msig.next = uprange[i + 1]
                assert msig.val == uprange[i]
                shared.t = now()
                i += 1

        def slave(ssig):
            """ Double-check proper operation """
            i = 0
            while 1:
                yield clk.posedge
                ssig.next = msig.val
                assert ssig.val == uprange[i - 1]
                i += 1

        def deltaSlave(dsig):
            """ Expect delta cycle races """
            i = 0
            while 1:
                yield deltaClk.posedge
                dsig.next = msig.val
                assert now() == shared.t
                assert dsig.val == uprange[i]
                i += 1

        return (slave(ssig[1]), deltaSlave(dsig[1]),
                master(), clkGen(), deltaClkGen(),
                slave(ssig[0]), deltaSlave(dsig[0]))

    def testDeltaCycleRace(self):
        """ Check delta cycle races """
        bench = self.bench()
        Simulation(bench).run(quiet=QUIET)


class DelayLine(TestCase):

    """ Check that delay lines work properly """

    def bench(self):
        uprange = range(500)
        sig_Z = [Signal(uprange[-i]) for i in range(7)]
        clk = Signal(0)

        def clkGen():
            for i in uprange[:-1]:
                yield delay(10)
                clk.next = 1
                yield delay(10)
                clk.next = 0

        def delayElement(n, i):
                sig_Z[n].next = sig_Z[n - 1].val
                assert sig_Z[n].val == uprange[i - n]

        def stage(n):
            i = 0
            while 1:
                yield clk.posedge
                delayElement(n, i)
                i += 1

        def stage012():
            i = 0
            while 1:
                yield clk.posedge
                delayElement(1, i)
                sig_Z[0].next = uprange[i + 1]
                delayElement(2, i)
                i += 1

        return [stage(6), stage(4), clkGen(), stage(3), stage012(), stage(5)]

    def testZeroDelay(self):
        """ Zero delay behavior """
        bench = self.bench()
        Simulation(bench).run(quiet=QUIET)


def initSignal(waveform):
    __, val, sigdelay = waveform[0]
    if sigdelay:
        return Signal(val=val, delay=sigdelay)
    else:
        return Signal(val=val)


def isPosedge(oldval, val):
    return not oldval and val


def isNegedge(oldval, val):
    return oldval and not val


def isEvent(oldval, val):
    return oldval != val


def isEdge(oldval, val):
    return isPosedge(oldval, val) or isNegedge(oldval, val)


def getExpectedTimes(waveform, eventCheck):
    interval, val, sigdelay = waveform[0]
    # print waveform[0]
    expected = []
    time = interval
    oldval = val
    i = 1
    while i < len(waveform):
        interval, val, sigdelay = waveform[i]
        # print waveform[i]
        time += interval
        # check future events within inertial delay interval
        j = i + 1
        inctime = 0
        while j < len(waveform) and inctime + waveform[j][0] < sigdelay:
            inctime += waveform[j][0]
            newval = waveform[j][1]
            newsigdelay = waveform[j][2]
            if newval != val:  # cancel event
                break
            else:  # same vals
                if inctime + newsigdelay < sigdelay:
                    # special case: there is a later event, with same val,
                    # but smaller delay: presumably, this should win,
                    # so cancel the present one
                    break
            j += 1
        else:  # if event was not cancelled by a break
            if eventCheck(oldval, val):
                expected.append(time + sigdelay)
                # print expected[-1]
            oldval = val
        i += 1
    # print expected
    return expected


class Waveform(TestCase):

    """ Test of all sorts of event response in a waveform """

    waveform = []
    duration = 0
    sigdelay = 0
    for i in range(2000):
        interval = randrange(0, 150)
        val = randrange(0, 4)
        waveform.append((interval, val, sigdelay))
        duration = interval + duration

    def stimulus(self):
        for interval, val, sigdelay in self.waveform:
            yield delay(interval)
            self.sig.next = val
            if sigdelay:
                self.sig.delay = sigdelay

    def response(self, clause, expected):
        assert len(expected) > 100  # we should test something
        i = 0
        while 1:
            yield clause
            assert now() == expected[i]
            i += 1

    def setUp(self):
        self.sig = initSignal(self.waveform)

    def runSim(self, sim):
        sim.run(quiet=QUIET)

    def testPosedge(self):
        """ Posedge waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isPosedge)
        response = self.response(clause=s.posedge, expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testNegedge(self):
        """ Negedge waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isNegedge)
        response = self.response(clause=s.negedge, expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testEdge(self):
        """ Edge waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isEdge)
        response = self.response(clause=(s.negedge, s.posedge),
                                 expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testEvent(self):
        """ Event waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isEvent)
        # print expected
        response = self.response(clause=s, expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testRedundantEvents(self):
        """ Redundant event waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isEvent)
        response = self.response(clause=(s,) * 6, expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testRedundantEventAndEdges(self):
        """ Redundant edge waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isEvent)
        response = self.response(clause=(s, s.negedge, s.posedge),
                                 expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testRedundantPosedges(self):
        """ Redundant posedge waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isPosedge)
        response = self.response(clause=(s.posedge,) * 3, expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()

    def testRedundantNegedges(self):
        """ Redundant negedge waveform test """
        s = self.sig
        stimulus = self.stimulus()
        expected = getExpectedTimes(self.waveform, isNegedge)
        response = self.response(clause=(s.negedge,) * 9, expected=expected)
        self.runSim(Simulation(stimulus, response))
        assert self.duration <= now()


class WaveformSigDelay(Waveform):

    """ Repeat waveform in tests with a delayed signal """

    waveform = []
    duration = 0
    sigdelay = 0
    for i in range(2000):
        interval = randrange(20, 150)
        val = randrange(0, 4)
        sigdelay = randrange(1, 20)
        waveform.append((interval, val, sigdelay))
        duration += interval


class WaveformInertialDelay(Waveform):

    """ Repeat waveform tests to check inertial delay """

    waveform = []
    duration = 0
    sigdelay = 0
    for i in range(2000):
        interval = randrange(3, 10)
        val = randrange(0, 3)
        sigdelay = randrange(1, 5)
        waveform.append((interval, val, sigdelay))
        duration += interval


class WaveformInertialDelayStress(Waveform):

    """ Repeat waveform tests to stress inertial delay """

    waveform = []
    duration = 0
    sigdelay = 0
    for i in range(2000):
        interval = randrange(1, 3)
        val = randrange(0, 3)
        sigdelay = randrange(1, 3)
        waveform.append((interval, val, sigdelay))
        duration += interval


class SimulationRunMethod(Waveform):

    """ Basic test of run method of Simulation object """

    def runSim(self, sim):
        duration = randrange(1, 300)
        while sim.run(duration, quiet=QUIET):
            duration = randrange(1, 300)


class TimeZeroEvents(TestCase):

    """ Check events at time 0 """

    def bench(self, sig, nextval, clause, timeout=1):

        def stimulus():
            sig.next = nextval
            yield delay(10)

        def response():
            yield clause, delay(timeout)
            assert now() == 0
            assert sig.val == nextval

        return [stimulus(), response()]

    def testEvent(self):
        """ Event at time 0 """
        s = Signal(0)
        testBench = self.bench(sig=s, nextval=1, clause=s)
        Simulation(testBench).run(quiet=QUIET)

    def testPosedge(self):
        """ Posedge at time 0 """
        s = Signal(0)
        testBench = self.bench(sig=s, nextval=1, clause=s.posedge)
        Simulation(testBench).run(quiet=QUIET)

    def testNegedge(self):
        """ Negedge at time 0 """
        s = Signal(1)
        testBench = self.bench(sig=s, nextval=0, clause=s.negedge)
        Simulation(testBench).run(quiet=QUIET)
