/* Copyright (c) 2012 Massachusetts Institute of Technology
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "model/optical/RingDetector.h"

#include <cmath>

#include "util/Constants.h"
#include "model/PortInfo.h"
#include "model/TransitionInfo.h"
#include "model/EventInfo.h"
#include "model/std_cells/StdCell.h"
#include "model/std_cells/StdCellLib.h"
#include "model/optical_graph/OpticalWaveguide.h"
#include "model/optical_graph/OpticalDetector.h"
#include "model/optical_graph/OpticalFilter.h"
#include "model/timing_graph/ElectricalDriver.h"
#include "model/timing_graph/ElectricalNet.h"

namespace DSENT
{
    // TODOs for this model
    // Add the other receiver topologies from [Georgas, CICC 2011]
    // Split integ_time_ratio = SA integ time ratio
    // Right now perfect clock gating is assumed...may not be what we want
        
    // Constants
    const String RingDetector::INTEGRATINGSENSEAMP = "INTSA";

    RingDetector::RingDetector(const String& instance_name_, const TechModel* tech_model_)
        : OpticalModel(instance_name_, tech_model_), OpticalReceiver()
    {
        initParameters();
        initProperties();
    }

    RingDetector::~RingDetector()
    {}
    
    void RingDetector::initParameters()
    {
        addParameterName("DataRate");
        addParameterName("InStart");
        addParameterName("InEnd");
        addParameterName("DetStart");
        addParameterName("DetEnd");
        addParameterName("DropAll");
        addParameterName("Topology");
        return;
    }

    void RingDetector::initProperties()
    {
        return;
    }
    
    void RingDetector::constructModel()
    {
        // Get parameters
        WavelengthGroup in_wavelengths = makeWavelengthGroup(getParameter("InStart"), getParameter("InEnd"));
        WavelengthGroup det_wavelengths = makeWavelengthGroup(getParameter("DetStart"), getParameter("DetEnd"));
        int number_wavelengths = det_wavelengths.second - det_wavelengths.first + 1;
        bool drop_all = getParameter("DropAll");
        const String& topology = getParameter("Topology");
    
        // Set some generated properties
        getGenProperties()->set("NumberWavelengths", number_wavelengths);
    
        // Create device area result
        addAreaResult(new AtomicResult("Photonic"));
        // Create electrical results
        createElectricalAtomicResults();
        if (topology == INTEGRATINGSENSEAMP) addEventResult(new AtomicResult("Receive"));
        else ASSERT(false, "[Error] " + getInstanceName() + " -> Unknown receiver topology (" + topology + ")!");
    
        // Create optical ports
        createOpticalInputPort(         "In",   in_wavelengths);
        createOpticalOutputPort(        "Out",  in_wavelengths);
        // Create the filter and modulator
        createFilter(                   "RingFilter",   in_wavelengths, drop_all, det_wavelengths);
        createDetector(                 "RingDetector", det_wavelengths, this);
        OpticalFilter* ring_filter = getFilter("RingFilter");
        OpticalDetector* ring_detector = getDetector("RingDetector");        
        // Connect the filter and modulator
        getWaveguide("In")->addDownstreamNode(ring_filter);
        ring_filter->addDownstreamNode(getWaveguide("Out"));
        ring_filter->setDropPort(ring_detector);
        
        // Create electrical ports
        createOutputPort("Out", makeNetIndex(0, number_wavelengths-1));
        // Create net
        createNet("OutVFO");
        // Create output driver
        createDriver("OutDriver", false);
        // Connect driver
        getDriver("OutDriver")->addDownstreamNode(getNet("OutVFO"));        
        // Connect output
        assignVirtualFanout("Out", "OutVFO");

        // Precompute some technology values
        precomputeTech();
        
        return;
    }
    
    void RingDetector::updateModel()
    {
        // Get some generated properties
        unsigned int number_wavelengths = getGenProperties()->get("NumberWavelengths");

        // Get tech model numbers
        double ring_area = getTechModel()->get("Ring->Area");
        double thru_loss = getTechModel()->get("Ring->ThroughLoss");
        double drop_loss = getTechModel()->get("Ring->DropLoss");
        double pd_loss = getTechModel()->get("Photodetector->Loss");
        double pd_responsivity = getTechModel()->get("Photodetector->Responsivity");
        
        // Design the receiver
        designReceiver();
        
        // Update losses
        // Connect the filter and modulator
        OpticalFilter* ring_filter = getFilter("RingFilter");
        OpticalDetector* ring_detector = getDetector("RingDetector");        
        ring_filter->setLoss(thru_loss * number_wavelengths);
        ring_filter->setDropLoss(drop_loss + thru_loss * number_wavelengths);
        ring_detector->setLoss(pd_loss);
        ring_detector->setResponsivity(pd_responsivity);
        // Update device area
        getAreaResult("Photonic")->setValue(ring_area * (number_wavelengths));

        return;
    }
    
    void RingDetector::useModel()
    {
        // Get parameters
        const String& topology = getParameter("Topology");

        // Get some generated properties
        unsigned int number_wavelengths = getGenProperties()->get("NumberWavelengths");

        // Get optical input transition info
        const TransitionInfo& in_trans = getOpticalInputPort("In")->getTransitionInfo();
        
        // Get tech models
        double vdd = getTechModel()->get("Vdd");
        // Get caps
        double unit_gate_cap = getTechModel()->get("Gate->MinWidth").toDouble() * getTechModel()->get("Gate->CapPerWidth").toDouble();
        double unit_drain_cap = getTechModel()->get("Gate->MinWidth").toDouble() * getTechModel()->get("Drain->CapPerWidth").toDouble();
        double inv_x1_gate_cap = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Cap->A");
        double inv_x1_drain_cap = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Cap->Y");
        
        // Construct a simple sense-amp model
        if(topology == INTEGRATINGSENSEAMP)
        {
            // Use ratios from the receiver published in [Georgas, ESSCIRC 2011]
            // Note:
            // The numbers in the paper (43fJ/b, 50 fJ/b in the cited work) is done with the clock buffer (there are 4 receivers),
            // capacitive DAC, and extra output flops used in the physical layout, as the compared receiver is extremely conservative
            // We simplified this model to not have the capacitive DAC, the clock buffer (since this is an individual receiver), or
            // the extra output flops (since receiver structure is already a posedge flop functionally).
            // Look for an upcoming paper [Georgas, JSSC 2012] (when it is published) for the power breakdown pie-chart for the receiver.
            // This model only models the latch (sampler) and the dynamic to static (RS latch) part of the design, which is all you really
            // need in the receiver.
            
            // Gate caps
            double c_gate_sampler = unit_gate_cap * (4 * 2.0 + 2 * 1.0 + 2 * 3.0 + 2 * 5.0) + unit_gate_cap * (2 * 6.0 + 2 * 1.0) + inv_x1_gate_cap;
            double c_gate_rslatch = unit_gate_cap * (4 * 1.0) + inv_x1_gate_cap;
            // Drain caps
            double c_drain_sampler = unit_drain_cap * (2 * 2.0 + 2 * 1.0 + 3 * 5.0 + 1 * 3.0) + inv_x1_drain_cap;
            double c_drain_rslatch = unit_drain_cap * (2 * 6.0) + inv_x1_drain_cap;
            // Sum up cap switched for the sampler
            double c_sampler = c_gate_sampler + c_drain_sampler;
            double c_rslatch = c_gate_rslatch + c_drain_rslatch;
            // Average cap switched 
            // Sampler is differential, one side will always switch (R or S in the latch) regardless of probability
            double avg_cap = c_sampler + c_rslatch * in_trans.getProbability0() * in_trans.getProbability1();

            // Get parameters corresponding to a unit-inverter
            double unit_leak_0 = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Leakage->!A");
            double unit_leak_1 = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Leakage->A");        
            
            // Approximate leakage (curve fit with design)
            double total_leakage = 0.5 * (unit_leak_0 + unit_leak_1) * 7.43;            

            // Create results
            getEventResult("Receive")->setValue(vdd * vdd * avg_cap * number_wavelengths);
            getNddPowerResult("Leakage")->setValue(total_leakage * number_wavelengths);

        }
        else ASSERT(false, "[Error] " + getInstanceName() + " -> Unknown receiver topology (" + topology + ")!");        
        
        return;
    }
    
    void RingDetector::propagateTransitionInfo()
    {
        // Propagate probabilities from optical input to electrical output port
        getOutputPort("Out")->setTransitionInfo(getOpticalInputPort("In")->getTransitionInfo());        
            
        return;        
    }
    
    void RingDetector::precomputeTech()
    {
        // Get parameters
        const double data_rate = getParameter("DataRate");
        const String& topology = getParameter("Topology");

        // Get tech model numbers
        double pd_cap = getTechModel()->get("Photodetector->Cap");
        double parasitic_cap = getTechModel()->get("Photodetector->ParasiticCap");
        double apd = getTechModel()->get("Photodetector->AvalancheGain");
        double vdd = getTechModel()->get("Vdd");

        // Constants shortcuts
        double pi = Constants::pi;
        double k = Constants::k;
        double q = Constants::q;   
        double T = getTechModel()->get("Temperature");
                
        if(topology == INTEGRATINGSENSEAMP)
        {
            // Get more tech parameters
            double integ_time_ratio = getTechModel()->get("Receiver->Int->IntegrationTimeRatio");
            double BER = getTechModel()->get("SenseAmp->BER");
            double CMRR = getTechModel()->get("SenseAmp->CMRR");
            double offset_comp_bits = getTechModel()->get("SenseAmp->OffsetCompensationBits");
            double offset = getTechModel()->get("SenseAmp->OffsetRatio").toDouble() * vdd;
            double supply_noise_rand = getTechModel()->get("SenseAmp->SupplyNoiseRandRatio").toDouble() * vdd;
            double supply_noise_det = getTechModel()->get("SenseAmp->SupplyNoiseDetRatio").toDouble() * vdd;
            double noise_margin = getTechModel()->get("SenseAmp->NoiseMargin");
            double jitter_ratio = getTechModel()->get("SenseAmp->JitterRatio");
            
            // Approximate tao using FO4
            double unit_drain_cap = getTechModel()->get("Gate->MinWidth").toDouble() * getTechModel()->get("Drain->CapPerWidth").toDouble();
            double c_g = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Cap->A");
            double c_d = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Cap->Y");
            double r_o = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->DriveRes->Y");
            // Calculate sense amp tau from sense amp output loading
            double tau = r_o * (c_g + c_d);
            // Set output inverter drive strength
            getDriver("OutDriver")->setOutputRes(r_o);

            // Calculate sense amp input cap based on schematic
            double sense_amp_cap_in = unit_drain_cap * (2.0 + 3.0 + 5.0 + 1.0);

            // Residual offset
            double v_residual = 3 * offset / pow(2, offset_comp_bits);
            // Noise
            double v_noise = supply_noise_rand * supply_noise_rand / (CMRR * CMRR);
            // Sense amp voltage build-up minimum
            double v_sense = vdd * exp(-(1 - integ_time_ratio) / (data_rate * tau)) + noise_margin + v_residual + supply_noise_det / CMRR;
            // Sigmas corresponding to BER
            double sigma = calcInvNormCdf(BER);
            
            //K_int is the time the bit is valid for evaluation

            // Total input cap load
            double input_node_cap = sense_amp_cap_in + pd_cap + parasitic_cap;
            double z_int = integ_time_ratio / (data_rate * input_node_cap); //should use K_int
            
            // Store precalculated values
            m_quad_a_ = 1 - (sigma * sigma * jitter_ratio * jitter_ratio);
            m_quad_b1_ = - 2 * pi / 2 * sigma * sigma * q * 0.7 * data_rate;
            m_quad_b2_ = -2 * v_sense / (z_int * apd);
            m_quad_c_ = 1 / (z_int * z_int) * (v_sense * v_sense - sigma * sigma * (k * T / input_node_cap + v_noise));
        }
        else ASSERT(false, "[Error] " + getInstanceName() + " -> Unknown receiver topology (" + topology + ")!");

        return;
    }

    void RingDetector::designReceiver()
    {
        // Get some generated properties
        unsigned int number_wavelengths = getGenProperties()->get("NumberWavelengths");

        // Get relevant properties/parameters
        const String& topology = getParameter("Topology");
                
        // Construct a simple sense-amp model
        if(topology == INTEGRATINGSENSEAMP)
        {
            // No really good way to estimate the area...can assume each receiver is the size of 40 inverters, which is
            // about the right size for just the sense amp in the layout
            double unit_area_active = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Area->Active");
            double unit_area_metal1 = getTechModel()->getStdCellLib()->getStdCellCache()->get("INV_X1->Area->Metal1Wire");          
            getAreaResult("Active")->setValue(unit_area_active * 40 * number_wavelengths);
            getAreaResult("Metal1Wire")->setValue(unit_area_metal1 * 40 * number_wavelengths);
        }
        else ASSERT(false, "[Error] " + getInstanceName() + " -> Unknown receiver topology (" + topology + ")!");
        
        return;
    }
    
    double RingDetector::getSensitivity(double ER_dB_) const
    {
        // Get parameters
        const String& topology = getParameter("Topology");
        // Turn extinction ratio into a ratio from dB scale
        double ER = pow(10, ER_dB_ / 10);
                
        // Initialize sensitivity
        double sensitivity = 1e99;
        // Construct a simple sense-amp model
        if(topology == INTEGRATINGSENSEAMP)
        {
            // Scale photodetector shot noise using ER, add rest of noise source
            double b = m_quad_b1_ * (1 + ER) / (2 * (ER - 1)) + m_quad_b2_;            
        
            // Find sensitivity (-b + sqrt(b^2-4ac)) / 2a
            sensitivity = ((-b + sqrt(b * b - 4 * m_quad_a_ * m_quad_c_)) / (2 * m_quad_a_));
        }
        else ASSERT(false, "[Error] " + getInstanceName() + " -> Unknown receiver topology (" + topology + ")!");        
        
        return sensitivity;
    }
    
    double RingDetector::calcInvNormCdf(double num_)
    {
        // 53 bit precision for double FP
        unsigned int num_iterations = 20;
        // Upperbound the step
        double step = 20;
        double out = step;                
        // Iteratively guess and check calculation
        for (unsigned int i = 0; i < num_iterations; ++i)
        {
            double current = 0.5 * erfc(out / sqrt(2));
            if (current > num_) out += step;
            else out -= step;            
            step = step * 0.5;
        }
        
        return out;
    }
    
} // namespace DSENT