#!/usr/bin/perl -w
use strict;

# To use the pad frame generator, you have to create a new project, edit the layout in magic, add a single core from another project beneath your home directory, and save it as a .mag file in the current directory.
# Then this generator parses this file, and generates a pad-frame for all the pins in the referenced design



# Configuration:
our $fillers=0; # Have this many fillers between each pad to make the padframe larger for larger cores with fewer pads

# Start Position and direction:
our $x = 0;
our $y = 0;
our $dir = 0;


our $blockwidth=16800; # These are the dimensions of the pad-frame blocks from the X-Fab XH018 process
our $blockheight=32000;

print "QFlow-PadFrameGenerator 1.0\n";

sub padtype($) # Which IO-Block type should we choose, based on the name of the pad?
{
  return "VDDORPADF" if($_[0]=~m/qvdd3v3/); # VDDORPADF is for 3.3 V core voltage and is also needed!
  return "VDDPADF" if($_[0]=~m/vdd/i); # VDDPADF is for 1.8 V, VDDORPADF is for 3.3 V core voltage, but for 1.8 V designs we need both!
  return "VDDIPADF" if($_[0]=~m/vcc/i);
  return "GNDORPADF" if($_[0]=~m/gnd/i);
  return "ICF" if($_[0]=~m/reset/i);
  return "ICF" if($_[0]=~m/rst/i);
  return "BT4F" if($_[0]=~m/out/i);
  return "FILLER84F" if($_[0]=~m/qdummy/);
  return "BBCUD4F" if($_[0]=~m/gpio/i);
  return "ICF" if($_[0]=~m/in/i);
  return "ICF"; # Default is an Input pin
}
sub verilogtype($) # Which IO type should we choose, based on the name of the pad?
{
  return "input real" if($_[0]=~m/vdd/i); # VDDPADF is for 1.8 V, VDDORPADF is for 3.3 V core voltage
  return "input real" if($_[0]=~m/vcc/i);
  return "input real" if($_[0]=~m/gnd/i);
  return "input" if($_[0]=~m/reset/i);
  return "input" if($_[0]=~m/rst/i);
  return "output" if($_[0]=~m/out/i);
  return "input" if($_[0]=~m/in/i);
  return "input"; # Default is an Input pin
}

sub nextPos() # Move the current position one block further, in the current direction
{
  if($dir==0)
  {
    $x+=$blockwidth;
  }
  elsif($dir==1)
  {
    $y-=$blockwidth;
  }
  elsif($dir==2)
  {
    $x-=$blockwidth;
  }
  elsif($dir==3)
  {
    $y+=$blockwidth;
  }
}
sub nextCorner() # Move the current position one block further, in the current direction
{
  if($dir==0)
  {
    $x+=$blockheight;
  }
  elsif($dir==1)
  {
    $y-=$blockheight;
  }
  elsif($dir==2)
  {
    $x-=$blockheight;
  }
  elsif($dir==3)
  {
    $y+=$blockheight;
  }
}



# Direction constants
our %dir1=(2=>"1 0",3=>"0 1",0=>"-1 0",1=>"0 -1");
our %dir2=(2=>"0 1",3=>"-1 0",0=>"0 -1",1=>"1 0");
sub midCoords() # Coordinates in the middle of an IO-Block
{
  if($dir==0)
  {
    return "".($x+$blockwidth/2)." ".($y-$blockheight/2);
  }
  elsif($dir==1)
  {
    return "".($x-$blockheight/2)." ".($y-$blockwidth/2);
  }
  elsif($dir==2)
  {
    return "".($x-$blockwidth/2)." ".($y+$blockheight/2);
  }
  elsif($dir==3)
  {
    return "".($x+$blockheight/2)." ".($y+$blockwidth/2);
  }
}

sub edgeCoords() # Coordinates in the edge of an IO-Block
{
  if($dir==0)
  {
    return "".($x+$blockwidth/2)." ".($y-$blockheight);
  }
  elsif($dir==1)
  {
    return "".($x-$blockheight)." ".($y-$blockwidth/2);
  }
  elsif($dir==2)
  {
    return "".($x-$blockwidth/2)." ".($y+$blockheight);
  }
  elsif($dir==3)
  {
    return "".($x+$blockheight)." ".($y+$blockwidth/2);
  }
}




