entity Second(clk: in, ready: out) {
  let index: uint{..1200000} = 0;

  on clk.posedge {
      if index == 12000000 - 1 {
          ready <= 1;
      } else {
          index <= index + 1;
          ready <= 0;
      }
  }
}

entity Half(clk: in, ready: out) {
  let index: uint{..1200000} = 0;

  on clk.posedge {
      if index == 10000000 - 1 {
          ready <= 1;
      } else {
          index <= index + 1;
          ready <= 0;
      }
  }
}



entity MiniCounter(clk: in, rst: in, ready: out) {
  let index: uint{..400} = 0;

  on clk.posedge {
      if !rst {
          ready <= 0;
          index <= 0;
      } else {
          if index == 240 - 1 {
              ready <= 1;
          } else {
              index <= index + 1;
              ready <= 0;
          }
      }
  }
}

entity SpiMaster(
  rst: in,
  clk: in,
  tx_trigger: in,
  tx_ready: out,
  tx_byte: in[8],
  rx_byte: out[8],
  spi_clk: out,
  spi_tx: out,
  spi_rx: in,
) {
  let live_clk: bit[1] = 0;

  // Internal signals.
  let read_index: uint{0..8} = 0;
  let internal_clk: bit[1] = 0;

  let _FSM: bit[32] = 0;

  // Generate SPI signal from internal clock + SPI state.
  always {
  //    if live_clk {
  //        spi_clk = internal_clk;
  //    } else {
          spi_clk = live_clk && internal_clk;
  //    }
  }

  // Generate divided SPI clock.
  let div_idx: uint{..40} = 0;
  on clk.negedge {
      if !rst {
          div_idx <= 0;
          internal_clk <= 0;
      } else {
          if div_idx == 40 - 1 {
              internal_clk <= !internal_clk;
              div_idx <= 0;
          } else {
              div_idx <= div_idx + 1;
          }
      }
  }

  // Sample read values from positive clock edge.
  on internal_clk.posedge {
      rx_byte[read_index] <= spi_rx;
  }

  let transmitting = 0;
  let transmit_save = 1;

  on clk.posedge {
      if !rst {
          tx_ready <= 0;
          transmit_save <= 1;
      } else {
          // if tx_trigger is high, and we are not transmitting, start
          if transmit_save == transmitting {
              tx_ready <= 1;
              transmit_save <= !transmitting;
          } else if tx_trigger {
              tx_ready <= 0;
          }
      }
  }

  // SPI output state machine.
  on internal_clk.negedge {
      fsm {
          // Wait for transition trigger.
          spi_tx <= 0;
          await tx_ready == 0;

          // Enable output clock.
          live_clk <= 1;

          // Start sequence.
          read_index <= 7;
          spi_tx <= tx_byte[7];
          yield;

          // Write bits.
          while read_index > 0 {
              spi_tx <= tx_byte[read_index - 1];
              read_index <= read_index - 1;
              yield;
          }

          // Disable output clock.
          live_clk <= 0;
          transmitting <= !transmitting;

          // Loop forever.
          //loop {
          //    yield;
          //}
      }
  }
}

// entity SpiRunner(
//   rst: in,
//   clk: in,
//   tx_trigger: in,
//   tx_ready: out,
//   tx_byte: in[8],
//   rx_byte: out[8],
//   spi_clk: out,
//   spi_tx: out,
//   spi_rx: in,
// ) {
// }

