// Copyright 2022 hrpccs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package peripheral

import chisel3._
import chisel3.util._

class HDMISync extends Module {
  val io = IO(new Bundle {
    val hsync = Output(Bool())
    val vsync = Output(Bool())
    val video_on = Output(Bool())
    val p_tick = Output(Bool())
    val f_tick = Output(Bool())
    val x = Output(UInt(10.W))
    val y = Output(UInt(10.W))
    val x_next = Output(UInt(10.W))
    val y_next = Output(UInt(10.W))
  })

  val DisplayHorizontal = ScreenInfo.DisplayHorizontal
  val DisplayVertical = ScreenInfo.DisplayVertical

  val BorderLeft = 48
  val BorderRight = 16
  val BorderTop = 10
  val BorderBottom = 33

  val RetraceHorizontal = 96
  val RetraceVertical = 2

  val MaxHorizontal = DisplayHorizontal + BorderLeft + BorderRight + RetraceHorizontal - 1
  val MaxVertical = DisplayVertical + BorderTop + BorderBottom + RetraceVertical - 1

  val RetraceHorizontalStart = DisplayHorizontal + BorderRight
  val RetraceHorizontalEnd = RetraceHorizontalStart + RetraceHorizontal - 1

  val RetraceVerticalStart = DisplayVertical + BorderBottom
  val RetraceVerticalEnd = RetraceVerticalStart + RetraceVertical - 1

  val pixel = RegInit(UInt(3.W), 0.U)
  val pixel_next = Wire(UInt(3.W))
  val pixel_tick = Wire(Bool())

  val v_count_reg = RegInit(UInt(10.W), 0.U)
  val h_count_reg = RegInit(UInt(10.W), 0.U)

  val v_count_next = Wire(UInt(10.W))
  val h_count_next = Wire(UInt(10.W))

  val vsync_reg = RegInit(Bool(), false.B)
  val hsync_reg = RegInit(Bool(), false.B)

  val vsync_next = Wire(Bool())
  val hsync_next = Wire(Bool())

  pixel_next := Mux(pixel === 4.U, 0.U, pixel + 1.U)
  pixel_tick := pixel === 0.U

  h_count_next := Mux(
    pixel_tick,
    Mux(h_count_reg === MaxHorizontal.U, 0.U, h_count_reg + 1.U),
    h_count_reg
  )

  v_count_next := Mux(
    pixel_tick && h_count_reg === MaxHorizontal.U,
    Mux(v_count_reg === MaxVertical.U, 0.U, v_count_reg + 1.U),
    v_count_reg
  )

  hsync_next := h_count_reg >= RetraceHorizontalStart.U && h_count_reg <= RetraceHorizontalEnd.U
  vsync_next := v_count_reg >= RetraceVerticalStart.U && v_count_reg <= RetraceVerticalEnd.U

  pixel := pixel_next
  hsync_reg := hsync_next
  vsync_reg := vsync_next
  v_count_reg := v_count_next
  h_count_reg := h_count_next

  io.video_on := h_count_reg < DisplayHorizontal.U && v_count_reg < DisplayVertical.U
  io.hsync := hsync_reg
  io.vsync := vsync_reg
  io.x := h_count_reg
  io.y := v_count_reg
  io.x_next := h_count_next
  io.y_next := v_count_next
  io.p_tick := pixel_tick
  io.f_tick := io.x === 0.U && io.y === 0.U
}

class TMDS_encoder extends Module {
  val io = IO(new Bundle() {
    val video_data = Input(UInt(8.W)) //r,g,b,8bit
    val control_data = Input(UInt(2.W))
    val video_on = Input(Bool())
    val TMDS = Output(UInt(10.W))
  })
  val Nb1s = PopCount(io.video_data)
  val xored = xorfct(io.video_data)
  val xnored = xnorfct(io.video_data)
  val XNOR = (Nb1s > 4.U(4.W)) || (Nb1s === 4.U(4.W) && io.video_data(0) === 0.U)
  val q_m = RegInit(0.U(9.W))
  val diffSize = 4
  val diff = RegInit(0.S(diffSize.W))
  q_m := Mux(
    XNOR,
    xnored,
    xored
  )
  val disparitySize = 4
  val disparityReg = RegInit(0.S(disparitySize.W))
  diff := PopCount(q_m).asSInt - 4.S
  val doutReg = RegInit("b1010101011".U(10.W))

  def xorfct(value: UInt): UInt = {
    val vin = VecInit(value.asBools)
    val res = VecInit(511.U.asBools)
    res(0) := vin(0)
    for (i <- 1 to 7) {
      res(i) := res(i - 1) ^ vin(i)
    }
    res(8) := 1.U
    res.asUInt
  }

