'''
    File name      : verilog_to_blif_converter.py
    Author         : Jinwook Jung (jinwookjungs@gmail.com)
    Created on     : Sat 05 Aug 2017 02:35:14 PM KST
    Last modified  : 2017-08-07 15:48:54
    Description    : Provides BlifConverter class.
'''

from __future__ import print_function
from time import gmtime, strftime
import sys


class BlifConverter(object):
    """ Blif netlist generator. """
    def __init__(self):
        self.__circuit = None
        self._inputs   = list()
        self._outputs  = list()
        self._gates    = list()
        self._assigns  = list()
        self._arrivals = list()
        self._reqtime  = list()
        self._default_reqtime = None

    def read_verilog(self, src):
        """ Reading the source verilog file. """
        lines = [x.rstrip() for x in open(src)]

        for line in lines:
            # if current line is blank or a comment:
            if line == '' or line.startswith('//'): 
                continue

            tokens = line.split()
            if tokens[0] == 'wire':
                continue
            elif tokens[0] == 'module':
                self._circuit = tokens[1]
            elif tokens[0] == 'input':
                self._inputs.append( [pin[:-1] for pin in tokens[1:]] )
            elif tokens[0] == 'output':
                self._outputs.append( [pin[:-1] for pin in tokens[1:]] )
            elif tokens[0] == 'assign':
                if tokens[3] not in ("1'b0;", "1'b1;"):
                    sys.stderr.write("(E) Unsupported netlist: %s" % line)
                    sys.exit(-1)
                self._assigns.append( [tokens[1], tokens[3]])
            elif tokens.__len__() > 3:    # gate definitions
                gate_type = tokens[0] # string
                pins = tokens[3:-1] # list
                self._gates.append( [gate_type, pins] )

    def read_timing(self, asserts):
        """ Reading timing asserions. """

        lines = [l.strip() for l in open(asserts) if l.strip()]
        for line in lines:
            tokens = line.split()
            if tokens[0] == 'clock':
                self._default_reqtime = tokens[2]
            elif tokens[0] == 'at':
                input_name, arrtime_r, arrtime_f = tokens[1], tokens[2], tokens[3]
                self._arrivals.append( [input_name, arrtime_r, arrtime_f] )
            elif tokens[0] == 'rat':
                output_name, reqtime_r, reqtime_f = tokens[1], tokens[2], tokens[3]
                self._reqtime.append( [output_name, reqtime_r, reqtime_f] )

    def write_blif(self, dest, exists_asserts=False):
        with open(dest, 'w') as f:
            f.write("# Generated by verilog_to_blif_converter.py, %s\n"
                    % (strftime("%Y-%m-%d %H:%M:%S", gmtime())))
            f.write(".model %s\n\n" % (self._circuit))
            self._print_ports("inputs", self._inputs, f)
            self._print_ports("outputs", self._outputs, f)
            if exists_asserts:
                f.write("\n# Timing assertion\n")
                self._print_default_assertions(f)
                self._print_arrivals(self._arrivals, f)
                self._print_reqtime(self._reqtime, f)
            self._print_assigns(self._assigns, f)
            self._print_gates(self._gates, f)
            f.write(".end\n")

    def _print_ports(self, port_type, port_list, f):
        """ Print port definition. """
        # Flatten lists
        ports = [port for sublist in port_list for port in sublist]
        f.write(".%s " % (port_type))
        f.write(" ".join(ports))
        f.write("\n")

    def _print_default_assertions(self, f):
        f.write(".default_input_arrival 0 0\n")
        f.write(".default_output_required {0} {0}\n".format(self._default_reqtime))

    def _print_arrivals(self, arrivals_list, f):
        for arr in arrivals_list:
            f.write(".input_arrival %s %s %s\n" % (arr[0], arr[1], arr[2]))

    def _print_reqtime(self, reqtime_list, f):
        for arr in reqtime_list:
            f.write(".output_required %s %s %s\n" % (arr[0], arr[1], arr[2]))

    def _print_assigns(self, assign_list, f):
        if len(assign_list) > 0:
            f.write("\n# Assigns\n")
        for assign in assign_list:
            if assign[1] == "1'b0;":
                f.write(".gate _const0_ z=%s\n" % assign[0])
            else:
                f.write(".gate _const1_ z=%s\n" % assign[0])

    def _print_gates(self, gate_list, f, inv_name=("INV_X1", "A", "ZN")):
        def extract_pin_and_net(token):
            # token should be of .PIN(NET)
            # replace .,() with blank
            for c in ('.', ',', '(', ')'):
                token = token.replace(c, ' ')

            token = token.strip().split()
            pin, net = token[0], token[1]
            return pin, net

        f.write("\n# Gates\n")
        for gate in gate_list:
            ref_name = gate[0].upper()

            if ref_name == "MS00F80":
                for pin in gate[1]:
                    pin_name, net_name = extract_pin_and_net(pin)
                    if pin_name.upper() == 'O': o = net_name
                    elif pin_name.upper() == 'D': d = net_name

                f.write(".latch %s %s\n" % (d, o))

            elif ref_name.startswith('VSS'):
                pin_name, net_name = extract_pin_and_net(gate[1][0])
                f.write(".gate _const0_ z=%s\n" % (net_name))

            elif ref_name.startswith('VCC'):
                pin_name, net_name = extract_pin_and_net(gate[1][0])
                f.write(".gate _const1_ z=%s\n" % (net_name))

            else:
                f.write(".gate %s " % (gate[0]))
                # pins
                for pin in gate[1]:
                    pin_name, net_name = extract_pin_and_net(pin)
                    f.write("%s=%s " % (pin_name, net_name))  
                f.write("\n")

