1 module nes.memory;
2 
3 import std.experimental.logger;
4 import std.format;
5 import std.stdio;
6 
7 import nes.console;
8 
9 interface Memory {
10     ubyte read(ushort address);
11     void write(ushort address, ubyte value);
12 }
13 
14 class MemoryException : Exception
15 {
16     import std.exception : basicExceptionCtors;
17 
18     mixin basicExceptionCtors;
19 }
20 
21 class CPUMemory : Memory {
22     this(Console console) {
23         this.console = console;
24     }
25 
26     ubyte read(ushort address) {
27         if (address < 0x2000) {
28             return this.console.ram[address % 0x0800];
29         }
30         else if (address < 0x4000) {
31             return this.console.ppu.readRegister(0x2000 + address % 8);
32         }
33         else if (address == 0x4014) {
34             return this.console.ppu.readRegister(address);
35         }
36         else if (address == 0x4015) {
37             return this.console.apu.readRegister(address);
38         }
39         else if (address == 0x4016) {
40             return this.console.controller1.read();
41         }
42         else if (address == 0x4017) {
43             return this.console.controller2.read();
44         }
45         else if (address < 0x6000) {
46             // TODO: I/O registers
47         }
48         else if (address >= 0x6000) {
49             return this.console.mapper.read(address);
50         }
51         else {
52             throw new MemoryException(format("unhandled cpu memory read at address: 0x%04X", address));
53         }
54 
55         return 0;
56     }
57 
58     void write(ushort address, ubyte value) {
59         if (address < 0x2000) {
60             this.console.ram[address % 0x0800] = value;
61         }
62         else if (address < 0x4000) {
63             this.console.ppu.writeRegister(0x2000 + address % 8, value);
64         }
65         else if (address < 0x4014) {
66             this.console.apu.writeRegister(address, value);
67         }
68         else if (address == 0x4014) {
69             this.console.ppu.writeRegister(address, value);
70         }
71         else if (address == 0x4015) {
72             this.console.apu.writeRegister(address, value);
73         }
74         else if (address == 0x4016) {
75             this.console.controller1.write(value);
76             this.console.controller2.write(value);
77         }
78         else if (address == 0x4017) {
79             this.console.apu.writeRegister(address, value);
80         }
81         else if (address < 0x6000) {
82             // TODO: I/O registers
83         }
84         else if (address >= 0x6000) {
85             this.console.mapper.write(address, value);
86         }
87         else {
88             throw new MemoryException(format("unhandled cpu memory write at address: 0x%04X", address));
89         }
90     }
91 
92     package Console console;
93 }
94 
95 class PPUMemory : Memory {
96     this(Console console) {
97         this.console = console;
98     }
99 
100     ubyte read(ushort address) {
101         address = address % 0x4000;
102 
103         if (address < 0x2000) {
104             return this.console.mapper.read(address);
105         }
106         else if (address < 0x3F00) {
107             auto mode = this.console.cartridge.mirror;
108             return this.console.ppu.nameTableData[MirrorAddress(mode, address) % 2048];
109         }
110         else if (address < 0x4000) {
111             return this.console.ppu.readPalette(address % 32);
112         }
113         else {
114             throw new MemoryException(format("unhandled ppu memory read at address: 0x%04X", address));
115         }
116     }
117 
118     void write(ushort address, ubyte value) {
119         address = address % 0x4000;
120 
121         if (address < 0x2000) {
122             this.console.mapper.write(address, value);
123         }
124         else if (address < 0x3F00) {
125             auto mode = this.console.cartridge.mirror;
126             this.console.ppu.nameTableData[MirrorAddress(mode, address) % 2048] = value;
127         }
128         else if (address < 0x4000) {
129             this.console.ppu.writePalette(address % 32, value);
130         }
131         else {
132             throw new MemoryException(format("unhandled ppu memory write at address: 0x%04X", address));
133         }
134     }
135 
136     private Console console;
137 }
138 
139 // Mirroring Modes
140 
141 enum {
142     MirrorHorizontal = 0,
143     MirrorVertical   = 1,
144     MirrorSingle0    = 2,
145     MirrorSingle1    = 3,
146     MirrorFour       = 4
147 }
148 
149 immutable ushort[4][] MirrorLookup = [
150     [0, 0, 1, 1],
151     [0, 1, 0, 1],
152     [0, 0, 0, 0],
153     [1, 1, 1, 1],
154     [0, 1, 2, 3]
155 ];
156 
157 ushort MirrorAddress(ubyte mode, ushort address) {
158     address = cast(ushort)(address - 0x2000) % 0x1000;
159     auto table = address / 0x0400;
160     auto offset = address % 0x0400;
161 
162     return cast(ushort)(0x2000 + MirrorLookup[mode][table] * 0x0400 + offset);
163 }