module M65C02_Core #(
    parameter pStkPtr_Rst  = 8'hFF, // Stk Ptr Value after Reset
    parameter pInt_Hndlr   = 0,     // _Int microroutine address, Reset default
    parameter pM65C02_uPgm = "M65C02_uPgm_V3a.coe",
    parameter pM65C02_IDec = "M65C02_Decoder_ROM.coe"
)(
    input   Rst,            // System Reset Input
    input   Clk,            // System Clock Input
    
    //  Processor Core Interrupt Interface
    
    output  IRQ_Msk,        // Interrupt mask from P to Interrupt Handler
    input   xIRQ,           // External Maskable Interrupt Request Input
    input   Int,            // Interrupt input from Interrupt Handler
    input   [15:0] Vector,  // ISR Vector from Interrupt Handler
    
    //  Processor Core Status Interface
    
    output  Done,           // Instruction Complete/Fetch Strobe
    output  SC,             // Single Cycle Instruction
    output  [2:0] Mode,     // Mode - Instruction Type/Mode
    output  RMW,            // Read-Modify-Write Operation
    output  reg IntSvc,     // Interrupt Service Start Indicator
    output  ISR,            // Interrupt Vector Pull Start Flag
    
    //  Processor Core Memory Controller Interface
    
    output  [2:0] MC,       // Microcycle state:   2-C1; 3-C4; 1-C3; 0-C4;
    output  [1:0] MemTyp,   // Memory access Type: 0-Pgm Memory; 1-Page 0;
                            //                     2-Page 1;     3-Data Memory;
    input   Wait,           // Wait Input (in C3, adds wait state sequence)
    output  Rdy,            // Internal Ready
    
    //  Processor Core Memory Cycle Interface    
    
    output  [ 1:0] IO_Op,   // Instruction Fetch Strobe
    output  [15:0] AO,      // External Address
    input   [ 7:0] DI,      // External Data In
    output  reg [7:0] DO,   // External Data Out

    // Processor Core Internal Registers

    output  [ 7:0] A,       // Accumulator
    output  [ 7:0] X,       // Index Register X
    output  [ 7:0] Y,       // Index Register Y
    output  [ 7:0] S,       // Stack Pointer
    output  [ 7:0] P,       // Processor Status Word
    output  [15:0] PC,      // Program Counter
    
    output  reg [7:0] IR,   // Instruction Register
    output  reg [7:0] OP1,  // Operand Register 1
    output  reg [7:0] OP2   // Operand Register 2
);

///////////////////////////////////////////////////////////////////////////////
//
// Local Parameter Declarations
//

localparam  pROM_AddrWidth = 8'd9;
localparam  pROM_Width     = 8'd32;
localparam  pROM_Depth     = (2**pROM_AddrWidth);

localparam  pDEC_AddrWidth = 8'd8;
localparam  pDEC_Width     = 8'd32;
localparam  pDEC_Depth     = (2**pDEC_AddrWidth);

//

localparam  pBA_Fill = (pROM_AddrWidth - pDEC_AddrWidth);

localparam  pBRV1    = 2'b01;   // MPC Via[1:0] code for BRV1 instruction
localparam  pBRV2    = 2'b10;   // MPC Via[1:0] code for BRV2 instruction
localparam  pBRV3    = 2'b11;   // MPC Via[1:0] code for BRV3 instruction
localparam  pBMW     = 4'b0011; // MPC I[3:0] code for BMW instruction

localparam  pNOP     = 8'hEA;   // NOP opcode

localparam  pPC_Pls  = 2'b01;   // PC Increment
localparam  pPC_Jmp  = 2'b10;   // PC Absolute Jump
localparam  pPC_Rel  = 2'b11;   // PC Conditional Branch

localparam  pIO_IF   = 2'b11;   // Instruction Fetch
localparam  pIO_RD   = 2'b10;   // Memory Read
localparam  pIO_WR   = 2'b01;   // Memory Write

