/*
 * vgen.v
 *
 * vim: ts=4 sw=4
 *
 * Copyright (C) 2019  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 vgen #(
	parameter ADDR_BASE = 24'h040000,
	parameter integer N_FRAMES = 30,
	parameter integer N_ROWS   = 64,	// # of rows (must be power of 2!!!)
	parameter integer N_COLS   = 64,	// # of columns
	parameter integer BITDEPTH = 24,

	// Auto-set
	parameter integer LOG_N_ROWS  = $clog2(N_ROWS),
	parameter integer LOG_N_COLS  = $clog2(N_COLS)
)(
	// SPI reader interface
	output wire [23:0] sr_addr,
	output wire [15:0] sr_len,
	output wire sr_go,
	input  wire sr_rdy,

	input wire [7:0] sr_data,
	input wire sr_valid,

	// Frame Buffer write interface
	output wire [LOG_N_ROWS-1:0] fbw_row_addr,
	output wire fbw_row_store,
	input  wire fbw_row_rdy,
	output wire fbw_row_swap,

	output wire [BITDEPTH-1:0] fbw_data,
	output wire [LOG_N_COLS-1:0] fbw_col_addr,
	output wire fbw_wren,

	output wire frame_swap,
	input  wire frame_rdy,

	// UI
	input  wire ui_up,
	input  wire ui_mode,
	input  wire ui_down,

	// Clock / Reset
	input  wire clk,
	input  wire rst
);

	localparam integer FW = 23 - LOG_N_ROWS - LOG_N_COLS;

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

	// FSM
	localparam
		ST_FRAME_WAIT	= 0,
		ST_ROW_SPI_CMD	= 1,
		ST_ROW_SPI_READ	= 2,
		ST_ROW_WRITE	= 3,
		ST_ROW_WAIT		= 4;

	reg  [2:0] fsm_state;
	reg  [2:0] fsm_state_next;

	// UI
	reg mode;
	reg [3:0] cfg_rep;
	reg [1:0] frame_sel;

	// Counters
	reg [FW-1:0] cnt_frame;
	reg cnt_frame_first;
	reg cnt_frame_last;

	reg [3:0] cnt_rep;
	reg cnt_rep_last;

	reg [LOG_N_ROWS-1:0] cnt_row;
	reg cnt_row_last;

	reg [LOG_N_COLS:0] cnt_col;

	// SPI
	reg [7:0] sr_data_r;
	wire [15:0] sr_data16;

	// Output
	wire [7:0] color [0:2];


	// FSM
	// ---

	// State register
	always @(posedge clk or posedge rst)
		if (rst)
			fsm_state <= ST_FRAME_WAIT;
		else
			fsm_state <= fsm_state_next;

	// Next-State logic
	always @(*)
	begin
		// Default is not to move
		fsm_state_next = fsm_state;

		// Transitions ?
		case (fsm_state)
			ST_FRAME_WAIT:
				if (frame_rdy & sr_rdy)
					fsm_state_next = ST_ROW_SPI_CMD;

			ST_ROW_SPI_CMD:
				fsm_state_next = ST_ROW_SPI_READ;

			ST_ROW_SPI_READ:
				if (sr_rdy)
					fsm_state_next = ST_ROW_WRITE;

			ST_ROW_WRITE:
				if (fbw_row_rdy)
					fsm_state_next = cnt_row_last ? ST_ROW_WAIT : ST_ROW_SPI_CMD;

			ST_ROW_WAIT:
				if (fbw_row_rdy)
					fsm_state_next = ST_FRAME_WAIT;
		endcase
	end


	// UI handling
	// -----------

	// Mode toggle
	always @(posedge clk or posedge rst)
		if (rst)
			mode <= 1'b0;
		else
			mode <= mode ^ ui_mode;

	// Repetition counter
	always @(posedge clk or posedge rst)
		if (rst)
			cfg_rep <= 4'h6;
		else if (~mode) begin
			if (ui_down & ~&cfg_rep)
				cfg_rep <= cfg_rep + 1;
			else if (ui_up & |cfg_rep)
				cfg_rep <= cfg_rep - 1;
		end

	// Latch request for prev / next frame
	always @(posedge clk)
		if (~mode)
			frame_sel <= cnt_rep_last ? 2'b11 : 2'b00;
		else if ((fsm_state == ST_ROW_WAIT) && fbw_row_rdy)
			frame_sel <= 2'b00;
		else if (ui_up)
			frame_sel <= 2'b10;
		else if (ui_down)
			frame_sel <= 2'b11;


	// Counters
	// --------

	// Frame counter
	always @(posedge clk or posedge rst)
		if (rst)
			cnt_frame <= 0;
		else if ((fsm_state == ST_ROW_WAIT) && fbw_row_rdy && frame_sel[1])
			if (frame_sel[0])
				cnt_frame <= cnt_frame_last  ? { (FW){1'b0} } : (cnt_frame + 1);
			else
				cnt_frame <= cnt_frame_first ? (N_FRAMES - 1) : (cnt_frame - 1);

	always @(posedge clk)
	begin
		// Those end up one cycle late vs 'cnt_frame' but that's fine, they
		// won't be used until a while later
		cnt_frame_last  <= (cnt_frame == (N_FRAMES - 1));
		cnt_frame_first <= (cnt_frame == 0);
	end

	// Repeat counter
	always @(posedge clk)
		if ((fsm_state == ST_ROW_WAIT) && fbw_row_rdy) begin
			cnt_rep <= cnt_rep_last ? 4'h0 : (cnt_rep + 1);
			cnt_rep_last <= (cnt_rep == cfg_rep);
		end

	// Row counter
	always @(posedge clk)
		if (fsm_state == ST_FRAME_WAIT) begin
			cnt_row <= 0;
			cnt_row_last <= 1'b0;
		end else if ((fsm_state == ST_ROW_WRITE) && fbw_row_rdy) begin
			cnt_row <= cnt_row + 1;
			cnt_row_last <= (cnt_row == (1 << LOG_N_ROWS) - 2);
		end

	// Column counter
	always @(posedge clk)
		if (fsm_state != ST_ROW_SPI_READ)
			cnt_col <= 0;
		else if (sr_valid)
			cnt_col <= cnt_col + 1;


	// SPI reader
	// ----------

	// Requests
	assign sr_addr = { cnt_frame, cnt_row, {(LOG_N_COLS+1){1'b0}} } + ADDR_BASE;
	assign sr_len = (N_COLS << 1) - 1;
	assign sr_go = (fsm_state == ST_ROW_SPI_CMD);

	// Data
	always @(posedge clk)
		if (sr_valid)
			sr_data_r <= sr_data;
	
	assign sr_data16 = { sr_data, sr_data_r };


	// Front-Buffer write
	// ------------------

	assign fbw_wren = sr_valid & cnt_col[0];
	assign fbw_col_addr = cnt_col[6:1];

	// Map to color
	assign color[2] = { sr_data16[15:11], sr_data16[15:13] };
	assign color[1] = { sr_data16[10: 5], sr_data16[10: 9] };
	assign color[0] = { sr_data16[ 4: 0], sr_data16[ 4: 2] };

	generate
		if (BITDEPTH == 8)
			assign fbw_data = { color[2][7:5], color[1][7:5], color[0][7:6] };
		else if (BITDEPTH == 16)
			assign fbw_data = { color[2][7:3], color[1][7:2], color[0][7:3] };
		else if (BITDEPTH == 24)
			assign fbw_data = { color[2], color[1], color[0] };
	endgenerate


	// Back-Buffer store
	// -----------------

	assign fbw_row_addr  = cnt_row;
	assign fbw_row_store = (fsm_state == ST_ROW_WRITE) && fbw_row_rdy;
	assign fbw_row_swap  = (fsm_state == ST_ROW_WRITE) && fbw_row_rdy;


	// Next frame
	// ----------

	assign frame_swap = (fsm_state == ST_ROW_WAIT) && fbw_row_rdy;

endmodule // vgen
