/*
Jason Luu 2008
Print complex block information to a file
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "vpr_types.h"
#include "globals.h"
#include "output_clustering.h"
#include "read_xml_arch_file.h"

#define LINELENGTH 1024
#define TAB_LENGTH 4

/****************** Subroutines local to this module ************************/


/**************** Subroutine definitions ************************************/

static void print_tabs(FILE *fpout, int num_tabs) {
	int i;
	for(i = 0; i < num_tabs; i++) {
		fprintf(fpout, "\t");
	}
}

static void
print_string(char *str_ptr,
	     int *column,
		 int num_tabs,
	     FILE * fpout)
{

/* Prints string without making any lines longer than LINELENGTH.  Column  *
 * points to the column in which the next character will go (both used and *
 * updated), and fpout points to the output file.                          */

    int len;

    len = strlen(str_ptr);
    if(len + 3 > LINELENGTH)
	{
	    printf
		("Error in print_string: String %s is too long for desired\n"
		 "maximum line length.\n", str_ptr);
	    exit(1);
	}

    if(*column + len + 2 > LINELENGTH)
	{
	    fprintf(fpout, "\n");
		print_tabs(fpout, num_tabs);
	    *column = num_tabs * TAB_LENGTH;
	}

    fprintf(fpout, "%s ", str_ptr);
    *column += len + 1;
}


static void
print_net_name(int inet,
	       int *column,
		   int num_tabs,
	       FILE * fpout)
{

/* This routine prints out the vpack_net name (or open) and limits the    *
 * length of a line to LINELENGTH characters by using \ to continue *
 * lines.  net_num is the index of the vpack_net to be printed, while     *
 * column points to the current printing column (column is both     *
 * used and updated by this routine).  fpout is the output file     *
 * pointer.                                                         */

    char *str_ptr;

    if(inet == OPEN)
		str_ptr = "open";
    else
		str_ptr = vpack_net[inet].name;

    print_string(str_ptr, column, num_tabs, fpout);
}

static void
print_interconnect(int inode,
				   int *column,
				   int num_tabs,
				   FILE * fpout)
{

/* This routine prints out the vpack_net name (or open) and limits the    *
 * length of a line to LINELENGTH characters by using \ to continue *
 * lines.  net_num is the index of the vpack_net to be printed, while     *
 * column points to the current printing column (column is both     *
 * used and updated by this routine).  fpout is the output file     *
 * pointer.                                                         */

    char *str_ptr, *name;
	int prev_node, prev_edge;
	int len;

	if(rr_node[inode].net_num == OPEN) {
		print_string("open", column, num_tabs, fpout);
	} else {
		str_ptr = NULL;
		prev_node = rr_node[inode].prev_node;
		prev_edge = rr_node[inode].prev_edge;

		if(prev_node == OPEN && 
			rr_node[inode].pb_graph_pin->port->parent_pb_type->num_modes == 0 &&
			rr_node[inode].pb_graph_pin->port->type == OUT_PORT) { /* This is a primitive output */
			print_net_name(rr_node[inode].net_num, column, num_tabs, fpout);
		} else {
			name = rr_node[prev_node].pb_graph_pin->output_edges[prev_edge]->interconnect->name;
			if(rr_node[prev_node].pb_graph_pin->port->parent_pb_type->depth >= rr_node[inode].pb_graph_pin->port->parent_pb_type->depth) {
				/* Connections from siblings or children should have an explicit index, connections from parent does not need an explicit index */
				len = strlen(rr_node[prev_node].pb_graph_pin->parent_node->pb_type->name) + 
					rr_node[prev_node].pb_graph_pin->parent_node->placement_index / 10 +
					strlen(rr_node[prev_node].pb_graph_pin->port->name) + 
					rr_node[prev_node].pb_graph_pin->pin_number / 10 +
					strlen(name) + 11;
				str_ptr = my_malloc(len * sizeof(char));
				sprintf(str_ptr, "%s[%d].%s[%d]->%s ", rr_node[prev_node].pb_graph_pin->parent_node->pb_type->name, 
					rr_node[prev_node].pb_graph_pin->parent_node->placement_index,
					rr_node[prev_node].pb_graph_pin->port->name, 
					rr_node[prev_node].pb_graph_pin->pin_number,
					name);
			} else {
				len = strlen(rr_node[prev_node].pb_graph_pin->parent_node->pb_type->name) + 
					strlen(rr_node[prev_node].pb_graph_pin->port->name) + 
					rr_node[prev_node].pb_graph_pin->pin_number / 10 +
					strlen(name) + 8;
				str_ptr = my_malloc(len * sizeof(char));
				sprintf(str_ptr, "%s.%s[%d]->%s ", rr_node[prev_node].pb_graph_pin->parent_node->pb_type->name, 
					rr_node[prev_node].pb_graph_pin->port->name, 
					rr_node[prev_node].pb_graph_pin->pin_number,
					name);
			}			
			print_string(str_ptr, column, num_tabs, fpout);
		}
		if(str_ptr)
			free(str_ptr);
	}
}

