/**
 *	gl_avalon_intf
 *
 *	Avalon-MM interface for GL modules
 *
 *	Slave 1: GL MM Registers
 *	* gl[0]:	Status flags (readonly)
 *	* gl[1]:	Draw time (readonly)
 *	* gl[2]:	Draw command
 *	* gl[4-7]:	Unused
 *	* gl[8-15]: Args
 *
 *	Before issuing any new commands, please first make sure no command is in
 *	progress by waiting for GL_DONE flag going low. You should also check the
 *	GL_TIMEOUT flag.
 *	To issue a drawing command, first set the args registers. Then set gl[2]
 *	to the desired drawing command. This will instantly starts drawing and set
 *	draw enable to high. You could then wait for GL_DONE flag to go low, but
 *	postponing this waiting till the beginning of the next command is
 *	recommended so that software could utilize the meantime. To issue the next
 *	command, set the command register again, even if the command is the same
 *	because painting execution is initiated on each write request. In the same
 *	way, the command register should be set exactly once per execution, otherwse
 *	the same operation may be repeated and cause undefined behavior.
 *
 *	Slave 2: SRAM
 */

module gl_avalon_intf (
	// Avalon Clock Input
	input logic CLOCK, RESET,

	// GL Controller Slave
	input  logic AVL_GL_READ,
	input  logic AVL_GL_WRITE,
	input  logic AVL_GL_CS,
	input  logic [3:0] AVL_GL_BYTE_EN,
	input  logic [3:0] AVL_GL_ADDR,
	input  logic [31:0] AVL_GL_WRITEDATA,
	output logic [31:0] AVL_GL_READDATA,

	// SRAM Slave
	input  logic AVL_SRAM_READ,
	input  logic AVL_SRAM_WRITE,
	input  logic AVL_SRAM_CS,
	input  logic [3:0] AVL_SRAM_BYTE_EN,
	input  logic [19:0] AVL_SRAM_ADDR,
	input  logic [31:0] AVL_SRAM_WRITEDATA,
	output logic [31:0] AVL_SRAM_READDATA,
	output logic AVL_SRAM_WAITREQ,

	// Palette Slave
	input  logic AVL_PLT_READ,
	input  logic AVL_PLT_WRITE,
	input  logic AVL_PLT_CS,
	input  logic [3:0] AVL_PLT_BYTE_EN,
	input  logic [7:0] AVL_PLT_ADDR,
	input  logic [31:0] AVL_PLT_WRITEDATA,
	output logic [31:0] AVL_PLT_READDATA,

	// SRAM Conduit
	output logic [19:0] SRAM_ADDR,
	inout wire [15:0] SRAM_DQ,
	output logic SRAM_UB_N, SRAM_LB_N, SRAM_CE_N, SRAM_OE_N, SRAM_WE_N,

	// VGA Conduit
	output logic VGA_CLK,
	output logic [7:0] VGA_R, VGA_G, VGA_B,
	output logic VGA_SYNC_N, VGA_BLANK_N, VGA_VS, VGA_HS
);

// GL
logic gl_frame_finished, gl_done, gl_timeout;
logic [9:0] gl_drawtime;
logic [3:0] gl_cmd, gl_cmd_in;
logic [31:0] gl_args[8], gl_args_in[8];
logic gl_exec, gl_exec_next;

logic avl_sram_ready;

