/* top.v - Implements FPGA and Board specific circuitry

   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/>.  */

`include "error_header.v"
`ifdef OUTPUT_JSON_STATISTICS
`include "config.v"
`endif

module fpga_top(
	input clk48,
	input user_button,
//	output reset_n,

	output rgb_led0_r,
	output rgb_led0_g,
	output rgb_led0_b,

`ifdef SYNTHESIS
	output wire   [15:0] ddram_a, // [15:13] are unused in litex as well, they just also route them through a trellis block
	output wire    [2:0] ddram_ba,
	output wire          ddram_cas_n,
	output wire          ddram_cke,
	output wire          ddram_clk_p,
	output wire          ddram_cs_n,
	output wire    [1:0] ddram_dm,
	input  wire   [15:0] ddram_dq,
	input  wire    [1:0] ddram_dqs_p,
	output wire    [1:0] ddram_gnd,
	output wire          ddram_odt,
	output wire          ddram_ras_n,
	output wire          ddram_reset_n,
	output wire    [5:0] ddram_vccio,
	output wire          ddram_we_n,

	inout i2c_sda,/*sda*/
	output i2c_scl /*scl*/
`else
	output i2c_dir,
	output i2c_scl,
	input i2c_sda_in,
	output i2c_sda_out
`endif
);

`ifndef SYNTHESIS
string waveform_name;
initial begin
	if($value$plusargs("WAVEFORM=%s",waveform_name))begin
		$dumpfile(waveform_name);
		$dumpvars(0,p,cycles);
	end
end
//TODO: should there be some common file between all the fpga_tops and system.v for this stuff?
always @(posedge clk48) begin
	if(HALT==1&&disp_cache_start==disp_cache_end)begin
		$finish;
	end
end
reg sane;
always @(posedge reset)begin sane<=1; end
always @( ERROR ) begin
	if ( ERROR != `ERR_NO_ERROR && sane == 1 ) begin
			$display("PROCESSOR RUN INTO AN ERROR.");
			case (ERROR)
				default:begin
				end
				`ERR_UNIMPL_INSTRUCTION:begin
					$display("Unimplemented instruction");
				end
				`ERR_UNIMPL_ADDRESSING_MODE: begin
					$display("Unimplemented addressing mode");
				end
			endcase
			$finish;
	end
end
`endif

`ifdef SYNTHESIS
assign ddram_a[15:13] = 3'b0;
assign ddram_vccio = 6'd63;
assign ddram_gnd = 2'd0;
`endif

wire HALT;
wire [`ERROR_BITS-1:0]ERROR;
wire [19:0] address_bus;
wire [15:0] data_bus_read,data_bus_write;
wire rd,wr,BHE,IOMEM;


wire CPU_SPEED;

processor p(
	.clock(CPU_SPEED),
	.reset(reset),
	.HALT(HALT),
	.ERROR(ERROR),
	.external_address_bus(address_bus),
	.external_data_bus_read(data_bus_read),
	.external_data_bus_write(data_bus_write),
	.wait_state(wait_state),
	.read(rd),
	.write(wr),
	.BHE(BHE),
	.IOMEM(IOMEM)
`ifdef CALCULATE_IPC
	/*  STATISTICS */ ,.new_instruction(new_instruction)
`endif
`ifdef OUTPUT_JSON_STATISTICS
	/*             */ ,.L1_SIZE_STAT(L1_SIZE_STAT), .VALID_INSTRUCTION_STAT(VALID_INSTRUCTION_STAT), .jump_req_debug(jump_req_debug)
`endif
);

/* verilator lint_off UNUSEDSIGNAL */
`ifdef CALCULATE_IPC
wire new_instruction;
`endif
`ifdef OUTPUT_JSON_STATISTICS
wire [`L1_CACHE_SIZE-1:0]L1_SIZE_STAT;
wire VALID_INSTRUCTION_STAT;
wire jump_req_debug;
`endif
/* verilator lint_on UNUSEDSIGNAL */


reg [2:0]rgb_led_color;
assign rgb_led0_r=rgb_led_color[0];
assign rgb_led0_g=rgb_led_color[1];
assign rgb_led0_b=rgb_led_color[2];

// A bit useless since if the cpu ERRORS out or HALTS it will continue executing anyway
//always @(HALT or ERROR or user_button) begin
//	if (HALT==1) begin
//		/* yellow */
//		rgb_led_color<=3'b100;
//	end else if (ERROR != `ERROR_BITS'b0) begin
//		/* red */
//		rgb_led_color<=3'b110;
//	end else begin
//		/* green */
//		rgb_led_color<=3'b101;
//	end
//end

// Create a 27 bit register
reg [26:0] counter = 0;

// Every positive edge increment register by 1
always @(posedge clk48) begin
    counter <= counter + 1;
end

/*** RESET CIRCUIT ***/

reg reset=0;
reg [1:0] state=0;

always @(posedge counter[15]) begin
	if(user_button==0)
		state<=2'b00;
	case (state)
		2'b00:begin
			reset<=0;
			state<=2'b01;
		end
		2'b01:begin
			reset<=1;
			state<=2'b10;
		end
		default: begin
		end
	endcase
end

//------------------------------------------//
// Cache to allow the slow display to have a
// chance to keep up with the relentless CPU

reg [7:0] disp_cache_start=0;
reg [7:0] disp_cache_end=0;
reg [7:0] disp_write_cache [255:0];
reg ascii_state=0;
always @(posedge CPU_SPEED)begin

	if(wr==0)begin
		if(IOMEM==1'b1 && address_bus[7:0]==8'hA5 )begin
			disp_write_cache[disp_cache_end]<=data_bus_write[15:8];
			disp_cache_end<=disp_cache_end+8'd1;
		end else if(IOMEM==1'b1 && address_bus[7:0]==8'hB0 )begin
			if(data_bus_write[0:0]==1)
				rgb_led_color<=3'b000;
			else
				rgb_led_color<=3'b111;
		end
	end else if(ascii_state==1'b0)begin
		if(ascii_data_ready&disp_cache_start!=disp_cache_end)begin
`ifndef SYNTHESIS
			$write("%s" ,disp_write_cache[disp_cache_start]); // TODO: maybe simulate the i2c lcd
`endif
			ascii_data<=disp_write_cache[disp_cache_start];
			disp_cache_start<=disp_cache_start+8'd1;
			ascii_data_write_req<=1;
			ascii_state<=1'b1;
		end
	end

	if(ascii_state==1'b1)begin
		if(!ascii_data_ready)begin
			ascii_data_write_req<=0;
			ascii_state<=1'b0;
		end
	end
end

wire I2C_SPEED=counter[7];


wire [15:0]boot_rom_data_bus_read;
wire [15:0]boot_rom_data_bus_write;

assign boot_rom_data_bus_write=data_bus_write;

wire boot_rom_cs_n;

doublemem #( .RAM_SIZE_IN_BYTES(2048) ) boot_rom
	( address_bus[10:0],boot_rom_data_bus_read,boot_rom_data_bus_write,rd,wr,BHE,boot_rom_cs_n,CPU_SPEED );

/// Memory Map

assign data_bus_read = ( IOMEM == 1 ) ? data_bus_read_IO : data_bus_read_MEM ;
wire [15:0] data_bus_read_IO = ( address_bus[7:1] == 7'h10 ) ? LITEDRAM_STATUS_REGISTER : ( (address_bus[7:4] == 4'h4) ? Wishbone_driver_data_bus_read:CPU_I2C_data_bus_read);


assign boot_rom_cs_n = !((IOMEM==0)&&(address_bus[15:12]==4'hF));

wire [15:0] data_bus_read_MEM = (address_bus[15:12]==4'hF)? boot_rom_data_bus_read:data_bus_read_DDR3;

wire DDR3_ram_cs   = (IOMEM==0)&&(address_bus[15:12]!=4'hF);
//
// 0x20 | DDR STATUS BYTE1 |
// 0x21 | DDR STATUS BYTE1 |
//      |                  |
//   -  |       --         |
//      |                  |
// 0x30 |    WISHBONE      |
//  ..  |       ..         |
// 0x3F |    WISHBONE      |
//
assign wishbone_cs=!((IOMEM==1)&&(address_bus[7:4] == 4'h4));
assign i2c_cs=     !((IOMEM==1)&&(address_bus[7:4] == 4'h6));

assign wait_state_I2C=(i2c_cs==1'b0&&rd==1'b0)^i2c_cs_1d;

reg i2c_cs_1d;

always @(posedge CPU_SPEED)begin
	i2c_cs_1d<=(i2c_cs==1'b0&&rd==1'b0);
end // TODO: Probably would need to so the same for the wishbone IO

wire wait_state_I2C;
/// DDR3 Controller

wire ddr3_init_done,ddr3_init_error,ddr3_pll_locked;
/* verilator lint_off UNUSEDSIGNAL */
wire user_rst;
/* verilator lint_on UNUSEDSIGNAL */
`ifndef SYNTHESIS
assign ddr3_pll_locked=1;
wire sim_trace;
assign sim_trace=0;//signal is not connected on litedram, not sure what was the idea behind it
`endif

wire [15:0]Wishbone_driver_data_bus_read;
wire [15:0]Wishbone_driver_data_bus_write=data_bus_write;

wire wishbone_cs;

Wishbone_IO_driver Wishbone_IO_driver(
	/////  GENERAL  //////
	.clock(CPU_SPEED),
	.reset_n(reset),

	////// CPU INTERFACE ///////
	.address(address_bus[3:1]),
	.data_bus_in(Wishbone_driver_data_bus_write),
	.data_bus_out(Wishbone_driver_data_bus_read),
	.read_n(rd),
	.write_n(wr),
	.chip_select_n(wishbone_cs),

	////// WISHBONE INTERFACE /////
	.wb_ctrl_ack(wb_ctrl_ack),
	.wb_ctrl_adr(wb_ctrl_adr),
	.wb_ctrl_cyc(wb_ctrl_cyc),
	.wb_ctrl_err(wb_ctrl_err),
	.wb_ctrl_sel(wb_ctrl_sel),
	.wb_ctrl_stb(wb_ctrl_stb),
	.wb_ctrl_we(wb_ctrl_we),
	.wb_ctrl_bte(wb_ctrl_bte),
	.wb_ctrl_cti(wb_ctrl_cti),
	.wb_ctrl_dat_r(wb_ctrl_dat_r),
	.wb_ctrl_dat_w(wb_ctrl_dat_w)
);


wire [15:0] LITEDRAM_STATUS_REGISTER = { 13'd0, ddr3_pll_locked ,ddr3_init_error, ddr3_init_done };
wire [15:0] data_bus_read_DDR3;

Wishbone_memory_driver Wishbone_memory_driver(
	/////  GENERAL  //////
	.clock(CPU_SPEED),
	.reset_n(reset),

	////// CPU INTERFACE ///////
	.address(address_bus),
	.BHE(BHE),
	.data_bus_in(data_bus_write),
	.data_bus_out(data_bus_read_DDR3),
	.wait_state(wait_state_WBIO),
	.read_n(rd),
	.write_n(wr),
	.chip_select_n(!(DDR3_ram_cs&&ddr3_init_done)),

	////// WISHBONE INTERFACE /////
	.wb_mem_ack(wb_mem_ack),
	.wb_mem_adr(wb_mem_adr),
	.wb_mem_cyc(wb_mem_cyc),
	.wb_mem_err(wb_mem_err),
	.wb_mem_sel(wb_mem_sel),
	.wb_mem_stb(wb_mem_stb),
	.wb_mem_we(wb_mem_we),
	.wb_mem_data_r(wb_mem_data_r),
	.wb_mem_data_w(wb_mem_data_w)
);

wire wait_state;
assign wait_state=wait_state_WBIO||wait_state_I2C;
wire wait_state_WBIO;

wire          wb_ctrl_ack;
wire   [29:0] wb_ctrl_adr;
wire    [1:0] wb_ctrl_bte;
wire    [2:0] wb_ctrl_cti;
wire          wb_ctrl_cyc;
wire   [31:0] wb_ctrl_dat_r;
wire   [31:0] wb_ctrl_dat_w;
wire          wb_ctrl_err;
wire    [3:0] wb_ctrl_sel;
wire          wb_ctrl_stb;
wire          wb_ctrl_we;

wire          wb_mem_ack;
wire   [24:0] wb_mem_adr;
wire          wb_mem_cyc;
wire   [31:0] wb_mem_data_r;
wire   [31:0] wb_mem_data_w;
wire          wb_mem_err;
wire    [3:0] wb_mem_sel;
wire          wb_mem_stb;
wire          wb_mem_we;

litedram_core DDR3_RAM_DRIVER(
	//////  GENERAL ///////
	.clk(clk48),
	.user_clk(CPU_SPEED),
`ifdef SYNTHESIS
	.rst(!reset),
	.pll_locked(ddr3_pll_locked),
`else
	.sim_trace(sim_trace),
`endif

	//////  DDR3 INTERFACE //////
`ifdef SYNTHESIS
	.ddram_a(ddram_a[12:0]), //also ignored on the litedram core
	.ddram_ba(ddram_ba),
	.ddram_cas_n(ddram_cas_n),
	.ddram_cke(ddram_cke),
	.ddram_clk_n(1'b0), // goes nowhere ...
	.ddram_clk_p(ddram_clk_p),
	.ddram_cs_n(ddram_cs_n),
	.ddram_dm(ddram_dm),
	.ddram_dq(ddram_dq),
	.ddram_dqs_n(2'b00), // goes nowhere ...
	.ddram_dqs_p(ddram_dqs_p),
	.ddram_odt(ddram_odt),
	.ddram_ras_n(ddram_ras_n),
	.ddram_reset_n(ddram_reset_n),
	.ddram_we_n(ddram_we_n),
`endif

	/////// SYSTEM MEMORY INTERFACE ////////////////
	.init_done(ddr3_init_done),
	.init_error(ddr3_init_error),
	.user_port_wishbone_0_ack(wb_mem_ack),
	.user_port_wishbone_0_adr(wb_mem_adr),
	.user_port_wishbone_0_cyc(wb_mem_cyc),
	.user_port_wishbone_0_dat_r(wb_mem_data_r),
	.user_port_wishbone_0_dat_w(wb_mem_data_w),
	.user_port_wishbone_0_err(wb_mem_err),
	.user_port_wishbone_0_sel(wb_mem_sel),
	.user_port_wishbone_0_stb(wb_mem_stb),
	.user_port_wishbone_0_we(wb_mem_we),
	.user_rst(user_rst),

	/////// WISHBONE CONTROL INTERFACE ///////////
	.wb_ctrl_ack(wb_ctrl_ack),
	.wb_ctrl_adr(wb_ctrl_adr),
	.wb_ctrl_cyc(wb_ctrl_cyc),
	.wb_ctrl_err(wb_ctrl_err),
	.wb_ctrl_sel(wb_ctrl_sel),
	.wb_ctrl_stb(wb_ctrl_stb),
	.wb_ctrl_we(wb_ctrl_we),
	.wb_ctrl_bte(wb_ctrl_bte),
	.wb_ctrl_cti(wb_ctrl_cti),
	.wb_ctrl_dat_r(wb_ctrl_dat_r),
	.wb_ctrl_dat_w(wb_ctrl_dat_w)
);

wire [15:0]CPU_I2C_data_bus_read;
wire i2c_cs;

wire [6:0] CPU_I2C_OUT_ADDRESS;
wire CPU_I2C_OUT_BUSY,CPU_I2C_OUT_TRANSACT,CPU_I2C_DIR;
wire [15:0]CPU_I2C_DATA_READ,CPU_I2C_DATA_WRITE;
wire CPU_I2C_IGN_ACK;
wire CPU_I2C_ERROR;

CPU_to_I2C_driver_bridge CPU_to_I2C_driver_bridge (
	.clock(CPU_SPEED),
	.reset_n(reset),

	// CPU INTERFACE
	.address(address_bus[2:0]),
	.data_bus_in(data_bus_write),
	.data_bus_out(CPU_I2C_data_bus_read),
	.read_n(rd),
	.write_n(wr),
	.chip_select_n(i2c_cs),

	// I2C DRIVER INTERFACE
	.OUT_ADDRESS(CPU_I2C_OUT_ADDRESS),
	.OUT_BUSY(CPU_I2C_OUT_BUSY),
	.OUT_TRANSACT(CPU_I2C_OUT_TRANSACT),
	.DIR(CPU_I2C_DIR),
	.OUT_I2C_DATA_READ(CPU_I2C_DATA_READ),
	.OUT_I2C_DATA_WRITE(CPU_I2C_DATA_WRITE),

	.TRANS_WIDTH(CPU_I2C_TRANS_WIDTH),
	.OUT_IGN_ACK(CPU_I2C_IGN_ACK),
	.IN_ERROR(CPU_I2C_ERROR)
);

// Display driver

wire ascii_data_ready;
reg ascii_data_write_req=0;
reg [7:0] ascii_data;
ascii_to_HD44780_driver LCD_DRIVER(
	/* system */
	I2C_SPEED,
	1'b1,

	/* Data Input */
	ascii_data_ready,
	ascii_data_write_req,
	ascii_data,

	/* write circuitry */
	!pcf_busy,
	pcf_write_req,
	pcf_data,
	pcf_command_data
);

// Port expander driver

wire pcf_write_req,pcf_command_data,pcf_busy;
wire [3:0]pcf_data;
/* verilator lint_off UNUSEDSIGNAL */
wire [7:0]DISP_I2C_DATA_WRITE;
wire [15:0]DISP_I2C_DATA_READ;
/* verilator lint_on UNUSEDSIGNAL */
wire DISP_I2C_BUSY,DISP_I2C_TRANSACT;
wire [6:0] DISP_I2C_ADDRESS;
assign DISP_I2C_ADDRESS=7'h27;
wire DISP_DIR;

pcf8574_for_HD44780 PCF8574_driver(
	.clock(I2C_SPEED),

	.pcf_write_req(pcf_write_req),
	.pcf_command_data(pcf_command_data),
	.pcf_data(pcf_data),

	.pcf_busy(pcf_busy),
	.new_backlight(1'b0),
	.backlight_update(1'b0),

	.DIR(DISP_DIR),
	.I2C_BUSY(DISP_I2C_BUSY),
	.I2C_TRANSACT(DISP_I2C_TRANSACT),

	.i2c_data_write(DISP_I2C_DATA_WRITE)
);

wire [6:0]MULT_TO_DRIV_I2C_ADDRESS;
wire MULT_TO_DRIV_I2C_BUSY;
wire MULT_TO_DRIV_I2C_TRANSACT;
wire [15:0]MULT_TO_DRIV_DATA_WRITE, MULT_TO_DRIV_DATA_READ;
wire MULT_TO_DRIV_DIR;
wire CPU_I2C_TRANS_WIDTH;

/* verilator lint_off UNUSEDSIGNAL */
wire DISP_ERROR; //TODO: It would be ideal to acknoledge any warnings but it's not really too important
/* verilator lint_on UNUSEDSIGNAL */

I2C_driver_multiplexer I2C_driver_multiplexer(
	.clock(I2C_SPEED),
	.reset_n(reset),

	////// INPUT 1 ///////
	.IN1_ADDRESS(DISP_I2C_ADDRESS),
	.IN1_BUSY(DISP_I2C_BUSY),
	.IN1_DIR(DISP_DIR),
	.IN1_TRANSACT(DISP_I2C_TRANSACT),
	.IN1_I2C_DATA_READ(DISP_I2C_DATA_READ),
	.IN1_I2C_DATA_WRITE({8'h0,DISP_I2C_DATA_WRITE}),
	.IN1_TRANS_WIDTH(1'b0),
	.IN1_IGN_ACK(1'b0),
	.IN1_ERROR(DISP_ERROR),

	////// INPUT 2 ///////
	.IN2_ADDRESS(CPU_I2C_OUT_ADDRESS),
	.IN2_BUSY(CPU_I2C_OUT_BUSY),
	.IN2_TRANSACT(CPU_I2C_OUT_TRANSACT),
	.IN2_DIR(CPU_I2C_DIR),
	.IN2_I2C_DATA_READ(CPU_I2C_DATA_READ),
	.IN2_I2C_DATA_WRITE(CPU_I2C_DATA_WRITE),
	.IN2_TRANS_WIDTH(CPU_I2C_TRANS_WIDTH),
	.IN2_IGN_ACK(CPU_I2C_IGN_ACK),
	.IN2_ERROR(CPU_I2C_ERROR),

	////// OUTPUT ///////
	.OUT_ADDRESS(MULT_TO_DRIV_I2C_ADDRESS),
	.OUT_BUSY(MULT_TO_DRIV_I2C_BUSY),
	.OUT_TRANSACT(MULT_TO_DRIV_I2C_TRANSACT),
	.OUT_DIR(MULT_TO_DRIV_DIR),
	.OUT_I2C_DATA_WRITE(MULT_TO_DRIV_DATA_WRITE),
	.OUT_I2C_DATA_READ(MULT_TO_DRIV_DATA_READ),
	.OUT_TRANS_WIDTH(MULT_TO_DRIV_TRANS_WIDTH),
	.OUT_IGN_ACK(MULT_TO_DRIV_IGN_ACK),
	.OUT_ERROR(MULT_TO_DRIV_ERROR)
);

// I2C driver

wire SDA_direction;
wire SCL,SDA_input,SDA_output;
wire MULT_TO_DRIV_TRANS_WIDTH;
wire MULT_TO_DRIV_IGN_ACK;
wire MULT_TO_DRIV_ERROR;

I2C_driver i2c_driver(
	.clock(I2C_SPEED),

	.SDA_input(SDA_input),
	.SDA_output(SDA_output),
	.SDA_direction(SDA_direction),
	.SCL(SCL),

	.address(MULT_TO_DRIV_I2C_ADDRESS),
	.I2C_BUSY(MULT_TO_DRIV_I2C_BUSY),
	.I2C_TRANSACT(MULT_TO_DRIV_I2C_TRANSACT),
	.DIR(MULT_TO_DRIV_DIR),
	.i2c_data_write(MULT_TO_DRIV_DATA_WRITE),
	.i2c_data_read(MULT_TO_DRIV_DATA_READ),

	.transact_width(MULT_TO_DRIV_TRANS_WIDTH),
	.ignore_ack(MULT_TO_DRIV_IGN_ACK),

	.error(MULT_TO_DRIV_ERROR)
);

`ifdef SYNTHESIS

TRELLIS_IO #(
	// Parameters.
	.DIR ("BIDIR")
) TRELLIS_IO_00 (
	// pin
	.B (i2c_sda),
	//input
	.I (1'd0),
	//Direction
	.T (~( SDA_direction & (~SDA_output) )),
	// Output
	.O (SDA_input)
);

assign i2c_scl=SCL;

`else

assign i2c_dir=SDA_direction;
assign i2c_scl=SCL;
assign SDA_input=i2c_sda_in;
assign i2c_sda_out=SDA_output;

`endif


endmodule