entity Ethernet(
    rst: in,
    tx_clk: in,
    LED1: out,
    LED2: out,
    LED3: out,
    LED4: out,
    CS: out,
    spi_bit: out, // MOSI
    spi_rx: in, // MISO
    spi_clk: out,
) {
    let tx_valid = 0;
    let tx_byte: bit[8] = 0;
    let spi_ready;
    let spi_rx_value: bit[8];
    let spi = SpiMaster {
        rst: rst,
        clk: tx_clk,
        tx_trigger: tx_valid,
        tx_ready: spi_ready,
        tx_byte: tx_byte,
        rx_byte: spi_rx_value,
        spi_clk: spi_clk,
        spi_tx: spi_bit,
        spi_rx: spi_rx
    };

    let sleep_counter: uint{..1200000} = 0;

    const ERCRU = 0x20;
    const EWCRU = 0x22;
    const EEUDASTL = (0x16 | 0x00);
    const ESSETETHRST = 0b11001010;
    const EECON2L = (0x0E | 0x60);
    const EERXSTL = (0x04 | 0x00);
    const EMAMXFLL = (0x0A | 0x40);
    const EERXTAILL = (0x06 | 0x00);

    const EMAADR3L = (0x00 | 0x60);
    const EMAADR3H = (0x01 | 0x60);
    const EMAADR2L = (0x02 | 0x60);
    const EMAADR2H = (0x03 | 0x60);
    const EMAADR1L = (0x04 | 0x60);
    const EMAADR1H = (0x05 | 0x60);

    const ESENABLERX = 0b11101000;

    const ERCR = 0x00;
    const EESTATL = (0x1A | 0x00);

    const EERXRDPTL = (0x8A);

    const ERRXDATA = 0b00101100;


    let mini_delay_trigger;
    let mini_delay_result;
    let mini_delay = MiniCounter {
      clk: tx_clk,
      rst: mini_delay_trigger,
      ready: mini_delay_result,
    };

#define do_mini_delay mini_delay_trigger <= 1; await mini_delay_result; mini_delay_trigger <= 0
#define do_cs_toggle tx_valid <= 0; yield; CS <= 1; mini_delay_trigger <= 1; await mini_delay_result; mini_delay_trigger <= 0; CS <= 0; tx_valid <= 1


#define write_16(reg, A, B) \
    tx_byte <= EWCRU; \
    await spi_ready; \
    tx_byte <= reg; \
    await spi_ready; \
    tx_byte <= A; \
    await spi_ready; \
    tx_byte <= B; \
    await spi_ready

#define read_16(reg, A, B) \
    tx_byte <= ERCRU; \
    await spi_ready; \
    tx_byte <= reg; \
    await spi_ready; \
    await spi_ready; \
    A <= spi_rx_value; \
    await spi_ready; \
    B := spi_rx_value

#define write_byte(reg) \
    tx_byte <= reg; \
    await spi_ready


#define read_byte(reg) \
    await spi_ready; \
    reg <= spi_rx_value

#define read_byte_imm(reg) \
    await spi_ready; \
    reg := spi_rx_value

    let received: bit[16] = 0;
    let NextPacketPointerL: bit[8] = 0x40;
    let NextPacketPointerH: bit[8] = 0x53;
    let _FSM: bit[9] = 0;

    let checksumL: bit[8] = 0;
    let checksumH: bit[8] = 0;

    let dummy: bit[8] = 0;
    let status_vector: bit[8] = 0;

    let opcode: bit[16] = 0;
    let proto: bit[16] = 0;
    let arp_test: bit[16] = 0;

    on tx_clk.negedge {
        if !rst {
            CS <= 1;
            _FSM <= 0;
            tx_valid <= 0;
        } else {
            fsm {
                LED1 <= 1;

                CS <= 0;
                tx_valid <= 1;

                // enc424j600_init()

                write_16(EEUDASTL, 0x34, 0x12);

                do_cs_toggle;

                read_16(EEUDASTL, checksumL, checksumH);
                if checksumL == 0x34 && checksumH == 0x12 {
                    LED2 <= 1;
                }

                do_cs_toggle;

                write_byte(ESSETETHRST);

                tx_valid <= 0;
                sleep_counter := 0;
                while sleep_counter < 360 {
                    sleep_counter := sleep_counter + 1;
                    yield;
                }
                yield;

                tx_valid <= 1;

                write_16(EECON2L, 0x00, 0xCB); // (magic number)
                do_cs_toggle;
                write_16(EERXSTL, 0x40, 0x53); // RX_BUFFER_START 0x5340
                do_cs_toggle;
                write_16(EMAMXFLL, 0x42, 0x02); // MAX_FRAMELEN 0x0242
                do_cs_toggle;
                write_16(EERXTAILL, 0xFE, 0x5F); // (magic number)
                do_cs_toggle;

                // Read MAC address
                read_16(EMAADR1L, dummy, dummy); // MAC 0:1
                do_cs_toggle;
                read_16(EMAADR2L, dummy, dummy); // MAC 2:3
                do_cs_toggle;
                read_16(EMAADR3L, dummy, dummy); // MAC 4:5

                do_cs_toggle;
                write_byte(ESENABLERX);

                //Memory configuration
                //The ENC424j600 has 0x6000 (24kB) bytes of memory
                //We have to make good use of it.
                // 0x0000
                //  [Scratchpad]
                // 0x0400
                //  [TCP packets (578+42)*TCP_SOCKETS
                // 0x1b84 (assuming 10 sockets)
                //  [unused area]
                // 0x5340 (RX_BUFFER_START (0x6000-RX_BUFFER_SIZE))
                //  [RX Buffer]
                // 0x6000 (End of standard SRAM)

                NextPacketPointerL <= 0x40;
                NextPacketPointerH <= 0x53;

                // enc424j600_recvpack()

                loop {
                    do_cs_toggle;
                    write_byte(ESENABLERX);
                    do_cs_toggle;
                    write_byte(ERCR | EESTATL);
                    read_byte_imm(dummy);

                    if dummy > 0 {
                        //Configure ERXDATA for reading.
                        do_cs_toggle;
                        write_16(EERXRDPTL, NextPacketPointerL, NextPacketPointerH);
                        do_cs_toggle;

                        //Start reading!!!
                        write_byte(ERRXDATA);

                        // Read next packet pointer.
                        read_byte(NextPacketPointerL);
                        read_byte(NextPacketPointerH);

                        // Read received byte count.
                        read_byte(received[0:8]);
                        read_byte(received[8:16]);

                        read_byte_imm(status_vector);
                        read_byte(dummy);
                        if status_vector & (1 << 7) {
                            LED3 <= 1;

                            // Good packet.
                            read_byte(dummy);
                            read_byte(dummy);

                            if received > 8 {
                                // macto (ignore) our mac filter handles this.
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);

                                // Download macfrom
                                //TODO enc424j600_popblob( macfrom, 6 );
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);
                                read_byte(dummy);

                                //Make sure it is ethernet!
                                read_byte_imm(dummy);
                                if (dummy != 0x08) {
                                    LED4 <= 1;
                                    // TODO break;
                                }

                                //Is it ARP?
                                read_byte_imm(arp_test);
                                if (arp_test == 0x06) {
                                    //Hardware type
                                    read_byte(dummy);
                                    read_byte(dummy);

                                    // Proto
                                    read_byte(proto[0:8]);
                                    read_byte(proto[8:16]);

                                    //hwsize, protosize
                                    read_byte(dummy);
                                    read_byte(dummy);

                                    //XXX: This includes "code" as well, it seems.
                                    read_byte_imm(opcode[0:8]);
                                    read_byte_imm(opcode[8:16]);

                                    // ARP Request
                                    if opcode == 1 {
                                        // unsigned char match;
                                        //
                                        // enc424j600_popblob( sendermac_ip_and_targetmac, 16 );
                                        //
                                        // match = 1;
                                        //
                                        // //Target IP (check for copy)
                                        // for( i = 0; i < 4; i++ )
                                        //     if( enc424j600_pop8() != MyIP[i] )
                                        //         match = 0;
                                        //
                                        // if( match == 0 )
                                        //     return;
                                        //
                                        // //We must send a response, so we termiante the packet now.
                                        // enc424j600_finish_callback_now();
                                        // enc424j600_startsend( NetGetScratch() );
                                        // send_etherlink_header( 0x0806 );
                                        //
                                        // write_reg(0x00); write_reg(0x01); //Ethernet
                                        // write_reg(proto[0:8]); write_reg(proto[8:16]);  //Protocol
                                        // write_reg(0x06); write_reg(0x04); //HW size, Proto size
                                        // write_reg(0x00); write_reg(0x02); //Reply
                                        //
                                        // enc424j600_pushblob( MyMAC, 6 );
                                        // enc424j600_pushblob( MyIP, 4 );
                                        // enc424j600_pushblob( sendermac_ip_and_targetmac, 10 ); // do not send target mac.
                                        //
                                        // enc424j600_endsend();
                                    }

                                    // ARP Reply
                                    if opcode == 2 {
                                        // uint8_t sender_mac_and_ip_and_comp_mac[16];
                                        // enc424j600_popblob( sender_mac_and_ip_and_comp_mac, 16 );
                                        // enc424j600_finish_callback_now();
                                        //
                                        //
                                        // //First, make sure that we are the ones who are supposed to receive the ARP.
                                        // for( i = 0; i < 6; i++ )
                                        // {
                                        //     if( sender_mac_and_ip_and_comp_mac[i+10] != MyMAC[i] )
                                        //         break;
                                        // }
                                        //
                                        // if( i != 6 )
                                        //     break;
                                        //
                                        // //Were the right recipent.  Put it in the table.
                                        // memcpy( &ClientArpTable[ClientArpTablePointer], sender_mac_and_ip_and_comp_mac, 10 );
                                        //
                                        // ClientArpTablePointer = (ClientArpTablePointer+1)%ARP_CLIENT_TABLE_SIZE;
                                    }
                                }

                                if (arp_test != 0x06) {
                                    // Standard IP
                                    //So, we're expecting a '45
                                    read_byte_imm(dummy);
                                    if (dummy != 0x45) {
                                        // ERROR: Not an IP packet
                                        LED4 <= 1;
                                    }

                                    //differentiated services field.
                                    read_byte(dummy);
                                    read_byte(dummy);

                                    // ip total len
                                    // iptotallen =
                                    read_byte(dummy);
                                    read_byte(dummy);

                                    //ID, Offset+FLAGS+TTL (5 bytes)
                                    read_byte(dummy);
                                    read_byte(dummy);
                                    read_byte(dummy);
                                    read_byte(dummy);
                                    read_byte(dummy);

                                    // TODO
                                    // ipproto = enc424j600_pop8();

                                    //header checksum
                                    read_byte(dummy);
                                    read_byte(dummy);

                                    // popblob
                                    // enc424j600_popblob( ipsource, 4 );
                                    //
                                    // for (i = 0; i < 4; i++) {
                                    //     unsigned char m = ~MyMask[i];
                                    //     unsigned char ch = enc424j600_pop8();
                                    //     if (ch == MyIP[i] || (ch & m) == 0xff) {
                                    //         continue;
                                    //     }
                                    //     is_the_packet_for_me = 0;
                                    // }
                                    //
                                    // //Tricky, for DHCP packets, we have to detect it even if it is not to us.
                                    // if (ipproto == 17) {
                                    //     remoteport = enc424j600_pop16();
                                    //     localport = enc424j600_pop16();
                                    // }
                                    //
                                    // if (!is_the_packet_for_me) {
                                    //     // ERROR: Packet is not for us
                                    //     return 1;
                                    // }
                                    //
                                    // //XXX TODO Handle IPL > 5  (IHL?)
                                    //
                                    // switch(ipproto) {
                                    //     // ICMP
                                    //     case 1: {
                                    //         HandleICMP();
                                    //         break;
                                    //     }
                                    //
                                    //     // UDP
                                    //     case 17: {
                                    //         HandleUDP(enc424j600_pop16());
                                    //         break;
                                    //     }
                                    //
                                    //     default: {
                                    //         break;
                                    //     }
                                    // }
                                    //
                                    // return 0;
                                }
                            }
                        }
                        // else {
                          // ERROR: Bad packet
                          // I have never observed tis code getting called, even when I saw dropped packets.
                        // }
                    }

                    tx_valid <= 0;
                    yield;
                }

                CS <= 1;
                loop {
                    yield;
                }
            }
        }
    }
}

entity Main(
    clk: in,
    LED1: out,
    LED2: out,
    LED3: out,
    LED4: out,
    LED5: out,
    PMOD1: out,
    PMOD2: out,
    PMOD3: in,
    PMOD4: out,
    PMOD7: out,
    //PMOD8: out,
    //PMOD9: out,
    //PMOD10: out,
) {
    // PMOD1 = CS
    // PMOD2 = MOSI
    // PMOD3 = MISO
    // PMOD4 = SCLK
    let ready;
    let sec = Second { clk: clk, ready: ready };
    let half = Half { clk: clk, ready: PMOD7 };
    let ether = Ethernet {
        rst: ready,
        tx_clk: clk,
        LED1: LED1,
        LED2: LED2,
        LED3: LED3,
        LED4: LED4,
        CS: PMOD1,
        spi_bit: PMOD2,
        spi_rx: PMOD3,
        spi_clk: PMOD4,
    };

    always {
        LED5 = !ready;
    }
}