  def xnorfct(value: UInt): UInt = {
    val vin = VecInit(value.asBools)
    val res = VecInit(511.U.asBools)
    res(0) := vin(0)
    for (i <- 1 to 7) {
      res(i) := !(res(i - 1) ^ vin(i))
    }
    res(8) := 0.U
    res.asUInt
  }

  when(io.video_on === false.B) {
    disparityReg := 0.S
    doutReg := "b1010101011".U(10.W)
    switch(io.control_data) {
      is("b00".U(2.W)) {
        doutReg := "b1101010100".U(10.W)
      }
      is("b01".U(2.W)) {
        doutReg := "b0010101011".U(10.W)
      }
      is("b10".U(2.W)) {
        doutReg := "b0101010100".U(10.W)
      }
    }
  }.otherwise {
    when(disparityReg === 0.S || diff === 0.S) {
      when(q_m(8) === false.B) {
        doutReg := "b10".U(2.W) ## ~q_m(7, 0)
        disparityReg := disparityReg - diff
      }.otherwise {
        doutReg := "b01".U(2.W) ## q_m(7, 0)
        disparityReg := disparityReg + diff
      }
    }.elsewhen((!diff(diffSize - 1) && !disparityReg(disparitySize - 1))
      || (diff(diffSize - 1) && disparityReg(disparitySize - 1))) {
      doutReg := 1.U(1.W) ## q_m(8) ## ~q_m(7, 0)
      when(q_m(8)) {
        disparityReg := disparityReg + 1.S - diff
      }.otherwise {
        disparityReg := disparityReg - diff
      }
    }.otherwise {
      doutReg := 0.U(1.W) ## q_m
      when(q_m(8)) {
        disparityReg := disparityReg + diff
      }.otherwise {
        disparityReg := disparityReg - 1.S + diff
      }
    }
  }

  io.TMDS := doutReg
}

class HDMIDisplay extends Module {
  val io = IO(new Bundle() {
    val rgb = Input(UInt(24.W))
    val x = Output(UInt(16.W))
    val y = Output(UInt(16.W))
    val x_next = Output(UInt(16.W))
    val y_next = Output(UInt(16.W))
    val video_on = Output(Bool())

    val TMDSclk_p = Output(Bool())
    val TMDSdata_p = Output(UInt(3.W))
    val TMDSclk_n = Output(Bool())
    val TMDSdata_n = Output(UInt(3.W))
  })
  val rgb = io.rgb
  val pixel_clk = Wire(Bool())
  val hsync = Wire(Bool())
  val vsync = Wire(Bool())
  val sync = Module(new HDMISync)

  io.x := sync.io.x
  io.y := sync.io.y
  io.x_next := sync.io.x_next
  io.y_next := sync.io.y_next
  io.video_on := sync.io.video_on

  hsync := sync.io.hsync
  vsync := sync.io.vsync
  pixel_clk := sync.io.p_tick

  // TMDS_PLLVR is a vivado IP core, check it in /verilog/pynq/TMDS_PLLVR.v
  val serial_clk = Wire(Clock())
  val pll_lock = Wire(Bool())
  val tmdspll = Module(new TMDS_PLLVR)
  val rst = Wire(Reset())
  tmdspll.io.clkin := pixel_clk.asClock
  serial_clk := tmdspll.io.clkout
  pll_lock := tmdspll.io.lock
  tmdspll.io.reset := reset
  rst := ~pll_lock

