/*	Jason Luu
	April 15, 2011
	Loads statistical information (min/max delays, power) onto the pb_graph.  */

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

#include "util.h"
#include "arch_types.h"
#include "vpr_types.h"
#include "globals.h"
#include "vpr_utils.h"
#include "pb_type_graph.h"
#include "token.h"
#include "pb_type_graph_annotations.h"



static void load_critical_path_annotations(INOUTP t_pb_graph_node *pb_graph_node, 
										   INP int mode,
										   INP enum e_pin_to_pin_annotation_format input_format,
										   INP enum e_pin_to_pin_delay_annotations delay_type,
										   INP char *annot_in_pins,
										   INP char *annot_out_pins,
										   INP char* value);

static t_tedge * find_edge();

void load_pb_graph_pin_to_pin_annotations(INOUTP t_pb_graph_node *pb_graph_node)
{
	int i, j, k, m;
	const t_pb_type *pb_type;
	t_pin_to_pin_annotation *annotations;
	
	pb_type = pb_graph_node->pb_type;

	/* Load primitive critical path delays */
	if(pb_type->num_modes == 0) {
		annotations = pb_type->annotations;
		for(i = 0; i < pb_type->num_annotations; i++) {
			if(annotations[i].type == E_ANNOT_PIN_TO_PIN_DELAY) {
				for(j = 0; j < annotations[i].num_value_prop_pairs; j++) {
					if(	annotations[i].prop[j] == E_ANNOT_PIN_TO_PIN_DELAY_MAX || 
						annotations[i].prop[j] == E_ANNOT_PIN_TO_PIN_DELAY_CLOCK_TO_Q_MAX ||
						annotations[i].prop[j] == E_ANNOT_PIN_TO_PIN_DELAY_TSETUP ) {
							load_critical_path_annotations(pb_graph_node, OPEN, annotations[i].format, annotations[i].prop[j], annotations[i].input_pins, annotations[i].output_pins, annotations[i].value[j]);
					} else {
						assert(annotations[i].prop[j] == E_ANNOT_PIN_TO_PIN_DELAY_MIN ||
							annotations[i].prop[j] == E_ANNOT_PIN_TO_PIN_DELAY_CLOCK_TO_Q_MIN ||
							annotations[i].prop[j] == E_ANNOT_PIN_TO_PIN_DELAY_THOLD );
					}
				}
			} else {
				/* Todo:
				load_hold_time_constraints_annotations(pb_graph_node); 
				load_power_annotations(pb_graph_node);
				*/
			}
		}
	} else {
		/* Load interconnect delays */
		for(i = 0; i < pb_type->num_modes; i++) {
			for(j = 0; j < pb_type->modes[i].num_interconnect; j++) {
				annotations = pb_type->modes[i].interconnect[j].annotations;
				for(k = 0; k < pb_type->modes[i].interconnect[j].num_annotations; k++) {
					if(annotations[k].type == E_ANNOT_PIN_TO_PIN_DELAY) {
						for(m = 0; m < annotations[k].num_value_prop_pairs; m++) {
							if(	annotations[k].prop[m] == E_ANNOT_PIN_TO_PIN_DELAY_MAX || 
								annotations[k].prop[m] == E_ANNOT_PIN_TO_PIN_DELAY_CLOCK_TO_Q_MAX ||
								annotations[k].prop[m] == E_ANNOT_PIN_TO_PIN_DELAY_TSETUP ) {
									load_critical_path_annotations(pb_graph_node, i, annotations[k].format, annotations[k].prop[m], 
										annotations[k].input_pins, annotations[k].output_pins, annotations[k].value[m]);
							} else {
								assert(annotations[k].prop[m] == E_ANNOT_PIN_TO_PIN_DELAY_MIN ||
									annotations[k].prop[m] == E_ANNOT_PIN_TO_PIN_DELAY_CLOCK_TO_Q_MIN ||
									annotations[k].prop[m] == E_ANNOT_PIN_TO_PIN_DELAY_THOLD );
							}
						}
					} else {
						/* Todo:
						load_hold_time_constraints_annotations(pb_graph_node); 
						load_power_annotations(pb_graph_node);
						*/
					}
				}
			}
		}
	}

	for(i = 0; i < pb_type->num_modes; i++) {
		for(j = 0; j < pb_type->modes[i].num_pb_type_children; j++) {
			for(k = 0; k < pb_type->modes[i].pb_type_children[j].num_pb; k++) {
				load_pb_graph_pin_to_pin_annotations(&pb_graph_node->child_pb_graph_nodes[i][j][k]);
			}
		}
	}
}