static void print_open_pb_graph_node(t_pb_graph_node * pb_graph_node, int pb_index, boolean is_used, int tab_depth, FILE * fpout) {
	int column = 0;
	int i, j, k, m;
	const t_pb_type * pb_type, * child_pb_type;
	t_mode * mode = NULL;
	int prev_edge, prev_node;
	t_pb_graph_pin *pb_graph_pin;
	int mode_of_edge, port_index, node_index;

	mode_of_edge = UNDEFINED;

	pb_type = pb_graph_node->pb_type;

	print_tabs(fpout, tab_depth);

	if(is_used) {
		/* Determine mode if applicable */
		port_index = 0;			
		for(i = 0; i < pb_type->num_ports; i++) {
			if(pb_type->ports[i].type == OUT_PORT) {
				assert(!pb_type->ports[i].is_clock);
				for(j = 0; j < pb_type->ports[i].num_pins; j++) {
					node_index = pb_graph_node->output_pins[port_index][j].pin_count_in_cluster;
					if(pb_type->num_modes > 0 && rr_node[node_index].net_num != OPEN) {
						prev_edge = rr_node[node_index].prev_edge;
						prev_node = rr_node[node_index].prev_node;
						pb_graph_pin = rr_node[prev_node].pb_graph_pin;
						mode_of_edge = pb_graph_pin->output_edges[prev_edge]->interconnect->parent_mode_index;
						assert(mode == NULL || &pb_type->modes[mode_of_edge] == mode);
						mode = &pb_type->modes[mode_of_edge];
					}
				}
				port_index++;
			}
		}

		assert(mode != NULL && mode_of_edge != UNDEFINED);
		fprintf(fpout, "<block name=\"open\" instance=\"%s[%d]\" mode=\"%s\">\n", pb_graph_node->pb_type->name, pb_index, mode->name);

		print_tabs(fpout, tab_depth);
		fprintf(fpout, "\t<inputs>\n");
		port_index = 0;
		for(i = 0; i < pb_type->num_ports; i++) {
			if(!pb_type->ports[i].is_clock && pb_type->ports[i].type == IN_PORT) {
				print_tabs(fpout, tab_depth);
				fprintf(fpout, "\t\t<port name=\"%s\">", pb_graph_node->pb_type->ports[i].name);
				for(j = 0; j < pb_type->ports[i].num_pins; j++) {
					node_index = pb_graph_node->input_pins[port_index][j].pin_count_in_cluster;
					print_interconnect(node_index, &column, tab_depth + 2, fpout);
				}
				fprintf(fpout, "</port>\n");
				port_index++;
			}
		}
		print_tabs(fpout, tab_depth);
		fprintf(fpout, "\t</inputs>\n");
		
		column = tab_depth * TAB_LENGTH + 8;	/* Next column I will write to. */
		print_tabs(fpout, tab_depth);
		fprintf(fpout, "\t<outputs>\n");
		port_index = 0;			
		for(i = 0; i < pb_type->num_ports; i++) {
			if(pb_type->ports[i].type == OUT_PORT) {
				print_tabs(fpout, tab_depth);
				fprintf(fpout, "\t\t<port name=\"%s\">", pb_graph_node->pb_type->ports[i].name);
				assert(!pb_type->ports[i].is_clock);
				for(j = 0; j < pb_type->ports[i].num_pins; j++) {
					node_index = pb_graph_node->output_pins[port_index][j].pin_count_in_cluster;
					print_interconnect(node_index, &column, tab_depth + 2, fpout);
				}
				fprintf(fpout, "</port>\n");			
				port_index++;
			}
		}
		print_tabs(fpout, tab_depth);
		fprintf(fpout, "\t</outputs>\n");

		column = tab_depth * TAB_LENGTH + 8;	/* Next column I will write to. */
		print_tabs(fpout, tab_depth);
		fprintf(fpout, "\t<globals>\n");
		port_index = 0;			
		for(i = 0; i < pb_type->num_ports; i++) {
			if(pb_type->ports[i].is_clock && pb_type->ports[i].type == IN_PORT) {
				print_tabs(fpout, tab_depth);
				fprintf(fpout, "\t\t<port name=\"%s\">", pb_graph_node->pb_type->ports[i].name);
				for(j = 0; j < pb_type->ports[i].num_pins; j++) {
					node_index = pb_graph_node->clock_pins[port_index][j].pin_count_in_cluster;
					print_interconnect(node_index, &column, tab_depth + 2, fpout);
				}
				fprintf(fpout, "</port>\n");
				port_index++;
			}
		}
		print_tabs(fpout, tab_depth);
		fprintf(fpout, "\t</globals>\n");

		if(pb_type->num_modes > 0) {
			for(i = 0; i < mode->num_pb_type_children; i++) {
				child_pb_type = &mode->pb_type_children[i];
				for(j = 0; j < mode->pb_type_children[i].num_pb; j++) {
					port_index = 0;
					is_used = FALSE;
					for(k = 0; k < child_pb_type->num_ports && !is_used; k++) {
						if(child_pb_type->ports[k].type == OUT_PORT) {
							for(m = 0; m < child_pb_type->ports[k].num_pins; m++) {
								node_index = pb_graph_node->child_pb_graph_nodes[mode_of_edge][i][j].output_pins[port_index][m].pin_count_in_cluster;
								if(rr_node[node_index].net_num != OPEN) {
									is_used = TRUE;
									break;
								}
							}
							port_index++;
						}
					}
					print_open_pb_graph_node(&pb_graph_node->child_pb_graph_nodes[mode_of_edge][i][j], j, is_used, tab_depth+1, fpout);
				}
			}
		}
			
		print_tabs(fpout, tab_depth);
		fprintf(fpout, "</block>\n");
	} else {
		fprintf(fpout, "<block name=\"open\" instance=\"%s[%d]\"/>\n", pb_graph_node->pb_type->name, pb_index);
	}
}

