module M65C02_Base #(
    parameter pStkPtr_Rst  = 8'hFF, // Stk Ptr Value after Reset
    parameter pInt_Hndlr   = 0,     // _Int microroutine address, Reset default
    parameter pM65C02_uPgm = "M65C02_uPgm_V3.coe",
    parameter pM65C02_IDec = "M65C02_Decoder_ROM.coe"
)(
    input   Rst,            // System Reset Input
    input   Clk,            // System Clock Input

    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

    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 Indicator
    
    output  reg Rdy,        // Internal Ready

    output  [1:0] IO_Op,    // Instruction Fetch Strobe
    input   Ack_In,         // Transfer Acknowledge
    output  [15:0] AO,      // External Address
    input   [ 7:0] DI,      // External Data In
    output  reg [7:0] DO,   // External Data Out

    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    Ack;                            // External Ack gated by Wait

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    Wait;
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
//wire    ISR;                            // Asserted during interrupt entry

reg     En;                             // ALU Enable Control Field

wire    DP;                             // Direct Page addressing mode
reg     rDP;                            // Registered DP
wire    ZP;                             // Address Generator % 256 Command

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

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

wire    D;                          // BCD mode bit in P

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

//  Gate Ack_In

assign Ack = ((Wait) ? Ack_In : 1'b1);

//  Generate Internal Ready Signal

always @(*)
begin
    case({Done, (|Op & |Reg_WE), Valid, Ack})
        4'b0000 : Rdy <= 0;
        4'b0001 : Rdy <= 1;     // Non-ALU external cycle ready
        4'b0010 : Rdy <= 0;
        4'b0011 : Rdy <= 1;     // Non-ALU external cycle ready
        4'b0100 : Rdy <= 0;
        4'b0101 : Rdy <= 1;     // Operands not ready, external cycle ready
        4'b0110 : Rdy <= 0;
        4'b0111 : Rdy <= 1;     // Operands not ready, external cycle ready
        4'b1000 : Rdy <= 0;
        4'b1001 : Rdy <= 1;     // Non-ALU op and external fetch ready
        4'b1010 : Rdy <= 0;
        4'b1011 : Rdy <= 1;     // Non-ALU op and external fetch ready
        4'b1100 : Rdy <= 0;     // ALU op and external fetch not ready
        4'b1101 : Rdy <= 0;     // ALU op not ready and external fetch ready
        4'b1110 : Rdy <= 0;     // ALU op ready and external fetch not ready
        4'b1111 : Rdy <= 1;     // ALU op and external fetch cycle ready
    endcase
end

///////////////////////////////////////////////////////////////////////////////
//
//  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_MPC #(
                .pAddrWidth(pROM_AddrWidth)
            ) MPC (
                .Rst(Rst), 
                .Clk(Clk),
                
                .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

                .En(Wait),                  // Enable Ready Input
                .Rdy(Rdy),                  // Ready Input

                .PLS(1'b1),                 // Set for Pipelined Mode 
                
                .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 Wait    = uPL[18];       // When Set, Conditional Execution Required (1)
assign SC      = uPL[17];       // Single Cycle Enable (1)
assign Done    = uPL[16];       // Multi-cycle Instruction Complete (1)
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

//  Compute Zero Page command to Address Generator
//      A page 0 addressing mode is commanded by the microcode in the NA_Op
//      field. Three such address calculations are generated. The zp and the
//      zp indexed address modes are the basis of all of the page 0 addressing
//      modes. The indirect zp addressing modes are generated using the direct
//      zp page addressing modes. The initial page 0 access for the lsb of the
//      pointer is followed by an automatic next operation on the MAR. Thus, if
//      a DP address mode command is followed by a Nxt address command, then the
//      second access is also a page 0 location and it must be wrapped. 

assign DP = ((NA_Op == pNA_DPN) | (NA_Op == pNA_DPX) | (NA_Op == pNA_DPY));

always @(posedge Clk)
begin
    if(Rst)
        rDP <= #1 0;
    else if(Rdy)
        rDP <= #1 DP;
end

assign ZP = (DP | ((NA_Op == pNA_Nxt) & rDP));

//  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(),
                    .AR(),
                    .NA(), 
                    .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 Instruction 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
assign D       = P[pBCD];           // BCD Mode Bit   

//  External Bus Data Output

always @(*)
begin
    if(Rst)
        DO <= #1 0;
    else
        case(DO_Op)
            pDO_PSW : DO <= P;                                  // M65C02 PSW
            pDO_PCL : DO <= ((IntSvc) ? dPC[ 7:0] : PC[ 7:0]);  // M65C02 PCL
            pDO_PCH : DO <= ((IntSvc) ? dPC[15:8] : PC[15:8]);  // M65C02 PCH
            default : DO <= Out;                                // M65C02 ALU
        endcase
end

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

endmodule