gl_mgr gl_inst(
	.CLOCK            (CLOCK),
	.RESET            (RESET),
	.GL_FRAME_FINISHED(gl_frame_finished),
	.GL_TIMEOUT       (gl_timeout),
	.GL_DRAWTIME      (gl_drawtime),
	.GL_CMD           (gl_cmd),
	.GL_ARG1          (gl_args[0]),
	.GL_ARG2          (gl_args[1]),
	.GL_ARG3          (gl_args[2]),
	.GL_ARG4          (gl_args[3]),
	.GL_ARG5          (gl_args[4]),
	.GL_ARG6          (gl_args[5]),
	.GL_ARG7          (gl_args[6]),
	.GL_ARG8          (gl_args[7]),
	.GL_EXEC          (gl_exec),
	.GL_DONE          (gl_done),
	.AVL_REQ          (avl_sram_writereq),
	.AVL_ADDR         (avl_sram_addr),
	.AVL_DATA         (avl_sram_data),
	.AVL_READY        (avl_sram_ready),
	.AVL_PLT_RD       (AVL_PLT_CS & AVL_PLT_READ),
	.AVL_PLT_WR       (AVL_PLT_CS & AVL_PLT_WRITE),
	.AVL_PLT_INDEX    (AVL_PLT_ADDR),
	.AVL_PLT_RD_COLOR (AVL_PLT_READDATA[23:0]),
	.AVL_PLT_WR_COLOR ({8'h0,AVL_PLT_WRITEDATA}),
.*);

assign AVL_SRAM_READDATA = 32'hCCCCCCCC; // SRAM is write-only
assign AVL_PLT_READDATA[31:24] = 8'h0;

// Avalon SRAM write pipeline
logic avl_sram_wait_next, avl_sram_writereq, avl_sram_writereq_next;
logic [19:0] avl_sram_addr, avl_sram_addr_next;
logic [16:0] avl_sram_data, avl_sram_data_next;

always_ff @(posedge CLOCK) begin
	if(RESET) begin
		AVL_SRAM_WAITREQ <= 1'b0;
		avl_sram_writereq <= 1'b0;
	end else begin
		AVL_SRAM_WAITREQ <= avl_sram_wait_next;
		avl_sram_writereq <= avl_sram_writereq_next;
	end
	avl_sram_addr <= avl_sram_addr_next;
	avl_sram_data <= avl_sram_data_next;
end

always_comb begin
	avl_sram_wait_next = AVL_SRAM_WAITREQ;
	avl_sram_writereq_next = avl_sram_writereq;
	avl_sram_addr_next = avl_sram_addr;
	avl_sram_data_next = avl_sram_data;
	if(AVL_SRAM_WAITREQ) begin
		// IO inhibited, wait for ready signal
		if (avl_sram_ready) begin
			// Done!
			avl_sram_wait_next = 1'b0;
			avl_sram_writereq_next = 1'b0;
		end
	end else begin
		// IO available, listen for incoming write request
		if (AVL_SRAM_CS & AVL_SRAM_WRITE & (AVL_SRAM_BYTE_EN[1:0] == 2'b11)) begin
			avl_sram_wait_next = 1'b1;
			avl_sram_writereq_next = 1'b1;
			avl_sram_addr_next = AVL_SRAM_ADDR;
			avl_sram_data_next = AVL_SRAM_WRITEDATA[16:0];
		end
	end
end

// GL controller
always_ff @(posedge CLOCK) begin
	if(RESET) begin
		gl_cmd <= 4'h0;
		gl_exec <= 1'b0;
		for (int i = 0; i < 8; i++) begin
			gl_args[i] <= 0;
		end
	end else begin
		gl_cmd <= gl_cmd_in;
		gl_exec <= gl_exec_next;
		for (int i = 0; i < 8; i++) begin
			gl_args[i] <= gl_args_in[i];
		end
	end
end

always_comb begin
	gl_cmd_in = gl_cmd;
	gl_exec_next = gl_exec;
	for (int i = 0; i < 8; i++) begin
		gl_args_in[i] = gl_args[i];
	end

	AVL_GL_READDATA = 32'hCCCCCCCC;
	if(AVL_GL_CS & AVL_GL_READ) begin
		case (AVL_GL_ADDR)
			4'd0: AVL_GL_READDATA = {29'b0, gl_timeout, gl_frame_finished, gl_done};
			4'd1: AVL_GL_READDATA = {21'b0, gl_drawtime};
			4'd2: AVL_GL_READDATA = {28'b0, gl_cmd};
		endcase
		if (AVL_GL_ADDR[3]) begin
			AVL_GL_READDATA = gl_args[AVL_GL_ADDR[2:0]];
		end
	end

	if (AVL_GL_CS & AVL_GL_WRITE) begin
		if (AVL_GL_ADDR[3]) begin
			if (AVL_GL_BYTE_EN == 4'hf)
				gl_args_in[AVL_GL_ADDR[2:0]] = AVL_GL_WRITEDATA;
		end else if (AVL_GL_ADDR == 4'd2) begin
			if (AVL_GL_BYTE_EN[0]) begin
				// Initiate new command
				gl_cmd_in = AVL_GL_WRITEDATA[3:0];
				gl_exec_next = 1'b1;
			end
		end
	end

end

endmodule // gl_avalon_intf