module up(
	input	            clk,
	input	            nRst,                      // Active low reset
   input             int,                       // Active high interrupt
   input             mem_map_load,              // Write = 1
   input    [8:0]    mem_map_address,           // 0..255 == Memory, > 255 processor registers
   input    [7:0]    mem_map_in,                // Data in can be written to memory
   output   [7:0]    mem_map_out                // Data out from memory and processor registers
);

   parameter   SIZE           = 256,
               LOAD_REGS_0    = 4'h0,
               LOAD_REGS_1    = 4'h1,
               LOAD_REGS_2    = 4'h2,
               LOAD_REGS_3    = 4'h3,
               LOAD_REGS_4    = 4'h4,
               FETCH          = 4'h5,
               DECODE         = 4'h6,
               EXECUTE_1      = 4'h7,
               EXECUTE_2      = 4'h8,
               EXECUTE_3      = 4'h9,
               INT_1          = 4'hA,
               INT_2          = 4'hB,
               INT_3          = 4'hC,
               INT_4          = 4'hD,
               IR_ADD         = 4'h0,
               IR_SUB         = 4'h1,
               IR_MUL         = 4'h2,
               IR_NAND        = 4'h3,
               IR_SW01        = 4'h4,
               IR_SW12        = 4'h5,
               IR_SW23        = 4'h6,
               IR_BE          = 4'h7,
               IR_POPC        = 4'h8,
               IR_PUSHC       = 4'h9,
               IR_POP         = 4'hA,
               IR_PUSH        = 4'hB,
               IR_LDW         = 4'hC,
               IR_STW         = 4'hD,
               IR_REF         = 4'hE,
               IR_INT         = 4'hF;


	reg   [7:0]    mem         [SIZE-1:0];
   reg   [3:0]    state;
   reg            int_on_off;
   reg            int_1;
   reg            int_2;
   reg            int_in;
   reg   [3:0]    ir;
   reg            ir_we;
   reg            pc_we;
   reg   [2:0]    rb_sel;
   reg            rb_we;
   reg            sp_we;
   reg            mem_we;
   reg            ale;
   reg   [7:0]    sp;
   reg   [7:0]    pc;
   reg   [7:0]    r0;
   reg   [7:0]    r1;
   reg   [7:0]    r2;
   reg   [7:0]    r3;  
   reg   [7:0]    addr;
   reg   [7:0]    data_out;
   reg   [7:0]    mem_map_out;
   wire  [7:0]    data_in; 
   wire           int_go;
   
   assign         data_in        = mem[addr];                                                   // Output from memory
   assign         z              = (r1 == r2) ? 1'b1 : 1'b0;                                    // branch signal
   assign         int_go         = (int_1 ^ int_2) & int_1 & int_on_off & ~int_in;               // Interrupt signal

   always @(*) begin
      ir_we       = 1'b0;
      pc_we       = 1'b0;
      rb_sel      = 3'b100;
      rb_we       = 1'b0;
      sp_we       = 1'b0;
      mem_we      = 1'b0;
      ale         = 1'b0;
      mem_map_out = 8'h00;
      data_out    = 8'h00;
      if(mem_map_address[8]) begin                                                              // Registers are also available on memory map output
         case(mem_map_address[7:0])  
            8'h00:   mem_map_out    = state;
            8'h01:   mem_map_out    = int_on_off;
            8'h02:   mem_map_out    = int_1;
            8'h03:   mem_map_out    = int_1;
            8'h04:   mem_map_out    = int_in;
            8'h05:   mem_map_out    = ir;
            8'h06:   mem_map_out    = ir_we;
            8'h07:   mem_map_out    = pc_we;
            8'h08:   mem_map_out    = rb_sel;
            8'h09:   mem_map_out    = rb_we;
            8'h0A:   mem_map_out    = sp_we;
            8'h0B:   mem_map_out    = mem_we;
            8'h0D:   mem_map_out    = ale;
            8'h0E:   mem_map_out    = sp;
            8'h0F:   mem_map_out    = pc;
            8'h0E:   mem_map_out    = r0;
            8'h10:   mem_map_out    = r1;
            8'h11:   mem_map_out    = r2;
            8'h12:   mem_map_out    = r3;  
            8'h13:   mem_map_out    = addr;
            8'h14:   mem_map_out    = data_out;
         endcase
      end else begin
         mem_map_out    = mem[mem_map_address[7:0]];                                            // Memory output 
      end
      casex({state,ir})
         {LOAD_REGS_0,4'bxxxx },                                                                // Get mem for R0
         {EXECUTE_1,IR_REF    }:    begin                                                       // Load referance
                                                      data_out = 8'h00;
                                                      ale      = 1'b1;
                                    end
         {LOAD_REGS_1,4'bxxxx }:    begin                                                       // Load R0
                                                      data_out = 8'h01;
                                                      rb_sel   = 3'b000;
                                                      rb_we    = 1'b1;
                                                      ale      = 1'b1;

                                    end
         {LOAD_REGS_2,4'bxxxx }:    begin                                                       // Load R1
                                                      data_out = 8'h02;
                                                      rb_sel   = 3'b001;
                                                      rb_we    = 1'b1;
                                                      ale      = 1'b1;

                                    end
         {LOAD_REGS_3,4'bxxxx }:    begin                                                       // Load R2
                                                      data_out = 8'h03;
                                                      rb_sel   = 3'b010;
                                                      rb_we    = 1'b1;
                                                      ale      = 1'b1;

                                    end
         {LOAD_REGS_4,4'bxxxx }:    begin                                                       // Load R3
                                                      data_out = {1'b0,pc[7:1]};
                                                      rb_sel   = 3'b011;
                                                      rb_we    = 1'b1;
                                                      ale      = 1'b1;
                                    end
         {FETCH,4'bxxxx       }:    begin                                                       // Get from memory or do an interupt
                                       if(int_in)     data_out = {1'b1,pc[7:1]};
                                       else           data_out = {1'b0,pc[7:1]};
                                                      ale      = 1'b1;
                                    end
         {DECODE,4'bxxxx      }:    begin                                                       // Write to PC and IR
                                                      data_out = pc + 1'b1;
                                                      ir_we    = 1'b1;
                                                      pc_we    = 1'b1;
                                    end

         {EXECUTE_1,IR_ADD    }:    begin                                                       // R0 = R1 + R2  
                                                      data_out = r1 + r2;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_SUB    }:    begin                                                       // R0 - R1 - R2
                                                      data_out = r1 - r2;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_MUL    }:    begin                                                       // R0 = R1 * R2
                                                      data_out = r1 * r2;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_NAND   }:    begin                                                       // R0 = R1 NAND R2
                                                      data_out = ~(r1 & r2);
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_SW01   },
         {EXECUTE_3,IR_SW01   }:    begin                                                       // Switrch R0 and R1
                                                      data_out = r0 ^ r1;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_SW12   },
         {EXECUTE_3,IR_SW12   }:    begin                                                       // Switch R2 and R2
                                                      data_out = r1 ^ r2;
                                                      rb_sel   = 3'b101;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_SW23   },                                                                
         {EXECUTE_3,IR_SW23   }:    begin                                                       // Switch R2 and R3 
                                                      data_out = r2 ^ r3;;
                                                      rb_sel   = 3'b110;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_BE     }:    if(z) begin                                                 // Branch to loaction in R3 if R1 == R2
                                                      data_out = r3;
                                                      pc_we    = 1'b1;
                                    end
         {EXECUTE_1,IR_POP    },                                                                // Pop R3 from stack
         {EXECUTE_1,IR_POPC   }:    begin                                                       // Pop propgram counter from stack
                                                      data_out = sp + 1'b1; 
                                                      sp_we    = 1'b1;
                                                      ale      = 1'b1;
                                    end
         {EXECUTE_1,IR_PUSHC  },                                                                // Push prgram counter to stack
         {EXECUTE_1,IR_PUSH   },                                                                // Push R3 to stack  
         {INT_1,4'bxxxx       }:    begin                                                       // Interrupt
                                                      data_out = sp;
                                                      ale      = 1'b1;
                                    end
         {EXECUTE_1,IR_LDW    },                                                                // Load
         {EXECUTE_1,IR_STW    }:    begin                                                       // Store
                                                      data_out = r3;
                                                      ale      = 1'b1;
                                    end
         {EXECUTE_1,IR_REF    }:    begin                                                       // Load reference memory location
                                                      data_out = 8'h00;
                                                      ale      = 1'b1;
                                    end
         {EXECUTE_2,IR_SW01   }:    begin                                                       // Switch R0 and r1
                                                      data_out =  r0 ^ r1;
                                                      rb_sel   = 3'b101;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_2,IR_SW12   }:    begin                                                       // Switch R1 and R2
                                                      data_out = r1 ^ r2;
                                                      rb_sel   = 3'b110;
                                                      rb_we    = 1'b1;
                                    end   
         {EXECUTE_2,IR_SW23   }:    begin                                                       // Switch R2 and R3
                                                      data_out = r2 ^ r3;
                                                      rb_sel   = 3'b111;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_2,IR_POPC   }:    begin                                                       // Pop in program counter
                                                      data_out = data_in;
                                                      pc_we    = 1'b1;
                                    end
         {EXECUTE_2,IR_PUSHC  },                                                                // Push program counter to stack
         {EXECUTE_2,IR_PUSH   }:    begin                                                       // Push R3 to stack
                                                      data_out = sp - 1'b1;
                                                      sp_we    = 1'b1;                    
                                    end
         {EXECUTE_2,IR_POP    },                                                                // Pop stack in top R3
         {EXECUTE_2, IR_LDW   }:    begin                                                       // Load
                                                      data_out = data_in;
                                                      rb_sel   = 3'b010;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_2,IR_STW    },                                                                // Store 
         {EXECUTE_3,IR_PUSH   }:    begin                                                       // Push R3 to stack
                                                      data_out = r2;
                                                      mem_we   = 1'b1;
                                    end
         {EXECUTE_2,IR_REF    }:    begin                                                       // Load reference memory location
                                                      data_out = 8'h00;
                                                      rb_sel   = 3'b000;
                                                      rb_we    = 1'b1;
                                    end
         {EXECUTE_3,IR_PUSHC  }:    begin
                                                      data_out = pc - 1'b1;                     // Push program counter to stack
                                                      mem_we   = 1'b1;
                                    end
         {INT_2,4'bxxxx       }:    begin
                                                      data_out = pc;                            // Write PC
                                                      mem_we   = 1'b1;
                                    end
         {INT_3,4'bxxxx       }:    begin
                                                      data_out = sp - 1'b1;                     // Dec SP
                                                      sp_we    = 1'b1;
                                    end
         {INT_4,4'bxxxx       }:    begin
                                                      data_out = 8'h00;                         // Jump to fixed location
                                                      pc_we    = 1'b1;
                                    end
      endcase
   end
   always@(posedge clk or negedge nRst) begin 
      if(nRst) begin      
         if(!int_go) begin
                                                int_1                      <= int;              // Clock in int 
                                                int_2                      <= int_1;
         end
         state <= FETCH;
         casex({int_go,state,ir})
            {1'bx,LOAD_REGS_0,   4'bxxxx  }:    state                      <= LOAD_REGS_1;      // The load regs from bottom four bytes 
            {1'bx,LOAD_REGS_1,   4'bxxxx  }:    state                      <= LOAD_REGS_2;
            {1'bx,LOAD_REGS_2,   4'bxxxx  }:    state                      <= LOAD_REGS_3;
            {1'bx,LOAD_REGS_3,   4'bxxxx  }:    state                      <= LOAD_REGS_4;
            {1'b1,FETCH,         4'bxxxx  }:    state                      <= INT_1;            // An interrupt has happened
            {1'b0,FETCH,         4'bxxxx  }:    state                      <= DECODE;           
            {1'bx,DECODE,        4'bxxxx  }:    state                      <= EXECUTE_1;        // Simple ops finish here
            {1'bx,EXECUTE_1,     IR_SW01  },
            {1'bx,EXECUTE_1,     IR_SW12  },
            {1'bx,EXECUTE_1,     IR_SW23  },
            {1'bx,EXECUTE_1,     IR_PUSHC },
            {1'bx,EXECUTE_1,     IR_POP   },
            {1'bx,EXECUTE_1,     IR_PUSH  },
            {1'bx,EXECUTE_1,     IR_LDW   },
            {1'bx,EXECUTE_1,     IR_STW   },
            {1'bx,EXECUTE_1,     IR_REF   }:    state                      <= EXECUTE_2;        
            {1'bx,EXECUTE_1,     IR_POPC  }:    begin
                                                   state                   <= EXECUTE_2;
                                                   int_in                  <= 1'b0;
                                                end
            {1'bx,EXECUTE_1,     IR_INT   }:    int_on_off                 <= ~int_on_off;      // Toggle ints
            {1'bx,EXECUTE_2,     IR_SW01  },                                                    // Multi cycle operations
            {1'bx,EXECUTE_2,     IR_SW12  },
            {1'bx,EXECUTE_2,     IR_SW23  },
            {1'bx,EXECUTE_2,     IR_PUSHC },
            {1'bx,EXECUTE_2,     IR_PUSH  }:    state                      <= EXECUTE_3;
            {1'bx,INT_1,         4'bxxxx  }:    begin                                           // Interrupt jump happens here
                                                   int_in                  <= 1'b1;
                                                   state                   <= INT_2;
                                                end
            {1'bx,INT_2,         4'bxxxx  }:    state                      <= INT_3;
            {1'bx,INT_3,         4'bxxxx  }:    state                      <= INT_4;
         endcase
         if(sp_we)                              sp                         <= data_out;
         if(pc_we)                              pc                         <= data_out;
         case({ir_we,pc[0]})                                                                    // Flip between upper and lower nibble of instructions
            2'b11:                              ir                         <= data_in[3:0];
            2'b10:                              ir                         <= data_in[7:4];
         endcase
         case({rb_we,rb_sel})                                                                   // Reg select 
            4'h8:                               r0                         <= data_in;      
            4'h9:                               r1                         <= data_in;      
            4'hA:                               r2                         <= data_in;      
            4'hB:                               r3                         <= data_in;      
            4'hC:                               r0                         <= data_out;     
            4'hD:                               r1                         <= data_out;     
            4'hE:                               r2                         <= data_out;     
            4'hF:                               r3                         <= data_out;     
         endcase
         casex({ale,mem_we})
            2'b1x:                              addr                       <= data_out;         // Interface to memory
            2'bx1:                              mem[addr]                  <= data_out;
         endcase 
         if(mem_map_load) 
            if(mem_map_address[8])              begin
                                                state                      <= LOAD_REGS_0;      // Soft reset
                                                int_on_off                 <= 1'b0;
                                                int_1                      <= 1'b0;
                                                int_2                      <= 1'b0;
                                                int_in                     <= 1'b0;
                                                pc                         <= 8'h08;
                                                sp                         <= 8'hFF;
                                                ir                         <= IR_ADD;
                                                r0                         <= 8'h00;
                                                r1                         <= 8'h00;
                                                r2                         <= 8'h00;
                                                r3                         <= 8'h00;
                                                addr                       <= 8'h00; 

            end else                            mem[mem_map_address[7:0]]  <= mem_map_in;       // Load any byte in memory
      end else begin                                                                            // Hard reset
                                                state                      <= LOAD_REGS_0;
                                                int_on_off                 <= 1'b0;
                                                int_1                      <= 1'b0;
                                                int_2                      <= 1'b0;
                                                int_in                     <= 1'b0;
                                                pc                         <= 8'h08;
                                                sp                         <= 8'hFF;
                                                ir                         <= IR_ADD;
                                                r0                         <= 8'h00;
                                                r1                         <= 8'h00;
                                                r2                         <= 8'h00;
                                                r3                         <= 8'h00;
                                                addr                       <= 8'h00; 
      end
   end  
endmodule