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

#define NTSC 0
#define PAL 1

// This matches the verilator behavior in terms of when a signal becomes
// valid when we set a register based on pos or neg edge of the clock.
#define POS_TICK(n) ((n+1)%num_dc)
#define NEG_TICK(n) (n)
#define UNUSED() (-1)

// Final combined waves
int *ras_wave;
int *cas_wave;

// These are dot 4x clock signals
int *ras_wave_dc_p;
int *cas_wave_dc_p;
int *ras_wave_dc_n;
int *cas_wave_dc_n;

// These are color 16x clock signals
int *ras_wave_cc_p;
int *cas_wave_cc_p;
int *ras_wave_cc_n;
int *cas_wave_cc_n;
int *addr_wave_dc_p;

// Other signals useful for viewing in gtkview
int *clk_phi;
int *clk_cc; // dot4x
int *clk_dc; // col16x

int num_repeats = 10;

int fs_per_tick;

void init_signals(int n) {

    ras_wave = malloc(n * sizeof(int));
    cas_wave = malloc(n * sizeof(int));

    ras_wave_dc_p = malloc(n * sizeof(int));
    cas_wave_dc_p = malloc(n * sizeof(int));
    ras_wave_dc_n = malloc(n * sizeof(int));
    cas_wave_dc_n = malloc(n * sizeof(int));

    ras_wave_cc_p = malloc(n * sizeof(int));
    cas_wave_cc_p = malloc(n * sizeof(int));
    ras_wave_cc_n = malloc(n * sizeof(int));
    cas_wave_cc_n = malloc(n * sizeof(int));

    addr_wave_dc_p = malloc(n * sizeof(int));

    clk_phi = malloc(n * sizeof(int));
    clk_cc = malloc(n * sizeof(int));
    clk_dc = malloc(n * sizeof(int));

    int s = n * sizeof(int);
    bzero (cas_wave, s);
    bzero (ras_wave, s);

    bzero (ras_wave_dc_p, s);
    bzero (cas_wave_dc_p, s);
    bzero (ras_wave_dc_n, s);
    bzero (cas_wave_dc_n, s);

    bzero (ras_wave_cc_p, s);
    bzero (cas_wave_cc_p, s);
    bzero (ras_wave_cc_n, s);
    bzero (cas_wave_cc_n, s);

    bzero (addr_wave_dc_p, s);

    bzero (clk_phi, s);
    bzero (clk_cc, s);
    bzero (clk_dc, s);
}

