/*
 * hdmi_out.v
 *
 * vim: ts=4 sw=4
 *
 * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
 * All rights reserved.
 *
 * BSD 3-clause, see LICENSE.bsd
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

`default_nettype none

module hdmi_out #(
	parameter integer DW = 4,
)(
	// HDMI pads
	output wire [DW-1:0] hdmi_data,
	output wire          hdmi_hsync,
	output wire          hdmi_vsync,
	output wire          hdmi_de,
	output wire          hdmi_clk,

	// Memory interface
	output wire [31:0] mi_addr,
	output wire [ 6:0] mi_len,
	output wire        mi_rw,
	output wire        mi_valid,
	input  wire        mi_ready,

	output wire [31:0] mi_wdata,	// Not used
	input  wire        mi_wack,		// Not used
	input  wire        mi_wlast,	// Not used

	input  wire [31:0] mi_rdata,
	input  wire        mi_rstb,
	input  wire        mi_rlast,

	// Wishbone interface
	input  wire [31:0] wb_wdata,
	output wire [31:0] wb_rdata,
	input  wire [ 6:0] wb_addr,
	input  wire        wb_we,
	input  wire        wb_cyc,
	output reg         wb_ack,

	// Clocks / Sync / Reset
	input  wire clk_1x,
	input  wire clk_4x,
	input  wire sync_4x,
	input  wire rst
);

	genvar i;


	// Signals
	// -------

	// Timing Generator
	wire vt_hsync;
	wire vt_vsync;
	wire vt_de;
	wire vt_hfirst;
	wire vt_vfirst;
	wire vt_vlast;

	wire vt_trig;

	// DMA config
	reg  [31:0] dma_cfg_base;
	reg  [ 6:0] dma_cfg_bn_cnt;
	reg  [ 6:0] dma_cfg_bn_len;
	reg  [ 6:0] dma_cfg_bl_len;
	reg  [ 7:0] dma_cfg_bl_inc;

	reg         dma_run;

	// DMA runtime
	reg  [31:0] dma_addr;
	reg  [ 7:0] dma_cnt;
	wire        dma_last;
	wire        dma_valid;

	// Video Buffer
	reg         vb_pingpong;
	reg  [ 7:0] vb_waddr;
	wire [31:0] vb_wdata;
	wire        vb_wren;
	reg  [ 8:0] vb_raddr;
	wire [15:0] vb_rdata;

	// Palette
	wire [DW-1:0] pal_wdata;
	wire [   5:0] pal_waddr;
	reg           pal_wren;

	// Video Out
	reg  [   1:0] frame_cnt;

	wire [DW-1:0] vo_data[0:3];
	wire          vo_hsync;
	wire          vo_vsync;
	wire          vo_de;


	// Wishbone interface
	// ------------------

	// Ack
	always @(posedge clk_1x)
		wb_ack <= wb_cyc & ~wb_ack;

	// Register Write
	always @(posedge clk_1x or posedge rst)
		if (rst) begin
			dma_run <= 1'b0;
			dma_cfg_base   <= 0;
			dma_cfg_bn_cnt <= 0;
			dma_cfg_bn_len <= 0;
			dma_cfg_bl_len <= 0;
			dma_cfg_bl_inc <= 0;
		end else if (wb_cyc & ~wb_ack & ~wb_addr[6]) begin
			if (wb_addr[0])
				dma_cfg_base <= wb_wdata;
			else begin
				dma_run        <= wb_wdata[31];
				dma_cfg_bn_cnt <= wb_wdata[30:24];
				dma_cfg_bn_len <= wb_wdata[22:16];
				dma_cfg_bl_len <= wb_wdata[14: 8];
				dma_cfg_bl_inc <= wb_wdata[ 7: 0];
			end
		end

	// Palette write
	assign pal_wdata = wb_wdata[DW-1:0];
	assign pal_waddr = wb_addr[5:0];

	always @(posedge clk_1x)
		pal_wren <= wb_cyc & ~wb_ack & wb_addr[6];

	// No read support
	assign wb_rdata = 32'h00000000;


	// Timing generator
	// ----------------

		// Standard 1080p60
	vid_tgen #(
		.H_WIDTH(12),
		.V_WIDTH(12),
		.H_FP     (  88 / 4),
		.H_SYNC   (  44 / 4),
		.H_BP     ( 148 / 4),
		.H_ACTIVE (1920 / 4),
		.V_FP     (   4),
		.V_SYNC   (   5),
		.V_BP     (  36),
		.V_ACTIVE (1080)
	) hdmi_tgen_I (
		.vid_hsync(vt_hsync),
		.vid_vsync(vt_vsync),
		.vid_active(vt_de),
		.vid_h_first(vt_hfirst),
		.vid_h_last(),
		.vid_v_first(vt_vfirst),
		.vid_v_last(vt_vlast),
		.clk(clk_1x),
		.rst(rst)
	);

	assign vt_trig = vt_de & vt_hfirst;


	// DMA
	// ---

	// DMA requests
	assign dma_last = (dma_cnt[6:0] == 4'h0);

	always @(posedge clk_1x)
	begin
		if (~dma_run)
			dma_cnt <= 8'h00;
		else if (vt_trig)
			dma_cnt <= { 1'b1, dma_cfg_bn_cnt };
		else if (mi_ready & mi_valid)
			dma_cnt <= dma_cnt - 1;
	end

	assign dma_valid = dma_cnt[7];

	always @(posedge clk_1x)
		if (vt_trig & vt_vlast)
			dma_addr <= dma_cfg_base;
		else if (mi_ready & mi_valid)
			dma_addr <= dma_addr + (dma_last ? dma_cfg_bl_inc : dma_cfg_bn_len) + 1;

	// DMA Memory interface
	assign mi_addr  = dma_addr;
	assign mi_len   = dma_last ? dma_cfg_bl_len : dma_cfg_bn_len;
	assign mi_rw    = 1'b1;
	assign mi_valid = dma_valid;

	assign mi_wdata = 32'hxxxxxxxx;

	// Buffer write path
	always @(posedge clk_1x)
		if (vt_trig)
			vb_waddr <= 8'h00;
		else
			vb_waddr <= vb_waddr + mi_rstb;

	assign vb_wdata = mi_rdata;
	assign vb_wren  = mi_rstb;


	// Video Buffer
	// ------------

	// Ping-Pong
	always @(posedge clk_1x)
		if (rst)
			vb_pingpong <= 1'b0;
		else
			vb_pingpong <= vb_pingpong ^ vt_trig;

	// Memory
	hdmi_buf line_I (
		.waddr({vb_pingpong, vb_waddr}),
		.wdata(vb_wdata),
		.wren (vb_wren),
		.raddr({~vb_pingpong, vb_raddr}),
		.rdata(vb_rdata),
		.clk(clk_1x)
	);


	// Output
	// ------

	// Frame counter (for temporal dither)
	always @(posedge clk_1x)
		if (vt_trig & vt_vfirst)
			frame_cnt <= frame_cnt + 1;

	// Buffer read
	always @(posedge clk_1x)
		if (vt_trig)
			vb_raddr <= 9'h000;
		else
			vb_raddr <= vb_raddr + vt_de;

	// Palette lookup
	generate
		for (i=0; i<4; i=i+1)
			ram_sdp #(
				.AWIDTH(6),
				.DWIDTH(DW)
			) pal_I (
				.wr_addr(pal_waddr),
				.wr_data(pal_wdata),
				.wr_ena(pal_wren),
				.rd_addr({frame_cnt, vb_rdata[(3-i)*4+:4]}),
				.rd_data(vo_data[i]),
				.rd_ena(1'b1),
				.clk(clk_1x)
			);
	endgenerate

	// Control delay
	delay_bus #(3, 3) dly_vs_I (
		.d({vt_hsync, vt_vsync, vt_de}),
		.q({vo_hsync, vo_vsync, vo_de}),
		.clk(clk_1x)
	);

	// PHY
	hdmi_phy_4x #(
		.DW(DW)
	) phy_I (
		.hdmi_data(hdmi_data),
		.hdmi_hsync(hdmi_hsync),
		.hdmi_vsync(hdmi_vsync),
		.hdmi_de(hdmi_de),
		.hdmi_clk(hdmi_clk),
		.in_data0(vo_data[0]),
		.in_data1(vo_data[1]),
		.in_data2(vo_data[2]),
		.in_data3(vo_data[3]),
		.in_hsync(vo_hsync),
		.in_vsync(vo_vsync),
		.in_de(vo_de),
		.clk_1x(clk_1x),
		.clk_4x(clk_4x),
		.clk_sync(sync_4x)
	);

endmodule
