#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "macro.H"
#include "techno.H"

#include "params.h"

int addcache=FALSE;  // add cost of cache
int onlysum=FALSE;   // only print total
int robtable=FALSE;  // print rob size comparison table
int rstable=FALSE;   // print rs size comparison table
int qplot=FALSE;     // make a quality plot
int comptable=FALSE; // print comparison table
int no_rs;           // number of reservation stations for table

#define CACHE_COST 374979 // Cost of 16 kb direct mapped cache

// cost of a on chip n-bit RAM
// A addresses, ro_ports is the number of read only ports,
// w_ports is the number of write only ports

long cram(int A, int n, int ro_ports, int wo_ports)
 {
  return long(ceil(cram(A, n)*(0.4+0.6*(2*wo_ports+ro_ports)/2)));
 }

// cost of a on chip register based n-bit RAM
// A addresses, ro_ports is the number of read only ports,
// wo_ports is the number of write only ports

long c_reg_ram(int A, int n, int ro_ports, int wo_ports)
 {
  long cost_din;

  if(wo_ports<=1)
    cost_din=0;
  else if(wo_ports==2)
    cost_din=cmux*n*A;
  else
    cost_din=wo_ports*cdriv*n*A;

  return (ro_ports+wo_ports)*cdecf(ld(A))+ // decoders
         ro_ports*cdriv*n*A+               // drivers for data out
         cost_din+                         // drivers for data in
         n*A*cff+                          // actual flipflops
         ctree(wo_ports)*cor*A;            // write enable signal
 }

// cost of a reservation station operand
// the f flag denotes a floating point reservation station operand

long rs_operand(int f)
 {
  return cff*(TAG_WIDTH+1+32)+       // register
         cEQ(TAG_WIDTH)+2*cand+cinv+ // readCDB
         2*32*cmux+                  // multiplexer
         cor+                        // clock enable
         cor;                        // valid;
 }

// cost of a reservation station
// the f flag denotes a floating point reservation station
// some reservation stations use less than four bits for op

long rs(int f)
 {
  return (TAG_WIDTH+4+1)*(cmux+cff)+2*cor+
         (f?6:2)*rs_operand(f)+
         (f?ctree(7):2)*cand;
 }

// cost of reservation station control
// nr_rs is the number of reservation stations

long rs_control(int f, int nr_rs)
 {
  return ctree(nr_rs+1)*cand+    // FUj.full
         cFL1u(nr_rs)+cnor+cand+ // valid / RSi.doe
         nr_rs*cinv+nr_rs*cor+cPP(nr_rs)*cor; // RSi.fill
 }

// cost of single adjust before fu
// integer function units do not need this circuit

long single_adjust1(int f)
 {
  if(!f) return 0;
  return 4*cmux*32;
 }

// cost of single adjust after fu
// integer function units do not need this circuit

long single_adjust2(int f)
 {
  if(!f) return 0;
  return 32*cmux;
 }

// cost of reservation station producer

long rs_producer(int f)
 {
  return cff*(1+TAG_WIDTH+(f?32:64)+4)+
         cinv+cinv+cand+cand+cor;
 }

// cost of a reservation station set with nr_rs reservation
// stations, without function unit

long rs(int f, int nr_rs)
 {
  return nr_rs*rs(f)+                          // reservation stations
         nr_rs*cdriv*(TAG_WIDTH+4+(f?6:2)*32)+ // internal data bus drivers
         rs_control(f, nr_rs)+                 // control
         single_adjust1(f)+                    // single adjust before FU
         single_adjust2(f)+                    // single adjust after FU
         rs_producer(f)+                       // producer
         cdriv*(TAG_WIDTH+1+64+3+5+32);        // CDB driver
 }

// cost of the instruction memory environment

long im()
 {
  return 32*cand+2*cor+cnor;
 }

// calculation of co1

long co1gen()
 {
  return 15*cmux;
 }

// cost of the instruction register environment

