Skip to main content
NESdev Wiki
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Action 53 mapper/Verilog

infiniteneslives

//--------------------------------------------------------------------------------
//  INL-ROM Action53 Multi-discrete mapper
//  Designed for use with a multicart that supports many discrete mappers
//  such as NROM, AOROM, BNROM, CNROM, UxROM, UxROM variant #180
//  written for Xilinx 9500XL series CPLD XC9536XL
//
//  mapper design by: Tepples
//  verilog code by: infiniteneslives (paul@InfiniteNesLives.com)
//  Created: 6 Oct 2012
//  Modified: 27 Oct 2012 
//
//  Description:  Designed to support 32KB of CHR-RAM, and 2MB PRG-ROM
//  see nesdev wiki for details:
//  http://wiki.nesdev.org/w/index.php/User:Tepples/Multi-discrete_mapper
//
//  Modifications:
//  10/6/12: Corrected prg bank switching 27/36Mcells (75%), 25/34 pins.
//      Reduced mirror[0] freeing 1 Mcell (26/36 73%)
//      Corrected fixed banks for both UxROM versions (26/36 73%)
//
//  10/27/12: Fixed several bugs, and tested to Tepples' Action53 interactive 
//      mapper behavior test of 21Oct12.  Everything appears operational at 
//      this point.  29/36Mcells with 2MB and 8KB WRAM.
//
//  10/28/12: Corrected fix bank behavior, now fixes to current bank based
//      on outer_bank and PRG A14 value.  No change in logic consumption.
//      Script output of mapper behavior test of 21Oct12 matches for 512KB ROM.
//  
//  Options:
//  8KB WRAM: control available at the cost of 2 Mcells and 
//      2 pins for WRAM /CE and /WE. Leaving 27/34 pins used.
//  1MB PRG-ROM: shaving off PRG ROM A20 saves 1Mcell and 1pin.
//  note: Hardwiring WRAM R/W and PRG A13 saves 1Mcell of logic each.
//
//  Possible features with remaining logic:
//  Upto 32KB of WRAM and/or finer CHR bankswitching.
//
//--------------------------------------------------------------------------------

module action53 (
    input           m2,     //clock input negedge triggered
    input           prg_rw,     //PRG R/W signal from NES
    input           prg_ce,     //PRG /CE signal from NES
    input   [7:0]       data,       //PRG Data bus input
    input   [14:12]     prg_addr,   //PRG A14-12 input
    input   [11:10]     chr_addr,   //CHR A11-10 input for mirroring
    output  reg[20:13]  pa,     //PRG ROM A20-13 outputs
    output  reg[14:13]  ca,     //CHR RAM A14-13 outputs
    output  reg     p_ce,       //PRG-ROM /CE signal output
    output  reg     ciram_a10,  //CIRAM A10 output
    output  reg     w_ce_n,     //WRAM /CE signal output    
    output  reg     w_we        //WRAM /WE signal output
);

wire write_reg_sel, write_reg_val;
//active high when writing to $5000->reg_sel,  $8000->reg_val
assign write_reg_sel = (prg_addr[14] & ~prg_addr[13] & prg_addr[12] & m2 & prg_ce & ~prg_rw);
assign write_reg_val = (~prg_ce & ~prg_rw);

reg [1:0] reg_sel;  //register select S: supervisor=1/user=0 (D7),  R: register select (D0)
//In a multicart, registers $00 and $01 change the bank within a game, and registers $80 and $81 
//remain constant throughout a given game's execution. 

//$00 CHR bank: used for CNROM, and one screen mirroring
reg [1:0] chr_bank; //D1-0 set CHR RAM A14-13

//$01 Inner bank: prg bank for A/B/UxROM etc.
reg [3:0] prg_inner_bank;   //D4-0 set PRG ROM A14/15 and up for single game in game use

//$80 Mode: used by multicart supervisor software
reg [1:0] prg_size; //D5-4  0: 32KB, 1: 64KB, 2:128KB, 3:256KB
reg [1:0] prg_mode; //D3-2  0,1: current 32KB @ $8000, 2: UNROM #180, 3: UNROM standard
reg [1:0] mirror;   //D1-0  0: 1scn lower, 1: 1scn upper, 2: vert, 3: horiz

//$81 Outer bank: sets the upper PRG ROM bits to select current game
reg [5:0] prg_outer_bank;   //sets PRG ROM A15-20

//REGISTER SELECT:  chooses which of the above registers is written to
always @ (negedge write_reg_sel)
begin
    reg_sel[1] <= data[7];
    reg_sel[0] <= data[0];
end

