/* ascii_to_HD44780_driver.v - driver that takes in ascii and outputs control
   sequences for an HD44780 display

   This file is part of the 9086 project.

   Copyright (c) 2024 Efthymios Kritikos

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

module ascii_to_HD44780_driver (
	/* system */
	input wire clock,
	input wire rst_n,

	/* Data Input */
	output reg in_data_ready=0,
	input data_write_req,
	input [7:0] in_ascii_data,

	/* write circuitry */
	input wire done_writing,
	output reg write_req=0,
	output reg [3:0] data,
	output reg cmd_data=0
);

initial begin
	init_cmd_data[ 0] = 4'h3;
	init_cmd_data[ 1] = 4'h3;
	init_cmd_data[ 2] = 4'h3;
	init_cmd_data[ 3] = 4'h2;
	init_cmd_data[ 4] = 4'h2; //0x28
	init_cmd_data[ 5] = 4'h8;
	init_cmd_data[ 6] = 4'h0; //0x07
	init_cmd_data[ 7] = 4'h7;
	init_cmd_data[ 8] = 4'h0; //0x0F
	init_cmd_data[ 9] = 4'hF;
	init_cmd_data[10] = 4'h0; //0x01
	init_cmd_data[11] = 4'h1;
	init_cmd_data[12] = 4'h0; //0x06
	init_cmd_data[13] = 4'h6;
	init_cmd_data[14] = 4'h0; // 0x02
	init_cmd_data[15] = 4'h2;
end

reg [4:0] init_seq=0;

reg [3:0] init_cmd_data [15:0];

reg [7:0]print_data;

reg [1:0]line=0;
reg [5:0]col=0;

reg [7:0] driver_state =0;

reg next_line;

reg [7:0]clear_count;