localparam  pDO_ALU  = 2'b00;   // DO    <= ALU_Out
localparam  pDO_PCH  = 2'b01;   // DO    <= PC[15:8]
localparam  pDO_PCL  = 2'b10;   // DO    <= PC[ 7:0]
localparam  pDO_PSW  = 2'b11;   // DO    <= P (also available on ALU_Out)
//
localparam  pDI_Mem  = 2'b00;   // ALU_M <= DI
localparam  pDI_OP2  = 2'b01;   // OP2   <= DI
localparam  pDI_OP1  = 2'b10;   // OP1   <= DI
localparam  pDI_IR   = 2'b11;   // IR    <= DI

localparam  pStk_Psh = 2'b10;   // StkPtr <= S;
localparam  pStk_Pop = 2'b11;   // StkPtr <= S + 1;

localparam  pNA_Inc  = 4'h1;    // NA <= PC + 1
localparam  pNA_MAR  = 4'h2;    // NA <= MAR + 0
localparam  pNA_Nxt  = 4'h3;    // NA <= MAR + 1
localparam  pNA_Stk  = 4'h4;    // NA <= SP + 0
localparam  pNA_DPN  = 4'h5;    // NA <= {0, OP1} + 0
localparam  pNA_DPX  = 4'h6;    // NA <= {0, OP1} + {0, X}
localparam  pNA_DPY  = 4'h7;    // NA <= {0, OP1} + {0, Y}
localparam  pNA_LDA  = 4'h8;    // NA <= {OP2, OP1} + 0
//
//
//
//
//
localparam  pNA_LDAX = 4'hE;    // NA <= {OP2, OP1} + {0, X}
localparam  pNA_LDAY = 4'hF;    // NA <= {OP2, OP1} + {0, Y}

localparam  pBCD     = 3;       // Bit number of BCD Mode bit in P
localparam  pIntMsk  = 2;       // Bit number of Interrupt mask bit in P

localparam  pADC     = 4;       // ALU Operation Add w/ Carry
localparam  pSBC     = 5;       // ALU Operation Subtract w/ Carry

///////////////////////////////////////////////////////////////////////////////
//
// Local Signal Declarations
//

wire    WAI;                            // Instruction Mode Decode for WAI

wire    BRV1;                           // MPC BRV1 Instruction Decode
wire    BRV2;                           // MPC BRV2 Instruction Decode
wire    BRV3;                           // MPC BRV3 Instruction Decode
wire    BMW;                            // MPC BMW Instruction Decode

reg     [(pROM_Width - 1):0] uP_ROM [(pROM_Depth - 1):0]; // Microprogram ROM

wire    [3:0] I;                        // MPC Instruction Input
wire    [3:0] T;                        // MPC Test Inputs
wire    [2:0] MW;                       // MPC Multi-way Branch Select
reg     [(pROM_AddrWidth - 1):0] BA;    // MPC Branch Address Input
wire    [1:0] Via;                      // MPC Via Mux Control Output
wire    [(pROM_AddrWidth - 1):0] MA;    // MPC uP ROM Address Output
//
reg     [(pROM_Width - 1):0] uPL;       // MPC uP ROM Pipeline Register
//
wire    [(pROM_AddrWidth - 1):0] uP_BA; // uP Branch Address Field
wire    ZP;                             // Zero Page Addressing Control Field
wire    [3:0] NA_Op;                    // Memory Address Register Control Fld
wire    [1:0] PC_Op;                    // Program Counter Control Field
wire    [1:0] DI_Op;                    // Memory Data Input Control Field
wire    [1:0] DO_Op;                    // Memory Data Output Control Field
wire    [1:0] Stk_Op;                   // Stack Pointer Control Field
wire    [2:0] Reg_WE;                   // Register Write Enable Control Field

reg     En;                             // ALU Enable Control Field

//  Instruction Decoder ROM

reg     [(pDEC_Width - 1):0] ID_ROM [(pDEC_Depth - 1):0]; // Inst. Decode ROM

//  Instruction Decoder Pipeline Register (Asynchronous Distributed ROM)

reg     [(pDEC_Width - 1):0] IDEC;  // Instruction Decode ROM Pipeline Reg.

//  Instruction Decoder (Fixed) Output