//REGISTER VALUE WRITING 
always @ (negedge write_reg_val)
begin

    case (reg_sel)
        0:  chr_bank  <= data[1:0];
        1:  prg_inner_bank <= data[3:0];
        2:  {prg_size, prg_mode, mirror[1]} <= data[5:1];    
        3:  prg_outer_bank <= data[5:0];
    endcase

    if (reg_sel[1] == 0  &  mirror[1] == 0) 
    begin
        //$0x registers while in mirror mode 0 & 1 only
        mirror[0] <= data[4];
    end

    else if (reg_sel == 2)  
    begin
        //$80 register
        mirror[0] <= data[0];
    end
end

//OUTPUTS combinational logic
always @ (m2, chr_bank, prg_ce, prg_rw, prg_inner_bank, prg_size, prg_mode, mirror, prg_outer_bank, chr_addr, prg_addr)
begin

    //fix to 16KB PRG ROM banks
    pa[13] = prg_addr[13];

    //wram control
    if ((prg_addr[14:13] == 3) & m2 & prg_ce)
        w_ce_n = 0; //enabled
    else
        w_ce_n = 1; //disabled

    //wram write control
    w_we = prg_rw;

    //prevent bus conflicts for writes to $8000-FFFF
    if (prg_ce == 0 & prg_rw == 1)
        p_ce = 0;   //enabled
    else
        p_ce = 1;   //disabled

    //mirroring
    case (mirror)
        0: ciram_a10 = mirror[0];
        1: ciram_a10 = mirror[0];
        2: ciram_a10 = chr_addr[10]; //vert
        3: ciram_a10 = chr_addr[11]; //horiz
    endcase

    //chr bankswitching
    ca = chr_bank;

    //prg bankswitching
    pa[20:18] = prg_outer_bank[5:3];  //always controlled directly (independent of prg_mode)

    //prg bank selecting must control PRG ROM A17-14

    if (prg_mode[1] == 0)   //modes 0 and 1 NROM, A/BNROM 32KB banks
    begin
        pa[14] = prg_addr[14];  //32KB banks

        //32KB NROM
        if (prg_size == 0) 
            pa[17:15] = prg_outer_bank[2:0];    //set to 32KB

        //64KB A/BNROM
        else if (prg_size[1] == 0) 
        begin
            pa[17:16] = prg_outer_bank[2:1];    //set to 64KB
            pa[15] = prg_inner_bank[0];
        end

        //128KB A/BNROM
        else if (prg_size == 2) 
        begin
            pa[17] = prg_outer_bank[2]; //set to 128KB
            pa[16:15] = prg_inner_bank[1:0];
        end

        //256KB A/BNROM
        else 
            pa[17:15] = prg_inner_bank[2:0];
    end

    else if (prg_mode == 2) //mode 2 UNROM #180 variant first 16KB bank fixed to first bank
    begin
        //32KB
        if (prg_size == 0) 
        begin
            pa[17:15] = prg_outer_bank[2:0];    //set to 32KB

            if (prg_addr[14] == 1)  //$C000-FFFF
                pa[14] = prg_inner_bank[0];
            else
                //first bank fixed to first bank
                pa[14] = 1'b0;
        end

        //64KB
        else if (prg_size == 1) 
        begin
            pa[17:16] = prg_outer_bank[2:1];    //set to 64KB

            if (prg_addr[14] == 1)  //$C000-FFFF
                pa[15:14] = prg_inner_bank[1:0];
            else
                //first bank fixed to first bank
                pa[15:14] = {prg_outer_bank[0], 1'b0};  
        end

        //128KB
        else if (prg_size == 2) 
        begin
            pa[17] = prg_outer_bank[2]; //set to 128KB

            if (prg_addr[14] == 1)  //$C000-FFFF
                pa[16:14] = prg_inner_bank[2:0];
            else
                //first bank fixed to first bank
                pa[16:14] =  {prg_outer_bank[1:0], 1'b0};
        end

        //256KB
        else 
        begin
            if (prg_addr[14] == 1)  //$C000-FFFF
                pa[17:14] = prg_inner_bank[3:0];
            else
                //first bank fixed to first bank
                pa[17:14] = {prg_outer_bank[2:0], 1'b0};
        end
    end

    else // mode 3 standard UNROM 16KB banks last fixed
    begin
        //32KB
        if (prg_size == 0) 
        begin
            pa[17:15] = prg_outer_bank[2:0];    //set to 32KB

            if (prg_addr[14] == 0)  //$8000-BFFF
                pa[14] = prg_inner_bank[0];
            else
                //last bank fixed to last bank
                pa[14] = 1'b1;
        end

        //64KB
        else if (prg_size == 1) 
        begin
            pa[17:16] = prg_outer_bank[2:1];    //set to 64KB

            if (prg_addr[14] == 0)  //$8000-BFFF
                pa[15:14] = prg_inner_bank[1:0];
            else
                //last bank fixed to last bank
                pa[15:14] = {prg_outer_bank[0], 1'b1};  
        end

        //128KB
        else if (prg_size == 2) 
        begin
            pa[17] = prg_outer_bank[2]; //set to 128KB

            if (prg_addr[14] == 0)  //$8000-BFFF
                pa[16:14] = prg_inner_bank[2:0];
            else
                //last bank fixed to last bank
                pa[16:14] = {prg_outer_bank[1:0], 1'b1};
        end

        //256KB
        else 
        begin
            if (prg_addr[14] == 0)  //$8000-BFFF
                pa[17:14] = prg_inner_bank[3:0];
            else
                //last bank fixed to last bank
                pa[17:14] = {prg_outer_bank[2:0], 1'b1};
        end
    end

end 
endmodule

kevtris

The following code was written by Kevin Horton for his FPGA NES and forms the basis of thefox’s PowerPak mapper:

// "Action 53" mapper by tepples
// rendered into verilog by k.horton
// 11022012
// V4 now with syntax errors fixed!
//
module action53 (   C_A, C_D, C_WR, CLK0, RST, P_A,     // inputs
                    a53prg, a53chr, a53mir              // outputs
                );

    input wire [15:0] C_A;      // NES A0-15 (only A12-15 used)
    input wire [7:0] C_D;       // NES D0-7
    input wire C_WR;            // NES /WR signal
    input wire CLK0;            // NES M2 signal
    input wire RST;             // NES reset signal
    input wire [13:0] P_A;      // PPU A0-13 (only A10/11 used)

    output reg [6:0] a53prg;    // output PRG ROM (A14-A20 on ROM)
    output reg [1:0] a53chr;    // output CHR RAM (A13-A14 on RAM)
    output reg a53mir;          // CIRAM A10

    reg [3:0] inner;    // "inner" bank at 01h
    reg [5:0] mode;     // mode register at 80h
    reg [5:0] outer;    // "outer" bank at 81h  
    reg [1:0] selreg;   // selector register

    always @ (posedge CLK0)
    begin

        if ((C_A[15:12] == 4'h5) & C_WR) selreg <= {C_D[7], C_D[0]};        // select register

        if (RST)
        begin
            mode[5:2] <= 0;         // NROM mode, 32K mode
            outer[5:0] <= 6'h3f;    // last bank
        end
        else if (C_A[15] & C_WR)
        begin
            case (selreg)
            2'h0:  {mode[0], a53chr}  <= {((mode[1]) ? mode[0] : C_D[4]), C_D[1:0]};  // CHR RAM bank
            2'h1:  {mode[0], inner}   <= {((mode[1]) ? mode[0] : C_D[4]), C_D[3:0]};  // "inner" bank
            2'h2:  {mode}             <= {C_D[5:0]};              // mode register
            2'h3:  {outer}            <= {C_D[5:0]};              // "outer" bank
            endcase
        end

    end

    always
    begin

        // mirroring mode
        casex(mode[1:0])
        2'b0x   :   a53mir <= {mode[0]};    // 1 screen lower
        2'b10   :   a53mir <= {P_A[10]};    // vertical
        2'b11   :   a53mir <= {P_A[11]};    // horizontal
        endcase

        // PRG ROM bank size select
        casex({mode[5:2], C_A[14]})
        5'b00_0x_x  :  a53prg <= {outer[5:0],             C_A[14]};  // 32K banks, (B)NROM mode
        5'b01_0x_x  :  a53prg <= {outer[5:1], inner[0],   C_A[14]};  // 64K banks, (B)NROM mode
        5'b10_0x_x  :  a53prg <= {outer[5:2], inner[1:0], C_A[14]};  // 128K banks, (B)NROM mode
        5'b11_0x_x  :  a53prg <= {outer[5:3], inner[2:0], C_A[14]};  // 256K banks, (B)NROM mode

        5'b00_10_1,
        5'b00_11_0  :  a53prg <= {outer[5:0], inner[0]};             // 32K banks, UNROM mode
        5'b01_10_1,
        5'b01_11_0  :  a53prg <= {outer[5:1], inner[1:0]};           // 64K banks, UNROM mode
        5'b10_10_1,
        5'b10_11_0  :  a53prg <= {outer[5:2], inner[2:0]};           // 128K banks, UNROM mode
        5'b11_10_1,
        5'b11_11_0  :  a53prg <= {outer[5:3], inner[3:0]};           // 256K banks, UNROM mode

        default     :  a53prg <= {outer[5:0],             C_A[14]};  // 16K fixed bank
        endcase

    end

endmodule

Edit this page on GitHub