always @(posedge clock) begin
	if(rst_n==0)begin
		in_data_ready<=0;
		write_req<=1'b0;
		init_seq<=5'd0;
		cmd_data<=1'b0;
		line<=0;
		col<=0;
		driver_state<=0;
	end else begin
		case(driver_state)
			8'd0:begin
				if(init_seq!=5'd16)begin
					/** Initialise display **/
					data<=init_cmd_data[init_seq[3:0]];
					cmd_data<=1'b0;
					in_data_ready<=0;
					if(!done_writing)begin
						init_seq<=init_seq+5'd1;
						driver_state<=8'd12;
						write_req<=1'b0;
					end else begin
						write_req<=1'b1;
					end
				end else if(col==6'd20)begin
					/** Check if we run out of screen **/
					driver_state<=8'd6;
					cmd_data<=1'b0;
					next_line<=1;
				end else if(data_write_req==1)begin
					if(in_ascii_data==8'h0A)begin // '\n'
						driver_state<=8'd6;
						cmd_data<=1'b0;
						next_line<=1;
					end else if(in_ascii_data==8'h0D)begin // '\r'
						driver_state<=8'd6;
						cmd_data<=1'b0;
						next_line<=0;
					end else if(in_ascii_data==8'h1b)begin // '\e'
						driver_state<=8'd13;
						cmd_data<=1'b1;
						in_data_ready<=0;
					end else begin
						if (in_ascii_data[7:5]==3'b000||in_ascii_data[7:7]==1'b1)
							if(in_ascii_data==8'h00)
								print_data<=8'h23;
							else
								print_data<=8'h3F;
						else
							print_data<=in_ascii_data;
						cmd_data<=1'b1;
						driver_state<=8'd9;
					end
					in_data_ready<=0;
				end else begin
					in_data_ready<=1;
				end
			end
			8'd6:begin
				if(next_line)begin
					case(line)
						2'd0: data<=4'hC;
						2'd1: data<=4'h9;
						2'd2: data<=4'hD;
						2'd3: data<=4'hD;
					endcase
				end else begin
					case(line)
						2'd0: data<=4'h8;
						2'd1: data<=4'hC;
						2'd2: data<=4'h9;
						2'd3: data<=4'hD;
					endcase
				end
				if(done_writing==0)begin
					driver_state<=8'd7;
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd7:begin
				if(done_writing==1)
					driver_state<=8'd8;
			end
			8'd8:begin
				if(next_line)begin
					case(line)
						2'd0: begin data<=4'h0; end
						2'd1: begin data<=4'h4; end
						2'd2: begin data<=4'h4; end
						2'd3: begin data<=4'h4; end
					endcase
				end else begin
					case(line)
						2'd0: begin data<=4'h0; end
						2'd1: begin data<=4'h0; end
						2'd2: begin data<=4'h4; end
						2'd3: begin data<=4'h4; end
					endcase
				end
				if(!done_writing)begin
					driver_state<=8'd12;
					col<=0;
					write_req<=1'b0;
					if(next_line)begin
						case(line)
							2'd0: begin  line<=1; end
							2'd1: begin  line<=2; end
							2'd2: begin  line<=3; end
							2'd3: begin  line<=3; end
						endcase
					end
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd9:begin
				data<=print_data[7:4];
				if(!done_writing)begin
					driver_state<=8'd10;
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd10:begin
				if(done_writing==1)
					driver_state<=8'd11;
			end
			8'd11:begin
				data<=print_data[3:0];
				if(!done_writing)begin
					driver_state<=8'd12;
					write_req<=1'b0;
					col<=col+1;
				end else
					write_req<=1'b1;
			end
			8'd12:begin
				if(done_writing)
					driver_state<=8'd0;
			end
			8'd13:begin // Escape sequence start
				if(data_write_req==1'b0)begin
					driver_state<=8'd14;
					in_data_ready<=1;
				end
			end
			8'd14:begin
				if(data_write_req==1'b1)begin
					in_data_ready<=1'b0;
					case(in_ascii_data[7:0])
						8'h5b:begin// '['
							driver_state<=8'd16;
						end
						default:begin
							driver_state<=8'd15;
						end
					endcase
				end
			end
			8'd15:begin
				if(data_write_req==1'b0)begin
					driver_state<=8'd0;
				end
			end
			8'd16:begin // Second part of escape sequency
				if(data_write_req==1'b0)begin
					in_data_ready<=1'b1;
					driver_state<=8'd17;
				end
			end
			8'd17:begin
				if(data_write_req==1'b1)begin
					driver_state<=8'd17;
					in_data_ready<=1'b0;
					case(in_ascii_data[7:0])
						8'h48:begin// 'H'
							driver_state<=8'd25;
							cmd_data<=1'b0;
							clear_count<=8'd80;
							end
						8'h32:begin// '2'
							driver_state<=8'd23;
							end
						default: begin
							driver_state<=8'd15;
						end
					endcase
				end
			end
			8'd18:begin
				data<=4'h2;
				if(!done_writing)begin
					driver_state<=8'd19;
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd19:begin
				if(done_writing==1)
					driver_state<=8'd20;
			end
			8'd20:begin
				data<=4'h0;
				if(!done_writing)begin
					driver_state<=8'd21;
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd21:begin
				if(done_writing)begin
					driver_state<=8'd22;
					clear_count<=clear_count-8'd1;
				end
			end
			8'd22:begin
				if(clear_count==8'd0)begin
					driver_state<=8'd0;
				end else begin
					driver_state<=8'd18;
				end
			end
			8'd23:begin
				if(data_write_req==1'b0)begin
					in_data_ready<=1'b1;
					driver_state<=8'd24;
				end
			end
			8'd24:begin
				if(data_write_req==1'b1)begin
					driver_state<=8'd17;
					in_data_ready<=1'b0;
					case(in_ascii_data[7:0])
						8'h4a:begin// 'J'
							driver_state<=8'd18;
							clear_count<=8'd80;
							end
						default:begin
							driver_state<=8'd0;
							end
					endcase
				end
			end
			8'd25:begin
				data<=4'h8;
				if(!done_writing)begin
					driver_state<=8'd26;
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd26:begin
				if(done_writing==1)
					driver_state<=8'd27;
			end
			8'd27:begin
				data<=4'h0;
				if(!done_writing)begin
					driver_state<=8'd28;
					write_req<=1'b0;
				end else
					write_req<=1'b1;
			end
			8'd28:begin
				if(done_writing)begin
					driver_state<=8'd0;
					col<=0;
					line<=0;
				end
			end
			default: begin driver_state<=0; end
		endcase
	end
end

endmodule