long ir()
 {
  return 2*cff+cinv+32*cmux+co1gen();
 }

// cost of the pc environment

long pc()
 {
  return 6*32*cmux+
         3*32*cff+
         cCLA(32)+
         cincf(30)+
         5*cor+
         1*cinv+cff;
 }

// cost of the cdb control

long cdb_control()
 {
  return PRODUCERS*(cinv+cand+cor)+
         2*cinv+
         cPP(PRODUCERS)*cor+
         cFL1u(2*PRODUCERS)+
         PRODUCERS*cff+
         cmux*PRODUCERS;
 }

// cost of the gpr

long gpr()
 {
  return ctree(5)*cor*2+
         2*cand*32+2*cdriv*32+
         cram(32, 32, 2, 1); // 3 port RAM RF
 }

// cost of the gpr producer table

long prod_gpr()
 {
  return 2*ctree(5)*cor+2*(TAG_WIDTH+1)*cmux+
         2*(TAG_WIDTH+1)*cdriv+2*cand+
         c_reg_ram(32, 5, 2, 2); // 4 port RAM producer table
 }

// cost of the fpr

long fpr()
 {
  return 4*32*cdriv+2*cand+
         2*cram(32, 32, 2, 1); // 3 port RAM RF
 }

// cost of the fpr producer table

long prod_fpr()
 {
  return 4*cand+4*(TAG_WIDTH+1)*cdriv+
         2*c_reg_ram(32, 5, 2, 2); // 4 port RAM producer table
 }

// cost of the spr

long spr()
 {
  return 9*32*cff+               // nine registers
         3*cdecf(5)+             // decoders
         9*32*2*cdriv+           // bus drivers of the read ports
         32*3*cand+              // calculation of the Px[] signals
         cand+
         5*cand+5*cor+           // new value of the IEEEf register
         cor+32*cmux+            // new value of SR
         32*9*cmux+              // input multiplexer
         7*cor;
 }

// cost of the spr producer table

long prod_spr()
 {
  return 2*cand+2*(TAG_WIDTH+1)*cdriv+
         c_reg_ram(32, 5, 2, 2); // 4 port RAM producer table
 }

// cost of the register files

long rf()
 {
  return gpr()+fpr()+spr();
 }

// cost of the producer tables

long prod()
 {
  return prod_gpr()+prod_fpr()+prod_spr();
 }

// cost of the integer alu with CSA,
// including shifter taken from MP95
// (Motorola values)

long alu()
 {
  return 2341+        // ALU
         1338+        // shifter
         4*cmux+cand; // ALU glue
 }

// cost of the reorder buffer and auxiliary circuits

long rob()
 {
  long robramcost;

  if(trick1) // Use two ROB1 RAMs to save delay
   {
    robramcost=(trick2?c_reg_ram:cram)(ROB_SIZE, 105, 4, 2)+ // ROB1, six port RAM
               (trick2?c_reg_ram:cram)(ROB_SIZE, 105, 3, 2)+ // ROB1, five port RAM
               (trick2?c_reg_ram:cram)(ROB_SIZE, 78, 1, 1);  // ROB2
   }
  else
   {
    robramcost=(trick2?c_reg_ram:cram)(ROB_SIZE, 105, 7, 2)+ // ROB1, nine port RAM
               (trick2?c_reg_ram:cram)(ROB_SIZE, 78, 1, 1);  // ROB2
   }

  return cor+cinv+          // tailce
         cinv+cand*32+      // EData
         cor+               // imal
         2*cor+             // bj
         cinv+cand+         // retire
         cor+               // mal
         cand*31+           // CA
         cand*25+           // MCA
         ctree(32)*cor+     // JISR
         ctree(5)*cor+cinv+ // IRQcontinue
         cinv+cor+cand+     // writeback
         cand*2+3*cor+cinv+ // wb.l.RF.w
         cand*2+cor+        // wb.h.RF.w
         cEQ(TAG_WIDTH)*2+  // wb.l/h.Prod.w
         cand*2+                     

         TAG_WIDTH*cff+cincf(TAG_WIDTH)+cand+cinv+ // ROB.head
         TAG_WIDTH*cff+cincf(TAG_WIDTH)+cand+cinv+ // ROB.tail
         (TAG_WIDTH+2)*cff+cand+cinv+
         2*(TAG_WIDTH+2)*cxor+
         cincf(TAG_WIDTH+2)+czero(TAG_WIDTH+2)+
         cEQ(TAG_WIDTH+2)+                         // ROB.count

         robramcost;
 }

