/*
Jason Luu 2008
Print blif representation of circuit
 Assumptions: Assumes first valid rr input to node is the correct rr input
              Assumes clocks are routed globally
*/

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

#define LINELENGTH 1024
#define TABLENGTH 1


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


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


static void
print_string(char *str_ptr,
	     int *column,
	     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");
	    *column = TABLENGTH;
	}

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


static void
print_net_name(int inet,
	       int *column,
	       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, fpout);
}

static int find_fanin_rr_node(t_pb *cur_pb, enum PORTS type, int rr_node_index) {
	/* finds first fanin rr_node */
	t_pb *parent, *sibling, *child;
	int net_num, irr_node;
	int i, j, k, ichild_type, ichild_inst;
	int hack_empty_route_through;
	t_pb_graph_node *hack_empty_pb_graph_node;

	hack_empty_route_through = OPEN;

	net_num = rr_node[rr_node_index].net_num;

	parent = cur_pb->parent_pb;

	if(net_num == OPEN) {
		return OPEN;
	}

	if (type == IN_PORT) {
		/* check parent inputs for valid connection */
		for(i = 0; i < parent->pb_graph_node->num_input_ports; i++) {
			for(j = 0; j < parent->pb_graph_node->num_input_pins[i]; j++) {
				irr_node = parent->pb_graph_node->input_pins[i][j].pin_count_in_cluster;
				if(rr_node[irr_node].net_num == net_num) {
					if(cur_pb->pb_graph_node->pb_type->model && strcmp(cur_pb->pb_graph_node->pb_type->model->name, MODEL_LATCH) == 0) {
						/* HACK: latches are special becuase LUTs can be set to route-through mode for them 
						   I will assume that the input to a LATCH can always come from a parent input pin
						   this only works for hierarchical soft logic structures that follow LUT -> LATCH design
						   must do it better later
						*/
						return irr_node;
					}
					hack_empty_route_through = irr_node;
					for(k = 0; k < rr_node[irr_node].num_edges; k++) {
						if(rr_node[irr_node].edges[k] == rr_node_index) {
							return irr_node;
						}
					}
				}
			}
		}
		/* check parent clocks for valid connection */
		for(i = 0; i < parent->pb_graph_node->num_clock_ports; i++) {
			for(j = 0; j < parent->pb_graph_node->num_clock_pins[i]; j++) {
				irr_node = parent->pb_graph_node->clock_pins[i][j].pin_count_in_cluster;
				if(rr_node[irr_node].net_num == net_num) {
					for(k = 0; k < rr_node[irr_node].num_edges; k++) {
						if(rr_node[irr_node].edges[k] == rr_node_index) {
							return irr_node;
						}
					}
				}
			}
		}
		/* check siblings for connection */
		if(parent) {
			for(ichild_type = 0; ichild_type < parent->pb_graph_node->pb_type->modes[parent->mode].num_pb_type_children; ichild_type++) {
				for(ichild_inst = 0; ichild_inst < parent->pb_graph_node->pb_type->modes[parent->mode].pb_type_children[ichild_type].num_pb; ichild_inst++) {
					if(parent->child_pbs[ichild_type] && parent->child_pbs[ichild_type][ichild_inst].name != NULL) {
						sibling = &parent->child_pbs[ichild_type][ichild_inst];
						for(i = 0; i < sibling->pb_graph_node->num_output_ports; i++) {
							for(j = 0; j < sibling->pb_graph_node->num_output_pins[i]; j++) {
								irr_node = sibling->pb_graph_node->output_pins[i][j].pin_count_in_cluster;
								if(rr_node[irr_node].net_num == net_num) {
									for(k = 0; k < rr_node[irr_node].num_edges; k++) {
										if(rr_node[irr_node].edges[k] == rr_node_index) {
											return irr_node;
										}
									}
								}
							}
						}						
					} else {
						/* hack just in case routing is down through an empty cluster */
						hack_empty_pb_graph_node = &parent->pb_graph_node->child_pb_graph_nodes[ichild_type][0][ichild_inst];
						for(i = 0; i < hack_empty_pb_graph_node->num_output_ports; i++) {
							for(j = 0; j < hack_empty_pb_graph_node->num_output_pins[i]; j++) {
								irr_node = hack_empty_pb_graph_node->output_pins[i][j].pin_count_in_cluster;
								if(rr_node[irr_node].net_num == net_num) {
									for(k = 0; k < rr_node[irr_node].num_edges; k++) {
										if(rr_node[irr_node].edges[k] == rr_node_index) {
											return irr_node;
										}
									}
								}
							}
						}			
					}
				}
			}
		}
	} else {
		assert(type == OUT_PORT);
		
		/* check children for connection */
		for(ichild_type = 0; ichild_type < cur_pb->pb_graph_node->pb_type->modes[cur_pb->mode].num_pb_type_children; ichild_type++) {
			for(ichild_inst = 0; ichild_inst < cur_pb->pb_graph_node->pb_type->modes[cur_pb->mode].pb_type_children[ichild_type].num_pb; ichild_inst++) {
				if(cur_pb->child_pbs[ichild_type] && cur_pb->child_pbs[ichild_type][ichild_inst].name != NULL) {
					child = &cur_pb->child_pbs[ichild_type][ichild_inst];
					for(i = 0; i < child->pb_graph_node->num_output_ports; i++) {
						for(j = 0; j < child->pb_graph_node->num_output_pins[i]; j++) {
							irr_node = child->pb_graph_node->output_pins[i][j].pin_count_in_cluster;
							if(rr_node[irr_node].net_num == net_num) {
								for(k = 0; k < rr_node[irr_node].num_edges; k++) {
									if(rr_node[irr_node].edges[k] == rr_node_index) {
										return irr_node;
									}
								}
								hack_empty_route_through = irr_node;
							}
						}
					}						
				}
			}
		}

		/* If not in children, check current pb inputs for valid connection */
		for(i = 0; i < cur_pb->pb_graph_node->num_input_ports; i++) {
			for(j = 0; j < cur_pb->pb_graph_node->num_input_pins[i]; j++) {
				irr_node = cur_pb->pb_graph_node->input_pins[i][j].pin_count_in_cluster;
				if(rr_node[irr_node].net_num == net_num) {
					hack_empty_route_through = irr_node;
					for(k = 0; k < rr_node[irr_node].num_edges; k++) {
						if(rr_node[irr_node].edges[k] == rr_node_index) {
							return irr_node;
						}
					}
				}
			}
		}
	}

	/* TODO: Once I find a way to output routing in empty blocks then code should never reach here, for now, return OPEN */
	printf("Can't find connecting net %s #%d for pb %s type %s\n", vpack_net[net_num].name, net_num, 
		cur_pb->name, cur_pb->pb_graph_node->pb_type->name);
		
	assert(hack_empty_route_through != OPEN);
	return hack_empty_route_through;
}

