/* 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/electrical/router/Router.h"

#include <cmath>
#include <vector>

#include "model/PortInfo.h"
#include "model/EventInfo.h"
#include "model/TransitionInfo.h"
#include "model/ModelGen.h"
#include "model/std_cells/StdCellLib.h"
#include "model/std_cells/StdCell.h"
#include "model/electrical/router/RouterInputPort.h"
#include "model/electrical/router/RouterSwitchAllocator.h"
#include "model/timing_graph/ElectricalNet.h"

namespace DSENT
{
    using std::sqrt;
    using std::vector;

    using LibUtil::castStringVector;
    using LibUtil::vectorToString;

    Router::Router(const String& instance_name_, const TechModel* tech_model_)
        : ElectricalModel(instance_name_, tech_model_)
    {
        initParameters();
        initProperties();
    }

    Router::~Router()
    {}

    void Router::initParameters()
    {
        addParameterName("NumberInputPorts");
        addParameterName("NumberOutputPorts");
        addParameterName("NumberBitsPerFlit");
        addParameterName("NumberVirtualNetworks");
        addParameterName("NumberVirtualChannelsPerVirtualNetwork");
        addParameterName("NumberBuffersPerVirtualChannel");
        // Spec for input port
        addParameterName("InputPort->BufferModel");
        // Spec for crossbar
        addParameterName("CrossbarModel");
        // Spec for switch allocator
        addParameterName("SwitchAllocator->ArbiterModel");
        // Spec for clock tree
        addParameterName("ClockTreeModel");
        addParameterName("ClockTree->NumberLevels");
        addParameterName("ClockTree->WireLayer");
        addParameterName("ClockTree->WireWidthMultiplier");
        addParameterName("ClockTree->WireSpacingMultiplier", 3.0);
        return;
    }

    void Router::initProperties()
    {
        return;
    }

    Router* Router::clone() const
    {
        // TODO
        return NULL;
    }

    void Router::constructModel()
    {
        // Get parameters
        unsigned int number_input_ports = getParameter("NumberInputPorts").toUInt();
        unsigned int number_output_ports = getParameter("NumberOutputPorts").toUInt();
        unsigned int number_bits_per_flit = getParameter("NumberBitsPerFlit").toUInt();

        ASSERT(number_input_ports > 0, "[Error] " + getInstanceName() + 
                " -> Number of input ports must be > 0!");
        ASSERT(number_output_ports > 0, "[Error] " + getInstanceName() + 
                " -> Number of output ports must be > 0!");
        ASSERT(number_bits_per_flit > 0, "[Error] " + getInstanceName() + 
                " -> Number of bits per buffer must be > 0!");

        // Create ports
        createInputPort("CK");
        for(unsigned int i = 0; i < number_input_ports; ++i)
        {
            createInputPort("FlitIn" + (String)i, makeNetIndex(0, number_bits_per_flit-1));
        }
        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            createOutputPort("FlitOut" + (String)i, makeNetIndex(0, number_bits_per_flit-1));
        }

        // Create area, power, event results
        createElectricalResults();
        getEventInfo("Idle")->setStaticTransitionInfos();
        getEventInfo("Idle")->setTransitionInfo("CK", TransitionInfo(0.0, 1.0, 0.0));

        createElectricalEventResult("ReadBuffer");
        getEventInfo("ReadBuffer")->setTransitionInfo("CK", TransitionInfo(0.0, 1.0, 0.0));
        createElectricalEventResult("WriteBuffer");
        getEventInfo("WriteBuffer")->setTransitionInfo("CK", TransitionInfo(0.0, 1.0, 0.0));
        for(unsigned int i = 1; i <= number_output_ports; ++i)
        {
            createElectricalEventResult("TraverseCrossbar->Multicast" + (String)i);
            getEventInfo("TraverseCrossbar->Multicast" + (String)i)->setTransitionInfo("CK", TransitionInfo(0.0, 1.0, 0.0));
        }
        createElectricalEventResult("ArbitrateSwitch->ArbitrateStage1");
        createElectricalEventResult("ArbitrateSwitch->ArbitrateStage2");
        createElectricalEventResult("DistributeClock");
        getEventInfo("DistributeClock")->setTransitionInfo("CK", TransitionInfo(0.0, 1.0, 0.0));

        // Create intermediate nets
        createNet("PipelineReg0_In");
        createNet("PipelineReg0_Out");
        createNet("PipelineReg1_In");
        createNet("PipelineReg1_Out");
        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            createNet("PipelineReg2_In" + (String)i);
            createNet("PipelineReg2_Out" + (String)i);
        }

        createRouterInputPort();
        createSwitchAllocator();
        createVirtualChannelAllocator();
        createCrossbar();
        createClockTree();
        createPipelineReg();

        // Get generated numbers
        unsigned int number_crossbar_selects = getGenProperties()->get("Crossbar->NumberSelects");

        // Add write buffer event
        getEventResult("WriteBuffer")->addSubResult(getSubInstance("PipelineReg0")->getEventResult("DFFD"), "PipelineReg0", number_bits_per_flit);
        getEventResult("WriteBuffer")->addSubResult(getSubInstance("PipelineReg0")->getEventResult("DFFQ"), "PipelineReg0", number_bits_per_flit);
        getEventResult("WriteBuffer")->addSubResult(getSubInstance("PipelineReg0")->getEventResult("CK"), "PipelineReg0", number_bits_per_flit);
        getEventResult("WriteBuffer")->addSubResult(getSubInstance("InputPort")->getEventResult("WriteBuffer"), "InputPort", 1.0);

        // Add read buffer event
        getEventResult("ReadBuffer")->addSubResult(getSubInstance("InputPort")->getEventResult("ReadBuffer"), "InputPort", 1.0);
        getEventResult("ReadBuffer")->addSubResult(getSubInstance("PipelineReg1")->getEventResult("DFFD"), "PipelineReg1", number_bits_per_flit);
        getEventResult("ReadBuffer")->addSubResult(getSubInstance("PipelineReg1")->getEventResult("DFFQ"), "PipelineReg1", number_bits_per_flit);
        getEventResult("ReadBuffer")->addSubResult(getSubInstance("PipelineReg1")->getEventResult("CK"), "PipelineReg1", number_bits_per_flit);

        // Add crossbar traversal event
        for(unsigned int i = 1; i <= number_output_ports; ++i)
        {
            Result* traverse_crossbar_event = getEventResult("TraverseCrossbar->Multicast" + (String)i);
            traverse_crossbar_event->addSubResult(getSubInstance("Crossbar_Sel_DFF")->getEventResult("DFFD"), "Crossbar_Sel_DFF", number_crossbar_selects);
            traverse_crossbar_event->addSubResult(getSubInstance("Crossbar_Sel_DFF")->getEventResult("DFFQ"), "Crossbar_Sel_DFF", number_crossbar_selects);
            traverse_crossbar_event->addSubResult(getSubInstance("Crossbar_Sel_DFF")->getEventResult("CK"), "Crossbar_Sel_DFF", number_crossbar_selects);
            traverse_crossbar_event->addSubResult(getSubInstance("Crossbar")->getEventResult("Multicast" + (String)i), "Crossbar", 1.0);
            for(unsigned int j = 0; j < i; ++j)
            {
                traverse_crossbar_event->addSubResult(getSubInstance("PipelineReg2_" + (String)j)->getEventResult("DFFD"), "PipelineReg2_" + (String)j, number_bits_per_flit);
                traverse_crossbar_event->addSubResult(getSubInstance("PipelineReg2_" + (String)j)->getEventResult("DFFQ"), "PipelineReg2_" + (String)j, number_bits_per_flit);
                traverse_crossbar_event->addSubResult(getSubInstance("PipelineReg2_" + (String)j)->getEventResult("CK"), "PipelineReg2_" + (String)j, number_bits_per_flit);
            }
        }

        // Add stage1 allocator arbitrate
        Result* arb_sw_stage1_event = getEventResult("ArbitrateSwitch->ArbitrateStage1");
        arb_sw_stage1_event->addSubResult(getSubInstance("SwitchAllocator")->getEventResult("ArbitrateStage1"), "SwitchAllocator", 1.0);

        // Add stage2 allocator arbitrate
        Result* arb_sw_stage2_event = getEventResult("ArbitrateSwitch->ArbitrateStage2");
        arb_sw_stage2_event->addSubResult(getSubInstance("SwitchAllocator")->getEventResult("ArbitrateStage2"), "SwitchAllocator", 1.0);

        // Add CK event
        getEventResult("DistributeClock")->addSubResult(getSubInstance("ClockTree")->getEventResult("Send"), "ClockTree", 1.0);
        return;
    }

    void Router::updateModel()
    {
        // Get parameters
        unsigned int number_output_ports = getParameter("NumberOutputPorts").toUInt();

        // Update other components
        getSubInstance("PipelineReg0")->update();
        getSubInstance("InputPort")->update();
        getSubInstance("PipelineReg1")->update();
        getSubInstance("Crossbar_Sel_DFF")->update();
        getSubInstance("Crossbar")->update();
        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            getSubInstance("PipelineReg2_" + (String)i)->update();
        }
        getSubInstance("SwitchAllocator")->update();

        // Update clock tree
        double total_clock_tree_cap = getNet("CK")->getTotalDownstreamCap();
        double router_area = getAreaResult("Active")->calculateSum();
        Model* clock_tree = getSubInstance("ClockTree");
        clock_tree->setProperty("SitePitch", sqrt(router_area));
        clock_tree->setProperty("TotalLoadCapPerBit", total_clock_tree_cap);
        clock_tree->update();

        return;
    }

    void Router::propagateTransitionInfo()
    {
        // Update probability
        unsigned int number_output_ports = getParameter("NumberOutputPorts");

        // Current event
        const String& current_event = getGenProperties()->get("UseModelEvent");

        ElectricalModel* pipeline_reg0 = (ElectricalModel*)getSubInstance("PipelineReg0");
        propagatePortTransitionInfo(pipeline_reg0, "D", "FlitIn0");
        propagatePortTransitionInfo(pipeline_reg0, "CK", "CK");
        pipeline_reg0->use();

        ElectricalModel* input_port = (ElectricalModel*)getSubInstance("InputPort");
        propagatePortTransitionInfo(input_port, "FlitIn", pipeline_reg0, "Q");
        propagatePortTransitionInfo(input_port, "CK", "CK");
        input_port->getGenProperties()->set("UseModelEvent", "ReadWrite");
        input_port->use();

        ElectricalModel* pipeline_reg1 = (ElectricalModel*)getSubInstance("PipelineReg1");
        propagatePortTransitionInfo(pipeline_reg1, "D", "FlitIn0");
        propagatePortTransitionInfo(pipeline_reg1, "CK", "CK");
        pipeline_reg1->use();

        ElectricalModel* crossbar_sel_dff = (ElectricalModel*)getSubInstance("Crossbar_Sel_DFF");
        assignPortTransitionInfo(crossbar_sel_dff, "D", TransitionInfo());
        propagatePortTransitionInfo(crossbar_sel_dff, "CK", "CK");
        crossbar_sel_dff->use();

        ElectricalModel* crossbar = (ElectricalModel*)getSubInstance("Crossbar");
        bool is_crossbar_event = false;
        for(unsigned int i = 1; i <= number_output_ports; ++i)
        {
            if(current_event == ("TraverseCrossbar->Multicast" + (String)i))
            {
                is_crossbar_event = true;
                // Assume the flit is sent from port 0 to port 0~i-1
                // Apply default transition info
                crossbar->applyTransitionInfo("Multicast" + (String)i);
                // Overwrite transition info
                propagatePortTransitionInfo(crossbar, "In0", "FlitIn0");
                break;
            }
        }
        if(is_crossbar_event == false)
        {
            crossbar->applyTransitionInfo("Multicast1");
            propagatePortTransitionInfo(crossbar, "In0", "FlitIn0");
        }
        crossbar->use();

        vector<ElectricalModel*> pipeline_reg2s(number_output_ports, NULL);
        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            pipeline_reg2s[i] = (ElectricalModel*)getSubInstance("PipelineReg2_" + (String)i);
            propagatePortTransitionInfo(pipeline_reg2s[i], "D", "FlitIn0");
            propagatePortTransitionInfo(pipeline_reg2s[i], "CK", "CK");
            pipeline_reg2s[i]->use();
        }

        ElectricalModel* sw_allocator = (ElectricalModel*)getSubInstance("SwitchAllocator");
        if(current_event == "ArbitrateSwitch->ArbitrateStage1")
        {
            sw_allocator->applyTransitionInfo("ArbitrateStage1");
        }
        else if(current_event == "ArbitrateSwitch->ArbitrateStage2")
        {
            sw_allocator->applyTransitionInfo("ArbitrateStage2");
        }
        else
        {
            sw_allocator->applyTransitionInfo("Idle");
        }
        sw_allocator->use();

        ElectricalModel* clock_tree = (ElectricalModel*)getSubInstance("ClockTree");
        propagatePortTransitionInfo(clock_tree, "In", "CK");
        clock_tree->use();
        return;
    }

    void Router::createRouterInputPort()
    {
        // Get parameters
        unsigned int number_input_ports = getParameter("NumberInputPorts").toUInt();
        unsigned int number_vns = getParameter("NumberVirtualNetworks").toUInt();
        const String& number_vcs_per_vn = getParameter("NumberVirtualChannelsPerVirtualNetwork");
        const String& number_bufs_per_vc = getParameter("NumberBuffersPerVirtualChannel");
        unsigned int number_bits_per_flit = getParameter("NumberBitsPerFlit").toUInt();
        const String& buffer_model = getParameter("InputPort->BufferModel");

        // Init input port model
        const String& input_port_name = "InputPort";
        RouterInputPort* input_port = new RouterInputPort(input_port_name, getTechModel());
        input_port->setParameter("NumberVirtualNetworks", number_vns);
        input_port->setParameter("NumberVirtualChannelsPerVirtualNetwork", number_vcs_per_vn);
        input_port->setParameter("NumberBuffersPerVirtualChannel", number_bufs_per_vc);
        input_port->setParameter("NumberBitsPerFlit", number_bits_per_flit);
        input_port->setParameter("BufferModel", buffer_model);
        input_port->construct();

        unsigned int number_input_port_outputs = input_port->getGenProperties()->get("NumberOutputs");
        unsigned int number_input_port_addr_bits = input_port->getGenProperties()->get("NumberAddressBits");
        getGenProperties()->set("InputPort->NumberOutputs", number_input_port_outputs);
        getGenProperties()->set("InputPort->NumberAddressBits", number_input_port_addr_bits);

        unsigned int total_number_vcs = input_port->getGenProperties()->get("TotalNumberVirtualChannels");
        getGenProperties()->set("TotalNumberVirtualChannels", total_number_vcs);

        // Add the instance and the results
        addSubInstances(input_port, number_input_ports);
        addElectricalSubResults(input_port, number_input_ports);

        // Create connections
        createNet("InputPort_In", makeNetIndex(0, number_bits_per_flit-1));
        createNet("InputPort_Out", makeNetIndex(0, number_bits_per_flit-1));

        assignVirtualFanout("InputPort_In", "PipelineReg0_Out");
        portConnect(input_port, "FlitIn", "InputPort_In");
        portConnect(input_port, "CK", "CK");
        portConnect(input_port, "FlitOut", "InputPort_Out");
        assignVirtualFanin("PipelineReg1_In", "InputPort_Out");

        return;
    }

    void Router::createVirtualChannelAllocator()
    {}

    void Router::createSwitchAllocator()
    {
        // Get parameters
        unsigned int number_input_ports = getParameter("NumberInputPorts").toUInt();
        unsigned int number_output_ports = getParameter("NumberOutputPorts").toUInt();
        unsigned int total_number_vcs = getGenProperties()->get("TotalNumberVirtualChannels").toUInt();
        const String& arb_model = getParameter("SwitchAllocator->ArbiterModel");

        // Init switch allocator model
        const String& sw_allocator_name = "SwitchAllocator";
        RouterSwitchAllocator* sw_allocator = new RouterSwitchAllocator(sw_allocator_name, getTechModel());
        sw_allocator->setParameter("NumberInputPorts", number_input_ports);
        sw_allocator->setParameter("NumberOutputPorts", number_output_ports);
        sw_allocator->setParameter("TotalNumberVirtualChannels", total_number_vcs);
        sw_allocator->setParameter("ArbiterModel", arb_model);
        sw_allocator->construct();

        // Add the instance and the results
        addSubInstances(sw_allocator, 1.0);
        addElectricalSubResults(sw_allocator, 1.0);

        // Create connections (currently connect CK only)
        portConnect(sw_allocator, "CK", "CK");
        return;
    }

    void Router::createCrossbar()
    {
        // Get parameters
        const String& crossbar_model = getParameter("CrossbarModel");
        unsigned int number_input_ports = getParameter("NumberInputPorts").toUInt();
        unsigned int number_output_ports = getParameter("NumberOutputPorts").toUInt();
        unsigned int number_bits_per_flit = getParameter("NumberBitsPerFlit").toUInt();
        unsigned int number_input_port_outputs = getGenProperties()->get("InputPort->NumberOutputs").toUInt();

        unsigned int number_crossbar_inputs = number_input_port_outputs * number_input_ports;
        unsigned int number_crossbar_outputs = number_output_ports;
        getGenProperties()->set("Crossbar->NumberInputs", number_crossbar_inputs);
        getGenProperties()->set("Crossbar->NumberOutputs", number_crossbar_outputs);

        // Init crossbar model
        const String& crossbar_name = "Crossbar";
        ElectricalModel* crossbar = ModelGen::createCrossbar(crossbar_model, crossbar_name, getTechModel());
        crossbar->setParameter("NumberInputs", number_crossbar_inputs);
        crossbar->setParameter("NumberOutputs", number_crossbar_outputs);
        crossbar->setParameter("NumberBits", number_bits_per_flit);
        crossbar->setParameter("BitDuplicate", "TRUE");
        crossbar->construct();

        unsigned int number_crossbar_selects = crossbar->getGenProperties()->get("NumberSelectsPerPort");
        getGenProperties()->set("Crossbar->NumberSelects", number_crossbar_selects);

        // Init DFF for crossbar selections
        const String& crossbar_sel_dff_name = "Crossbar_Sel_DFF";
        StdCell* crossbar_sel_dff = getTechModel()->getStdCellLib()->createStdCell("DFFQ", crossbar_sel_dff_name);
        crossbar_sel_dff->construct();

        // Add instances and results
        addSubInstances(crossbar, 1.0);
        addElectricalSubResults(crossbar, 1.0);

        addSubInstances(crossbar_sel_dff, number_crossbar_outputs * number_crossbar_selects);
        addElectricalSubResults(crossbar_sel_dff, number_crossbar_outputs * number_crossbar_selects);

        // Create connections
        createNet("Crossbar_Sel_DFF_Out");
        for(unsigned int i = 0; i < number_crossbar_outputs; ++i)
        {
            for(unsigned int j = 0; j < number_crossbar_selects; ++j)
            {
                createNet(String::format("Crossbar_Sel%d_%d", i, j));
            }
            createNet("Crossbar_Out" + (String)i, makeNetIndex(0, number_bits_per_flit-1));
        }
        for(unsigned int i = 0; i < number_crossbar_inputs; ++i)
        {
            createNet("Crossbar_In" + (String)i, makeNetIndex(0, number_bits_per_flit-1));
        }

        for(unsigned int i = 0; i < number_crossbar_selects; ++i)
        {
            portConnect(crossbar_sel_dff, "CK", "CK");
        }
        portConnect(crossbar_sel_dff, "Q", "Crossbar_Sel_DFF_Out");
        for(unsigned int i = 0; i < number_crossbar_inputs; ++i)
        {
            assignVirtualFanout("Crossbar_In" + (String)i, "PipelineReg1_Out");
            portConnect(crossbar, "In" + (String)i, "Crossbar_In" + (String)i);
        }
        for(unsigned int i = 0; i < number_crossbar_outputs; ++i)
        {
            for(unsigned int j = 0; j < number_crossbar_selects; ++j)
            {
                assignVirtualFanout(String::format("Crossbar_Sel%d_%d", i, j), "Crossbar_Sel_DFF_Out");
                portConnect(crossbar, String::format("Sel%d_%d", i, j), String::format("Crossbar_Sel%d_%d", i, j));
            }
            portConnect(crossbar, "Out" + (String)i, "Crossbar_Out" + (String)i);
            assignVirtualFanin("PipelineReg2_In" + (String)i, "Crossbar_Out" + (String)i);
        }

        return;
    }

    void Router::createPipelineReg()
    {
        // Get parameters
        unsigned int number_input_ports = getParameter("NumberInputPorts").toUInt();
        unsigned int number_output_ports = getParameter("NumberOutputPorts").toUInt();
        unsigned int number_bits_per_flit = getParameter("NumberBitsPerFlit").toUInt();
        unsigned int number_crossbar_inputs = getGenProperties()->get("Crossbar->NumberInputs");

        // Init pipeline reg model
        // First stage: from router input to input port
        const String& pipeline_reg0_name = "PipelineReg0";
        StdCell* pipeline_reg0 = getTechModel()->getStdCellLib()->createStdCell("DFFQ", pipeline_reg0_name);
        pipeline_reg0->construct();
        // Second stage: from input port to crossbar
        const String& pipeline_reg1_name = "PipelineReg1";
        StdCell* pipeline_reg1 = getTechModel()->getStdCellLib()->createStdCell("DFFQ", pipeline_reg1_name);
        pipeline_reg1->construct();

        // Third stage: from crossbar to router output
        vector<StdCell*> pipeline_reg2s(number_output_ports, (StdCell*)NULL);
        vector<String> pipeline_reg2_names(number_output_ports, "");
        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            pipeline_reg2_names[i] = "PipelineReg2_" + (String)i;
            pipeline_reg2s[i] = getTechModel()->getStdCellLib()->createStdCell("DFFQ", pipeline_reg2_names[i]);
            pipeline_reg2s[i]->construct();
        }

        // Add instances and results
        addSubInstances(pipeline_reg0, number_input_ports * number_bits_per_flit);
        addElectricalSubResults(pipeline_reg0, number_input_ports * number_bits_per_flit);

        addSubInstances(pipeline_reg1, number_crossbar_inputs * number_bits_per_flit);
        addElectricalSubResults(pipeline_reg1, number_crossbar_inputs * number_bits_per_flit);

        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            addSubInstances(pipeline_reg2s[i], number_bits_per_flit);
            addElectricalSubResults(pipeline_reg2s[i], number_bits_per_flit);
        }

        // Create data connections
        for(unsigned int i = 0; i < number_input_ports; ++i)
        {
            assignVirtualFanin("PipelineReg0_In", "FlitIn" + (String)i);
        }
        portConnect(pipeline_reg0, "D", "PipelineReg0_In");
        portConnect(pipeline_reg0, "Q", "PipelineReg0_Out");
        portConnect(pipeline_reg1, "D", "PipelineReg1_In");
        portConnect(pipeline_reg1, "Q", "PipelineReg1_Out");
        for(unsigned int i = 0; i < number_output_ports; ++i)
        {
            portConnect(pipeline_reg2s[i], "D", "PipelineReg2_In" + (String)i);
            portConnect(pipeline_reg2s[i], "Q", "PipelineReg2_Out" + (String)i);
            assignVirtualFanout("FlitOut" + (String)i, "PipelineReg2_Out" + (String)i);
        }

        // Create CK connections
        for(unsigned int n = 0; n < number_bits_per_flit; ++n)
        {
            for(unsigned int i = 0; i < number_input_ports; ++i)
            {
                portConnect(pipeline_reg0, "CK", "CK");
            }
            for(unsigned int i = 0; i < number_crossbar_inputs; ++i)
            {
                portConnect(pipeline_reg1, "CK", "CK");
            }
            for(unsigned int i = 0; i < number_output_ports; ++i)
            {
                portConnect(pipeline_reg2s[i], "CK", "CK");
            }
        }
        return;
    }

    void Router::createClockTree()
    {
        // Get parameters
        const String& clock_tree_model = getParameter("ClockTreeModel");
        const String& clock_tree_number_levels = getParameter("ClockTree->NumberLevels");
        const String& clock_tree_wire_layer = getParameter("ClockTree->WireLayer");
        const String& clock_tree_wire_width_multiplier = getParameter("ClockTree->WireWidthMultiplier");
        const String& clock_tree_wire_spacing_multiplier = getParameter("ClockTree->WireSpacingMultiplier");

        // Init clock tree model
        const String& clock_tree_name = "ClockTree";
        ElectricalModel* clock_tree = (ElectricalModel*)ModelGen::createModel(clock_tree_model, clock_tree_name, getTechModel());
        clock_tree->setParameter("NumberLevels", clock_tree_number_levels);
        clock_tree->setParameter("NumberBits", 1);
        clock_tree->setParameter("WireLayer", clock_tree_wire_layer);
        clock_tree->setParameter("WireWidthMultiplier", clock_tree_wire_width_multiplier);
        clock_tree->setParameter("WireSpacingMultiplier", clock_tree_wire_spacing_multiplier);
        clock_tree->construct();

        // Add instances and results
        addSubInstances(clock_tree, 1.0);
        addElectricalSubResults(clock_tree, 1.0);

        return;
    }
} // namespace DSENT