static void
print_basic_pb(FILE * fpout,
		     int bnum)
{

/* Prints out the pb connectivity.  Used only when *
 * the -no_clustering option is selected -- prints out   *
 * basically redundant connection info for a VPACK_LUT + FF    *
 * logic logical_block.                                          */
	assert(0);
}



static void print_pb(FILE *fpout, t_pb * pb, int pb_index, int tab_depth) {

	int column;
	int i, j, k, m;
	const t_pb_type *pb_type, *child_pb_type;
	t_pb_graph_node *pb_graph_node;
	t_mode *mode;
	int port_index, node_index;
	boolean is_used;

	pb_type = pb->pb_graph_node->pb_type;
	pb_graph_node = pb->pb_graph_node;
	mode = &pb_type->modes[pb->mode];
	column = tab_depth * TAB_LENGTH + 8;	/* Next column I will write to. */
	print_tabs(fpout, tab_depth);
	if(pb_type->num_modes == 0) {
		fprintf(fpout, "<block name=\"%s\" instance=\"%s[%d]\">\n", pb->name, pb_type->name, pb_index);
	} else {
		fprintf(fpout, "<block name=\"%s\" instance=\"%s[%d]\" mode=\"%s\">\n", pb->name, pb_type->name, pb_index, mode->name);
	}
	
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "\t<inputs>\n");
	port_index = 0;
	for(i = 0; i < pb_type->num_ports; i++) {
		if(!pb_type->ports[i].is_clock && pb_type->ports[i].type == IN_PORT) {
			print_tabs(fpout, tab_depth);
			fprintf(fpout, "\t\t<port name=\"%s\">", pb_graph_node->pb_type->ports[i].name);
			for(j = 0; j < pb_type->ports[i].num_pins; j++) {
				node_index = pb->pb_graph_node->input_pins[port_index][j].pin_count_in_cluster;
				if(pb_type->parent_mode == NULL) {
					print_net_name(rr_node[node_index].net_num, &column, tab_depth, fpout);
				} else {
					print_interconnect(node_index, &column, tab_depth + 2, fpout);
				}
			}
			fprintf(fpout, "</port>\n");
			port_index++;
		}
	}
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "\t</inputs>\n");
	
	column = tab_depth * TAB_LENGTH + 8;	/* Next column I will write to. */
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "\t<outputs>\n");
	port_index = 0;			
	for(i = 0; i < pb_type->num_ports; i++) {
		if(pb_type->ports[i].type == OUT_PORT) {
			assert(!pb_type->ports[i].is_clock);
			print_tabs(fpout, tab_depth);
			fprintf(fpout, "\t\t<port name=\"%s\">", pb_graph_node->pb_type->ports[i].name);
			for(j = 0; j < pb_type->ports[i].num_pins; j++) {
				node_index = pb->pb_graph_node->output_pins[port_index][j].pin_count_in_cluster;
				print_interconnect(node_index, &column, tab_depth + 2, fpout);				
			}
			fprintf(fpout, "</port>\n");
			port_index++;
		}
	}
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "\t</outputs>\n");

	column = tab_depth * TAB_LENGTH + 8;	/* Next column I will write to. */
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "\t<globals>\n");
	port_index = 0;			
	for(i = 0; i < pb_type->num_ports; i++) {
		if(pb_type->ports[i].is_clock && pb_type->ports[i].type == IN_PORT) {
			print_tabs(fpout, tab_depth);
			fprintf(fpout, "\t\t<port name=\"%s\">", pb_graph_node->pb_type->ports[i].name);
			for(j = 0; j < pb_type->ports[i].num_pins; j++) {
				node_index = pb->pb_graph_node->clock_pins[port_index][j].pin_count_in_cluster;
				if(pb_type->parent_mode == NULL) {
					print_net_name(rr_node[node_index].net_num, &column, tab_depth, fpout);
				} else {
					print_interconnect(node_index, &column, tab_depth + 2, fpout);
				}
			}
			fprintf(fpout, "</port>\n");
			port_index++;
		}
	}
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "\t</globals>\n");

	if(pb_type->num_modes > 0) {
		for(i = 0; i < mode->num_pb_type_children; i++) {
			for(j = 0; j < mode->pb_type_children[i].num_pb; j++) {
				/* If child pb is not used but routing is used, I must print things differently */
				if((pb->child_pbs[i] != NULL) && 
					(pb->child_pbs[i][j].name != NULL)) {
					print_pb(fpout, &pb->child_pbs[i][j], j, tab_depth + 1);
				} else {
					is_used = FALSE;
					child_pb_type = &mode->pb_type_children[i];
					port_index = 0;
						
					for(k = 0; k < child_pb_type->num_ports && !is_used; k++) {
						if(child_pb_type->ports[k].type == OUT_PORT) {
							for(m = 0; m < child_pb_type->ports[k].num_pins; m++) {
								node_index = pb_graph_node->child_pb_graph_nodes[pb->mode][i][j].output_pins[port_index][m].pin_count_in_cluster;
								if(rr_node[node_index].net_num != OPEN) {
									is_used = TRUE;
									break;
								}
							}
							port_index++;
						}
					}
					print_open_pb_graph_node(&pb_graph_node->child_pb_graph_nodes[pb->mode][i][j], j, is_used, tab_depth+1, fpout);
				}
			}
		}
	}
	print_tabs(fpout, tab_depth);
	fprintf(fpout, "</block>\n");
}