// cost of a data memory reservation station address operand
// a conditional sum adder is used to save cycle time
// with a carry lookahead adder, the adder would be on the
// critical path

long rs_address_operand()
 {
  return cff*(TAG_WIDTH+1+32)+
         cor+3*cand+2*cinv+2*cor+
         cEQ(TAG_WIDTH)+
         2*cmux*32+
         cCSA(32);
 }

// cost of a data memory reservation station

long rs_dmem()
 {
  return (TAG_WIDTH+4+1)*(cmux+cff)+cor+cor+
         3*rs_operand(TRUE)+
         rs_address_operand()+
         ctree(6)*cand+
         cEQ(TAG_WIDTH)+cor;
 }

// cost of single adjust before dmem fu

long single_adjust1_dmem()
 {
  return 2*cmux*32;
 }

// align4l is an auxiliary circuit for the data memory

long align4l()
 {
  return 32*cmux+
         16*cmux+
         8*cmux+
         2*cand+
         16*cmux+
         32*cmux+
         64*cmux;
 }

// align4s is an auxiliary circuit for the data memory

long align4s()
 {
  return 3*64*cmux;
 }

// calcuation of the B, H, W, D, misa, and mw[] signals

long mwgen()
 {
  return cdecf(3)+4*cinv+32*cand+32*cor+8*cand;
 }

// cost of the data memory interface

long dmem(int nr_rs)
 {
  return nr_rs*rs_dmem()+
         nr_rs*cdriv*(TAG_WIDTH+4+4*32)+
         rs(TRUE, nr_rs)+
         single_adjust1_dmem()+
         single_adjust2(TRUE)+
         rs_producer(TRUE)+
         cdriv*(TAG_WIDTH+1+64+3+5+32)+       // CDB driver
         cEQ(29)+cEQ(1)+cor+cinv+cor+cand+cand+cand+cor+
         (19+TAG_WIDTH+32)*(cff+cand)+2*cor+2*cinv+2*cand+
         64*cand+
         mwgen()+align4l()+align4s();
 }

// cost of the decode / issue environment

long datagen(int f)
 {
  if(f)
    return 8*32*cmux+2*cand+cor+cinv+
           2*cEQ(TAG_WIDTH);
  else
    return 4*32*cmux+cand+
           cEQ(TAG_WIDTH);
 }

long destgen()
 {
  return cor+                        // dest.gpr
         cor+                        // dest.spr
         cor+cand+                   // dest.db
         cor+2*cand+ctree(6)*5*cmux; // dest.A
 }

// calculation of AEQZ/FCCEQZ

long eqz()
 {
  return czero(32)+2*cinv;
 }

// issuestall calculation

long stallgen()
 {
  return cand+cEQ(5)+cand+cinv+ // IEEEfstall
         cor+cand+cinv+         // bstall
         cand+cinv+             // rfestall
         cand+                  // DOrfe
         5*cor+cand+cinv;       // issuestall
 }

// calculation of fubusy

long futest()
 {
  return ctree(FUS)*cor+FUS*cand+  // FUbusy
         FUS*cand+cinv;            // D.FUi.issue
 }

// the control automata
// cost calculation according to MP95

long opgen()
 {
  return (196-45)*cor+             // output signals ID1
            (1-1)*cor+             // output signals ID2
         13*cinv+(340-39)*cand+    // CM next state ID1
          3*cinv+   (8-4)*cand+    // CM next state ID2
         38*(cand+cor)-(37-1)*cor+ // CN next state ID1
          8*(cand+cor)- (2-1)*cor; // CN next state ID2
 }

