mirror of
https://github.com/mist-devel/mist-board.git
synced 2026-02-07 08:27:07 +00:00
341 lines
14 KiB
Verilog
341 lines
14 KiB
Verilog
// Copyright (c) 2012-2013 Ludvig Strigeus
|
|
// This program is GPL Licensed. See COPYING for the full license.
|
|
|
|
//`include "cpu.v"
|
|
//`include "apu.v"
|
|
//`include "ppu.v"
|
|
//`include "mmu.v"
|
|
|
|
// Sprite DMA Works as follows.
|
|
// When the CPU writes to $4014 DMA is initiated ASAP.
|
|
// DMA runs for 512 cycles, the first cycle it reads from address
|
|
// xx00 - xxFF, into a latch, and the second cycle it writes to $2004.
|
|
|
|
// Facts:
|
|
// 1) Sprite DMA always does reads on even cycles and writes on odd cycles.
|
|
// 2) There are 1-2 cycles of cpu_read=1 after cpu_read=0 until Sprite DMA starts (pause_cpu=1, aout_enable=0)
|
|
// 3) Sprite DMA reads the address value on the last clock of cpu_read=0
|
|
|
|
/*
|
|
|
|
=== DMC State Machine ===
|
|
|
|
//
|
|
if (dmc_state == 0 && dmc_trigger && cpu_read && !odd_cycle) dmc_state <= 1;
|
|
if (dmc_state == 1) dmc_state <= (spr_state[1] ? 3 : 2);
|
|
pause_cpu = dmc_state[1] && cpu_read;
|
|
if (dmc_state == 2 && cpu_read && !odd_cycle) dmc_state <= 3;
|
|
aout_enable = (dmc_state == 3 && !odd_cycle)
|
|
dmc_ack = (dmc_state == 3 && !odd_cycle)
|
|
read = 1
|
|
if (dmc_state == 3 && !odd_cycle) dmc_state <= 0;
|
|
|
|
== Sprite State Machine ==
|
|
if (sprite_trigger) { sprite_dma_addr <= data_from_cpu; spr_state <= 1; }
|
|
pause_cpu = spr_state[0] && cpu_read;
|
|
if (spr_state == 1 && cpu_read && odd_cycle) spr_state <= 3;
|
|
if (spr_state == 3 && !odd_cycle) { if (dmc_state == 3) spr_state <= 1; else DO_READ; }
|
|
if (spr_state == 3 && odd_cycle) { DO_WRITE; }
|
|
|
|
|
|
// 4) If DMC interrupts Sprite, then it runs on the even cycle, and the odd cycle will be idle (pause_cpu=1, aout_enable=0)
|
|
// 5) When DMC triggers && interrupts CPU, there will be 2-3 cycles (pause_cpu=1, aout_enable=0) before DMC DMA starts.
|
|
*/
|
|
|
|
|
|
module DmaController(input clk, input ce, input reset,
|
|
input odd_cycle, // Current cycle even or odd?
|
|
input sprite_trigger, // Sprite DMA trigger?
|
|
input dmc_trigger, // DMC DMA trigger?
|
|
input cpu_read, // CPU is in a read cycle?
|
|
input [7:0] data_from_cpu, // Data written by CPU?
|
|
input [7:0] data_from_ram, // Data read from RAM?
|
|
input [15:0] dmc_dma_addr, // DMC DMA Address
|
|
output [15:0] aout, // Address to access
|
|
output aout_enable, // DMA controller wants bus control
|
|
output read, // 1 = read, 0 = write
|
|
output [7:0] data_to_ram, // Value to write to RAM
|
|
output dmc_ack, // ACK the DMC DMA
|
|
output pause_cpu); // CPU is paused
|
|
reg dmc_state;
|
|
reg [1:0] spr_state;
|
|
reg [7:0] sprite_dma_lastval;
|
|
reg [15:0] sprite_dma_addr; // sprite dma source addr
|
|
wire [8:0] new_sprite_dma_addr = sprite_dma_addr[7:0] + 8'h01;
|
|
always @(posedge clk) if (reset) begin
|
|
dmc_state <= 0;
|
|
spr_state <= 0;
|
|
sprite_dma_lastval <= 0;
|
|
sprite_dma_addr <= 0;
|
|
end else if (ce) begin
|
|
if (dmc_state == 0 && dmc_trigger && cpu_read && !odd_cycle) dmc_state <= 1;
|
|
if (dmc_state == 1 && !odd_cycle) dmc_state <= 0;
|
|
|
|
if (sprite_trigger) begin sprite_dma_addr <= {data_from_cpu, 8'h00}; spr_state <= 1; end
|
|
if (spr_state == 1 && cpu_read && odd_cycle) spr_state <= 3;
|
|
if (spr_state[1] && !odd_cycle && dmc_state == 1) spr_state <= 1;
|
|
if (spr_state[1] && odd_cycle) sprite_dma_addr[7:0] <= new_sprite_dma_addr[7:0];
|
|
if (spr_state[1] && odd_cycle && new_sprite_dma_addr[8]) spr_state <= 0;
|
|
if (spr_state[1]) sprite_dma_lastval <= data_from_ram;
|
|
end
|
|
assign pause_cpu = (spr_state[0] || dmc_trigger) && cpu_read;
|
|
assign dmc_ack = (dmc_state == 1 && !odd_cycle);
|
|
assign aout_enable = dmc_ack || spr_state[1];
|
|
assign read = !odd_cycle;
|
|
assign data_to_ram = sprite_dma_lastval;
|
|
assign aout = dmc_ack ? dmc_dma_addr : !odd_cycle ? sprite_dma_addr : 16'h2004;
|
|
endmodule
|
|
|
|
// Multiplexes accesses by the PPU and the PRG into a single memory, used for both
|
|
// ROM and internal memory.
|
|
// PPU has priority, its read/write will be honored asap, while the CPU's reads
|
|
// will happen only every second cycle when the PPU is idle.
|
|
// Data read by PPU will be available on the next clock cycle.
|
|
// Data read by CPU will be available within at most 2 clock cycles.
|
|
|
|
module MemoryMultiplex(input clk, input ce, input reset,
|
|
input [21:0] prg_addr, input prg_read, input prg_write, input [7:0] prg_din,
|
|
input [21:0] chr_addr, input chr_read, input chr_write, input [7:0] chr_din,
|
|
// Access signals for the SRAM.
|
|
output [21:0] memory_addr, // address to access
|
|
output memory_read_cpu, // read into CPU latch
|
|
output memory_read_ppu, // read into PPU latch
|
|
output memory_write, // is a write operation
|
|
output [7:0] memory_dout);
|
|
reg saved_prg_read, saved_prg_write;
|
|
assign memory_addr = (chr_read || chr_write) ? chr_addr : prg_addr;
|
|
assign memory_write = (chr_read || chr_write) ? chr_write : saved_prg_write;
|
|
assign memory_read_ppu = chr_read;
|
|
assign memory_read_cpu = !(chr_read || chr_write) && (prg_read || saved_prg_read);
|
|
assign memory_dout = chr_write ? chr_din : prg_din;
|
|
always @(posedge clk) if (reset) begin
|
|
saved_prg_read <= 0;
|
|
saved_prg_write <= 0;
|
|
end else if (ce) begin
|
|
if (chr_read || chr_write) begin
|
|
saved_prg_read <= prg_read || saved_prg_read;
|
|
saved_prg_write <= prg_write || saved_prg_write;
|
|
end else begin
|
|
saved_prg_read <= 0;
|
|
saved_prg_write <= prg_write;
|
|
end
|
|
end
|
|
endmodule
|
|
|
|
|
|
module NES(input clk, input reset, input ce,
|
|
input [31:0] mapper_flags,
|
|
output [15:0] sample, // sample generated from APU
|
|
output [5:0] color, // pixel generated from PPU
|
|
output joypad_strobe,// Set to 1 to strobe joypads. Then set to zero to keep the value.
|
|
output [1:0] joypad_clock, // Set to 1 for each joypad to clock it.
|
|
input [3:0] joypad_data, // Data for each joypad + 1 powerpad.
|
|
input fds_swap,
|
|
input [4:0] audio_channels, // Enabled audio channels
|
|
|
|
|
|
// Access signals for the SRAM.
|
|
output [21:0] memory_addr, // address to access
|
|
output memory_read_cpu, // read into CPU latch
|
|
input [7:0] memory_din_cpu, // next cycle, contents of latch A (CPU's data)
|
|
output memory_read_ppu, // read into CPU latch
|
|
input [7:0] memory_din_ppu, // next cycle, contents of latch B (PPU's data)
|
|
output memory_write, // is a write operation
|
|
output [7:0] memory_dout,
|
|
|
|
output [8:0] cycle,
|
|
output [8:0] scanline,
|
|
input int_audio,
|
|
input ext_audio
|
|
);
|
|
reg [7:0] from_data_bus;
|
|
wire [7:0] cpu_dout;
|
|
wire odd_or_even; // Is this an odd or even clock cycle?
|
|
|
|
// The CPU runs at one third the speed of the PPU.
|
|
// CPU is clocked at cycle #2. PPU is clocked at cycle #0, #1, #2.
|
|
// CPU does its memory I/O on cycle #0. It will be available in time for cycle #2.
|
|
reg [1:0] cpu_cycle_counter;
|
|
always @(posedge clk) begin
|
|
if (reset)
|
|
cpu_cycle_counter <= 0;
|
|
else if (ce)
|
|
cpu_cycle_counter <= (cpu_cycle_counter == 2) ? 2'd0 : cpu_cycle_counter + 1'd1;
|
|
end
|
|
|
|
// Sample the NMI flag on cycle #0, otherwise if NMI happens on cycle #0 or #1,
|
|
// the CPU will use it even though it shouldn't be used until the next CPU cycle.
|
|
wire nmi;
|
|
reg nmi_active;
|
|
always @(posedge clk) begin
|
|
if (reset)
|
|
nmi_active <= 0;
|
|
else if (ce && cpu_cycle_counter == 0)
|
|
nmi_active <= nmi;
|
|
end
|
|
|
|
wire apu_ce = ce && (cpu_cycle_counter == 2);
|
|
|
|
// -- CPU
|
|
wire [15:0] cpu_addr;
|
|
wire cpu_rnw;
|
|
wire pause_cpu;
|
|
reg apu_irq_delayed;
|
|
reg mapper_irq_delayed;
|
|
|
|
T65 cpu
|
|
(
|
|
.mode(0),
|
|
.BCD_en(0),
|
|
|
|
.res_n(~reset),
|
|
.clk(clk),
|
|
.enable(apu_ce && !pause_cpu),
|
|
|
|
.IRQ_n(~(apu_irq_delayed | mapper_irq_delayed)),
|
|
.NMI_n(~nmi_active),
|
|
.R_W_n(cpu_rnw),
|
|
|
|
.A(cpu_addr),
|
|
.DI(cpu_rnw ? from_data_bus : cpu_dout),
|
|
.DO(cpu_dout)
|
|
);
|
|
|
|
|
|
// -- DMA
|
|
wire [15:0] dma_aout;
|
|
wire dma_aout_enable;
|
|
wire dma_read;
|
|
wire [7:0] dma_data_to_ram;
|
|
wire apu_dma_request, apu_dma_ack;
|
|
wire [15:0] apu_dma_addr;
|
|
|
|
// Determine the values on the bus outgoing from the CPU chip (after DMA / APU)
|
|
wire [15:0] addr = dma_aout_enable ? dma_aout : cpu_addr;
|
|
wire [7:0] dbus = dma_aout_enable ? dma_data_to_ram : cpu_dout;
|
|
wire mr_int = dma_aout_enable ? dma_read : cpu_rnw;
|
|
wire mw_int = dma_aout_enable ? !dma_read : !cpu_rnw;
|
|
|
|
DmaController dma(clk, apu_ce, reset,
|
|
odd_or_even, // Even or odd cycle
|
|
(addr == 'h4014 && mw_int), // Sprite trigger
|
|
apu_dma_request, // DMC Trigger
|
|
cpu_rnw, // CPU in a read cycle?
|
|
cpu_dout, // Data from cpu
|
|
from_data_bus, // Data from RAM etc.
|
|
apu_dma_addr, // DMC addr
|
|
dma_aout,
|
|
dma_aout_enable,
|
|
dma_read,
|
|
dma_data_to_ram,
|
|
apu_dma_ack,
|
|
pause_cpu);
|
|
|
|
// -- Audio Processing Unit
|
|
wire apu_cs = addr >= 'h4000 && addr < 'h4018;
|
|
wire [7:0] apu_dout;
|
|
wire apu_irq;
|
|
wire [15:0] sample_apu;
|
|
APU apu(0, clk, apu_ce, reset,
|
|
addr[4:0], dbus, apu_dout,
|
|
mw_int && apu_cs, mr_int && apu_cs,
|
|
audio_channels,
|
|
sample_apu,
|
|
apu_dma_request,
|
|
apu_dma_ack,
|
|
apu_dma_addr,
|
|
from_data_bus,
|
|
odd_or_even,
|
|
apu_irq);
|
|
|
|
// Joypads are mapped into the APU's range.
|
|
wire joypad1_cs = (addr == 'h4016);
|
|
wire joypad2_cs = (addr == 'h4017);
|
|
assign joypad_strobe = (joypad1_cs && mw_int && cpu_dout[0]);
|
|
assign joypad_clock = {joypad2_cs && mr_int, joypad1_cs && mr_int};
|
|
|
|
|
|
// -- PPU
|
|
// PPU _reads_ need to happen on the same cycle the cpu runs on, to guarantee we
|
|
// see proper values of register $2002.
|
|
wire mr_ppu = mr_int && (cpu_cycle_counter == 2);
|
|
wire mw_ppu = mw_int && (cpu_cycle_counter == 0);
|
|
wire ppu_cs = addr >= 'h2000 && addr < 'h4000;
|
|
wire [7:0] ppu_dout; // Data from PPU to CPU
|
|
wire chr_read, chr_write; // If PPU reads/writes from VRAM
|
|
wire [13:0] chr_addr; // Address PPU accesses in VRAM
|
|
wire [7:0] chr_from_ppu; // Data from PPU to VRAM
|
|
wire [7:0] chr_to_ppu;
|
|
wire [19:0] mapper_ppu_flags; // PPU flags for mapper cheating
|
|
PPU ppu(clk, ce, reset, color, dbus, ppu_dout, addr[2:0],
|
|
ppu_cs && mr_ppu, ppu_cs && mw_ppu,
|
|
nmi,
|
|
chr_read, chr_write, chr_addr, chr_to_ppu, chr_from_ppu,
|
|
scanline, cycle, mapper_ppu_flags);
|
|
|
|
// -- Memory mapping logic
|
|
wire [15:0] prg_addr = addr;
|
|
wire [7:0] prg_din = dbus;
|
|
wire prg_read = mr_int && (cpu_cycle_counter == 0) && !apu_cs && !ppu_cs;
|
|
wire prg_write = mw_int && (cpu_cycle_counter == 0) && !apu_cs && !ppu_cs;
|
|
wire prg_allow, vram_a10, vram_ce, chr_allow;
|
|
wire [21:0] prg_linaddr, chr_linaddr;
|
|
wire [7:0] prg_dout_mapper, chr_from_ppu_mapper;
|
|
wire cart_ce = (cpu_cycle_counter == 0) && ce;
|
|
wire mapper_irq;
|
|
wire has_chr_from_ppu_mapper;
|
|
wire [15:0] sample_ext;
|
|
reg [16:0] sample_sum;
|
|
assign sample = sample_sum[16:1]; //loss of 1 bit of resolution. Add control for when no external audio to boost back up?
|
|
MultiMapper multi_mapper(clk, cart_ce, ce, reset, mapper_ppu_flags, mapper_flags,
|
|
prg_addr, prg_linaddr, prg_read, prg_write, prg_din, prg_dout_mapper, from_data_bus, prg_allow,
|
|
chr_read, chr_addr, chr_linaddr, chr_from_ppu_mapper, has_chr_from_ppu_mapper, chr_allow, vram_a10,
|
|
vram_ce, mapper_irq, sample_ext, fds_swap);
|
|
assign chr_to_ppu = has_chr_from_ppu_mapper ? chr_from_ppu_mapper : memory_din_ppu;
|
|
|
|
// Mapper IRQ seems to be delayed by one PPU clock.
|
|
// APU IRQ seems delayed by one APU clock.
|
|
always @(posedge clk) if (reset) begin
|
|
mapper_irq_delayed <= 0;
|
|
apu_irq_delayed <= 0;
|
|
end else begin
|
|
if (ce)
|
|
mapper_irq_delayed <= mapper_irq;
|
|
if (apu_ce)
|
|
apu_irq_delayed <= apu_irq;
|
|
if (ce | apu_ce) begin
|
|
case ({int_audio, ext_audio})
|
|
0: sample_sum <= 17'b0;
|
|
1: sample_sum <= {1'b0,sample_ext};
|
|
2: sample_sum <= {1'b0,sample_apu};
|
|
3: sample_sum <= {1'b0,sample_ext} + {1'b0,sample_apu};
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// -- Multiplexes CPU and PPU accesses into one single RAM
|
|
MemoryMultiplex mem(clk, ce, reset, prg_linaddr, prg_read && prg_allow, prg_write && prg_allow, prg_din,
|
|
chr_linaddr, chr_read, chr_write && (chr_allow || vram_ce), chr_from_ppu,
|
|
memory_addr, memory_read_cpu, memory_read_ppu, memory_write, memory_dout);
|
|
|
|
always @* begin
|
|
if (reset)
|
|
from_data_bus = 0;
|
|
else if (apu_cs) begin
|
|
if (joypad1_cs)
|
|
from_data_bus = {7'b0100000, joypad_data[0]};
|
|
else if (joypad2_cs)
|
|
from_data_bus = {3'b010, joypad_data[3:2] ,2'b00, joypad_data[1]};
|
|
else
|
|
from_data_bus = apu_dout;
|
|
end else if (ppu_cs) begin
|
|
from_data_bus = ppu_dout;
|
|
end else if (prg_allow) begin
|
|
from_data_bus = memory_din_cpu;
|
|
end else begin
|
|
from_data_bus = prg_dout_mapper;
|
|
end
|
|
end
|
|
|
|
endmodule
|