static void
print_clusters(
	   t_block *clb,
	   int num_clusters,
	   FILE * fpout)
{

/* Prints out one cluster (clb).  Both the external pins and the *
 * internal connections are printed out.                         */

    int icluster;

	for(icluster = 0; icluster < num_clusters; icluster++)
	{
		rr_node = clb[icluster].pb->rr_graph;
		
		/* TODO: Must do check that total CLB pins match top-level pb pins, perhaps check this earlier? */

		print_pb(fpout, clb[icluster].pb, icluster, 1);
	}
}

static void
print_stats(
	   t_block *clb,
	   int num_clusters) {

/* Prints out one cluster (clb).  Both the external pins and the *
 * internal connections are printed out.                         */

    int ipin, icluster, itype, inet, iblk, num_pins, i;
	int MAX_LUT_INPUTS;
	int unabsorbable_ffs, total_ffs;
	int num_luts_total;
	int total_nets_absorbed;
    boolean* nets_absorbed;
	
	int *num_clb_types, *num_clb_inputs_used, *num_clb_outputs_used, *num_lut_of_size;
	
	nets_absorbed = NULL;
	num_clb_types = num_clb_inputs_used = num_clb_outputs_used = NULL;

	num_clb_types = (int*)my_calloc(num_types, sizeof(int));
	num_clb_inputs_used = (int*)my_calloc(num_types, sizeof(int));
	num_clb_outputs_used = (int*)my_calloc(num_types, sizeof(int));

	MAX_LUT_INPUTS = 0;
	for(iblk = 0; iblk < num_logical_blocks; iblk++) {
		if(strcmp(logical_block[iblk].model->name, "names") == 0) {
			MAX_LUT_INPUTS = logical_block[iblk].model->inputs->size;
			break;
		}
	}
	num_lut_of_size = (int*)my_calloc(MAX_LUT_INPUTS + 1, sizeof(int));

	
	nets_absorbed = (boolean *)my_calloc(num_logical_nets, sizeof(boolean));
	for(inet = 0; inet < num_logical_nets; inet++) {
		nets_absorbed[inet] = TRUE;
	}

	unabsorbable_ffs = 0;
	total_ffs = 0;
	for(iblk = 0; iblk < num_logical_blocks; iblk++) {
		if(strcmp(logical_block[iblk].model->name, "names") == 0) {
			num_pins = 0;
			for(ipin = 0; ipin < logical_block[iblk].model->inputs->size; ipin++) {
				if(logical_block[iblk].input_nets[0][ipin] != OPEN) {
					num_pins++;
				}
			}
			num_lut_of_size[num_pins]++;
		} else if(strcmp(logical_block[iblk].model->name, "latch") == 0) {
			if(vpack_net[logical_block[iblk].input_nets[0][0]].num_sinks > 1 ||
				strcmp(logical_block[vpack_net[logical_block[iblk].input_nets[0][0]].node_block[0]].model->name, "names") != 0) {
					unabsorbable_ffs++;
			}
			total_ffs++;
		}
	}
	printf("\n");
	num_luts_total = 0;
	for(i = 0; i <= MAX_LUT_INPUTS; i++) {
		printf("%d LUTs of size %d\n", num_lut_of_size[i], i);
		num_luts_total += num_lut_of_size[i];
	}
	printf("%d LUTs in input netlist\n", num_luts_total);
	printf("%d FFs in input netlist\n", total_ffs);
	printf("%d FFs in input netlist not absorbable\n", unabsorbable_ffs);


	/* Counters used only for statistics purposes. */

    for(icluster = 0; icluster < num_clusters; icluster++)
	{
		for(ipin = 0; ipin < clb[icluster].type->num_pins; ipin++) {
			if(clb[icluster].nets[ipin] != OPEN) {
				nets_absorbed[clb[icluster].nets[ipin]] = FALSE;
				if(clb[icluster].type->class_inf[clb[icluster].type->pin_class[ipin]].type == RECEIVER) {
					num_clb_inputs_used[clb[icluster].type->index]++;
				} else if(clb[icluster].type->class_inf[clb[icluster].type->pin_class[ipin]].type == DRIVER){
					num_clb_outputs_used[clb[icluster].type->index]++;
				}
			}
		}		
		num_clb_types[clb[icluster].type->index]++;
	}

	for(itype = 0; itype < num_types; itype++) {
		if(num_clb_types[itype] == 0) {
			printf("\t%s: # blocks %d, avg # input + clock pins used %g, avg # output pins used %g\n", 
				type_descriptors[itype].name,
				num_clb_types[itype],
			    0.0,
				0.0);
		} else {
			printf("\t%s: # blocks %d, avg # input + clock pins used %g, avg # output pins used %g\n", 
				type_descriptors[itype].name,
				num_clb_types[itype],
			    (float)num_clb_inputs_used[itype] / (float)num_clb_types[itype],
				(float)num_clb_outputs_used[itype] / (float)num_clb_types[itype]);
		}
	}
	
	total_nets_absorbed = 0;
	for(inet = 0; inet < num_logical_nets; inet++) {
		if(nets_absorbed[inet] == TRUE) {
			total_nets_absorbed++;
		}
	}
    printf("Absorbed logical nets %d out of %d nets, %d nets not absorbed\n", total_nets_absorbed, num_logical_nets, num_logical_nets - total_nets_absorbed);
	free(nets_absorbed);
	free(num_lut_of_size);
	free(num_clb_types);
	free(num_clb_inputs_used);
	free(num_clb_outputs_used);
	/* TODO: print more stats */
}