wire    [3:0] Op;                   // M65C02 ALU Operation Select Field
wire    [1:0] QSel;                 // M65C02 ALU Q Operand Select Field
wire    RSel;                       // M65C02 ALU R Operand Select Field
wire    Sub;                        // M65C02 ALU Adder Control Field
wire    CSel;                       // M65C02 ALU Adder Carry In Select Field
wire    [2:0] WSel;                 // M65C02 ALU Register Write Select Field
wire    [2:0] OSel;                 // M65C02 ALU Output Select Field
wire    [4:0] CCSel;                // M65C02 ALU Condition Code Control Field
wire    [7:0] Msk;                  // M65C02 Rockwell Instruction Mask Field
  
wire    [7:0] Out;                  // M65C02 ALU Data Output Bus
wire    Valid;                      // M65C02 ALU Output Valid Signal
wire    [7:0] StkPtr;               // M65C02 ALU Stack Pointer Logic Output
wire    CC;                         // ALU Condition Code Output

wire    [15:0] dPC;                 // Pipeline Compensation Register for PC

wire    CE_IR, CE_OP1, CE_OP2;      // Clock Enables: IR, OP1, and OP2

reg     [5:0] DO_Sel;               // Data Output Multiplexer Decode ROM

///////////////////////////////////////////////////////////////////////////////
//
//  Start Implementation
//
///////////////////////////////////////////////////////////////////////////////

//  Define Microcycle and Instruction Cycle Status Signals

assign Done = (|Via);         // Instruction Complete (1)     - ~BRV0
assign SC   = (&Via);         // Single Cycle Instruction (1) -  BRV3             
assign Rdy  = (MC == 4);      // Microcycle Complete Signal

///////////////////////////////////////////////////////////////////////////////
//
//  Microprogram Controller Interface
//
//  Decode MPC Instructions being used for strobes

assign BRV1 = (Via == pBRV1);
assign BRV2 = (Via == pBRV2);
assign BRV3 = (Via == pBRV3);
assign BMW  = (I   == pBMW ); 

//  Define the Multi-Way Input Signals
//      Implement a 4-way branch when executing WAI, and a 2-way otherwise

assign MW = ((WAI) ? {uP_BA[2], xIRQ, Int} : {uP_BA[2:1], Int});

//  Implement the Branch Address Field Multiplexer for Instruction Decode

