Thank you for the clarification! You’re proposing that the FPGA itself acts as the CHR-RAM, directly presenting VIC-2-rendered graphics data to the NES PPU bus, rather than using a separate CHR-RAM chip on the cartridge. This approach changes the design significantly, as the FPGA would dynamically generate and serve tile/sprite data in real-time to the PPU, emulating CHR-RAM behavior internally. Let’s analyze this and provide an updated Markdown design addressing this approach, confirming feasibility, and including relevant VHDL and example game code.
Using the FPGA to emulate CHR-RAM (instead of a physical SRAM chip) is feasible but introduces specific considerations:
- FPGA as CHR-RAM: The FPGA can present tile/sprite data on the PPU bus (CHR address/data lines) as if it were CHR-RAM. It would internally generate VIC-2 graphics and format them into NES-compatible 8x8 tiles, serving them in real-time as the PPU requests data.
- Advantages:
- Eliminates the need for a separate CHR-RAM chip, reducing cartridge complexity and cost.
- Allows dynamic, on-the-fly rendering of VIC-2 graphics, potentially enabling more complex effects.
- Challenges:
- Timing Precision: The FPGA must respond to PPU CHR address requests (~5.37 MHz, NTSC) with precise timing to mimic CHR-RAM’s read behavior.
- FPGA Resources: Emulating VIC-2 and serving CHR data requires significant logic (LUTs) and internal memory (BRAM) to buffer rendered tiles.
- PPU Bus Interface: The FPGA must handle the PPU’s 13-bit address bus (A0-A12) and 8-bit data bus (D0-D7) correctly, acting as a memory device.
- VBlank Updates: Unlike physical CHR-RAM (updated during VBlank), the FPGA can update its internal tile buffer continuously, but synchronization with the PPU’s scanline rendering is critical.
- Conclusion: This approach is viable with a sufficiently capable FPGA (e.g., Lattice iCE40HX8K or Xilinx Spartan-6) and careful timing design. It simplifies the cartridge hardware but increases FPGA complexity.
Below is the updated Markdown design, reflecting the FPGA acting as CHR-RAM.
This cartridge uses an FPGA to emulate the Commodore 64’s VIC-2 graphics chip and act as CHR-RAM, dynamically serving NES-compatible tile/sprite data to the PPU bus. A custom mapper expands memory, enabling C64 games to run on the NES 6502 CPU with minimal modification. The solution is cartridge-only, using no expansion slot or physical CHR-RAM chip.
- FPGA: Emulates VIC-2, generates CHR-RAM data, and implements a custom mapper.
- PRG-ROM: Stores game code for the 6502 CPU.
- Mapper: FPGA-based, handles VIC-2 register access, memory banking, and PPU bus interfacing.
- NES Interface: 72-pin cartridge connector for CPU and PPU bus access.
- No Physical CHR-RAM: The FPGA internally buffers and serves tile/sprite data to the PPU bus, emulating CHR-RAM.
- Role: The FPGA acts as CHR-RAM, responding to PPU read requests on the CHR address/data bus (A0-A12, D0-D7).
- Operation:
- The FPGA renders VIC-2 graphics (e.g., 320x200 bitmap, 40x25 text, 8 sprites) into NES-compatible 8x8 tiles and sprites.
- It stores rendered tiles in internal BRAM (e.g., 8KB equivalent for 128 tiles).
- When the PPU requests CHR data, the FPGA provides the appropriate tile/sprite data in real-time, mimicking CHR-RAM behavior.
- Timing: The FPGA synchronizes with the PPU’s ~5.37 MHz (NTSC) clock, responding within ~186 ns per read cycle.
- Capacity: Emulates 8KB CHR-RAM (128 tiles); larger sizes (e.g., 32KB) possible with more BRAM.
- Registers: VIC-2 registers ($D000-$D03F) are mapped to FPGA at $6000-$603F (CPU address space).
- Rendering:
- Converts VIC-2 graphics (16 colors, sprites, scrolling) to NES tiles/sprites.
- Updates internal BRAM tile buffer, potentially continuously or during VBlank.
- Maps C64’s 16 colors to NES’s 52-color palette.
- Sprites: Maps VIC-2’s 8 sprites to NES sprites, with FPGA multiplexing for >8 sprites per scanline.
- Memory Expansion:
- Option 1: Provides 32KB SRAM (on-cart) at $6000-$7FFF, banked like MMC5.
- Option 2: Implements a flat 64KB+ address space, bypassing NES’s 2KB WRAM (requires game code to use custom mapper).
- Bank Switching: Supports 16KB/32KB PRG-ROM banks.
- VIC-2 Memory: Maps C64 memory ($0000-$FFFF) to FPGA-managed RAM for game data.
- Minimal Changes: C64 games access VIC-2 registers via FPGA, with graphics served to the PPU bus.
- Input: NES controllers (2 buttons + D-pad) may require patches for C64 joystick compatibility.
- Timing: Adjust for 6502 (1.79 MHz) vs. 6510 (1 MHz) differences.
- Power: 5V from NES, ~500mA budget for FPGA.
- Size: FPGA (e.g., Lattice iCE40HX8K) fits in a standard NES cartridge shell.
- No Expansion Slot: All functionality via 72-pin connector.
- No CHR-RAM Chip: FPGA’s internal BRAM replaces physical CHR-RAM.
This module emulates CHR-RAM, serving tile data to the PPU bus.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity chr_ram_emulator is
Port (
clk : in STD_LOGIC; -- PPU clock (~5.37 MHz)
reset : in STD_LOGIC;
ppu_addr : in STD_LOGIC_VECTOR(12 downto 0); -- PPU CHR address (8KB)
ppu_data : out STD_LOGIC_VECTOR(7 downto 0); -- PPU CHR data
vic2_tile_data : in STD_LOGIC_VECTOR(7 downto 0); -- VIC-2 rendered tile
vblank : in STD_LOGIC -- VBlank signal for tile updates
);
end chr_ram_emulator;
architecture Behavioral of chr_ram_emulator is
type chr_mem_t is array (0 to 8191) of STD_LOGIC_VECTOR(7 downto 0); -- 8KB
signal chr_mem : chr_mem_t; -- Internal BRAM for tiles
signal tile_counter : unsigned(12 downto 0) := (others => '0');
begin
process(clk, reset)
begin
if reset = '1' then
chr_mem <= (others => (others => '0'));
tile_counter <= (others => '0');
elsif rising_edge(clk) then
-- Update tile buffer (e.g., during VBlank)
if vblank = '1' then
chr_mem(to_integer(tile_counter)) <= vic2_tile_data;
tile_counter <= tile_counter + 1;
end if;
-- Serve CHR data to PPU
ppu_data <= chr_mem(to_integer(unsigned(ppu_addr)));
end if;
end process;
end Behavioral;This code assumes the FPGA handles VIC-2 rendering and CHR-RAM emulation.
; Set VIC-2 background color (emulated in FPGA)
LDA #$01 ; C64 black
STA $6000 ; FPGA VIC-2 register $D021
LDA #$07 ; C64 white
STA $6001 ; FPGA VIC-2 register
; Main loop
main_loop:
JSR read_input ; NES controller
JSR update_game; Game logic
JSR wait_vblank; Wait for VBlank (FPGA updates tile buffer)
JMP main_loop
wait_vblank:
LDA $2002 ; PPU status
BPL wait_vblank; Wait for VBlank
RTS- FPGA Choice: Lattice iCE40HX8K (8K LUTs, ~128KB BRAM) or Xilinx Spartan-6 for VIC-2 emulation and CHR-RAM emulation.
- Timing: FPGA must respond to PPU reads within ~186 ns, synchronized to the PPU clock.
- BRAM Usage: 8KB BRAM for CHR-RAM emulation; additional BRAM for VIC-2 buffers.
- Testing: Simulate with Mesen emulator, configuring a custom mapper to emulate FPGA CHR-RAM behavior.
- PPU Timing: Precise response to PPU CHR reads is critical to avoid graphical glitches.
- FPGA Resources: VIC-2 emulation + CHR-RAM emulation may push smaller FPGAs to their limits.
- Sprite Multiplexing: FPGA must handle VIC-2 sprite-to-NES sprite conversion, respecting NES’s 8-sprite-per-scanline limit.
Copy the Markdown above into a .md file for documentation. Save the VHDL as chr_ram_emulator.vhd for FPGA synthesis (e.g., with iCEcube2). The 6502 assembly can be saved as game.asm for use with assemblers like ca65.
This design leverages the FPGA’s flexibility to emulate CHR-RAM, simplifying the cartridge hardware. If you need a deeper dive into the VHDL (e.g., VIC-2 rendering logic), a specific C64 game example, or a diagram of the FPGA-PPU interface, let me know!
I understand you’re pivoting back to your FPGA-based NES cartridge design using an Artix-7 FPGA to emulate the C64’s memory map, specifically focusing on the addressing and mapper logic in VHDL. You want to map the C64’s $0800-$9FFF to the NES cartridge space ($6000-$EFFF) and handle $D000-$DFFF for VIC-2 registers or RAM, with minimal invasiveness to C64 game code. Since you’ve emphasized dropping buffering and focusing on direct writes, we’ll assume CPU writes to the FPGA (e.g., $D000-$DFFF for VIC-2, $6000-$7FFF for CHR-RAM) are immediately reflected, and we’ll address your earlier concern about VBlank and semaphore-free writes by ensuring the mapper logic supports direct writes during VBlank without conflicts. Let’s dive into designing the addressing and mapper logic in VHDL, tailored for your Artix-7 FPGA.
Design Goals
LDA #$05 : STA $6000to enable C64 mode (CHAREN=1 for VIC-2 I/O). No changes to $0800-$9FFF or $D000-$DFFF references.Addressing and Mapper Logic
The mapper logic will:
VHDL Design: Addressing and Mapper Logic
Below is a VHDL module for the Artix-7 FPGA, implementing the addressing and mapper logic. It handles CPU address masking, memory configuration, and PPU CHR-RAM access, assuming direct writes (no buffering) to reflect your latest preference.
Explanation of VHDL
c64_mode = '1', CPU addresses $0800-$9FFF are offset by $5800 to map to $6000-$EFFF, ensuring C64 program code accesses FPGA PRG-RAM without modification.c64_mode(bit 0) andmem_config(bits 2-0: LORAM, HIRAM, CHAREN).mem_config(2)) controls $D000-$DFFF: VIC-2 registers ($D000-$D03F) when ‘1’, RAM when ‘0’.Addressing Your Concerns
Example Game Code (6502 Assembly)
Unchanged C64 code, using original addresses.
Conclusion
The VHDL mapper logic masks $0800-$9FFF to $6000-$EFFF, handles $D000-$DFFF for VIC-2 or RAM, and supports direct CPU writes during VBlank (~568 bytes) without a semaphore, as the CPU is the sole writer and PPU reads occur later (rendering). The Artix-7’s fast BRAM ensures writes complete instantly, and no fixed byte count is guaranteed—writes depend on game code. Disabling rendering ($2001 = $00) extends the write window if needed.
Copy the VHDL (
c64_nes_mapper.vhd) and assembly (game.asm). If you need a constraint file, timing diagram, or a specific C64 game example, let me know!