#!/usr/bin/env python3
# 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 unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug

@unittest.skip("SKIPPING 26_pex_test")
class sram_func_test(openram_test):

    def runTest(self):
        config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
        globals.init_openram(config_file)

        OPTS.use_pex = True

        # This is a hack to reload the characterizer __init__ with the spice version
        from importlib import reload
        import characterizer
        reload(characterizer)
        from characterizer import setup_hold
        if not OPTS.spice_exe:
            debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1)

        self.func_test(bank_num=1)
        self.func_test(bank_num=2)
        self.func_test(bank_num=4)

        globals.end_openram()
        
    def func_test(self, bank_num):

        import sram
        import tech

        debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
        s = sram.sram(word_size=OPTS.word_size,
                      num_words=OPTS.num_words,
                      num_banks=OPTS.num_banks,
                      name="test_sram1")

        tempspice = OPTS.openram_temp + "temp.sp"
        tempgds = OPTS.openram_temp + "temp.gds"

        s.sp_write(tempspice)
        s.gds_write(tempgds)

        self.assertFalse(verify.run_drc(s.name, tempgds))
        self.assertFalse(verify.run_lvs(s.name, tempgds, tempspice))
        self.assertFalse(verify.run_pex(s.name, tempgds,
                                         tempspice, output=OPTS.openram_temp + "temp_pex.sp"))

        import sp_file
        stimulus_file = OPTS.openram_temp + "stimulus.sp"
        a_stimulus = sp_file.sp_file(stimulus_file)
        self.write_stimulus(a_stimulus)

        simulator_file = OPTS.openram_temp + "simulator.sp"
        a_simulator = sp_file.sp_file(simulator_file)
        self.write_simulator(a_simulator)

        result_file = OPTS.openram_temp + "result"

        import os

        if OPTS.spice_name == "hspice":
            cmd = "hspice -mt 2 -i {0} > {1} ".format(
                simulator_file, result_file)
        else:
            cmd = "ngspice -b  -i {0} > {1}  ".format(
                simulator_file, result_file)
        os.system(cmd)

        import re
        sp_result = open(result_file, "r")
        contents = sp_result.read()
        key = "vr1"
        val = re.search(
            r"{0}(\s*)=(\s*)(\d*(.).*)(\s*)(from)".format(key), contents)
        val = val.group(3)
        value1 = float(self.convert_voltage_unit(val))

        key = "vr2"
        val = re.search(
            r"{0}(\s*)=(\s*)(\d*(.).*)(\s*)(from)".format(key), contents)
        val = val.group(3)
        value2 = float(self.convert_voltage_unit(val))

        self.assertTrue(round(value1) > 0.5 * tech.spice["supply_voltage"])
        self.assertTrue(round(value2) < 0.5 * tech.spice["supply_voltage"])



    def convert_voltage_unit(self, string):
        newstring = ""
        for letter in string:
            if letter == "m":
                letter = "10e-3"
            elif letter == "u":
                letter = "10e-6"
            else:
                letter = letter
            newstring = str(newstring) + str(letter)
        return newstring

    def convert_time_unit(self, string):
        newstring = ""
        for letter in string:
            if letter == "f":
                letter = "10e-15"
            elif letter == "p":
                letter = "10e-12"
            elif letter == "n":
                letter = "10e-9"
            elif letter == "u":
                letter = "10e-6"
            elif letter == "m":
                letter = "10e-3"
            else:
                letter = letter
            newstring = str(newstring) + str(letter)
        return newstring

    def write_simulator(self, sim_file):
        sim_file.write("\n")
        import tech
        time_step = tech.spice["clock_period"]
        for model in tech.spice["fet_models"]:
            sim_file.write(".inc " + str(model) + "\n")
        sim_file.write(".inc stimulus.sp\n")
        sim_file.write(".inc temp_pex.sp\n")
        sim_file.write(".options post runlvl=6\n")
        sim_file.write("\n")

        sim_file.write(
            "Xsource DATA[0] ADDR[0] ADDR[1] ADDR[2] ADDR[3] CSb WEb WEb_inv OEb clk vdd vss source\n")
        sim_file.write(
            "Xsram DATA[0] ADDR[0] ADDR[1] ADDR[2] ADDR[3] CSb WEb OEb clk vdd vss test_sram1\n")
        sim_file.write("\n")

        sim_file.write(".MEASURE TRAN vr1 AVG V(DATA[0]) FROM ={0}ns TO ={1}ns\n".format(
            4.5 * tech.spice["clock_period"], 5 * tech.spice["clock_period"]))
        sim_file.write(".MEASURE TRAN vr2 AVG V(DATA[0]) FROM ={0}ns TO ={1}ns\n".format(
            9.5 * tech.spice["clock_period"], 10 * tech.spice["clock_period"]))
        sim_file.write("\n")

        if OPTS.spice_name in ["hspice","xa"]:
            sim_file.write(".probe v(x*.*)\n")
            sim_file.write(".tran 0.1ns {0}ns\n".format(
                10 * tech.spice["clock_period"]))
            sim_file.write(".end\n")
        else:
            sim_file.write(
                ".meas tran DELAY1.0 TRIG v(clk) VAL=0.5 RISE=6 TARG v(DATA[0]) VAL=0.5 TD=0.5n RISE=1\n")
            sim_file.write(".tran 0.1ns {0}ns\n".format(
                10 * tech.spice["clock_period"]))
            sim_file.write(".control\n")
            sim_file.write("run\n")
            #sim_file.write("plot CSb WEb OEb \n")
            #sim_file.write("plot clk DATA0 \n")
            sim_file.write("quit\n")
            sim_file.write(".endc\n")
            sim_file.write(".end\n")
        sim_file.file.close()

    def write_stimulus(self, sti_file):
        import tech
        import sp_file
        sti_file.write(
            ".subckt source DATA[0] ADDR[0] ADDR[1] ADDR[2] ADDR[3] CSb WEb WEb_inv OEb clk vdd vss\n")

        time_step = tech.spice["clock_period"]

        clk = sp_file.PWL(name="clk", port=["clk", "0"])
        for i in range(0, 11):
            clk.write_pulse(i * time_step, time_step, "UP")
        clk.write_to_sp(sti_file)

        WEB_inv = sp_file.PWL(name="WEb_inv", port=["WEb_inv", "0"])
        WEB = sp_file.PWL(name="WEB", port=["WEb", "0"])
        OEb = sp_file.PWL(name="OEb", port=["OEb", "0"])
        CSb = sp_file.PWL(name="CSb", port=["CSb", "0"])

        # write
        CSb.write_pulse(0.75 * time_step, time_step, "DN")
        WEB.write_pulse(0.75 * time_step, time_step, "DN")
        WEB_inv.write_pulse(0.75 * time_step, time_step, "UP")
        CSb.write_pulse(1.75 * time_step, time_step, "DN")
        WEB.write_pulse(1.75 * time_step, time_step, "DN")
        WEB_inv.write_pulse(1.75 * time_step, time_step, "UP")

        # read
        OEb.write_pulse(3.75 * time_step, time_step, "DN")
        CSb.write_pulse(3.75 * time_step, time_step, "DN")

        # write
        CSb.write_pulse(5.75 * time_step, time_step, "DN")
        WEB.write_pulse(5.75 * time_step, time_step, "DN")
        WEB_inv.write_pulse(5.75 * time_step, time_step, "UP")
        CSb.write_pulse(6.75 * time_step, time_step, "DN")
        WEB.write_pulse(6.75 * time_step, time_step, "DN")
        WEB_inv.write_pulse(6.75 * time_step, time_step, "UP")

        # read
        OEb.write_pulse(8.75 * time_step, time_step, "DN")
        CSb.write_pulse(8.75 * time_step, time_step, "DN")

        CSb.write_to_sp(sti_file)
        WEB.write_to_sp(sti_file)
        WEB_inv.write_to_sp(sti_file)
        OEb.write_to_sp(sti_file)

        sti_file.write("VA[0] A[0] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[
                       "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"]))
        sti_file.write("VA[1] A[1] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[
                       "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"]))
        sti_file.write("VA[2] A[2] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[
                       "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"]))
        sti_file.write("VA[3] A[3] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[
                       "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"]))

        sti_file.write(
            "xA[0]_buff A[0] ADDR[0]_inv ADDR[0] vdd vss test_buf\n")
        sti_file.write(
            "xA[1]_buff A[1] ADDR[1]_inv ADDR[1] vdd vss test_buf\n")
        sti_file.write(
            "xA[2]_buff A[2] ADDR[2]_inv ADDR[2] vdd vss test_buf\n")
        sti_file.write(
            "xA[3]_buff A[3] ADDR[3]_inv ADDR[3] vdd vss test_buf\n")

        VD_0 = sp_file.PWL(name="VD[0]", port=["D[0]", "0"])
        VD_0.write_pulse(0, 5 * time_step, "S1")
        VD_0.write_pulse(5 * time_step, 5 * time_step, "S0")
        VD_0.write_to_sp(sti_file)

        sti_file.write(
            "xD[0]_buff D[0] DATA[0]_inv DATA[0]s vdd vss test_buf\n")
        sti_file.write(
            "xD[0]_gate DATA[0]s WEb WEb_inv DATA[0] vdd vss tran_gate\n")
        sti_file.write("mp[0]_gate_vdd vdd write_v DATA[0] vdd " + str(tech.spice["pmos"]) +
                       " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write("mn[0]_gate_vss vss write_g DATA[0] vss " + str(tech.spice["nmos"]) +
                       " w=" + str(tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")

        Vwrite_v = sp_file.PWL(name="write_v", port=["write_vs", "0"])
        Vwrite_v.write_pulse(0, 0.5 * time_step, "S1")
        Vwrite_v.write_pulse(7.5 * time_step, time_step, "DN")
        Vwrite_v.write_to_sp(sti_file)
        sti_file.write(
            "xwrite_v write_vs  write_v_inv  write_v vdd vss test_buf\n")

        Vwrite_g = sp_file.PWL(name="write_g", port=["write_gs", "0"])
        Vwrite_g.write_pulse(0, 0.5 * time_step, "S0")
        Vwrite_g.write_pulse(3 * time_step, time_step, "UP")
        Vwrite_g.write_to_sp(sti_file)
        sti_file.write(
            "xwrite_g write_gs write_g_inv write_g vdd vss test_buf\n")

        sti_file.write("Vdd vdd 0 DC " +
                       str(tech.spice["supply_voltage"]) + "\n")
        sti_file.write("Vvss vss 0 DC 0\n")
        sti_file.write(".ENDS source\n")
        sti_file.write("\n")

        sti_file.write(".SUBCKT tran_gate in gate gate_inv out vdd vss\n")
        sti_file.write("mp0 in gate out vdd " + str(tech.spice["pmos"]) +
                       " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write("mn0 in gate_inv out vss " + str(tech.spice["nmos"]) +
                       " w=" + str(tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write(".ENDS tran_gate\n")
        sti_file.write("\n")

        sti_file.write(".SUBCKT test_buf in out_inv out_buf vdd vss\n")
        sti_file.write("mpinv1 out_inv in vdd vdd " + str(tech.spice["pmos"]) +
                       " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write("mninv1 out_inv in vss vss " + str(tech.spice["nmos"]) +
                       " w=" + str(tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write("mpinv2 out_buf out_inv vdd vdd " + str(tech.spice["pmos"]) +
                       " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write("mninv2 out_buf out_inv vss vss " + str(tech.spice["nmos"]) +
                       " w=" + str(tech.parameter["min_tx_size"]) + "u" +
                       " l=" + str(tech.drc["minlength_channel"]) + "u" +
                       "\n")
        sti_file.write(".ENDS test_buf\n")
        sti_file.write("\n")

        sti_file.file.close()


# run the test from the command line
if __name__ == "__main__":
    (OPTS, args) = globals.parse_args()
    del sys.argv[1:]
    header(__file__, OPTS.tech_name)
    unittest.main(testRunner=debugTestRunner())