static void print_primitive(FILE *fpout, int iblk) {
	t_pb *pb;
	int clb_index;
	int i, j, column, node_index;
	int in_port_index, out_port_index, clock_port_index;
	struct s_linked_vptr *truth_table;
	const t_pb_type *pb_type;

	pb = logical_block[iblk].pb;
	pb_type = pb->pb_graph_node->pb_type;
	clb_index = logical_block[iblk].clb_index;


	column = 7;
	if(logical_block[iblk].type == VPACK_INPAD || logical_block[iblk].type == VPACK_OUTPAD) {
		/* do nothing */
	} else if(logical_block[iblk].type == VPACK_LATCH) {
		fprintf(fpout, ".latch ");
		
		in_port_index = 0;
		out_port_index = 0;
		clock_port_index = 0;
		for(i = 0; i < pb_type->num_ports; i++) {
			if(pb_type->ports[i].type == IN_PORT && pb_type->ports[i].is_clock == FALSE) {
				assert(pb_type->ports[i].num_pins == 1);
				assert(logical_block[iblk].input_nets[i][0] != OPEN);
				node_index = pb->pb_graph_node->input_pins[in_port_index][0].pin_count_in_cluster;
				fprintf(fpout, "clb_%d_rr_node_%d ", clb_index, find_fanin_rr_node(pb, pb_type->ports[i].type, node_index));
				in_port_index++;
			}
		}
		for(i = 0; i < pb_type->num_ports; i++) {
			if(pb_type->ports[i].type == OUT_PORT) {
				assert(pb_type->ports[i].num_pins == 1 && out_port_index == 0);
				node_index = pb->pb_graph_node->output_pins[out_port_index][0].pin_count_in_cluster;
				fprintf(fpout, "clb_%d_rr_node_%d re ", clb_index, node_index);
				out_port_index++;
			}
		}
		for(i = 0; i < pb_type->num_ports; i++) {
			if(pb_type->ports[i].type == IN_PORT && pb_type->ports[i].is_clock == TRUE) {
				assert(logical_block[iblk].clock_net != OPEN);
				node_index = pb->pb_graph_node->clock_pins[clock_port_index][0].pin_count_in_cluster;
				fprintf(fpout, "clb_%d_rr_node_%d 2", clb_index, find_fanin_rr_node(pb, pb_type->ports[i].type, node_index));
				clock_port_index++;
			}
		}
		fprintf(fpout, "\n");				
	} else if(logical_block[iblk].type == VPACK_COMB) {
		if(strcmp(logical_block[iblk].model->name, ".names")) {
			fprintf(fpout, ".names ");
			in_port_index = 0;
			out_port_index = 0;
			for(i = 0; i < pb_type->num_ports; i++) {
				if(pb_type->ports[i].type == IN_PORT && pb_type->ports[i].is_clock == FALSE) {
					for(j = 0; j < pb_type->ports[i].num_pins; j++) {
						if(logical_block[iblk].input_nets[i][j] != OPEN) {
							node_index = pb->pb_graph_node->input_pins[in_port_index][j].pin_count_in_cluster;
							fprintf(fpout, "clb_%d_rr_node_%d ", clb_index, find_fanin_rr_node(pb, pb_type->ports[i].type, node_index));
						}
					}
					in_port_index++;
				}
			}
			for(i = 0; i < pb_type->num_ports; i++) {
				if(pb_type->ports[i].type == OUT_PORT) {
					for(j = 0; j < pb_type->ports[i].num_pins; j++) {
						node_index = pb->pb_graph_node->output_pins[out_port_index][j].pin_count_in_cluster;
						fprintf(fpout, "clb_%d_rr_node_%d\n", clb_index, node_index);
					}
					out_port_index++;
				}
			}
			truth_table = logical_block[iblk].truth_table;
			while(truth_table) {
				fprintf(fpout, "%s\n", (char *)truth_table->data_vptr);
				truth_table = truth_table->next;
			}
		} else {
			assert(0);
		}
	}
}

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

	int column;
	int i, j;
	const t_pb_type *pb_type;
	t_mode *mode;
	int in_port_index, out_port_index, node_index;

	pb_type = pb->pb_graph_node->pb_type;
	mode = &pb_type->modes[pb->mode];
	column = 0;	
	if(pb_type->num_modes == 0) {
		print_primitive(fpout, pb->logical_block);
	} else {
		in_port_index = 0;
		out_port_index = 0;
		for(i = 0; i < pb_type->num_ports; i++) {
			if(!pb_type->ports[i].is_clock) {
				for(j = 0; j < pb_type->ports[i].num_pins; j++) {
					/* print .blif buffer to represent routing */
					column = 0;
					if(pb_type->ports[i].type == OUT_PORT) {
						node_index = pb->pb_graph_node->output_pins[out_port_index][j].pin_count_in_cluster;
						if(rr_node[node_index].net_num != OPEN) {
							fprintf(fpout, ".names clb_%d_rr_node_%d ",
								clb_index, find_fanin_rr_node(pb, pb_type->ports[i].type, node_index));
							if(pb->parent_pb) {
								fprintf(fpout, "clb_%d_rr_node_%d ", clb_index, node_index);
							} else {
								print_net_name(rr_node[node_index].net_num, &column, fpout);
							}
							fprintf(fpout, "\n1 1\n");
						}
		
					} else { 
						node_index = pb->pb_graph_node->input_pins[in_port_index][j].pin_count_in_cluster;
						if(rr_node[node_index].net_num != OPEN) {
						
							fprintf(fpout, ".names ");
							if(pb->parent_pb) {
								fprintf(fpout, "clb_%d_rr_node_%d ", clb_index, find_fanin_rr_node(pb, pb_type->ports[i].type, node_index));
							} else {
								print_net_name(rr_node[node_index].net_num, &column, fpout);
							}
							fprintf(fpout, "clb_%d_rr_node_%d", clb_index, node_index);
							fprintf(fpout, "\n1 1\n");
						}
					}
				}
			}
			if(pb_type->ports[i].type == OUT_PORT) {
				out_port_index++;
			} else {
				in_port_index++;
			}				
		}
		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], clb_index);
				} else {
					/* do nothing for now, we'll print something later if needed */
				}
			}
		}
	}
}

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;
#if 0
		dump_rr_graph("cluster_rr_graph.echo"); 