void output_wave(int num_points) {
   // Now output for gtkwave
   printf ("$version Generated by VerilatedVcd $end\n");
   printf ("$date Wed Feb 14 22:15:28 2024\n");
   printf (" $end\n");
   printf ("$timescale   1fs $end\n");
   printf ("\n");
   printf (" $scope module TOP $end\n");
   printf ("  $var wire  1 a clk_cc $end\n");
   printf ("  $var wire  1 b clk_dc $end\n");
   printf ("  $var wire  1 c clk_phi $end\n");
   printf ("  $var wire  1 A ras_cc_p $end\n");
   printf ("  $var wire  1 B ras_cc_n $end\n");
   printf ("  $var wire  1 C cas_cc_p $end\n");
   printf ("  $var wire  1 D cas_cc_n $end\n");
   printf ("  $var wire  1 G ras_dc_p $end\n");
   printf ("  $var wire  1 H cas_dc_p $end\n");
   printf ("  $var wire  1 I ras_dc_n $end\n");
   printf ("  $var wire  1 J cas_dc_n $end\n");
   printf ("  $var wire  1 E cas $end\n");
   printf ("  $var wire  1 F ras $end\n");
   printf ("  $var wire  1 K addr $end\n");
   printf (" $upscope $end\n");
   printf ("$enddefinitions $end\n");
   printf ("\n");
   printf ("\n");

   int tick=0;
   for (int i=0;i<num_points * num_repeats;i++) {
      printf ("#%d\n",tick);
      printf ("%dc\n",clk_phi[i]);
      printf ("%da\n",clk_cc[i]);
      printf ("%db\n",clk_dc[i]);
      printf ("%dA\n",ras_wave_cc_p[i]);
      printf ("%dB\n",ras_wave_cc_n[i]);
      printf ("%dC\n",cas_wave_cc_p[i]);
      printf ("%dD\n",cas_wave_cc_n[i]);
      printf ("%dE\n",cas_wave[i]);
      printf ("%dF\n",ras_wave[i]);
      printf ("%dG\n",ras_wave_dc_p[i]);
      printf ("%dH\n",cas_wave_dc_p[i]);
      printf ("%dI\n",ras_wave_dc_n[i]);
      printf ("%dJ\n",cas_wave_dc_n[i]);
      printf ("%dK\n",addr_wave_dc_p[i]);
      tick = tick + fs_per_tick;
   }
}

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

   int firmware_version = -1;
   int standard = -1;

   while ((c = getopt (argc, argv, "hv:s:")) != -1) {
       switch (c) {
          case 'h':
             printf ("Usage: wave [-h] -v <firmare_version> -s <standard>\n");
             printf ("    -h : show help\n");
             printf ("    -v <firmware_version> : i.e. 17, 18\n");
             printf ("    -s <standard> : pal or ntsc\n");
             return 1;
          case 'v':
             firmware_version = atoi(optarg);
             break;
          case 's':
             if (strcmp(optarg,"pal") == 0) {
                 standard = PAL;
             } else if (strcmp(optarg,"ntsc") == 0) {
                 standard = NTSC;
             } else {
                 printf ("Bad standard arg\n");
                 exit(-1);
             }
             break;
          default:
             // Unknown option, bail...
             return -1;
       }
    }

   if (firmware_version < 0) {
      printf ("Bad/missing firmware version arg\n");
      exit(-1);
   }
   if (standard < 0) {
      printf ("Bad/missing standard arg\n");
      exit(-1);
   }

   // This represents how many ticks of the color16x or dot4x clock we get for each period 
   // of the PHI clock. For NTSC, it is 28.  For PAL it would be 36.
   int num_cc = (standard == PAL ? 36 : 28);
   int num_dc = 16;

   int num_cc_points = num_cc * 2;
   int num_dc_points = num_dc * 2;

   int num_points = num_cc_points * num_dc_points;

   double clock_freq = (standard == PAL ? 0.9852485937d : 1.02272725d);
   fs_per_tick = ((1.0d/clock_freq) * 1000000000.0d) / (num_points * 2);

   init_signals(num_points * num_repeats);
 
   int ras_rise_dc_p;
   int ras_fall_dc_p;

   int cas_rise_dc_p;
   int cas_fall_dc_p;

   int ras_rise_dc_n;
   int ras_fall_dc_n;

   int cas_rise_dc_n;
   int cas_fall_dc_n;

   int ras_rise_cc_p;
   int ras_fall_cc_p;

   int ras_rise_cc_n;
   int ras_fall_cc_n;

   int cas_rise_cc_p;
   int cas_fall_cc_p;

   int cas_rise_cc_n;
   int cas_fall_cc_n;

   int addr_mux_p = 0;

   // Current values of each signal
   int cur_ras_dc_p = 0;
   int cur_cas_dc_p = 0;

   int cur_ras_dc_n = 0;
   int cur_cas_dc_n = 0;

   int cur_ras_cc_p = 0;
   int cur_ras_cc_n = 0;

   int cur_cas_cc_p = 0;
   int cur_cas_cc_n = 0;

   int cur_phi = 0;
   int cur_cc = 0;
   int cur_dc = 0;

   int cur_addr = 0;

   // Notes to self;  The reason the col16x ras/cas pulses are so short is because
   // we only use them to shape one side of the overall signal.  There's no need to
   // extend them further since the dot4x signal is OR'd anyway.
   if (firmware_version <= 17) {
      ras_rise_dc_p = POS_TICK(0);
      ras_fall_dc_p = POS_TICK(4);

      cas_rise_dc_p = POS_TICK(0);
      cas_fall_dc_p = POS_TICK(6);

      ras_rise_dc_n = UNUSED();
      ras_fall_dc_n = UNUSED();

      cas_rise_dc_n = NEG_TICK(1);
      cas_fall_dc_n = NEG_TICK(7);

      ras_rise_cc_p = UNUSED();
      ras_fall_cc_p = UNUSED();

      ras_rise_cc_n = NEG_TICK(1);
      ras_fall_cc_n = NEG_TICK(3);

      cas_rise_cc_p = POS_TICK(0);
      cas_fall_cc_p = POS_TICK(2);

      cas_rise_cc_n = UNUSED();
      cas_fall_cc_n = UNUSED();

      addr_mux_p = POS_TICK(5);
   } else if (firmware_version >= 18) {
      // Still matches 17 for now...
      ras_rise_dc_p = POS_TICK(0);
      ras_fall_dc_p = POS_TICK(4);

      cas_rise_dc_p = POS_TICK(0);
      cas_fall_dc_p = POS_TICK(6);

      ras_rise_dc_n = UNUSED();
      ras_fall_dc_n = UNUSED();

      cas_rise_dc_n = NEG_TICK(1);
      cas_fall_dc_n = NEG_TICK(7);

      ras_rise_cc_p = UNUSED();
      ras_fall_cc_p = UNUSED();

      ras_rise_cc_n = NEG_TICK(1);
      ras_fall_cc_n = NEG_TICK(3);

      cas_rise_cc_p = POS_TICK(1);
      cas_fall_cc_p = POS_TICK(2);

      cas_rise_cc_n = UNUSED();
      cas_fall_cc_n = UNUSED();

      addr_mux_p = POS_TICK(5);
   }

   // Each signal arrays hold enough points to represent positive and negative edges for both clocks  
   // So as we iterate over the points, we determine whether we are on a positive or negative edge
   // for dot4x or col16x
   for (int i=0;i<num_points * num_repeats;i++) {

      int cc_tick = (i / (num_dc_points * 2)) % num_cc; // this is the col16x tick #
      int dc_tick = (i / (num_cc_points * 2)) % num_dc; // this is the dot4x tick #

      // Now determine whether we are at a positive or negative edge

      int pos_cc = 0;
      int neg_cc = 0;

      // Yes, this is correct. Use num_dc_points divisor.
      if (i % num_dc_points == 0) {
         // This is a CC tick
         if ((i / num_dc_points % 2) == 0) {
            // POS EDGE
            pos_cc = 1;
         } else {
            // NEG EDGE
            neg_cc = 1;
         }
      }

      int pos_dc = 0;
      int neg_dc = 0;

      // Yes, this is correct. Use num_cc_points divisor.
      if (i % num_cc_points == 0) {
         // This is a DC tick
         if ((i / num_cc_points % 2) == 0) {
            // POS EDGE
            pos_dc = 1;
         } else {
            // NEG EDGE
            neg_dc = 1;
         }
      }

      // Now generate the signals like our logic in addressgen
      if (pos_cc) cur_cc = 1;
      if (neg_cc) cur_cc = 0;

      if (pos_dc) cur_dc = 1;
      if (neg_dc) cur_dc = 0;

      if (pos_dc && dc_tick==0) cur_phi = 1 - cur_phi;

      if (pos_dc && dc_tick==ras_rise_dc_p) cur_ras_dc_p = 1;
      if (pos_dc && dc_tick==ras_fall_dc_p) cur_ras_dc_p = 0;

      if (pos_dc && dc_tick==cas_rise_dc_p) cur_cas_dc_p = 1;
      if (pos_dc && dc_tick==cas_fall_dc_p) cur_cas_dc_p = 0;

      if (neg_dc && dc_tick==ras_rise_dc_n) cur_ras_dc_n = 1;
      if (neg_dc && dc_tick==ras_fall_dc_n) cur_ras_dc_n = 0;

      if (neg_dc && dc_tick==cas_rise_dc_n) cur_cas_dc_n = 1;
      if (neg_dc && dc_tick==cas_fall_dc_n) cur_cas_dc_n = 0;

      if (pos_cc && cc_tick==ras_rise_cc_p) cur_ras_cc_p = 1;
      if (pos_cc && cc_tick==ras_fall_cc_p) cur_ras_cc_p = 0;

      if (pos_cc && cc_tick==cas_rise_cc_p) cur_cas_cc_p = 1;
      if (pos_cc && cc_tick==cas_fall_cc_p) cur_cas_cc_p = 0;
 
      if (neg_cc && cc_tick==ras_rise_cc_n) cur_ras_cc_n = 1;
      if (neg_cc && cc_tick==ras_fall_cc_n) cur_ras_cc_n = 0;

      if (neg_cc && cc_tick==cas_rise_cc_n) cur_cas_cc_n = 1;
      if (neg_cc && cc_tick==cas_fall_cc_n) cur_cas_cc_n = 0;

      if (pos_dc && dc_tick==addr_mux_p) cur_addr = 1 - cur_addr;

      // Mark HI or LO depending on current signal value
      clk_cc[i] = cur_cc;
      clk_dc[i] = cur_dc;
      clk_phi[i] = cur_phi;

      ras_wave_dc_p[i] = cur_ras_dc_p;
      cas_wave_dc_p[i] = cur_cas_dc_p;      

      ras_wave_dc_n[i] = cur_ras_dc_n;      
      cas_wave_dc_n[i] = cur_cas_dc_n;      

      ras_wave_cc_p[i] = cur_ras_cc_p;      
      ras_wave_cc_n[i] = cur_ras_cc_n;      

      cas_wave_cc_p[i] = cur_cas_cc_p;      
      cas_wave_cc_n[i] = cur_cas_cc_n;      

      addr_wave_dc_p[i] = cur_addr;

      // For CAS/RAS, we combine several signals:

      // See addressgen_efinix.v
      // assign cas = chip[0] ? (pal_cas_d4x_p | pal_cas_d4x_n | pal_cas_c16x_p) : (ntsc_cas_d4x_p | ntsc_cas_d4x_n | ntsc_cas_c16x_p);
      // assign ras = chip[0] ? (pal_ras_d4x | pal_ras_c16x_n) : (ntsc_ras_d4x | ntsc_ras_c16x_n);
      cas_wave[i] = cas_wave_dc_p[i] | cas_wave_dc_n[i] | cas_wave_cc_p[i];
      ras_wave[i] = ras_wave_dc_p[i] | ras_wave_cc_n[i];
   }

   output_wave(num_points);
}
