#  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 the unit tests for always_comb """
from __future__ import absolute_import

import random
from random import randrange

from myhdl import (AlwaysCombError, Signal, Simulation, StopSimulation, delay,
                   instances, intbv, now)
from myhdl._always_comb import _error, always_comb
from myhdl._Waiter import _SignalTupleWaiter, _SignalWaiter, _Waiter
from utils import raises_kind

# random.seed(3) # random, but deterministic


QUIET=1


def g():
    pass

x = Signal(0)


class TestAlwaysCombCompilation:

    def testArgIsFunction(self):
        h = 5
        with raises_kind(AlwaysCombError, _error.ArgType):
            always_comb(h)

    def testArgIsNormalFunction(self):
        def h():
            yield None
        with raises_kind(AlwaysCombError, _error.ArgType):
            always_comb(h)

    def testArgHasNoArgs(self):
        def h(n):
            return n
        with raises_kind(AlwaysCombError, _error.NrOfArgs):
            always_comb(h)

##     def testScope(self):
##         try:
##             always_comb(g)
##         except AlwaysCombError, e:
##             self.assertEqual(e.kind, _error.Scope)
##         else:
##             self.fail()

    def testInfer1(self):
        a, b, c, d = [Signal(0) for i in range(4)]
        u = 1

        def h():
            c.next = a
            v = u
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a'])
        assert i.inputs == expected

    def testInfer2(self):
        a, b, c, d = [Signal(0) for i in range(4)]
        u = 1

        def h():
            c.next = x
            g = a
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a', 'x'])
        assert i.inputs == expected

    def testInfer3(self):
        a, b, c, d = [Signal(0) for i in range(4)]
        u = 1

        def h():
            c.next = a + x + u
            a = 1
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['x'])
        assert i.inputs == expected

    def testInfer4(self):
        a, b, c, d = [Signal(0) for i in range(4)]
        u = 1

        def h():
            c.next = a + x + u
            x = 1
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a'])
        assert i.inputs == expected

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

        def h():
            c.next += 1
            a += 1
        with raises_kind(AlwaysCombError, _error.SignalAsInout % "c"):
            g = always_comb(h).gen

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

        def h():
            c.next = a
            x.next = c
        with raises_kind(AlwaysCombError, _error.SignalAsInout % "c"):
            g = always_comb(h).gen

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

        def h():
            c.next[a:0] = x[b:0]
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a', 'b', 'x'])
        assert i.inputs == expected

    def testInfer8(self):
        a, b, c, d = [Signal(0) for i in range(4)]
        u = 1

        def h():
            v = 2
            c.next[8:1+a+v] = x[4:b*3+u]
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a', 'b', 'x'])
        assert i.inputs == expected

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

        def h():
            c.next[a-1] = x[b-1]
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a', 'b', 'x'])
        assert i.inputs == expected

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

        def f(x, y, z):
            return 0

        def h():
            c.next = f(a, 2*b, d*x)
        g = always_comb(h).gen
        i = g.gi_frame.f_locals['self']
        expected = set(['a', 'b', 'd', 'x'])
        assert i.inputs == expected

    def testEmbeddedFunction(self):
        a, b, c, d = [Signal(0) for i in range(4)]
        u = 1

        def h():
            def g():
                e = b
                return e
            c.next = x
            g = a
        with raises_kind(AlwaysCombError, _error.EmbeddedFunction):
            g = always_comb(h)


class TestAlwaysCombSimulation1:

    def bench(self, function):

        clk = Signal(0)
        a = Signal(0)
        b = Signal(0)
        c = Signal(0)
        d = Signal(0)
        z = Signal(0)
        vectors = [intbv(j) for i in range(32) for j in range(16)]
        random.shuffle(vectors)

        def combFunc():
            if __debug__:
                f = x
            x.next = function(a, b, c, d)

        comb = always_comb(combFunc)

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

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

        def stimulus():
            for v in vectors:
                a.next = v[0]
                b.next = v[1]
                c.next = v[2]
                d.next = v[3]
                yield clk.posedge
                yield clk.negedge
                assert x == z
            raise StopSimulation("always_comb simulation test")

        return instances()

    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 TestAlwaysCombSimulation2:

    def bench(self, funcName="and"):

        clk = Signal(0)
        a = Signal(0)
        b = Signal(0)
        c = Signal(0)
        d = Signal(0)
        k = Signal(0)
        z = Signal(0)
        x = Signal(0)
        vectors = [intbv(j) for i in range(32) for j in range(16)]
        random.shuffle(vectors)

        def andFunc():
            x.next = a & b & c & d

        def andGenFunc():
            while 1:
                z.next =  a & b & c & d
                yield a, b, c, d

        def orFunc():
            x.next = a | b | c | d

        def orGenFunc():
            while 1:
                z.next = a | b | c | d
                yield a, b, c, d

        def logicFunc():
            x.next = not (a & (not b)) | ((not c) & d)

        def logicGenFunc():
            while 1:
                z.next = not (a & (not b)) | ((not c) & d)
                yield a, b, c, d

        def incFunc():
            x.next = k + 1

        def incGenFunc():
            while 1:
                z.next = k + 1
                yield k

        combFunc = eval(funcName + "Func")
        comb = always_comb(combFunc)
        genFunc = eval(funcName + "GenFunc")
        gen = genFunc()

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

        def stimulus():
            for v in vectors:
                a.next = v[0]
                b.next = v[1]
                c.next = v[2]
                d.next = v[3]
                k.next = v
                yield clk.posedge
                yield clk.negedge
                assert x == z
            raise StopSimulation("always_comb simulation test")

        return comb, gen, clkGen(), stimulus()

    def testAnd(self):
        Simulation(self.bench("and")).run(quiet=QUIET)

    def testOr(self):
        Simulation(self.bench("or")).run(quiet=QUIET)

    def testLogic(self):
        Simulation(self.bench("logic")).run(quiet=QUIET)

    def testInc(self):
        Simulation(self.bench("inc")).run(quiet=QUIET)


def SignalGen1(a, b, c, d, r):

    @always_comb
    def logic():
        r.next = a

    return logic


def SignalTupleGen1(a, b, c, d, r):

    @always_comb
    def logic():
        r.next = a + b + c

    return logic


class TestInferWaiter:

    def bench(self, MyHDLFunc, waiterType):

        a, b, c, d, r, s = [Signal(intbv(0)) for i in range(6)]

        inst_r = MyHDLFunc(a, b, c, d, r)
        assert type(inst_r.waiter) == waiterType

        inst_s = MyHDLFunc(a, b, c, d, s)

        def stimulus():
            for i in range(1000):
                yield delay(randrange(1, 10))
                if randrange(2):
                    a.next = randrange(32)
                if randrange(2):
                       b.next = randrange(32)
                c.next = randrange(2)
                d.next = randrange(2)
            raise StopSimulation

        def check():
            while 1:
                yield a, b, c, r, s
                assert r == s

        return inst_r, _Waiter(inst_s.gen), _Waiter(stimulus()), _Waiter(check())

    def testSignal1(self):
        sim = Simulation(self.bench(SignalGen1, _SignalWaiter))
        sim.run()

    def testSignalTuple1(self):
        sim = Simulation(self.bench(SignalTupleGen1, _SignalTupleWaiter))
        sim.run()
