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