long agen()
 {
  return cor+10*cmux+
         cor+cor;
 }

long issue()
 {
  return 2*datagen(TRUE)+2*datagen(FALSE)+
         opgen()+destgen()+
         eqz()+stallgen()+futest();
 }

// cost of the float adder 
// taken from Holger Leister (Motorola values)

long float_adder()
 {
  return 7028+  // unpack
         7809+  // add/sub
         8898;  // rounding
 }

// cost of the float multiply / division unit
// taken from Holger Leister

long float_muldiv()
 {
  return 7028+   // unpack
         31631+  // mul/div
         8898;   // rounding
 }

// cost of the float converter
// ESTIMATED

long float_converter()
 {
  return 7028+   // unpack
         8898;   // rounding
 }

// cost of the float test / transfer
// taken from Holger Leister 

long float_transfer()
 {
  return 2209;
 }

void print(const char *name, long cost, int number,
  const char *figure, long total_cost)
 {
  printf("%s & %ld & %d & %ld & %.1f &",
    name, cost, number, cost*number, (float)cost*number/total_cost*100);

  if(figure[0]!=0)
    printf("\\ref{%s} p.\\pageref{%s}", figure, figure);

  printf(" \\\\ \\hline\n");
 }

void parse_args(int argc, char **argv)
 {
  int i;

  for(i=1; i<argc; i++)
   {
    if(strcasecmp(argv[i], "--addcache")==0)
      addcache=TRUE;
    else if(strcasecmp(argv[i], "--onlysum")==0)
      onlysum=TRUE;
    else if(strcasecmp(argv[i], "--robtable")==0)
      robtable=TRUE;
    else if(strcasecmp(argv[i], "--rstable")==0)
      rstable=TRUE;
    else if(strcasecmp(argv[i], "--comptable")==0)
      comptable=TRUE;
    else if(strcasecmp(argv[i], "--qplot")==0)
      qplot=TRUE;
    else if(checkparam(argv[i]))
     {
      fprintf(stderr, "Unknown option %s\n", argv[i]);
      exit(1);
     }
   }
 }

long int calc()
 {
  long total_cost=0;
  long rs_cost_float_1, rs_cost_float_2, rs_cost_int;
  long im_cost, ir_cost, pc_cost, cdb_control_cost, rf_cost, alu_cost,
       issue_cost, rob_cost, dmem_cost, prod_cost;
  long float_adder_cost, float_muldiv_cost,
       float_converter_cost, float_transfer_cost;
  long cache_cost;

  rs_cost_int=rs(FALSE, rstable?no_rs:4);
  rs_cost_float_1=rs(TRUE, rstable?no_rs:1);
  rs_cost_float_2=rs(TRUE, rstable?no_rs:2);
  im_cost=im();
  ir_cost=ir();
  pc_cost=pc();
  cdb_control_cost=cdb_control();
  rf_cost=rf();
  prod_cost=prod();
  alu_cost=alu();
  issue_cost=issue();
  rob_cost=rob();
  dmem_cost=dmem(rstable?no_rs:4);
  float_adder_cost=float_adder();
  float_muldiv_cost=float_muldiv();
  float_converter_cost=float_converter();
  float_transfer_cost=float_transfer();

  cache_cost=CACHE_COST; // Estimated

  total_cost=
    rs_cost_float_2*2+
    rs_cost_float_1*3+
    rs_cost_int*1+
    im_cost+
    ir_cost+
    pc_cost+
    cdb_control_cost+
    rf_cost+
    prod_cost+
    alu_cost+
    issue_cost+
    rob_cost+
    dmem_cost+
    float_adder_cost+
    float_muldiv_cost+
    float_converter_cost+
    float_transfer_cost;

  if(addcache) total_cost+=cache_cost;

  if(!onlysum)
   { 
    print("two float RS, without FU", rs_cost_float_2, 2, "rsfig", total_cost);
    print("one float RS, without FU", rs_cost_float_1, 3, "rsfig", total_cost);
    print("Floating point adder", float_adder_cost, 1, "", total_cost);
    print("Floating point mul/div unit", float_muldiv_cost, 1, "", total_cost);
    print("Floating point converter", float_converter_cost, 1, "", total_cost);
    print("Floating point transfer", float_transfer_cost, 1, "", total_cost);

    print("four integer RS, without FU", rs_cost_int, 1, "rsfig", total_cost);
    print("Integer ALU", alu_cost, 1, "alufig", total_cost);

    print("Data memory environment (four RS)", dmem_cost, 1, "rsdmemfig", total_cost);

    print("Instruction memory environment", im_cost, 1, "imfig", total_cost);
    print("Instruction register environment", ir_cost, 1, "irfig", total_cost);
    print("PC environment", pc_cost, 1, "pcfig", total_cost);
    print("CDB control environment", cdb_control_cost, 1, "cdb_controlfig", total_cost);
    print("Decode / issue environment", issue_cost, 1, "issuefig", total_cost);
    print("Reorder buffer environment", rob_cost, 1, "robfig", total_cost);
    print("Register files", rf_cost, 1, "rfgprfig", total_cost);
    print("Producer tables", prod_cost, 1, "prod_gprfig", total_cost);

    if(addcache)
      print("16 KB direct mapped cache", cache_cost, 1, "", total_cost);
  
    printf("\\hline\n");
    printf("{\\bf Total} & & & %ld & 100.0 & \\\\ \\hline\n", total_cost);
   }

  return total_cost;
 }

