`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// uLab to ARM GPMC interface
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (c) 2014 Timothy Pearson
// Raptor Engineering
// http://www.raptorengineeringinc.com
//
//////////////////////////////////////////////////////////////////////////////////

module main(
	input clk,

	input gpmc_advn,
	input gpmc_oen,
	input gpmc_wen,
	inout [7:0] gpmc_data,
	input [RAM_ADDR_BITS:0] gpmc_address,

	input usermem_wen,
	output reg usermem_wait,
	inout [7:0] usermem_data,
	inout [RAM_ADDR_BITS:0] usermem_address,

	output reg userproc_start,
	input userproc_done,
	output reg userlogic_reset,
	input userlogic_clock,

	output userlogic_serial_txd,
	input userlogic_serial_rxd,
	output host_serial_txd,
	input host_serial_rxd,

	input [3:0] four_bit_leds,
	input [7:0] eight_bit_leds,

	output reg [3:0] four_bit_switches,
	output reg [7:0] eight_bit_switches,

	inout [15:0] sixteen_bit_io,
	input sixteen_bit_io_wen,
	output reg sixteen_bit_io_mode,

	input [3:0] sseg_mux,
	input [7:0] sseg_data);

	parameter RAM_ADDR_BITS = 15;

	assign host_serial_txd = userlogic_serial_rxd;
	assign userlogic_serial_txd = host_serial_rxd;

	reg [15:0] sixteen_bit_io_in;
	reg [15:0] sixteen_bit_io_out;
	reg [15:0] sixteen_bit_io_reg;
	reg sixteen_bit_io_wen_reg;

 	assign sixteen_bit_io = (sixteen_bit_io_wen) ? sixteen_bit_io_out : 16'bz;

	always @(posedge clk) begin
		sixteen_bit_io_reg = sixteen_bit_io;
		sixteen_bit_io_wen_reg = sixteen_bit_io_wen;
		if (sixteen_bit_io_wen_reg == 1'b0) begin
			sixteen_bit_io_mode = 1'b1;
			sixteen_bit_io_in = sixteen_bit_io_reg;
		end else begin
			sixteen_bit_io_mode = 1'b0;
		end
	end

	reg [7:0] gpmc_data_out;
	reg gpmc_data_driven;

	assign gpmc_data = (gpmc_data_driven) ? gpmc_data_out : 8'bz;

	reg [7:0] usermem_data_out;

 	assign usermem_data = (usermem_wen) ? usermem_data_out : 8'bz;

	wire data_storage_clka;
	reg [7:0] data_storage_dina;
	reg [(RAM_ADDR_BITS-1):0] data_storage_addra;
	reg data_storage_write_enable;
	wire [7:0] data_storage_data_out;

	assign data_storage_clka = clk;

	data_storage #(RAM_ADDR_BITS) data_storage(.clka(data_storage_clka), .dina(data_storage_dina), .addra(data_storage_addra),
		.wea(data_storage_write_enable), .douta(data_storage_data_out));

	wire lcd_data_storage_clka;
	wire lcd_data_storage_clkb;
	reg [7:0] lcd_data_storage_dina;
	reg [7:0] lcd_data_storage_dinb;
	reg [4:0] lcd_data_storage_addra;
	reg [4:0] lcd_data_storage_addrb;
	reg lcd_data_storage_wea;
	reg lcd_data_storage_web;
	wire [7:0] lcd_data_storage_douta;
	wire [7:0] lcd_data_storage_doutb;

	assign lcd_data_storage_clka = clk;
	assign lcd_data_storage_clkb = clk;

	lcd_data_storage lcd_data_storage(.clka(lcd_data_storage_clka), .clkb(lcd_data_storage_clkb),
		.dina(lcd_data_storage_dina), .dinb(lcd_data_storage_dinb),
		.addra(lcd_data_storage_addra), .addrb(lcd_data_storage_addrb),
		.wea(lcd_data_storage_wea), .web(lcd_data_storage_web),
		.douta(lcd_data_storage_douta), .doutb(lcd_data_storage_doutb));

	wire logic_analyzer_clk;
	logic_analyzer_clock_generator logic_analyzer_clock_generator(.clkin(clk), .clkout(logic_analyzer_clk));

	wire logic_analyzer_data_storage_clka;
	wire logic_analyzer_data_storage_clkb;
	reg [63:0] logic_analyzer_data_storage_dina;
	reg [63:0] logic_analyzer_data_storage_dinb;
	reg [10:0] logic_analyzer_data_storage_addra;
	reg [10:0] logic_analyzer_data_storage_addrb;
	reg logic_analyzer_data_storage_wea;
	reg logic_analyzer_data_storage_web;
	wire [63:0] logic_analyzer_data_storage_douta;
	wire [63:0] logic_analyzer_data_storage_doutb;

	assign logic_analyzer_data_storage_clka = clk;
	assign logic_analyzer_data_storage_clkb = logic_analyzer_clk;

	logic_analyzer_data_storage logic_analyzer_data_storage(.clka(logic_analyzer_data_storage_clka), .clkb(logic_analyzer_data_storage_clkb),
		.dina(logic_analyzer_data_storage_dina), .dinb(logic_analyzer_data_storage_dinb),
		.addra(logic_analyzer_data_storage_addra), .addrb(logic_analyzer_data_storage_addrb),
		.wea(logic_analyzer_data_storage_wea), .web(logic_analyzer_data_storage_web),
		.douta(logic_analyzer_data_storage_douta), .doutb(logic_analyzer_data_storage_doutb));

	//-----------------------------------------------------------------------------------
	//
	// Create a 12.5MHz clock for the seven-segement LED emulator
	//
	//-----------------------------------------------------------------------------------
	
	reg clk_div_by_two;
	reg clk_div_by_two_oneeighty;
	reg clk_div_by_four;
	reg clk_div_by_eight;
	reg clk_div_by_sixteen;
	
	always @(posedge clk) begin
		clk_div_by_two = !clk_div_by_two;
	end

	always @(negedge clk_div_by_two) begin
		clk_div_by_two_oneeighty = !clk_div_by_two_oneeighty;
	end
	
	always @(posedge clk_div_by_two_oneeighty) begin
		clk_div_by_four = !clk_div_by_four;
	end
	
	always @(posedge clk_div_by_four) begin
		clk_div_by_eight = !clk_div_by_eight;
	end

	always @(posedge clk_div_by_eight) begin
		clk_div_by_sixteen = !clk_div_by_sixteen;
	end


	//-----------------------------------------------------------------------------------
	//
	// Keep track of what is on the LED display
	//
	//-----------------------------------------------------------------------------------
	
	reg [7:0] led_display_bytes [3:0];
	reg [17:0] digit_blanker_1 = 0;
	reg [17:0] digit_blanker_2 = 0;
	reg [17:0] digit_blanker_3 = 0;
	reg [17:0] digit_blanker_4 = 0;

	reg [7:0] sseg_data_latch;
	reg [3:0] sseg_mux_latch;
	
	always @(negedge clk_div_by_sixteen) begin
		sseg_data_latch = sseg_data;
		sseg_mux_latch = sseg_mux;

		if (sseg_mux_latch[0] == 0) begin
			led_display_bytes[0] = sseg_data_latch;
			digit_blanker_1 = 0;
			digit_blanker_2 = digit_blanker_2 + 1;
			digit_blanker_3 = digit_blanker_3 + 1;
			digit_blanker_4 = digit_blanker_4 + 1;
		end
		
		if (sseg_mux_latch[1] == 0) begin
			led_display_bytes[1] = sseg_data_latch;
			digit_blanker_1 = digit_blanker_1 + 1;
			digit_blanker_2 = 0;
			digit_blanker_3 = digit_blanker_3 + 1;
			digit_blanker_4 = digit_blanker_4 + 1;
		end
		
		if (sseg_mux_latch[2] == 0) begin
			led_display_bytes[2] = sseg_data_latch;
			digit_blanker_1 = digit_blanker_1 + 1;
			digit_blanker_2 = digit_blanker_2 + 1;
			digit_blanker_3 = 0;
			digit_blanker_4 = digit_blanker_4 + 1;
		end
		
		if (sseg_mux_latch[3] == 0) begin
			led_display_bytes[3] = sseg_data_latch;
			digit_blanker_1 = digit_blanker_1 + 1;
			digit_blanker_2 = digit_blanker_2 + 1;
			digit_blanker_3 = digit_blanker_3 + 1;
			digit_blanker_4 = 0;
		end

		if (digit_blanker_1 > 128000) begin
			led_display_bytes[0] = 255;
		end
		
		if (digit_blanker_2 > 128000) begin
			led_display_bytes[1] = 255;
		end
		
		if (digit_blanker_3 > 128000) begin
			led_display_bytes[2] = 255;
		end
		
		if (digit_blanker_4 > 128000) begin
			led_display_bytes[3] = 255;
		end
	end

	//-----------------------------------------------------------------------------------
	//
	// Logic analyzer
	//
	//-----------------------------------------------------------------------------------

	reg logic_analyzer_trigger;
	reg logic_analyzer_trigger_prev;
	reg [11:0] logic_analyzer_address_counter;
	always @(posedge logic_analyzer_clk) begin
		// Trigger
		logic_analyzer_trigger = ~userlogic_reset;	// Trigger on userlogic_reset falling edge
		if ((logic_analyzer_trigger == 1) && (logic_analyzer_trigger_prev == 0)) begin
			logic_analyzer_address_counter = 0;
		end

		// Data load
		if (logic_analyzer_address_counter[11] == 1'b0) begin
			logic_analyzer_data_storage_addrb = logic_analyzer_address_counter;

			// Connect signals to logic analyzer
			logic_analyzer_data_storage_dinb[3:0] = four_bit_leds;
			logic_analyzer_data_storage_dinb[7:4] = four_bit_switches;
			logic_analyzer_data_storage_dinb[15:8] = eight_bit_leds;
			logic_analyzer_data_storage_dinb[23:16] = eight_bit_switches;
			logic_analyzer_data_storage_dinb[27:24] = sseg_mux;
			logic_analyzer_data_storage_dinb[35:28] = sseg_data;
			logic_analyzer_data_storage_dinb[43:36] = usermem_data;
			logic_analyzer_data_storage_dinb[59:44] = usermem_address;
			logic_analyzer_data_storage_dinb[60] = usermem_wen;
			logic_analyzer_data_storage_dinb[61] = usermem_wait;
			logic_analyzer_data_storage_dinb[62] = 1'b0;		// UNUSED
			logic_analyzer_data_storage_dinb[63] = userlogic_clock;

			logic_analyzer_data_storage_web = 1'b1;
			logic_analyzer_address_counter = logic_analyzer_address_counter + 1;
		end

		logic_analyzer_trigger_prev = logic_analyzer_trigger;
	end


	//-----------------------------------------------------------------------------------
	//
	// Memory and register access
	//
	//-----------------------------------------------------------------------------------

	reg gpmc_advn_reg;
	reg gpmc_oen_reg;
	reg gpmc_wen_reg;
	reg [7:0] gpmc_data_reg;
	reg [RAM_ADDR_BITS:0] gpmc_address_reg;

	reg usermem_wen_reg;
	reg [7:0] usermem_data_reg;
	reg [RAM_ADDR_BITS:0] usermem_address_reg;

	always @(posedge clk) begin
		usermem_wen_reg = usermem_wen;
		usermem_data_reg = usermem_data;
		usermem_address_reg = usermem_address;

		gpmc_advn_reg = gpmc_advn;
		gpmc_oen_reg = gpmc_oen;
		gpmc_wen_reg = gpmc_wen;
		if (gpmc_wen_reg == 1'b0) begin
			gpmc_data_reg = gpmc_data;
		end
		if (gpmc_advn_reg == 1'b0) begin
			gpmc_address_reg = gpmc_address;
			data_storage_write_enable = 1'b0;
			lcd_data_storage_wea = 1'b0;
			logic_analyzer_data_storage_wea = 1'b0;
		end
		if (gpmc_wen_reg == 1'b1) begin
			data_storage_write_enable = 1'b0;
			lcd_data_storage_wea = 1'b0;
			logic_analyzer_data_storage_wea = 1'b0;
		end

		if (gpmc_address_reg[RAM_ADDR_BITS] == 1'b1) begin
			// System memory access
			usermem_wait = 1'b1;
			if (gpmc_wen_reg == 1'b0) begin
				data_storage_addra = gpmc_address_reg[(RAM_ADDR_BITS-1):0];
				data_storage_dina = gpmc_data_reg;
				data_storage_write_enable = 1'b1;
			end else begin
				data_storage_addra = gpmc_address_reg[(RAM_ADDR_BITS-1):0];
				data_storage_write_enable = 1'b0;
				gpmc_data_out = data_storage_data_out;
			end
		end else begin
			// User memory access
			usermem_wait = 1'b0;
			if (usermem_address_reg[RAM_ADDR_BITS] == 1'b1) begin
				// Interdevice communication region
				// MEMORY MAP
				// 0x20 - 0x3f: LCD data area
				if (usermem_wen_reg == 1'b0) begin
					if (usermem_address_reg[(RAM_ADDR_BITS-1):5] == 1) begin	// Address range 0x20 - 0x3f
						lcd_data_storage_addrb = usermem_address_reg[4:0];
						lcd_data_storage_dinb = usermem_data_reg;
						lcd_data_storage_web = 1'b1;
					end
				end else begin
					if (usermem_address_reg[(RAM_ADDR_BITS-1):5] == 1) begin	// Address range 0x20 - 0x3f
						lcd_data_storage_addrb = usermem_address_reg[4:0];
						lcd_data_storage_web = 1'b0;
						usermem_data_out = lcd_data_storage_doutb;
					end else begin
						// Default
						usermem_data_out = 8'b00000000;
					end
				end
			end else begin
				// Client scratchpad memory area
				if (usermem_wen_reg == 1'b0) begin
					data_storage_addra = usermem_address_reg[(RAM_ADDR_BITS-1):0];
					data_storage_dina = usermem_data_reg;
					data_storage_write_enable = 1'b1;
				end else begin
					data_storage_addra = usermem_address_reg[(RAM_ADDR_BITS-1):0];
					data_storage_write_enable = 1'b0;
					usermem_data_out = data_storage_data_out;
				end
			end

			// Configuration register access
			// MEMORY MAP
			// 0x00: Model number (read only)
			// 0x01: Version (read only)
			// 0x02: 4-bit I/O (lower 4 bits only)
			// 0x03: 8-bit I/O
			// 0x04: 16-bit I/O (upper 8 bits)
			// 0x05: 16-bit I/O (lower 8 bits)
			// 0x06: 7-segment LED digit 0 (read only)
			// 0x07: 7-segment LED digit 1 (read only)
			// 0x08: 7-segment LED digit 2 (read only)
			// 0x09: 7-segment LED digit 3 (read only)
			// 0x0a: User process register
			//       Bit 0: User processing start
			//       Bit 1: User processing done (read only)
			// 0x0b: Number of address bits of DSP RAM (read only)
			// 0x0c: User device control
			//       Bit 0: User logic reset
			// 0x20 - 0x3f: LCD data area
			// 0x4000 - 0x7fff: Logic analyzer data area (read only)
			if (gpmc_wen_reg == 1'b0) begin
				if (gpmc_address_reg[(RAM_ADDR_BITS-1):5] == 1) begin		// Address range 0x20 - 0x3f
					lcd_data_storage_addra = gpmc_address_reg[4:0];
					lcd_data_storage_dina = gpmc_data_reg;
					lcd_data_storage_wea = 1'b1;
				end else begin
					case (gpmc_address_reg[(RAM_ADDR_BITS-1):0])
						2: begin
							four_bit_switches = gpmc_data_reg[3:0];
						end
						3: begin
							eight_bit_switches = gpmc_data_reg;
						end
						4: begin
							sixteen_bit_io_out[15:8] = gpmc_data_reg;
						end
						5: begin
							sixteen_bit_io_out[7:0] = gpmc_data_reg;
						end
						10: begin
							userproc_start = gpmc_data_reg[0];
						end
						12: begin
							userlogic_reset = gpmc_data_reg[0];
						end
						default: begin
							// Do nothing
						end
					endcase
				end
			end else begin
				if (gpmc_address_reg[(RAM_ADDR_BITS-1):5] == 1) begin		// Address range 0x20 - 0x3f
					lcd_data_storage_addra = gpmc_address_reg[4:0];
					lcd_data_storage_wea = 1'b0;
					gpmc_data_out = lcd_data_storage_douta;
				end else if (gpmc_address_reg[(RAM_ADDR_BITS-1):14] == 1) begin	// Address range 0x4000 - 0x7fff
					logic_analyzer_data_storage_addra = gpmc_address_reg[13:3];
					logic_analyzer_data_storage_wea = 1'b0;
					case (gpmc_address_reg[2:0])
						0: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[7:0];
						end
						1: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[15:8];
						end
						2: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[23:16];
						end
						3: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[31:24];
						end
						4: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[39:32];
						end
						5: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[47:40];
						end
						6: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[55:48];
						end
						7: begin
							gpmc_data_out = logic_analyzer_data_storage_douta[63:56];
						end
					endcase
				end else begin
					case (gpmc_address_reg[(RAM_ADDR_BITS-1):0])
						0: begin
							gpmc_data_out = 8'b01000010;
						end
						1: begin
							gpmc_data_out = 8'b00000001;
						end
						2: begin
							gpmc_data_out[7:4] = 0;
							gpmc_data_out[3:0] = four_bit_leds;
						end
						3: begin
							gpmc_data_out = eight_bit_leds;
						end
						4: begin
							gpmc_data_out = sixteen_bit_io_in[15:8];
						end
						5: begin
							gpmc_data_out = sixteen_bit_io_in[7:0];
						end
						6: begin
							gpmc_data_out = led_display_bytes[0];
						end
						7: begin
							gpmc_data_out = led_display_bytes[1];
						end
						8: begin
							gpmc_data_out = led_display_bytes[2];
						end
						9: begin
							gpmc_data_out = led_display_bytes[3];
						end
						10: begin
							gpmc_data_out[0] = userproc_start;
							gpmc_data_out[1] = userproc_done;
							gpmc_data_out[7:2] = 0;
						end
						11: begin
							gpmc_data_out = RAM_ADDR_BITS;
						end
						12: begin
							gpmc_data_out[0] = userlogic_reset;
							gpmc_data_out[7:1] = 0;
						end
						default: begin
							gpmc_data_out = 0;
						end
					endcase
				end
			end
		end

		gpmc_data_driven = ((~gpmc_oen) && gpmc_wen);
	end
endmodule