void
output_clustering(
		  t_block *clb,
		  int num_clusters,
		  boolean global_clocks,
		  boolean * is_clock,
		  char *out_fname,
		  boolean skip_clustering)
{

/* 
 * This routine dumps out the output netlist in a format suitable for  *
 * input to vpr.  This routine also dumps out the internal structure of   *
 * the cluster, in essentially a graph based format.                           */

    FILE *fpout;
    int bnum, netnum, column;

    fpout = fopen(out_fname, "w");

	fprintf(fpout, "<block name=\"%s\" instance=\"FPGA_packed_netlist[0]\">\n", out_fname);
	fprintf(fpout, "\t<inputs>\n\t\t");

	column = 2*TAB_LENGTH; /* Organize whitespace to ident data inside block */
    for(bnum = 0; bnum < num_logical_blocks; bnum++)
	{	
		if(logical_block[bnum].type == VPACK_INPAD) {
			print_string(logical_block[bnum].name, &column, 2, fpout);
		}
	}
	fprintf(fpout, "\n\t</inputs>\n");
	fprintf(fpout, "\n\t<outputs>\n\t\t");

	column = 2*TAB_LENGTH;
    for(bnum = 0; bnum < num_logical_blocks; bnum++)
	{	
		if(logical_block[bnum].type == VPACK_OUTPAD) {
			print_string(logical_block[bnum].name, &column, 2, fpout);
		}
	}
	fprintf(fpout, "\n\t</outputs>\n");

	column = 2*TAB_LENGTH;
    if(global_clocks)
	{
		fprintf(fpout, "\n\t<globals>\n\t\t");
    
	    for(netnum = 0; netnum < num_logical_nets; netnum++)
		{
		    if(is_clock[netnum])
			{
				print_string(vpack_net[netnum].name, &column, 2, fpout);
			}
		}
		fprintf(fpout, "\n\t</globals>\n\n");
	}

/* Print out all input and output pads. */

    for(bnum = 0; bnum < num_logical_blocks; bnum++)
	{
	    switch (logical_block[bnum].type)
		{
		case VPACK_INPAD:
		case VPACK_OUTPAD:
		case VPACK_COMB:
		case VPACK_LATCH:
		    if(skip_clustering)
			{
				assert(0);
			}
		    break;

		case VPACK_EMPTY:
		    printf("Error in output_netlist -- logical_block %d is VPACK_EMPTY.\n",
			   bnum);
		    exit(1);
		    break;

		default:
		    printf
			("Error in output_netlist.  Unexpected type %d for logical_block"
			 "%d.\n", logical_block[bnum].type, bnum);
		}
	}

    if(skip_clustering == FALSE)
		print_clusters(clb, num_clusters, fpout);

	fprintf(fpout, "</block>\n\n");

    fclose(fpout);

	print_stats(clb, num_clusters);
}