char *robtablestrings[]=
 {
  " 16 & 4 & 1.4720 &  0.0\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  " 32 & 5 & 1.5186 & -3.1\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  " 64 & 6 & 1.4365 &  2.4\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  "128 & 7 & 1.4639 &  0.6\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  NULL
 };

char *rstablestrings[]=
 {
  "   1 & 1.6602 &  0.0\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  "   2 & 1.5644 &  6.1\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  "   4 & 1.5161 &  9.5\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  "   8 & 1.4720 & 12.7\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline \\hline\n",
  "var. & $\\sim$1.5 & 10.7\\%% & %ld & %.1f\\%% & %ld & %.1f\\%% \\\\ \\hline\n",
  NULL
 };

char *names[3]={"Pipelined", "RSR+ROB", "Tomasulo"};
double CPIs[3]={2.12, 1.73, 1.47};
long cost[3]={483928, 544680, 0}; // including cache

// Calculate a point of equal quality

double eq(double CPIa, double Ca, double CPIb, double Cb)
 {
  return 1/((log(Cb/Ca)/log(CPIa/CPIb))+1);
 }

// Calculate the Quality function

double Q(double CPI, double C, double q)
 {
  return 1/(pow(CPI, 1-q)*pow(C, q));
 }

int main(int argc, char *argv[])
 {
  long int total_cost;

  parse_args(argc, argv);

  init_techno(MOTOROLA);

  if(robtable)
   {
    long int refc1=0, refc2=0;
 
    onlysum=TRUE;

    for(int i=4; i<=7; i++)
     {
      long int c1, c2;

      tag_width=i;
      rob_size=power2(tag_width);

      addcache=FALSE;
      c1=calc();

      addcache=TRUE;
      c2=calc();

      if(i==4)
       {
        refc1=c1;
        refc2=c2;
       }

      printf(robtablestrings[i-4], 
             c1, (double)c1*100.0/refc1,
             c2, (double)c2*100.0/refc2);
     }
   }
  else if(rstable)
   {
    long int refc1=0, refc2=0;
 
    onlysum=TRUE;

    for(int i=0; i<=4; i++)
     {
      long int c1, c2;

      no_rs=atoi(rstablestrings[i]);
      rstable=(no_rs!=0);

      addcache=FALSE;
      c1=calc();

      addcache=TRUE;
      c2=calc();

      if(i==0)
       {
        refc1=c1;
        refc2=c2;
       }

      printf(rstablestrings[i], 
             c1, (double)c1*100.0/refc1,
             c2, (double)c2*100.0/refc2);
     }
   }
  else if(comptable)
   {
    onlysum=TRUE;
    addcache=TRUE;
    total_cost=calc();

    cost[2]=total_cost;
    
    printf("\\begin{tabular}{|r|rr|rr|rr|}\n"
           "\\hline\n"
           "                 & \\multicolumn{2}{|c|}{%s}\n"
           "                 & \\multicolumn{2}{|c|}{%s}\n"
           "                 & \\multicolumn{2}{|c|}{%s} \\\\ \\hline \\hline\n"
           "   CPU core only &  %ld & %ld\\%% &  %ld & %ld\\%% &  %ld & %ld\\%% \\\\ \\hline\n"
           "with 16 kb cache &  %ld & %ld\\%% &  %ld & %ld\\%% &  %ld & %ld\\%% \\\\ \\hline\n"
           "     CPI/speedup & %.2f & %ld\\%% & %.2f & %ld\\%% & %.2f & %ld\\%% \\\\ \\hline\n"
           "\\end{tabular}\n",
           names[0], names[1], names[2],
           cost[0]-CACHE_COST, 100L,
           cost[1]-CACHE_COST, (cost[1]-CACHE_COST)*100/(cost[0]-CACHE_COST),
           cost[2]-CACHE_COST, (cost[2]-CACHE_COST)*100/(cost[0]-CACHE_COST),
           cost[0], 100L,
           cost[1], cost[1]*100/cost[0],
           cost[2], cost[2]*100/cost[0],
           CPIs[0], 0L,
           CPIs[1], (long)(CPIs[0]*100/CPIs[1]-100),
           CPIs[2], (long)(CPIs[0]*100/CPIs[2]-100));
   }
  else if(qplot)
   {
    double eq1, eq2;   // points of equal quality
    double qeq1, qeq2; // quality at these points
    double dcost[3];   // nomalized cost

    onlysum=TRUE;
    addcache=TRUE;
    total_cost=calc();

    cost[2]=total_cost;

    for(int i=0; i<3; i++)
      dcost[i]=((double)cost[i])/100000;

    printf("set term pslatex\n"
           "set xlabel \"$q$\"\n"
           "set ylabel \"Quality $Q_q$\"\n"
           "set format x \"$%%g$\"\n"
           "set format y \"$%%.3g$\"\n"
           "set logscale y 2\n"
           "set key 0.7,0.7\n"
           "set label 1 \"only performance counts\" at 0,0.09 left\n"
           "set label 2 \"only cost counts\" at 1,0.09 right\n");

    eq1=eq(CPIs[0], dcost[0], CPIs[1], dcost[1]);
    eq2=eq(CPIs[1], dcost[1], CPIs[2], dcost[2]);
    qeq1=Q(CPIs[1], dcost[1], eq1);
    qeq2=Q(CPIs[2], dcost[2], eq2);

    printf("set arrow 1 from %f,0.120 to %f,%f nohead\n"
           "set arrow 2 from %f,0.120 to %f,%f nohead\n",
           eq1, eq1, qeq1,
           eq2, eq2, qeq2);

    printf("plot [0:1] 1/((%f**(1-x))*(%f**x)) title \"%s\",\\\n"
           "           1/((%f**(1-x))*(%f**x)) title \"%s\",\\\n"
           "           1/((%f**(1-x))*(%f**x)) title \"%s\" with line 4\n",
           CPIs[0], dcost[0], names[0],
           CPIs[1], dcost[1], names[1],
           CPIs[2], dcost[2], names[2]);
   }
  else
   {
    total_cost=calc();

    if(onlysum)
      printf("%ld\n", total_cost);
   }

  return 0;
 }
