# 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 pgate
from vector import vector
from sram_factory import factory


class pdriver(pgate.pgate):
    """
    This instantiates an even or odd number of inverters
    sized for driving a load.
    """
    
    def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None):

        debug.info(1, "creating pdriver {}".format(name))

        self.stage_effort = 3
        self.height = height
        self.neg_polarity = neg_polarity
        self.size_list = size_list
        self.fanout = fanout

        if not size_list and self.fanout == 0:
            debug.error("Either fanout or size list must be specified.", -1)
        if self.size_list and self.fanout != 0:
            debug.error("Cannot specify both size_list and fanout.", -1)
        if self.size_list and self.neg_polarity:
            debug.error("Cannot specify both size_list and neg_polarity.", -1)
 
        # Creates the netlist and layout
        pgate.pgate.__init__(self, name, height)
        
    def compute_sizes(self):
        # size_list specified
        if self.size_list:
            self.num_stages = len(self.size_list)
        else:
            # Find the optimal number of stages for the given effort
            self.num_stages = max(1,
                                  int(round(self.fanout ** (1 / self.stage_effort))))

            # Increase the number of stages if we need to fix polarity
            if self.neg_polarity and (self.num_stages % 2 == 0):
                self.num_stages += 1
            elif not self.neg_polarity and (self.num_stages % 2):
                self.num_stages += 1

            self.size_list = []
            # compute sizes backwards from the fanout
            fanout_prev = self.fanout
            for x in range(self.num_stages):
                fanout_prev = max(round(fanout_prev / self.stage_effort), 1)
                self.size_list.append(fanout_prev)

            # reverse the sizes to be from input to output
            self.size_list.reverse()

    def create_netlist(self):
        self.compute_sizes()
        self.add_comment("sizes: {}".format(str(self.size_list)))
        self.add_pins()
        self.add_modules()
        self.create_insts()

    def create_layout(self):
        self.place_modules()
        self.route_wires()
        self.add_layout_pins()

        self.width = self.inv_inst_list[-1].rx()
        self.height = self.inv_inst_list[0].height
        
    def add_pins(self):
        self.add_pin("A", "INPUT")
        self.add_pin("Z", "OUTPUT")
        self.add_pin("vdd", "POWER")
        self.add_pin("gnd", "GROUND")

    def add_modules(self):
        self.inv_list = []
        for size in self.size_list:
            temp_inv = factory.create(module_type="pinv",
                                      size=size,
                                      height=self.height)
            self.inv_list.append(temp_inv)
            self.add_mod(temp_inv)
    
    def create_insts(self):
        self.inv_inst_list = []
        for x in range(1, self.num_stages + 1):
            # Create first inverter
            if x == 1:
                zbx_int = "Zb{}_int".format(x)
                inst = self.add_inst(name="buf_inv{}".format(x),
                                     mod=self.inv_list[x - 1])
                self.inv_inst_list.append(inst)
                if self.num_stages == 1:
                    self.connect_inst(["A", "Z", "vdd", "gnd"])
                else:
                    self.connect_inst(["A", zbx_int, "vdd", "gnd"])
            
            # Create last inverter
            elif x == self.num_stages:
                zbn_int = "Zb{}_int".format(x - 1)
                inst = self.add_inst(name="buf_inv{}".format(x),
                                     mod=self.inv_list[x - 1])
                self.inv_inst_list.append(inst)
                self.connect_inst([zbn_int, "Z", "vdd", "gnd"])

            # Create middle inverters
            else:
                zbx_int = "Zb{}_int".format(x - 1)
                zbn_int = "Zb{}_int".format(x)
                inst = self.add_inst(name="buf_inv{}".format(x),
                                     mod=self.inv_list[x - 1])
                self.inv_inst_list.append(inst)
                self.connect_inst([zbx_int, zbn_int, "vdd", "gnd"])
        
    def place_modules(self):
        # Add the first inverter at the origin
        self.inv_inst_list[0].place(vector(0, 0))

        # Add inverters to the right of the previous inverter
        for x in range(1, len(self.inv_inst_list)):
            loc = vector(self.inv_inst_list[x - 1].rx(), 0)
            self.inv_inst_list[x].place(loc)
                
    def route_wires(self):
        z_inst_list = []
        a_inst_list = []
        # inv_current Z to inv_next A
        for x in range(0, len(self.inv_inst_list) - 1):
            z_inst_list.append(self.inv_inst_list[x].get_pin("Z"))
            a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A"))
            mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy())
            self.add_path("m1",
                          [z_inst_list[x].center(), mid_point,
                           a_inst_list[x].center()])

    def add_layout_pins(self):
        # Continous vdd rail along with label.
        vdd_pin = self.inv_inst_list[0].get_pin("vdd")
        self.add_layout_pin(text="vdd",
                            layer="m1",
                            offset=vdd_pin.ll().scale(0, 1),
                            width=self.width,
                            height=vdd_pin.height())
        
        # Continous gnd rail along with label.
        gnd_pin = self.inv_inst_list[0].get_pin("gnd")
        self.add_layout_pin(text="gnd",
                            layer="m1",
                            offset=gnd_pin.ll().scale(0, 1),
                            width=self.width,
                            height=vdd_pin.height())

        z_pin = self.inv_inst_list[len(self.inv_inst_list) - 1].get_pin("Z")
        self.add_layout_pin_rect_center(text="Z",
                                        layer=z_pin.layer,
                                        offset=z_pin.center(),
                                        width=z_pin.width(),
                                        height=z_pin.height())

        a_pin = self.inv_inst_list[0].get_pin("A")
        self.add_layout_pin_rect_center(text="A",
                                        layer=a_pin.layer,
                                        offset=a_pin.center(),
                                        width=a_pin.width(),
                                        height=a_pin.height())

    def get_sizes(self):
        """ Return the relative sizes of the buffers """
        return self.size_list
    
    def get_stage_efforts(self, external_cout, inp_is_rise=False):
        """ Get the stage efforts of the A -> Z path """
        cout_list = []
        for prev_inv, inv in zip(self.inv_list, self.inv_list[1:]):
            cout_list.append(inv.get_cin())
        
        cout_list.append(external_cout)
        
        stage_effort_list = []
        last_inp_is_rise = inp_is_rise
        for inv, cout in zip(self.inv_list, cout_list):
            stage = inv.get_stage_effort(cout, last_inp_is_rise)
            stage_effort_list.append(stage)
            last_inp_is_rise = stage.is_rise
            
        return stage_effort_list

    def get_cin(self):
        """ Returns the relative capacitance of the input """
        return self.inv_list[0].get_cin()