# Currently it searches for all *.mag files in the current directory
foreach my $filename(<*.mag>)
{
  my $source=undef;
  print "Analyzing $filename\n";
  open IN,"<$filename";
  while(<IN>)
  {
    if(m/use (\w+) +(\w+) (~\/.*)$/) # Does the .mag file reference another project? This is detected with a ~/ path
    {
      $source=$3;
    }
  }
  close IN;
  if(defined($source))
  {
    print "Found a referenced design in $filename: $source\n";
    $source=~s/~/..\/..\/../;

    # Read the whole .json file that contains the pin definitions of the core
    open IN,"<$source/comp.json";
    undef $/;
    my $content=<IN>;
    close IN;
    # Extract the pin definition from the json file
    my $pins=$1 if($content=~m/"pins": \[\s*\[\s*(.*?)\s*\]/s);
    my @pins=split "\n",$pins;
    print "Found the following pins that need pads:\n";

    # Start at 0/0 and start in x+ direction
    $x = 0;
    $y = 0;
    $dir=0;

    my $padframe="padframe.mag";
    open OUT,">$padframe";


print OUT <<EOF
magic
tech EFXH018C
magscale 1 2
timestamp 1528139636
EOF
;


    open VERILOG,">toplevel.v";
    print VERILOG "module toplevel (\n";

    my %padnums=();
    our %signalsmin=();
    our %signalsmax=();
    our $wires="";
    our $labels="";
    push @pins,"qvdd3v3";

    my $nPins=scalar(@pins);
    print "Number of Pins: $nPins\n";
    push @pins,"qdummy" if(scalar(@pins)&1); # We need a FILLER pad if we have an odd number of pads
    $nPins=scalar(@pins);
    my $half=int($nPins/2);
    my $quarter=int($half/2);
    #print "Half: $half Quarter: $quarter\n";

    my $counter=0;
    foreach my $signalname (@pins)
    {
      if($counter==0 || $counter==$quarter || $counter==$half || $counter==($quarter+$half))
      { # Adding corners
        nextCorner();

print OUT <<EOF
use CORNERESDF  CORNERESDF_$dir /ef/tech/XFAB.3/EFXH018C/libs.ref/maglef/IO_CELLS_F3V
timestamp 1515464069
transform $dir1{$dir} $x $dir2{$dir} $y
box 0 0 $blockheight $blockheight
EOF
;

        $dir++; $dir%=4;
        nextCorner();
      }
 
      foreach(1 .. $fillers)
      { # If the core is too large and has too few pads, we can add filler pads
        my $fillid=$padnums{'FILLER'}++;
        nextPos();
        print OUT <<EOF
use FILLER84F  FILLER84F_$fillid /ef/tech/XFAB.3/EFXH018C/libs.ref/maglef/IO_CELLS_F3V
timestamp 1515464069
transform $dir1{$dir} $x $dir2{$dir} $y
box 0 0 $blockwidth $blockheight
EOF
;
      }

      $signalname=~s/ *"//g;
      $signalname=~s/",//g;
      my $padtype=padtype($signalname);
      $padnums{$padtype}=0 if(!defined($padnums{$padtype}));
      $padnums{$padtype}++;
      my $padname=$padtype."_".$padnums{$padtype};
      print "Signal #$counter : $signalname gets assigned a $padtype pad: $padname\n";

      my $midco=midCoords();
      my $edgeco=edgeCoords();

      nextPos();


print OUT <<EOF
use $padtype  $padname /ef/tech/XFAB.3/EFXH018C/libs.ref/maglef/IO_CELLS_F3V
timestamp 1515464069
transform $dir1{$dir} $x $dir2{$dir} $y
box 0 0 $blockwidth $blockheight
EOF
;
      if($signalname ne "qdummy")
      {
        if($signalname=~m/(\w+)<(\d+)>/) # For busses, we search for the lowest and highest bit
        {
          $signalsmin{$1}=defined($signalsmin{$1})?(($signalsmin{$1}<$2)?$signalsmin{$1}:$2):$2;
          $signalsmax{$1}=defined($signalsmax{$1})?(($signalsmax{$1}>$2)?$signalsmax{$1}:$2):$2;
        }
        else
        {
          $signalsmin{$signalname}=undef;
          $signalsmax{$signalname}=undef;
        }
        $signalname=~s/,$//;
        #flabel metal1 s 37792 -66198 69354 -43184 0 FreeSans 160000 0 0 0 STICKY
        $labels.="flabel metal1 s $midco $midco 0 FreeSans 24000 0 0 0 $signalname\n";
        $labels.="flabel space s $edgeco $edgeco 0 FreeSans 12000 0 0 0 $signalname\n";

      }
      $counter++;
    }

    print OUT <<EOF
<< labels >>
$labels<< end >>
EOF
;
    close OUT;
    print "Writing generated PadFrame to padframe.mag\n";

    our $module="";
    foreach my $signalname (sort keys %signalsmin)
    {
      my $io=verilogtype($signalname);
      $module.="   $io $signalname\n";
      my $minmax=defined($signalsmin{$signalname})?"[".$signalsmax{$signalname}.":".$signalsmin{$signalname}."] ":"";
      $wires.="wire $minmax".($io=~m/real/? "real ":"").$signalname."\n";
    }

    print VERILOG "$module\n);\n\n$wires\n";
    close VERILOG;
    print "Writing generated top-level verilog to toplevel.v\n"; 
  }
}
print "Done.\n";

