# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import debug
import design
from tech import drc
from contact import contact
from vector import vector
from globals import OPTS
from sram_factory import factory

class delay_chain(design.design):
    """
    Generate a delay chain with the given number of stages and fanout.
    Input is a list contains the electrical effort (fanout) of each stage.
    Usually, this will be constant, but it could have varied fanout.
    """

    def __init__(self, name, fanout_list):
        """init function"""
        design.design.__init__(self, name)
        debug.info(1, "creating delay chain {0}".format(str(fanout_list)))
        self.add_comment("fanouts: {0}".format(str(fanout_list)))
        
        # Two fanouts are needed so that we can route the vdd/gnd connections
        for f in fanout_list:
            debug.check(f>=2,"Must have >=2 fanouts for each stage.")

        # number of inverters including any fanout loads.
        self.fanout_list = fanout_list
        
        self.create_netlist()
        if not OPTS.netlist_only:
            self.create_layout()
                

    def create_netlist(self):
        self.add_modules()
        self.add_pins()
        self.create_inverters()
        
    def create_layout(self):
        # Each stage is a a row
        self.height = len(self.fanout_list)*self.inv.height
        # The width is determined by the largest fanout plus the driver
        self.width = (max(self.fanout_list)+1) * self.inv.width

        self.place_inverters()
        self.route_inverters()
        self.add_layout_pins()
        self.add_boundary()
        self.DRC_LVS()
        
    def add_pins(self):
        """ Add the pins of the delay chain"""
        self.add_pin("in", "INPUT")
        self.add_pin("out", "OUTPUT")
        self.add_pin("vdd", "POWER")
        self.add_pin("gnd", "GROUND")

    def add_modules(self):
        self.inv = factory.create(module_type="pinv", route_output=False)
        self.add_mod(self.inv)

    def create_inverters(self):
        """ Create the inverters and connect them based on the stage list """
        self.driver_inst_list = []
        self.rightest_load_inst = {}
        self.load_inst_map = {}
        for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
            # Add the inverter
            cur_driver=self.add_inst(name="dinv{}".format(stage_num),
                                     mod=self.inv)
            # keep track of the inverter instances so we can use them to get the pins
            self.driver_inst_list.append(cur_driver)

            # Hook up the driver
            if stage_num+1==len(self.fanout_list):
                stageout_name = "out"
            else:
                stageout_name = "dout_{}".format(stage_num+1)
            if stage_num == 0:
                stagein_name = "in"
            else:
                stagein_name = "dout_{}".format(stage_num)  
            self.connect_inst([stagein_name, stageout_name, "vdd", "gnd"])
            
            # Now add the dummy loads to the right
            self.load_inst_map[cur_driver]=[]
            for i in range(fanout_size):
                cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num,i),
                                      mod=self.inv)
                # Fanout stage is always driven by driver and output is disconnected
                disconnect_name = "n_{0}_{1}".format(stage_num,i)  
                self.connect_inst([stageout_name, disconnect_name, "vdd", "gnd"])
            
                # Keep track of all the loads to connect their inputs as a load
                self.load_inst_map[cur_driver].append(cur_load)
            else:
                # Keep track of the last one so we can add the the wire later
                self.rightest_load_inst[cur_driver]=cur_load

    def place_inverters(self):
        """ Place the inverters and connect them based on the stage list """
        for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
            if stage_num % 2:
                inv_mirror = "MX"
                inv_offset = vector(0, (stage_num+1)* self.inv.height)
            else:
                inv_mirror = "R0"
                inv_offset = vector(0, stage_num * self.inv.height)                
                
            # Add the inverter
            cur_driver=self.driver_inst_list[stage_num]
            cur_driver.place(offset=inv_offset,
                             mirror=inv_mirror)
            
            # Now add the dummy loads to the right
            load_list = self.load_inst_map[cur_driver]
            for i in range(fanout_size):
                inv_offset += vector(self.inv.width,0)
                load_list[i].place(offset=inv_offset,
                                   mirror=inv_mirror)
            
                
    def add_route(self, pin1, pin2):
        """ This guarantees that we route from the top to bottom row correctly. """
        pin1_pos = pin1.center()
        pin2_pos = pin2.center()
        if pin1_pos.y == pin2_pos.y:
            self.add_path("m2", [pin1_pos, pin2_pos])
        else:
            mid_point = vector(pin2_pos.x, 0.5*(pin1_pos.y+pin2_pos.y))
            # Written this way to guarantee it goes right first if we are switching rows
            self.add_path("m2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos])
    
    def route_inverters(self):
        """ Add metal routing for each of the fanout stages """

        for i in range(len(self.driver_inst_list)):
            inv = self.driver_inst_list[i]
            for load in self.load_inst_map[inv]:
                # Drop a via on each A pin
                a_pin = load.get_pin("A")      
                self.add_via_center(layers=self.m1_stack,
                                    offset=a_pin.center())
                self.add_via_center(layers=self.m2_stack,
                                    offset=a_pin.center())

            # Route an M3 horizontal wire to the furthest
            z_pin = inv.get_pin("Z")
            a_pin = inv.get_pin("A")
            a_max = self.rightest_load_inst[inv].get_pin("A")
            self.add_via_center(layers=self.m1_stack,
                                offset=a_pin.center())
            self.add_via_center(layers=self.m1_stack,
                                offset=z_pin.center())
            self.add_via_center(layers=self.m2_stack,
                                offset=z_pin.center())
            self.add_path("m3",[z_pin.center(), a_max.center()])

            
            # Route Z to the A of the next stage
            if i+1 < len(self.driver_inst_list):
                z_pin = inv.get_pin("Z")
                next_inv = self.driver_inst_list[i+1]
                next_a_pin = next_inv.get_pin("A")
                y_mid = (z_pin.cy() + next_a_pin.cy())/2
                mid1_point = vector(z_pin.cx(), y_mid)
                mid2_point = vector(next_a_pin.cx(), y_mid)
                self.add_path("m2",[z_pin.center(), mid1_point, mid2_point, next_a_pin.center()])  
            
                
    def add_layout_pins(self):
        """ Add vdd and gnd rails and the input/output. Connect the gnd rails internally on
        the top end with no input/output to obstruct. """

        # Add power and ground to all the cells except:
        # the fanout driver, the right-most load
        # The routing to connect the loads is over the first and last cells
        # We have an even number of drivers and must only do every other
        # supply rail
        for i in range(0,len(self.driver_inst_list),2):
            inv = self.driver_inst_list[i]
            for load in self.load_inst_map[inv]:
                if load==self.rightest_load_inst[inv]:
                    continue
                for pin_name in ["vdd", "gnd"]:
                    pin = load.get_pin(pin_name)
                    self.add_power_pin(pin_name, pin.rc())
        else:
            # We have an even number of rows, so need to get the last gnd rail
            inv = self.driver_inst_list[-1]
            for load in self.load_inst_map[inv]:
                if load==self.rightest_load_inst[inv]:
                    continue
                pin_name = "gnd"
                pin = load.get_pin(pin_name)
                self.add_power_pin(pin_name, pin.rc())


        # input is A pin of first inverter
        a_pin = self.driver_inst_list[0].get_pin("A")
        self.add_via_center(layers=self.m1_stack,
                            offset=a_pin.center())
        self.add_layout_pin(text="in",
                            layer="m2",
                            offset=a_pin.ll().scale(1,0),
                            height=a_pin.cy())
        

        # output is A pin of last load inverter
        last_driver_inst = self.driver_inst_list[-1]
        a_pin = self.rightest_load_inst[last_driver_inst].get_pin("A")
        self.add_via_center(layers=self.m1_stack,
                            offset=a_pin.center())
        mid_point = vector(a_pin.cx()+3*self.m2_width,a_pin.cy())
        self.add_path("m2",[a_pin.center(), mid_point, mid_point.scale(1,0)])
        self.add_layout_pin_segment_center(text="out",
                                           layer="m2",
                                           start=mid_point,
                                           end=mid_point.scale(1,0))

    def get_cin(self):
        """Get the enable input ralative capacitance"""
        #Only 1 input to the delay chain which is connected to an inverter.
        dc_cin = self.inv.get_cin()
        return dc_cin        

    def determine_delayed_en_stage_efforts(self, ext_delayed_en_cout, inp_is_rise=True):
        """Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load."""
        stage_effort_list = []
        #Add a stage to the list for every stage in delay chain. Stages only differ in fanout except the last which has an external cout.
        last_stage_is_rise = inp_is_rise
        for stage_fanout in self.fanout_list:
            stage_cout = self.inv.get_cin()*(stage_fanout+1) 
            if len(stage_effort_list) == len(self.fanout_list)-1: #last stage
                stage_cout+=ext_delayed_en_cout
            stage = self.inv.get_stage_effort(stage_cout, last_stage_is_rise)
            stage_effort_list.append(stage)
            last_stage_is_rise = stage.is_rise
            
        return stage_effort_list