  val tmds = Wire(UInt(3.W))
  val tmds_clk = Wire(Bool())
  withClockAndReset(pixel_clk.asClock, rst) {
    val tmds_channel1 = Wire(UInt(10.W))
    val tmds_channel2 = Wire(UInt(10.W))
    val tmds_channel0 = Wire(UInt(10.W))

    val tmds_green = Module(new TMDS_encoder)
    val tmds_red = Module(new TMDS_encoder)
    val tmds_blue = Module(new TMDS_encoder)

    tmds_red.io.video_on := sync.io.video_on
    tmds_blue.io.video_on := sync.io.video_on
    tmds_green.io.video_on := sync.io.video_on

    tmds_blue.io.control_data := sync.io.vsync ## sync.io.hsync
    tmds_green.io.control_data := 0.U
    tmds_red.io.control_data := 0.U

    tmds_red.io.video_data := rgb(23, 16)
    tmds_blue.io.video_data := rgb(7, 0)
    tmds_green.io.video_data := rgb(15, 8)

    tmds_channel0 := tmds_blue.io.TMDS
    tmds_channel1 := tmds_green.io.TMDS
    tmds_channel2 := tmds_red.io.TMDS

    val serdesBlue = Module(new Oser10Module())
    serdesBlue.io.data := tmds_channel0
    serdesBlue.io.fclk := serial_clk

    val serdesGreen = Module(new Oser10Module())
    serdesGreen.io.data := tmds_channel1
    serdesGreen.io.fclk := serial_clk

    val serdesRed = Module(new Oser10Module())
    serdesRed.io.data := tmds_channel2
    serdesRed.io.fclk := serial_clk

    tmds := serdesRed.io.q ## serdesGreen.io.q ## serdesBlue.io.q

    //serdesCLk : 25Mhz ,Why not directly use p_tick?
    //cause Duty Ratio of p_tick is 10% , while which of serdesCLk is 50%
    val serdesClk = Module(new Oser10Module())
    serdesClk.io.data := "b1111100000".U(10.W)
    serdesClk.io.fclk := serial_clk

    tmds_clk := serdesClk.io.q

    val buffDiffBlue = Module(new OBUFDS)
    buffDiffBlue.io.I := tmds(0)
    val buffDiffGreen = Module(new OBUFDS)
    buffDiffGreen.io.I := tmds(1)
    val buffDiffRed = Module(new OBUFDS)
    buffDiffRed.io.I := tmds(2)
    val buffDiffClk = Module(new OBUFDS)
    buffDiffClk.io.I := tmds_clk

    io.TMDSclk_p := buffDiffClk.io.O
    io.TMDSclk_n := buffDiffClk.io.OB
    io.TMDSdata_p := buffDiffRed.io.O ## buffDiffGreen.io.O ## buffDiffBlue.io.O
    io.TMDSdata_n := buffDiffRed.io.OB ## buffDiffGreen.io.OB ## buffDiffBlue.io.OB
  }
}

//----------------------------------------
//PLL frequency multiplier using BlackBox
class TMDS_PLLVR extends BlackBox {
  val io = IO(new Bundle {
    val clkin = Input(Clock())
    val reset = Input(Reset())
    val clkout = Output(Clock())
    val clkoutd = Output(Clock())
    val lock = Output(Bool())
  })
}

/* OSER10 : serializer 10:1*/
class OSER10 extends Module {
  val io = IO(new Bundle {
    val Q = Output(Bool()) // OSER10 data output signal
    val D0 = Input(Bool())
    val D1 = Input(Bool())
    val D2 = Input(Bool())
    val D3 = Input(Bool())
    val D4 = Input(Bool())
    val D5 = Input(Bool())
    val D6 = Input(Bool())
    val D7 = Input(Bool())
    val D8 = Input(Bool())
    val D9 = Input(Bool()) //  OSER10 data input signal
    val PCLK = Input(Clock()) // Primary clock input signal
    val FCLK = Input(Clock()) // High speed clock input signal
    val RESET = Input(Reset()) // Asynchronous reset input signal,
    //active-high.
  })
  withClockAndReset(io.FCLK, io.RESET) {
    val count = RegInit(0.U(4.W))
    val countnext = Wire(UInt(4.W))
    io.Q := MuxLookup(
      count,
      0.U,
      IndexedSeq(
        0.U -> io.D0.asBool,
        1.U -> io.D1.asBool,
        2.U -> io.D2.asBool,
        3.U -> io.D3.asBool,
        4.U -> io.D4.asBool,
        5.U -> io.D5.asBool,
        6.U -> io.D6.asBool,
        7.U -> io.D7.asBool,
        8.U -> io.D8.asBool,
        9.U -> io.D9.asBool
      )
    )
    countnext := Mux(
      count === 9.U, 0.U, count + 1.U
    )
    count := countnext
  }
}

class Oser10Module extends Module {
  val io = IO(new Bundle {
    val q = Output(Bool())
    val data = Input(UInt(10.W))
    val fclk = Input(Clock()) // Fast clock
  })

  val osr10 = Module(new OSER10())
  io.q := osr10.io.Q
  osr10.io.D0 := io.data(0)
  osr10.io.D1 := io.data(1)
  osr10.io.D2 := io.data(2)
  osr10.io.D3 := io.data(3)
  osr10.io.D4 := io.data(4)
  osr10.io.D5 := io.data(5)
  osr10.io.D6 := io.data(6)
  osr10.io.D7 := io.data(7)
  osr10.io.D8 := io.data(8)
  osr10.io.D9 := io.data(9)
  osr10.io.PCLK := clock
  osr10.io.FCLK := io.fclk
  osr10.io.RESET := reset
}

/* lvds output */
class OBUFDS extends BlackBox {
  val io = IO(new Bundle {
    val O = Output(Bool())
    val OB = Output(Bool())
    val I = Input(Bool())
  })
}
//-----------------------------------------


//-----------------------------------------