always @(*)
begin
    case(Via)
        pBRV1   : BA <= {{pBA_Fill{1'b1}}, DI[3:0], DI[7:4]};
        pBRV3   : BA <= ((Int) ? pInt_Hndlr
                               : {{pBA_Fill{1'b1}}, DI[3:0], DI[7:4]});
        default : BA <= uP_BA;
    endcase
end

//  Assign Test Input Signals

assign T = {3'b000, Valid};

//  Instantiate Microprogram Controller/Sequencer - modified F9408A MPC

M65C02_MPCv4    #(
                    .pAddrWidth(pROM_AddrWidth)
                ) MPCv4 (
                    .Rst(Rst), 
                    .Clk(Clk),
                    
                    .Wait(Wait),            // Microcycle Wait state request
                    .MC(MC),                // Microcycle State

                    .I(I),                  // Instruction 
                    .T(T),                  // Test signal input
                    .MW(MW),                // Multi-way branch inputs
                    .BA(BA),                // Branch address input
                    .Via(Via),              // BRVx multiplexer control output

                    .MA(MA)                 // Microprogram ROM address output
                );

//  Infer Microprogram ROM and initialize with file created by MCP_Tool

initial
    $readmemb(pM65C02_uPgm, uP_ROM, 0, (pROM_Depth - 1));
    
always @(posedge Clk)
begin
    if(Rdy | Rst)
        uPL <= #1 uP_ROM[MA];
end

//  Assign uPL fields

assign I      = uPL[31:28];     // MPC Instruction Field (4)
assign uP_BA  = uPL[27:19];     // MPC Branch Address Field (9)
assign ZP     = uPL[18];        // When Set, ZP % 256 addressing required (1)
assign MemTyp = uPL[17:16];     // Memory Type (2)
assign NA_Op  = uPL[15:12];     // Next Address Operation (4)
assign PC_Op  = uPL[11:10];     // Program Counter Control (2)
assign IO_Op  = uPL[9:8];       // IO Operation Control (2)
assign DI_Op  = uPL[7:6];       // DI Demultiplexer Control (2)
assign DO_Op  = uPL[7:6];       // DO Multiplexer Control (2) (same as DI_Op)
assign Stk_Op = uPL[5:4];       // Stack Pointer Control Field (2)
assign Reg_WE = uPL[3:1];       // Register Write Enable Field (3)
assign ISR    = uPL[0];         // Set to clear D and set I on interrupts (1)

//  Decode DI_Op Control Field

assign Ld_OP2 = IO_Op[1] & (DI_Op == pDI_OP2);
assign Ld_OP1 = IO_Op[1] & (DI_Op == pDI_OP1);

//  Operand Register 2

assign CE_OP2 = Ld_OP2 & Rdy & ~CE_IR;   

always @(posedge Clk)
begin
    if(Rst)
        OP2 <= #1 0;
    else if(BRV2)
        OP2 <= #1 Vector[15:8];
    else if(CE_OP2)
        OP2 <= #1 DI;   // Load OP2 from DI
end

//  Operand Register 1

assign CE_OP1 = Ld_OP1 & Rdy & ~CE_IR;

always @(posedge Clk)
begin
    if(Rst)
        OP1 <= #1 0;
    else if(BRV2)
        OP1 <= #1 Vector[7:0];
    else if(CE_OP1)
        OP1 <= #1 DI;   // Load OP1 from DI
end

//  Instruction Register

assign CE_IR = (BRV1 | (BRV3 & ~Int)) & Rdy;  // Load IR from DI

always @(posedge Clk)
begin
    if(Rst)
        IR <= #1 pNOP;
    else if(CE_IR)
        IR <= #1 DI;
end

//  Infer Instruction Decode ROM and initialize with file created by MCP_Tool

initial
    $readmemb(pM65C02_IDec, ID_ROM, 0, (pDEC_Depth - 1));

always @(posedge Clk)
begin
    if(Rst)
        IDEC <= #1 ID_ROM[pNOP];
    else if(CE_IR)
        IDEC <= #1 ID_ROM[DI];
end

//  Decode Fixed Microcode Word

assign  Mode  = IDEC[31:29];       // M65C02 Instruction Type/Mode
assign  RMW   = IDEC[28];          // M65C02 Read-Modify-Write Instruction
assign  Op    = IDEC[27:24];       // M65C02 ALU Operation Select Field
assign  QSel  = IDEC[23:22];       // M65C02 ALU AU Q Bus Mux Select Field
assign  RSel  = IDEC[21];          // M65C02 ALU AU/SU R Bus Mux Select Field
assign  Sub   = IDEC[20];          // M65C02 ALU AU Mode Select Field
assign  CSel  = IDEC[19];          // M65C02 ALU AU/SU Carry Mux Select Field
assign  WSel  = IDEC[18:16];       // M65C02 ALU Register Write Select Field
assign  OSel  = IDEC[15:13];       // M65C02 ALU Register Output Select Field
assign  CCSel = IDEC[12: 8];       // M65C02 ALU Condition Code Control Field
assign  Msk   = IDEC[ 7: 0];       // M65C02 Rockwell Instruction Mask Field

// Decode Mode for internal signals

assign  WAI =  &Mode;               // Current Instruction is WAI

//  Next Address Generator

M65C02_AddrGen  AddrGen (
                    .Rst(Rst), 
                    .Clk(Clk),
                    
                    .Vector(Vector), 

                    .NA_Op(NA_Op),
                    .PC_Op(PC_Op),
                    .Stk_Op(Stk_Op),
                    
                    .ZP(ZP),

                    .CC(CC), 
                    .BRV3(BRV3), 
                    .Int(Int), 

                    .Rdy(Rdy), 

                    .DI(DI), 
                    .OP1(OP1), 
                    .OP2(OP2), 
                    .StkPtr(StkPtr), 
                    .X(X), 
                    .Y(Y), 

                    .AO(AO), 

                    .AL(AL),
                    .AR(AR),
                    .NA(NA), 
                    .MAR(MAR), 

                    .PC(PC), 
                    .dPC(dPC)
                );

//  Interrupt Service Flag

assign CE_IntSvc = (  (Int & (BRV3 | BMW))
                    | ((IO_Op == pIO_WR) & (DO_Op == pDO_PSW)));

always @(posedge Clk)
begin
    if(Rst)
        IntSvc <= #1 0;
    else if(CE_IntSvc)
        IntSvc <= #1 (Int & (BRV3 | BMW));
end

//  Instantiate the M65C02 ALU Module

always @(*) En <= (|Reg_WE);

M65C02_ALU  #(
                .pStkPtr_Rst(pStkPtr_Rst)
            ) ALU (
                .Rst(Rst),          // System Reset
                .Clk(Clk),          // System Clock

                .Rdy(Rdy),          // Ready

                .En(En),            // M65C02 ALU Enable Strobe Input
                .Reg_WE(Reg_WE),    // M65C02 ALU Register Write Enable
                .ISR(ISR),          // M65C02 ALU Interrupt Service Rtn Strb
                
                .Op(Op),            // M65C02 ALU Operation Select Input
                .QSel(QSel),        // M65C02 ALU Q Data Mux Select Input
                .RSel(RSel),        // M65C02 ALU R Data Mux Select Input
                .Sub(Sub),          // M65C02 ALU Adder Function Select Input
                .CSel(CSel),        // M65C02 ALU Adder Carry Select Input
                .WSel(WSel),        // M65C02 ALU Register Write Select Input
                .OSel(OSel),        // M65C02 ALU Output Register Select Input
                .CCSel(CCSel),      // M65C02 ALU Condition Code Select Input
                .Msk(Msk),          // M65C02 ALU Rockwell Instructions Mask

                .M(OP1),            // M65C02 ALU Memory Operand Input
                .Out(Out),          // M65C02 ALU Output Multiplexer
                .Valid(Valid),      // M65C02 ALU Output Valid Strobe

                .CC_Out(CC),        // M65C02 ALU Condition Code Mux Output

                .StkOp(Stk_Op),     // M65C02 ALU Stack Pointer Operation
                .StkPtr(StkPtr),    // M65C02 ALU Stack Pointer Multiplexer

                .A(A),              // M65C02 ALU Accumulator Register
                .X(X),              // M65C02 ALU Pre-Index Register
                .Y(Y),              // M65C02 ALU Post-Index Register
                .S(S),              // M65C02 ALU Stack Pointer Register
                
                .P(P)               // M65C02 Processor Status Word Register
            );

//  Decode P

assign IRQ_Msk = P[pIntMsk];        // Interrupt Mask Bit

//  External Bus Data Output

always @(*)                // P ddPP O
begin                      // S PPCC u
    case({IntSvc, DO_Op})  // W HLHL t
        3'b000 : DO_Sel <= 6'b0_0000_1;
        3'b001 : DO_Sel <= 6'b0_0010_0;
        3'b010 : DO_Sel <= 6'b0_0001_0;
        3'b011 : DO_Sel <= 6'b1_0000_0;
        3'b100 : DO_Sel <= 6'b0_0000_1;
        3'b101 : DO_Sel <= 6'b0_1000_0;
        3'b110 : DO_Sel <= 6'b0_0100_0;
        3'b111 : DO_Sel <= 6'b1_0000_0;
    endcase
end

always @(*) DO <= (  ((DO_Sel[5]) ? P         : 0)
                   | ((DO_Sel[4]) ? dPC[15:8] : 0)
                   | ((DO_Sel[3]) ? dPC[ 7:0] : 0)
                   | ((DO_Sel[2]) ?  PC[15:8] : 0)
                   | ((DO_Sel[1]) ?  PC[ 7:0] : 0)
                   | ((DO_Sel[0]) ? Out       : 0));

///////////////////////////////////////////////////////////////////////////////
//
//  End Implementation
//
///////////////////////////////////////////////////////////////////////////////

endmodule