#endif
		if(clb[icluster].type != IO_TYPE)
			print_pb(fpout, clb[icluster].pb, icluster);
	}
}

void
output_blif(
		  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, column;
	struct s_linked_vptr *p_io_removed;

	fpout = my_fopen(out_fname, "w", 0);

	column = 0;
	fprintf(fpout, ".model %s\n", blif_circuit_name);
		

	/* Print out all input and output pads. */
	fprintf(fpout, "\n.inputs ");
    for(bnum = 0; bnum < num_logical_blocks; bnum++)
	{	
		if(logical_block[bnum].type == VPACK_INPAD) {
			print_string(logical_block[bnum].name, &column, fpout);
		}
	}
	p_io_removed = circuit_p_io_removed;
	while(p_io_removed) {
		print_string((char*)p_io_removed->data_vptr, &column, fpout);
		p_io_removed = p_io_removed->next;
	}

	column = 0;
	fprintf(fpout, "\n.outputs ");
	for(bnum = 0; bnum < num_logical_blocks; bnum++)
	{	
		if(logical_block[bnum].type == VPACK_OUTPAD) {
			/* remove output prefix "out:" */
			print_string(logical_block[bnum].name + 4, &column, fpout);
		}
	}

	column = 0;

	fprintf(fpout, "\n\n");
	
	print_clusters(clb, num_clusters, fpout);
	fprintf(fpout, "\n.end\n");

    fclose(fpout);
}
