module moonbase_cpu_4bit #(parameter MAX_COUNT=1000) (input [7:0] io_in, output [7:0] io_out);
   
	//
	//	External interfacex
	//
	//	external address latch
	//		the external 7 bit address latch is loaded from io_out[6:0] when io_out[7] is 1	
	//	external SRAM (eg MWS5101AEL3):
	//		the external RAM always produces what is at the latch's addresses on io_in[5:2]
	//		the external SRAM is written when io_out[7] is 0 and io_out[5] is 0
	//		io_out[6] can be used as an extra address bit to split the address space between
	//			code (1) and data (0) to use a 256-nibble sram (woot!)
	//  external devices:
	//		external devices can be read from io_in[7:6] (at address pointed to by the address latch)
	//		external devices can be written from io_out[3:0] (at address pointed to by the address latch)
	//			when io_out[7] is 0 and io_out[4] is 0
	//
	//
	//	SRAM address space (data accesses):
	//		0-127	external
	//		128-131 internal	(internal ram cells, for filling up the die :-)
	//

	localparam N_LOCAL_RAM = 24;
     
    wire clk			= io_in[0];
    wire reset			= io_in[1];
    wire [3:0]ram_in	= io_in[5:2];
    wire [1:0]data_in	= io_in[7:6];
    
    reg       strobe_out;	// address strobe		- designed to be wired to a 7 bit latch and a MWS5101AEL3
    reg       write_data_n;	// write enable for data
    reg       write_ram_n;	// write enable for ram
    reg	      addr_pc;
    reg	      data_pc;
	wire [6:0]data_addr = ((r_tmp[3]?r_y[6:0]:r_x[6:0])+{4'b000, r_tmp[2:0]});
	wire	  is_local_ram = (r_tmp[3]?r_y[7]:r_x[7]);
	wire	  write_local_ram = is_local_ram & !write_ram_n;
	wire [$clog2(N_LOCAL_RAM)-1:0]local_ram_addr = data_addr[$clog2(N_LOCAL_RAM)-1:0];
    wire [6:0]addr_out = addr_pc ? r_pc : data_addr;							// address out mux (PC or X/Y+off)
    assign    io_out   = {strobe_out, strobe_out? addr_out : {data_pc, write_ram_n|is_local_ram, write_data_n, r_a}};  // mux address and data out

    reg  [6:0]r_pc, c_pc;	// program counter	// actual flops in the system 
    reg  [7:0]r_x, c_x;		// x index register	// by convention r_* is a flop, c_* is the combinatorial that feeds it
    reg  [7:0]r_y, c_y;		// y index register
    reg  [3:0]r_a, c_a;		// accumulator
    reg       r_c, c_c;		// carry flag
    reg  [3:0]r_tmp2, c_tmp2;// operand temp (high)
    reg  [3:0]r_tmp,  c_tmp;// operand temp (low)
	reg  [6:0]r_s0,   c_s0;	// call stack
	reg  [6:0]r_s1,   c_s1;
	reg  [6:0]r_s2,   c_s2;
	reg  [6:0]r_s3,   c_s3;

    //
    //	phase:
    //		0 - instruction fetch addr
    //		1 - instruction fetch data
    //		2 - const fetch addr 
    //		3 - const fetch data 
    //		4 - data/const fetch addr 
    //		5 - data/const fetch data 
    //		6 - execute/data store addr
    //		7 - data store data (might not do this)
    //
    reg [2:0]r_phase, c_phase;	// CPU internal state machine
	

    // instructions
    //
    //  0 v:	add a, v(x/y)	- sets C
    //  1 v: 	sub a, v(x/y)	- sets C
    //  2 v:	or a, v(x/y)
    //  3 v:	and a, v(x/y)
    //  4 v:	xor a, v(x/y)
    //  5 v:	mov a, v(x/y)
    //  6 v:	movd a, v(x/y)
    //  7 0:	swap x, y
    //	  1:    add a, c
    //	  2:    mov x.l, a
    //	  3:    ret
	//    4:    add y, a
    //	  5:    add x, a
	//    6:    add y, #1
	//    7:    add x, #1
    //	8 v:	mov a, #v
    //  9 v:	add a, #v 
    //  a v:	movd v(x/y), a
    //  b v:	mov  v(x/y), a
    //  c h l:	mov x, #hl
    //  d h l:	jne a/c, hl	if h[3] the test c otherwise test a
    //  e h l:	jeq a/c, hl	if h[3] the test c otherwise test a
    //  f h l:	jmp/call hl
    //
    //  Memory access - addresses are 7 bits - v(X/y) is a 3-bit offset v[2:0]
    //  	if  v[3] it's Y+v[2:0]
    //  	if !v[3] it's X+v[2:0]
    //
    //	The general idea is that X normally points to a bank of in sram 8 'registers',
    //		a bit like an 8051's r0-7, while X is a more general index register
	//		(but you can use both if you need to do	some copying)
    //		

    reg  [3:0]r_ins, c_ins;	// fetched instruction

	wire [4:0]c_add = {1'b0, r_a}+{1'b0, r_tmp};	// ALUs
	wire [4:0]c_sub = {1'b0, r_a}-{1'b0, r_tmp};
	wire [6:0]c_i_add = (r_tmp[0]?r_x:r_y)+(r_tmp[1]?7'b1:{3'b0, r_a});
	wire [6:0]c_pc_inc = r_pc+1;

	
	reg	 [3:0] r_local_ram[0:N_LOCAL_RAM-1];

	wire [3:0] local_ram = r_local_ram[local_ram_addr];
	always @(posedge clk)
	if (write_local_ram)
		r_local_ram[local_ram_addr] <= r_a;

    always @(*) begin
		c_ins  = r_ins;	
		c_x    = r_x;
		c_y    = r_y;
		c_a    = r_a;
		c_s0   = r_s0;
		c_s1   = r_s1;
		c_s2   = r_s2;
		c_s3   = r_s3;
		c_tmp  = r_tmp;
		c_tmp2 = r_tmp2;
		c_pc   = r_pc;
		c_c    = r_c;
		write_data_n = 1;
		write_ram_n = 1;
		addr_pc = 'bx;
		data_pc = 'bx;
    	if (reset) begin	// reset clears the state machine and sets PC to 0
			c_pc = 0;
			c_phase = 0;
			strobe_out = 1;
    	end else 
    	case (r_phase) // synthesis full_case parallel_case
    	0:	begin					// 0: address latch instruction PC
				strobe_out = 1;
				addr_pc = 1;
				c_phase = 1;
			end
    	1:	begin					// 1: read data in
				strobe_out = 0;
				data_pc = 1;
				c_ins = ram_in;
				c_pc = c_pc_inc;
				c_phase = 2;
			end
		2:	begin
				strobe_out = 1;			// 2: address latch operand PC
				addr_pc = 1;
				c_phase = 3;
			end
		3:	begin
				strobe_out = 0;			// 3: read operand
				c_tmp = ram_in;
				c_pc = c_pc_inc;
				data_pc = 1;
				case (r_ins) // synthesis full_case parallel_case
				7, 8, 9, 10, 11: c_phase = 6;// some instructions don't have a 2nd fetch
				default:	     c_phase = 4;
				endcase
			end
		4:	begin						// 4 address latch for next operand  
				strobe_out = 1;
				addr_pc = r_ins[3:2] == 3;	// some instructions read a 2nd operand, the rest the come here read a memory location
				c_phase = 5;
			end
		5:	begin						// 5 read next operand
				strobe_out = 0;
				data_pc = r_ins[3:2] == 3;
				c_tmp2 = r_tmp;				// low->high for 2 byte cases
				c_tmp = (r_ins[3:1] == 3?{2'b0,data_in}:is_local_ram&&r_ins[3:2] != 3?local_ram:ram_in);	// read the actial data, movd comes from upper bits
				if (r_ins[3:2] == 3)		// if we fetched from PC increment it
					c_pc = c_pc_inc;
				c_phase = 6;
			end
		6:	begin						// 6 execute stage 
				strobe_out = r_ins[3:1] == 5;	// if writing to anything latch address
				addr_pc = 0;
				c_phase = 0;					// if not writing go back
				case (r_ins)// synthesis full_case parallel_case
				0,												// add  a, v(x)
				9:	begin c_c = c_add[4]; c_a = c_add[3:0]; end	// add  a, #v
				1:	begin c_c = c_sub[4]; c_a = c_sub[3:0]; end	// sub  a, v(x)
				2:	c_a = r_a|r_tmp;							// or   a, v(x)
				3:	c_a = r_a&r_tmp;							// sub  a, v(x)
				4:	c_a = r_a^r_tmp;							// xor  a, v(x)
				5,												// mov  a, v(x)
				6,												// movd a, v(x)
				8:	c_a = r_tmp;								// mov  a, #v
				7:	case (r_tmp) // synthesis full_case parallel_case
    				0: begin c_x = r_y; c_y = r_x; end			// 0    swap  y, x
					1: c_a = r_a+{3'b000, r_c};					// 1	add   a, c
    				2: c_x[3:0] = r_a;							// 2    mov   x.l, a
    				3: begin									// 3    ret
							c_pc = r_s0;
							c_s0 = r_s1;
							c_s1 = r_s2;
							c_s2 = r_s3;
					   end
					4: c_y = c_i_add;							// 4    add   y, a
					5: c_x = c_i_add;							// 5    add   x, a
					6: c_y = c_i_add;							// 6    add   y, #1
					7: c_x = c_i_add;							// 7    add   y, #1
					default: ;
					endcase
				10,												// movd v(x), a
				11:	c_phase = 7;								// mov  v(x), a
				12:	c_x  = {r_tmp2, r_tmp};						// mov  x, #VV
				13:	c_pc = (r_tmp2[3]?!r_c : r_a != 0) ? {r_tmp2[2:0], r_tmp} : r_pc; // jne	a/c, VV
				14:	c_pc = (r_tmp2[3]? r_c : r_a == 0) ? {r_tmp2[2:0], r_tmp} : r_pc; // jeq        a/c, VV
				15:	begin c_pc = {r_tmp2[2:0], r_tmp};				// jmp  VV
						if (r_tmp2[3]) begin	// call
							c_s0 = r_pc;
							c_s1 = r_s0;
							c_s2 = r_s1;
							c_s3 = r_s2;
						end
					end
				endcase
			end
		7:	begin						// 7 write data stage - assert appropriate write strobe
				strobe_out = 0;
				data_pc = 0;
				write_data_n =  r_ins[0];
				write_ram_n  = ~r_ins[0];
				c_phase = 0;
			end
    	endcase
    end

    always @(posedge clk) begin
		r_a     <= c_a;
		r_c     <= c_c;
		r_x     <= c_x;
		r_y     <= c_y;
		r_ins   <= c_ins;
		r_tmp   <= c_tmp;
		r_tmp2  <= c_tmp2;
		r_pc    <= c_pc;
		r_phase <= c_phase;
		r_s0    <= c_s0;
		r_s1    <= c_s1;
		r_s2    <= c_s2;
		r_s3    <= c_s3;
    end

endmodule