static void load_critical_path_annotations(INOUTP t_pb_graph_node *pb_graph_node, 
										   INP int mode,
										   INP enum e_pin_to_pin_annotation_format input_format,
										   INP enum e_pin_to_pin_delay_annotations delay_type,
										   INP char *annot_in_pins,
										   INP char *annot_out_pins,
										   INP char* value) {

    int i, j, k, m, n, p, iedge;
	t_pb_graph_pin ***in_port, ***out_port;
	int *num_in_ptrs, *num_out_ptrs, num_in_sets, num_out_sets;
	float **delay_matrix;
	t_pb_graph_node **children = NULL;

	int count;
	int num_inputs, num_outputs;
	
	in_port = out_port = NULL;
	num_out_sets = num_in_sets = 0;
	num_out_ptrs = num_in_ptrs = NULL;


	/* Primarily 3 kinds of delays that affect critical path:
		1.  Intrablock interconnect delays
		2.  Combinational primitives (pin-to-pin delays of primitive)
		3.  Sequential primitives (setup and clock-to-q times)

		Note:	Proper I/O modelling requires knowledge of the extra-chip world (eg. the load that pin is driving, drive strength, etc)
				For now, I/O delays are modelled as a constant in the architecture file by setting the pad-I/O block interconnect delay to be a constant I/O delay

		Algorithm: Intrablock and combinational primitive delays apply to edges
		           Sequential delays apply to pins
				   1.  Determine if delay applies to pin or edge
				   2.  Format the delay information
				   3.  Load delay information
	*/


    /* Determine what pins to read based on delay type */
	num_inputs = num_outputs = 0;
	if(mode == OPEN) {
		children = NULL;
	} else {
		children = pb_graph_node->child_pb_graph_nodes[mode];
	}
	if(delay_type == E_ANNOT_PIN_TO_PIN_DELAY_TSETUP) {
		assert(pb_graph_node->pb_type->blif_model != NULL);
		in_port = alloc_and_load_port_pin_ptrs_from_string(pb_graph_node,
															children,
															annot_in_pins, 
															&num_in_ptrs,
															&num_in_sets,
															FALSE,
															FALSE);
	} else if (delay_type == E_ANNOT_PIN_TO_PIN_DELAY_CLOCK_TO_Q_MAX) {
		assert(pb_graph_node->pb_type->blif_model != NULL);
		in_port = alloc_and_load_port_pin_ptrs_from_string(pb_graph_node,
															children,
															annot_in_pins, 
															&num_in_ptrs,
															&num_in_sets,
															FALSE,
															FALSE);
	} else {
		assert(delay_type == E_ANNOT_PIN_TO_PIN_DELAY_MAX);
		in_port = alloc_and_load_port_pin_ptrs_from_string(pb_graph_node,
															children,
															annot_in_pins, 
															&num_in_ptrs,
															&num_in_sets,
															FALSE,
															FALSE);
		out_port = alloc_and_load_port_pin_ptrs_from_string(pb_graph_node,
															children,
															annot_out_pins, 
															&num_out_ptrs,
															&num_out_sets,
															FALSE,
															FALSE);
	}

	num_inputs = 0;
	for(i = 0; i < num_in_sets; i++) {
		num_inputs += num_in_ptrs[i];
	}

	if(out_port != NULL) {
		num_outputs = 0;
		for(i = 0; i < num_out_sets; i++) {
			num_outputs += num_out_ptrs[i];
		}
	} else {
		num_outputs = 1;
	}

	delay_matrix = my_malloc(sizeof(float*) * num_inputs);
	for(i = 0; i < num_inputs; i++) {
		delay_matrix[i] = my_malloc(sizeof(float) * num_outputs);
	}

	if(input_format == E_ANNOT_PIN_TO_PIN_MATRIX) {
		my_atof_2D(delay_matrix, num_inputs, num_outputs, value);
	} else {
		assert(input_format == E_ANNOT_PIN_TO_PIN_CONSTANT);
		for(i = 0; i < num_inputs; i++) {
			for(j = 0; j < num_outputs; j++) {
				delay_matrix[i][j] = atof(value);
			}
		}
	}

	if(delay_type == E_ANNOT_PIN_TO_PIN_DELAY_TSETUP || delay_type == E_ANNOT_PIN_TO_PIN_DELAY_CLOCK_TO_Q_MAX) {
		k = 0;
		for(i = 0; i < num_in_sets; i++) {
			for(j = 0; j < num_in_ptrs[i]; j++) {
				in_port[i][j]->tsu_tco = delay_matrix[k][0];
				k++;
			}
		}
	} else {
		if(pb_graph_node->pb_type->num_modes != 0) {
			/* Not a primitive, find pb_graph_edge */
			k = 0;
			for(i = 0; i < num_in_sets; i++) {
				for(j = 0; j < num_in_ptrs[i]; j++) {
					p = 0;
					for(m = 0; m < num_out_sets; m++) {
						for(n = 0; n < num_out_ptrs[m]; n++) {
							for(iedge = 0; iedge < in_port[i][j]->num_output_edges; iedge++) {
								if(in_port[i][j]->output_edges[iedge]->output_pins[0] == out_port[m][n]) {
									assert(in_port[i][j]->output_edges[iedge]->delay_max == 0);
									break;								
								}
							}
							/* jluu Todo: This is inefficient, I know the interconnect so I know what edges exist
							        can use this info to only annotate existing edges */
							if(iedge != in_port[i][j]->num_output_edges) {
								in_port[i][j]->output_edges[iedge]->delay_max = delay_matrix[k][p];								
							}
							p++;
						}
					}
					k++;
				}
			}
		} else {
			/* Primitive, allocate appropriate nodes */
			k = 0;
			for(i = 0; i < num_in_sets; i++) {
				for(j = 0; j < num_in_ptrs[i]; j++) {
					delay_matrix[k][0];
					count = p = 0;
					for(m = 0; m < num_out_sets; m++) {
						for(n = 0; n < num_out_ptrs[m]; n++) {
							/* OPEN indicates that connection does not exist */
							if(delay_matrix[k][p] != OPEN) {
								count++;								
							}
							p++;
						}
					}
					in_port[i][j]->num_pin_timing = count;
					in_port[i][j]->pin_timing_del_max = my_malloc(sizeof(float) * count);
					in_port[i][j]->pin_timing = my_malloc(sizeof(t_pb_graph_pin*) * count);
					p = 0;
					count = 0;
					for(m = 0; m < num_out_sets; m++) {
						for(n = 0; n < num_out_ptrs[m]; n++) {
							if(delay_matrix[k][p] != OPEN) {
								in_port[i][j]->pin_timing_del_max[count] = delay_matrix[k][p];
								in_port[i][j]->pin_timing[count] = out_port[m][n];
								count++;
							}
							p++;
						}
					}
					assert(in_port[i][j]->num_pin_timing == count);
					k++;
				}
			}
		}
	}
	if(in_port != NULL) {
		for(i = 0; i < num_in_sets; i++) {
			free(in_port[i]);
		}
		free(in_port);
		free(num_in_ptrs);
	}
	if(out_port != NULL) {
		for(i = 0; i < num_out_sets; i++) {
			free(out_port[i]);
		}
		free(out_port);
		free(num_out_ptrs);
	}
	for(i = 0; i < num_inputs; i++) {
		free(delay_matrix[i]);
	}
	free(delay_matrix);
}
