├── .project ├── APU ├── APU_Main.v ├── ac97wav.py ├── apu.py ├── apu_convert.py ├── apu_tb.py ├── clk_util.py ├── cpu_bus.py ├── nsf.py └── smb.nsf ├── CartridgeROM.vhd ├── CartridgeROM_Coregen.vhd ├── ColorPalette.vhd ├── Coregen ├── chr_rom_NEStress.ngc ├── chr_rom_smb.ngc ├── coregen.cgc ├── coregen.cgp ├── prg_rom_NESStress.ngc └── prg_rom_smb.ngc ├── Genesys_NES.vhd ├── HDMI ├── HDMIController.vhd ├── iic_init.v └── tft_interface.v ├── NES_2A03 ├── ClockDivider.vhd ├── DanPack.vhd ├── Dan_2A03.vhd ├── SRAM.vhd ├── T65.vhd ├── T65_ALU.vhd ├── T65_MCode.vhd └── T65_Pack.vhd ├── NES_Mainboard.vhd ├── NES_Pack.vhd ├── PPU ├── Loopy_Scrolling.vhd ├── PPU.vhd ├── PPU_Pack.vhd ├── SpriteSelector.vhd └── TileFetcher.vhd ├── README.md ├── TestBenches ├── DummySound_TB.vhd ├── NES_Framebuffer_TB.vhd ├── PPU_tb.vhd ├── SpriteSelector_TB.vhd └── nes_top_tb.vhd ├── ac97 ├── Talkthrough_Parts.vhd └── ac97_top.vhd ├── doc ├── implementation-issues.txt ├── myhdl-findings.txt ├── teaching.doc ├── todo.txt └── tutorial-genesys.txt ├── nes_top.ucf ├── roms ├── fpgasmb.nes ├── smb.nes ├── smb_chr.coe ├── smb_prg.coe ├── stress.nes ├── stress_chr.coe └── stress_prg.coe ├── synaesthesia ├── genesys.xst ├── run_xst.sh ├── synthesis.py ├── vhd2ngc └── xilinx.py ├── thesis.pdf └── tools ├── cartridge.py ├── fbview ├── SDLMain.h ├── SDLMain.m ├── build.sh └── fbview.cpp ├── file_rom.py └── romconv.py /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | nesfpga 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.xtext.ui.shared.xtextBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.sigasi.hdt.vhdl.ui.vhdlNature 16 | org.eclipse.xtext.ui.shared.xtextNature 17 | 18 | 19 | 20 | Common Libraries 21 | 2 22 | virtual:/virtual 23 | 24 | 25 | Common Libraries/IEEE 26 | 2 27 | sigasiresource:/vhdl/93/IEEE 28 | 29 | 30 | Common Libraries/IEEE Synopsys 31 | 2 32 | sigasiresource:/vhdl/93/IEEE%20Synopsys 33 | 34 | 35 | Common Libraries/STD 36 | 2 37 | sigasiresource:/vhdl/93/STD 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /APU/ac97wav.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | from clk_util import * 4 | 5 | def AC97_WavWriter(PCM, filename): 6 | import wave 7 | outfile = wave.open(filename, 'w') 8 | outfile.setnchannels(1) 9 | outfile.setframerate(48000) 10 | outfile.setsampwidth(1) 11 | 12 | CLK = Signal() 13 | 14 | AC97_CLK_period = 20833 15 | 16 | ac97_clk_gen = CLK_Gen(CLK, AC97_CLK_period) 17 | 18 | @always(CLK.posedge) 19 | def writeSample(): 20 | # Pack as signed byte 21 | import struct 22 | outfile.writeframes(struct.pack('b', PCM - 127)) 23 | #outfile.writeframes(chr(int(PCM - 127) * 4 + )) 24 | 25 | return ac97_clk_gen, writeSample 26 | 27 | -------------------------------------------------------------------------------- /APU/apu.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | NES_CLK_period = 46 * 12 4 | # constant AC97_CLK_period : time := 20.833333333333332 us; -- 48 kHz 5 | # constant CLK_period : time := 46.560848137510206 ns; -- 21.477272 MhZ 6 | 7 | def APU_Main( 8 | CLK, 9 | RSTN, 10 | PHI2_CE, 11 | RW10, 12 | 13 | Address, 14 | Data_read, 15 | Data_write, 16 | 17 | Interrupt, 18 | PCM_out 19 | ): 20 | 21 | APU_CE = Signal(False) 22 | APU_CE_cnt = Signal(False) 23 | 24 | Pulse1_CS = Signal(False) 25 | Pulse2_CS = Signal(False) 26 | Noise_CS = Signal(False) 27 | Triangle_CS = Signal(False) 28 | 29 | HalfFrame_CE = Signal(False) 30 | QuarterFrame_CE = Signal(False) 31 | 32 | PCM_pulse1 = Signal(intbv()[4:0]) 33 | PCM_pulse2 = Signal(intbv()[4:0]) 34 | PCM_noise = Signal(intbv()[4:0]) 35 | PCM_triangle = Signal(intbv()[4:0]) 36 | 37 | frameCounter = APU_FrameCounter(CLK, PHI2_CE, APU_CE, RW10, Address, Data_write, HalfFrame_CE, QuarterFrame_CE, Interrupt) 38 | pulse1 = APU_Pulse(CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, Pulse1_CS, HalfFrame_CE, QuarterFrame_CE, PCM_pulse1) 39 | pulse2 = APU_Pulse(CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, Pulse2_CS, HalfFrame_CE, QuarterFrame_CE, PCM_pulse2) 40 | noise = APU_Noise(CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, Noise_CS, HalfFrame_CE, QuarterFrame_CE, PCM_noise) 41 | triangle = APU_Triangle(CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, Triangle_CS, HalfFrame_CE, QuarterFrame_CE, PCM_triangle) 42 | 43 | @always(CLK.posedge) 44 | def ce(): 45 | if PHI2_CE: 46 | APU_CE_cnt.next = not APU_CE_cnt 47 | # print " CE", APU_CE 48 | # else: 49 | # print "NOT CE", APU_CE 50 | 51 | @always_comb 52 | def chipselect(): 53 | Pulse1_CS.next = 0x4000 <= Address and Address < 0x4004 54 | Pulse2_CS.next = 0x4004 <= Address and Address < 0x4008 55 | Triangle_CS.next = 0x4008 <= Address and Address < 0x400C 56 | Noise_CS.next = 0x400C <= Address and Address < 0x4010 57 | APU_CE.next = PHI2_CE and APU_CE_cnt 58 | 59 | PCM_out.next = PCM_pulse1 + PCM_pulse2 + PCM_noise + PCM_triangle 60 | 61 | return instances() 62 | 63 | 64 | 65 | def APU_FrameCounter( 66 | CLK, PHI2_CE, APU_CE, RW10, Address, Data_write, 67 | HalfFrame_CE, QuarterFrame_CE, Interrupt): 68 | 69 | timer = Signal(intbv()[15:0]) 70 | Mode = Signal(False) 71 | InterruptInhibit = Signal(False) 72 | 73 | @always(CLK.posedge) 74 | def logic(): 75 | if PHI2_CE and not RW10 and Address == 0x4017: 76 | Mode.next = Data_write[7] 77 | InterruptInhibit.next = Data_write[6] 78 | 79 | QuarterFrame_CE.next = False 80 | HalfFrame_CE.next = False 81 | 82 | if APU_CE: 83 | timer.next = timer + 1 84 | 85 | if timer == 3728: 86 | QuarterFrame_CE.next = True 87 | elif timer == 7456: 88 | HalfFrame_CE.next = True 89 | QuarterFrame_CE.next = True 90 | elif timer == 11186: 91 | QuarterFrame_CE.next = True 92 | elif not Mode and timer == 14914: 93 | HalfFrame_CE.next = True 94 | QuarterFrame_CE.next = True 95 | timer.next = 0 96 | elif Mode and timer == 18640: 97 | HalfFrame_CE.next = True 98 | QuarterFrame_CE.next = True 99 | timer.next = 0 100 | 101 | return instances() 102 | 103 | 104 | def APU_Envelope( 105 | CLK, 106 | QuarterFrame_CE, 107 | 108 | StartFlag, 109 | LoopFlag, 110 | ConstantFlag, 111 | 112 | VolumeDecay, 113 | 114 | VolumeOut 115 | ): 116 | 117 | divider = Signal(intbv()[4:0]) 118 | volume = Signal(intbv()[4:0]) 119 | 120 | @always(CLK.posedge) 121 | def logic(): 122 | if QuarterFrame_CE: 123 | if StartFlag: 124 | #print "Start Envelope, length: ", VolumeDecay, " constant: ", ConstantFlag 125 | volume.next = 15 126 | divider.next = VolumeDecay 127 | else: 128 | if divider == 0: 129 | divider.next = VolumeDecay 130 | if volume != 0: 131 | volume.next = volume - 1 132 | else: 133 | if LoopFlag: 134 | volume.next = 15 135 | else: 136 | divider.next = divider - 1 137 | 138 | @always_comb 139 | def comb(): 140 | if ConstantFlag: 141 | VolumeOut.next = VolumeDecay 142 | else: 143 | VolumeOut.next = volume 144 | 145 | 146 | return instances() 147 | 148 | def APU_Pulse( 149 | CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, 150 | ChipSelect, HalfFrame_CE, QuarterFrame_CE, 151 | PCM_out): 152 | 153 | DutyCycle = Signal(intbv()[2:0]) 154 | 155 | EnvelopeDecay = Signal(intbv()[4:0]) 156 | EnvelopeConstantFlag = Signal(False) 157 | EnvelopeStartFlag = Signal(False) 158 | EnvelopeVolume = Signal(intbv()[4:0]) 159 | 160 | LengthCounterHalt = Signal(False) 161 | LengthCounterLoadFlag = Signal(False) 162 | LengthCounterLoad = Signal(intbv()[5:0]) 163 | LengthCounterGate = Signal(False) 164 | 165 | lengthCounter = LengthCounter(CLK, HalfFrame_CE, LengthCounterHalt, LengthCounterLoad, LengthCounterLoadFlag, LengthCounterGate) 166 | envelope = APU_Envelope(CLK, QuarterFrame_CE, 167 | EnvelopeStartFlag, Signal(False), EnvelopeConstantFlag, 168 | EnvelopeDecay, EnvelopeVolume) 169 | 170 | TimerLoad = Signal(intbv()[11:0]) 171 | 172 | sequencer = Signal(intbv("00001111")) 173 | timer = Signal(intbv()[11:0]) 174 | 175 | @always(CLK.posedge) 176 | def logic(): 177 | if not RSTN: 178 | sequencer.next = intbv("00001111") 179 | else: 180 | if QuarterFrame_CE: 181 | EnvelopeStartFlag.next = False 182 | 183 | LengthCounterLoadFlag.next = False 184 | 185 | if APU_CE: 186 | if timer == 0: 187 | sequencer.next = concat(sequencer[0], sequencer[8:1]) 188 | PCM_out.next = EnvelopeVolume if sequencer[0] else 0x00 189 | if not LengthCounterGate: 190 | PCM_out.next = 0 191 | timer.next = TimerLoad 192 | else: 193 | timer.next = timer - 1 194 | 195 | if PHI2_CE and RW10 == 0 and ChipSelect: 196 | if Address[2:0] == 0x0: 197 | DutyCycle.next = Data_write[8:6] 198 | 199 | EnvelopeConstantFlag.next = Data_write[4] 200 | EnvelopeDecay.next = Data_write[4:0] 201 | elif Address[2:0] == 0x1: 202 | # Sweep unit unimplemented 203 | pass 204 | elif Address[2:0] == 0x2: 205 | TimerLoad.next[8:0] = Data_write 206 | elif Address[2:0] == 0x3: 207 | EnvelopeStartFlag.next = True 208 | TimerLoad.next[11:8] = Data_write[3:0] 209 | LengthCounterLoad.next = Data_write[8:3] 210 | LengthCounterLoadFlag.next = True 211 | return instances() 212 | 213 | def LengthCounter( 214 | CLK, HalfFrame_CE, 215 | LengthCounterHalt, LengthCounterLoad, LengthCounterLoadFlag, 216 | Enable_out 217 | ): 218 | 219 | LengthCounter = Signal(intbv()[8:0]) 220 | 221 | # Lookup Table for Length Counter values 222 | LC_lut = ( 223 | 10, 254, 20, 2, 40, 4, 80, 6, 224 | 160, 8, 60, 10, 14, 12, 26, 14, 225 | 12, 16, 24, 18, 48, 20, 96, 22, 226 | 192, 24, 72, 26, 16, 28, 32, 30 227 | ) 228 | 229 | @always(CLK.posedge) 230 | def logic(): 231 | if HalfFrame_CE: 232 | if LengthCounter > 0 and not LengthCounterHalt: 233 | LengthCounter.next = LengthCounter - 1 234 | 235 | if LengthCounterLoadFlag: 236 | LengthCounter.next = LC_lut[LengthCounterLoad] 237 | 238 | @always_comb 239 | def comb(): 240 | Enable_out.next = LengthCounter > 0 241 | 242 | return instances() 243 | 244 | def APU_Noise( 245 | CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, 246 | ChipSelect, HalfFrame_CE, QuarterFrame_CE, 247 | PCM_out): 248 | 249 | timer_lut = (4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068) 250 | lfsr = Signal(intbv("111111111111111")[15:0]) 251 | timer = Signal(intbv()[12:0]) 252 | 253 | TimerLoad = Signal(intbv()[4:0]) 254 | LFSRMode = Signal(False) 255 | 256 | LengthCounterHalt = Signal(False) 257 | LengthCounterLoadFlag = Signal(False) 258 | LengthCounterLoad = Signal(intbv()[5:0]) 259 | LengthCounterGate = Signal(False) 260 | 261 | EnvelopeDecay = Signal(intbv()[4:0]) 262 | EnvelopeConstantFlag = Signal(False) 263 | EnvelopeStartFlag = Signal(False) 264 | EnvelopeVolume = Signal(intbv()[4:0]) 265 | 266 | lengthCounter = LengthCounter(CLK, HalfFrame_CE, LengthCounterHalt, LengthCounterLoad, LengthCounterLoadFlag, LengthCounterGate) 267 | envelope = APU_Envelope(CLK, QuarterFrame_CE, EnvelopeStartFlag, Signal(False), EnvelopeConstantFlag, 268 | EnvelopeDecay, EnvelopeVolume) 269 | 270 | 271 | @always(CLK.posedge) 272 | def logic(): 273 | if not RSTN: 274 | lfsr.next = intbv("111111111111111") 275 | else: 276 | if QuarterFrame_CE: 277 | EnvelopeStartFlag.next = False 278 | 279 | LengthCounterLoadFlag.next = False 280 | 281 | if PHI2_CE and RW10 == 0 and ChipSelect: 282 | if Address[2:0] == 0x0: 283 | LengthCounterHalt.next = Data_write[5] 284 | EnvelopeConstantFlag.next = Data_write[4] 285 | EnvelopeDecay.next = Data_write[4:0] 286 | elif Address[2:0] == 0x2: 287 | LFSRMode.next = Data_write[7] 288 | TimerLoad.next[4:0] = Data_write[4:0] 289 | elif Address[2:0] == 0x3: 290 | EnvelopeStartFlag.next = True 291 | LengthCounterLoad.next = Data_write[8:3] 292 | LengthCounterLoadFlag.next = True 293 | if APU_CE: 294 | if timer == 0: 295 | fb_bit = bool(lfsr[1]) 296 | if LFSRMode: 297 | fb_bit = lfsr[6] 298 | lfsr.next = concat(fb_bit ^ lfsr[0], lfsr[15:1]) 299 | # This is the simple version, the MyHDL convertor does not like it 300 | #fb_bit = lfsr[0] ^ (lfsr[6] if LFSRMode else lfsr[1]) 301 | #lfsr.next = concat(fb_bit, lfsr[15:1]) 302 | PCM_out.next = EnvelopeVolume if lfsr[0] and LengthCounterGate else 0x00 303 | timer.next = timer_lut[TimerLoad] 304 | else: 305 | timer.next = timer - 1 306 | 307 | return instances() 308 | 309 | 310 | def APU_Triangle( 311 | CLK, RSTN, PHI2_CE, APU_CE, RW10, Address, Data_write, 312 | ChipSelect, HalfFrame_CE, QuarterFrame_CE, 313 | PCM_out): 314 | 315 | lut = (15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) 316 | sequencer = Signal(intbv()[5:0]) 317 | timer = Signal(intbv(0)[11:0]) 318 | 319 | linearCounter = Signal(intbv()[7:0]) 320 | linearCounterLoad = Signal(intbv()[7:0]) 321 | 322 | LengthCounterHalt = Signal(False) 323 | LengthCounterLoadFlag = Signal(False) 324 | LengthCounterLoad = Signal(intbv()[5:0]) 325 | LengthCounterGate = Signal(False) 326 | 327 | TimerLoad = Signal(intbv()[11:0]) 328 | 329 | 330 | lengthCounter = LengthCounter(CLK, HalfFrame_CE, LengthCounterHalt, LengthCounterLoad, LengthCounterLoadFlag, LengthCounterGate) 331 | 332 | @always(CLK.posedge) 333 | def logic(): 334 | if QuarterFrame_CE: 335 | if LengthCounterHalt: 336 | linearCounter.next = linearCounterLoad 337 | elif linearCounter > 0: 338 | linearCounter.next = linearCounter - 1 339 | 340 | 341 | LengthCounterLoadFlag.next = False 342 | 343 | if PHI2_CE and RW10 == 0 and ChipSelect: 344 | if Address[2:0] == 0x0: 345 | LengthCounterHalt.next = Data_write[7] 346 | linearCounterLoad.next = Data_write[7:0] 347 | elif Address[2:0] == 0x2: 348 | TimerLoad.next[8:0] = Data_write 349 | elif Address[2:0] == 0x3: 350 | TimerLoad.next[11:8] = Data_write[3:0] 351 | LengthCounterLoad.next = Data_write[8:3] 352 | LengthCounterLoadFlag.next = True 353 | 354 | if APU_CE: 355 | if timer == 0: 356 | sequencer.next = (sequencer + 1) % 32 357 | if LengthCounterGate and linearCounter > 0: 358 | PCM_out.next = lut[sequencer] 359 | else: 360 | PCM_out.next = 0 361 | timer.next = TimerLoad 362 | else: 363 | timer.next = timer - 1 364 | return instances() 365 | -------------------------------------------------------------------------------- /APU/apu_convert.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | from apu import APU_Main 4 | from cpu_bus import CPU_Bus 5 | 6 | def convert(): 7 | cpu_bus = CPU_Bus() 8 | interrupt = Signal(False) 9 | pcm = Signal(intbv()[8:0]) 10 | toVerilog(APU_Main, cpu_bus.CLK, cpu_bus.RSTN, cpu_bus.PHI2_CE, cpu_bus.RW10, 11 | cpu_bus.Address, cpu_bus.Data_read, cpu_bus.Data_write, 12 | interrupt, pcm) 13 | 14 | convert() 15 | -------------------------------------------------------------------------------- /APU/apu_tb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimal Testbench with an APU_Pulse unit that is sweeped via CPU_Bus writes 3 | The generated sweep can be used to implement a FIR filter in AC97_Writer 4 | """ 5 | 6 | from myhdl import * 7 | from apu import APU_Main 8 | from cpu_bus import CPU_Bus 9 | from ac97wav import AC97_WavWriter 10 | from clk_util import CLK_Gen 11 | from nsf import NSFSoftCPU 12 | 13 | NES_CLK_period = 46 # ns 14 | # constant AC97_CLK_period : time := 20.833333333333332 us; -- 48 kHz 15 | # constant CLK_period : time := 46.560848137510206 ns; -- 21.477272 MhZ 16 | 17 | import sys 18 | import os 19 | 20 | #assert len(sys.argv) >= 2, "Usage: {} FILE.NSF [SONGNUMBER]".format(sys.argv[0]) 21 | rom_filename = sys.argv[1] 22 | base_filename, _ = os.path.splitext(rom_filename) 23 | start_song = 0 if len(sys.argv) < 3 else sys.argv[2] 24 | wav_filename = "output/{}-{}.wav".format(base_filename, start_song) 25 | 26 | def APU_TB(): 27 | APU_Interrupt = Signal(False) 28 | PCM = Signal(intbv()[8:]) 29 | cpu_bus = CPU_Bus() 30 | 31 | clk_gen = CLK_Gen(cpu_bus.CLK, NES_CLK_period) 32 | cpu_bus.PHI2_CE = Signal(True) 33 | phi2_cnt = Signal(0) 34 | 35 | 36 | ac97 = AC97_WavWriter(PCM, wav_filename) 37 | apu = APU_Main(cpu_bus.CLK, cpu_bus.RSTN, cpu_bus.PHI2_CE, cpu_bus.RW10, 38 | cpu_bus.Address, cpu_bus.Data_read, cpu_bus.Data_write, 39 | APU_Interrupt, PCM) 40 | 41 | cpu = NSFSoftCPU(rom_filename) 42 | cpu.subscribe_to_write(range(0x4000, 0x4017), cpu_bus.fake_write) 43 | 44 | 45 | cpu.setup(int(start_song)) 46 | 47 | @always(cpu_bus.CLK.posedge) 48 | def cpu_step(): 49 | if cpu_bus.PHI2_CE: 50 | cpu.play_cycle() 51 | 52 | phi2_cnt.next = (phi2_cnt + 1) % 12 53 | cpu_bus.PHI2_CE.next = phi2_cnt == 0 54 | 55 | return instances(), cpu_bus.instances 56 | 57 | Simulation(APU_TB()).run() 58 | -------------------------------------------------------------------------------- /APU/clk_util.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | def CLK_Gen(CLK, period): 4 | @always(delay(period / 2)) 5 | def gen(): 6 | CLK.next = not CLK 7 | 8 | return gen 9 | 10 | -------------------------------------------------------------------------------- /APU/cpu_bus.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | 4 | class CPU_Bus(object): 5 | def __init__(self, AddressWidth=16): 6 | self.CLK = Signal(False) 7 | self.RSTN = Signal(True) 8 | self.PHI2_CE = Signal(False) 9 | self.RW10 = Signal(True) 10 | 11 | self.Address = Signal(intbv()[AddressWidth:]) 12 | self.Data_write = Signal(intbv()[8:]) 13 | self.Data_read = Signal(intbv()[8:]) 14 | 15 | self.Data_slaves = [] 16 | 17 | self.write_queue = [] 18 | 19 | @always(self.CLK.posedge) 20 | def write_proc(): 21 | if self.PHI2_CE: 22 | if self.write_queue: 23 | a, d = self.write_queue.pop(0) 24 | self.Address.next = a 25 | self.Data_write.next = d 26 | self.RW10.next = 0 27 | else: 28 | self.RW10.next = 1 29 | 30 | self.instances = write_proc 31 | 32 | 33 | def RegisterSlaveAddress(): 34 | assert False, "not implemented" 35 | 36 | 37 | def fake_write(self, address, data): 38 | print "Fake Write at $%04X: %02x" % (address, data) 39 | self.write_queue += [(address, data)] 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /APU/nsf.py: -------------------------------------------------------------------------------- 1 | from py65.memory import ObservableMemory 2 | from py65.devices.mpu6502 import MPU 3 | 4 | class NSFParser: 5 | def __init__(self, bytes): 6 | #import pdb; pdb.set_trace() 7 | assert bytes[0:5] == 'NESM\x1a', "Not a NSF File" 8 | assert bytes[5] == 1, "NSF Version != 0x01" 9 | 10 | self.num_songs = bytes[6] 11 | self.start_song = bytes[7] 12 | self.load_addr = bytes[0x08] + 0x100 * bytes[0x09] 13 | self.init_addr = bytes[0x0a] + 0x100 * bytes[0x0b] 14 | self.play_addr = bytes[0x0c] + 0x100 * bytes[0x0d] 15 | 16 | self.info_name = bytes[0x0e:0x2e].decode('ascii') 17 | self.info_artist = bytes[0x2e:0x4e].decode('ascii') 18 | self.info_copyright = bytes[0x4e:0x6e].decode('ascii') 19 | 20 | self.ntsc_ticks = bytes[0x6e] + 0x100 * bytes[0x6f] 21 | self.pal_ticks = bytes[0x78] + 0x100 * bytes[0x79] 22 | 23 | assert bytes[0x7a] == 0x00, "Not a NTSC Tune" 24 | assert bytes[0x7b] == 0x00, "Extra Sound Chips are not supported" 25 | 26 | assert bytes[0x70:0x78] == '\x00\x00\x00\x00\x00\x00\x00\x00', "Bank switching not supported at this time" 27 | 28 | self.data = bytes[0x80:] 29 | 30 | 31 | class NSFSoftCPU: 32 | NES_CLK_period = 46 * 12 33 | 34 | def __init__(self, filename): 35 | self.nsf = NSFParser(bytearray(file(filename).read())) 36 | 37 | # Set up Memory with Hooks 38 | self.mem = ObservableMemory() 39 | self.mem.write(self.nsf.load_addr, self.nsf.data) 40 | self.cpu = MPU(self.mem) 41 | self.deltaCallTime = 0 42 | self.totalCycles = 0 43 | 44 | def subscribe_to_write(self, range, callback): 45 | self.mem.subscribe_to_write(range, callback) 46 | 47 | # Call NSF Init code 48 | def setup(self, start_song = -1): 49 | if start_song == -1: 50 | start_song = self.nsf.start_song - 1 51 | 52 | # Push a special address -1 to the stack to implement function calls 53 | self.cpu.stPushWord(0x1337 - 1) 54 | 55 | # NSF Style init call 56 | self.cpu.a = start_song 57 | self.cpu.x = 0 58 | self.cpu.pc = self.nsf.init_addr 59 | 60 | while self.cpu.pc != 0x1337: 61 | self.cpu.step() 62 | 63 | # Execute 1 CPU Step, or wait to until enough cycles have passed 64 | def play_cycle(self): 65 | self.deltaCallTime = self.deltaCallTime + self.NES_CLK_period 66 | 67 | self.check_frame() 68 | if self.cpu.pc != 0x1337: 69 | self.totalCycles = self.totalCycles + 1 70 | if self.totalCycles == self.cpu.processorCycles: 71 | self.cpu.step() 72 | 73 | 74 | # Internal: Check if frame is completed and restart it periodically 75 | def check_frame(self): 76 | if self.cpu.pc == 0x1337: 77 | frame_time = self.nsf.ntsc_ticks * 1000 78 | if self.deltaCallTime >= frame_time: 79 | self.deltaCallTime = self.deltaCallTime - frame_time 80 | self.cpu.stPushWord(0x1337 - 1) 81 | self.cpu.pc = self.nsf.play_addr 82 | print "Frame Completed" 83 | 84 | 85 | -------------------------------------------------------------------------------- /APU/smb.nsf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strfry/nesfpga/5059f4b2394c47a59518675b1a63adfc50ff1b63/APU/smb.nsf -------------------------------------------------------------------------------- /CartridgeROM.vhd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Entity: CartridgeROM 3 | -- Date:2011-10-25 4 | -- Author: jonathansieber 5 | -- 6 | -- Description ${cursor} 7 | -------------------------------------------------------------------------------- 8 | library ieee; 9 | use ieee.std_logic_1164.all; 10 | use ieee.numeric_std.all; 11 | 12 | library std; 13 | use std.textio.all; 14 | 15 | entity CartridgeROM is 16 | port ( 17 | clk : in std_logic; -- input clock, xx MHz. 18 | rstn : in std_logic; 19 | 20 | PRG_Address : in std_logic_vector(14 downto 0); 21 | PRG_Data : out std_logic_vector(7 downto 0); 22 | 23 | CHR_Address : in unsigned(13 downto 0); 24 | CHR_Data : out std_logic_vector(7 downto 0) 25 | ); 26 | end CartridgeROM; 27 | 28 | architecture arch of CartridgeROM is 29 | 30 | type CHRRAMType is array(0 to 8191) of bit_vector(7 downto 0); 31 | 32 | impure function InitCHRRAMFromFile (RamFileName : in string) return CHRRAMType is 33 | FILE RamFile : text is in RamFileName; 34 | variable RamFileLine : line; 35 | variable RAM : CHRRAMType; 36 | begin 37 | for I in CHRRAMType'range loop 38 | readline (RamFile, RamFileLine); 39 | read (RamFileLine, RAM(I)); 40 | end loop; 41 | return RAM; 42 | end function; 43 | 44 | signal CHRRAM : CHRRAMType := InitCHRRAMFromFile("roms/smb_chr.dat"); 45 | 46 | type PRGRAMType is array(0 to 32767) of bit_vector(7 downto 0); 47 | 48 | impure function InitPRGRAMFromFile (RamFileName : in string) return PRGRAMType is 49 | FILE RamFile : text is in RamFileName; 50 | variable RamFileLine : line; 51 | variable RAM : PRGRAMType; 52 | begin 53 | for I in PRGRAMType'range loop 54 | readline (RamFile, RamFileLine); 55 | read (RamFileLine, RAM(I)); 56 | end loop; 57 | return RAM; 58 | end function; 59 | 60 | signal PRGRAM : PRGRAMType := InitPRGRAMFromFile("roms/smb_prg.dat"); 61 | 62 | begin 63 | 64 | process (clk) 65 | begin 66 | if rising_edge(clk) then 67 | CHR_Data <= to_stdlogicvector(CHRRAM(to_integer(CHR_Address(12 downto 0)))); 68 | 69 | PRG_Data <= to_stdlogicvector(PRGRAM(to_integer(unsigned(PRG_Address)))); 70 | end if; 71 | end process; 72 | 73 | end arch; 74 | 75 | 76 | -------------------------------------------------------------------------------- /CartridgeROM_Coregen.vhd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Entity: CartridgeROM 3 | -- Date:2011-10-25 4 | -- Author: jonathansieber 5 | -- 6 | -- Description ${cursor} 7 | -------------------------------------------------------------------------------- 8 | library ieee; 9 | use ieee.std_logic_1164.all; 10 | use ieee.numeric_std.all; 11 | 12 | library std; 13 | use std.textio.all; 14 | 15 | Library UNISIM; 16 | use UNISIM.vcomponents.all; 17 | 18 | entity CartridgeROM is 19 | port ( 20 | clk : in std_logic; -- input clock, xx MHz. 21 | rstn : in std_logic; 22 | 23 | PRG_Address : in std_logic_vector(14 downto 0); 24 | PRG_Data : out std_logic_vector(7 downto 0); 25 | 26 | CHR_Address : in unsigned(13 downto 0); 27 | CHR_Data : out std_logic_vector(7 downto 0) 28 | ); 29 | end CartridgeROM; 30 | 31 | 32 | architecture arch of CartridgeROM is 33 | COMPONENT prg_rom_NESStress 34 | PORT ( 35 | clka : IN STD_LOGIC; 36 | addra : IN STD_LOGIC_VECTOR(14 DOWNTO 0); 37 | douta : OUT STD_LOGIC_VECTOR(7 DOWNTO 0) 38 | ); 39 | END COMPONENT; 40 | 41 | COMPONENT chr_rom_NEStress 42 | PORT ( 43 | clka : IN STD_LOGIC; 44 | addra : IN STD_LOGIC_VECTOR(12 DOWNTO 0); 45 | douta : OUT STD_LOGIC_VECTOR(7 DOWNTO 0) 46 | ); 47 | END COMPONENT; 48 | 49 | COMPONENT prg_rom_smb 50 | PORT ( 51 | clka : IN STD_LOGIC; 52 | addra : IN STD_LOGIC_VECTOR(14 DOWNTO 0); 53 | douta : OUT STD_LOGIC_VECTOR(7 DOWNTO 0) 54 | ); 55 | END COMPONENT; 56 | 57 | COMPONENT chr_rom_smb 58 | PORT ( 59 | clka : IN STD_LOGIC; 60 | addra : IN STD_LOGIC_VECTOR(12 DOWNTO 0); 61 | douta : OUT STD_LOGIC_VECTOR(7 DOWNTO 0) 62 | ); 63 | END COMPONENT; 64 | begin 65 | -- 66 | --prg : prg_rom_NESStress 67 | -- PORT MAP ( 68 | -- clka => clk, 69 | -- addra => PRG_Address, 70 | -- douta => PRG_Data 71 | -- ); 72 | -- 73 | --chr : chr_rom_NEStress 74 | -- PORT MAP ( 75 | -- clka => clk, 76 | -- addra => std_logic_vector(CHR_Address(12 downto 0)), 77 | -- douta => CHR_Data 78 | -- ); 79 | 80 | prg : prg_rom_smb 81 | PORT MAP ( 82 | clka => clk, 83 | addra => PRG_Address, 84 | douta => PRG_Data 85 | ); 86 | 87 | chr : chr_rom_smb 88 | PORT MAP ( 89 | clka => clk, 90 | addra => std_logic_vector(CHR_Address(12 downto 0)), 91 | douta => CHR_Data 92 | ); 93 | 94 | end arch; 95 | 96 | 97 | -------------------------------------------------------------------------------- /ColorPalette.vhd: -------------------------------------------------------------------------------- 1 | 2 | library IEEE; 3 | use IEEE.STD_LOGIC_1164.ALL; 4 | use ieee.numeric_std.all; 5 | 6 | entity ColorPalette is 7 | Port ( ColorIndex : in unsigned (5 downto 0); 8 | Red : out STD_LOGIC_VECTOR (5 downto 0); 9 | Green : out STD_LOGIC_VECTOR (5 downto 0); 10 | Blue : out STD_LOGIC_VECTOR (5 downto 0)); 11 | end ColorPalette; 12 | 13 | architecture Behavioral of ColorPalette is 14 | 15 | signal Entry : std_logic_vector(23 downto 0); 16 | begin 17 | -- Palettes taken from http://nesdev.parodius.com/nespalette.zip 18 | 19 | Red <= Entry(23 downto 18); 20 | Green <= Entry(15 downto 10); 21 | Blue <= Entry(7 downto 2); 22 | 23 | 24 | with to_integer(ColorIndex) select 25 | Entry <= 26 | X"727272" when 0, 27 | X"000080" when 1, 28 | X"08008A" when 2, 29 | X"2C007E" when 3, 30 | X"4A004E" when 4, 31 | X"500006" when 5, 32 | X"440000" when 6, 33 | X"260800" when 7, 34 | X"0A2000" when 8, 35 | X"002E00" when 9, 36 | X"003200" when 10, 37 | X"00260A" when 11, 38 | X"001C48" when 12, 39 | X"000000" when 13, 40 | X"000000" when 14, 41 | X"000000" when 15, 42 | X"A4A4A4" when 16, 43 | X"0038CE" when 17, 44 | X"3416EC" when 18, 45 | X"5E04DC" when 19, 46 | X"8C00B0" when 20, 47 | X"9A004C" when 21, 48 | X"901800" when 22, 49 | X"703600" when 23, 50 | X"4C5400" when 24, 51 | X"0E6C00" when 25, 52 | X"007400" when 26, 53 | X"006C2C" when 27, 54 | X"005E84" when 28, 55 | X"000000" when 29, 56 | X"000000" when 30, 57 | X"000000" when 31, 58 | X"FFFFFF" when 32, 59 | X"4C9CFF" when 33, 60 | X"7C78FF" when 34, 61 | X"A664FF" when 35, 62 | X"DA5AFF" when 36, 63 | X"F054C0" when 37, 64 | X"F06A56" when 38, 65 | X"D68610" when 39, 66 | X"BAA400" when 40, 67 | X"76C000" when 41, 68 | X"46CC1A" when 42, 69 | X"2EC866" when 43, 70 | X"34C2BE" when 44, 71 | X"3A3A3A" when 45, 72 | X"000000" when 46, 73 | X"000000" when 47, 74 | X"FFFFFF" when 48, 75 | X"B6DAFF" when 49, 76 | X"C8CAFF" when 50, 77 | X"DAC2FF" when 51, 78 | X"F0BEFF" when 52, 79 | X"FCBCEE" when 53, 80 | X"FAC2C0" when 54, 81 | X"F2CCA2" when 55, 82 | X"E6DA92" when 56, 83 | X"CCE68E" when 57, 84 | X"B8EEA2" when 58, 85 | X"AEEABE" when 59, 86 | X"AEE8E2" when 60, 87 | X"B0B0B0" when 61, 88 | X"000000" when 62, 89 | X"000000" when 63, 90 | X"000000" when others; 91 | 92 | 93 | end Behavioral; 94 | 95 | -------------------------------------------------------------------------------- /Coregen/coregen.cgp: -------------------------------------------------------------------------------- 1 | # Date: Sun May 5 00:40:47 2013 2 | 3 | SET addpads = false 4 | SET asysymbol = false 5 | SET busformat = BusFormatAngleBracketNotRipped 6 | SET createndf = false 7 | SET designentry = Advanced 8 | SET device = xc5vlx50t 9 | SET devicefamily = virtex5 10 | SET flowvendor = Foundation_ISE 11 | SET formalverification = false 12 | SET foundationsym = false 13 | SET implementationfiletype = Ngc 14 | SET package = ff1136 15 | SET removerpms = false 16 | SET simulationfiles = Structural 17 | SET speedgrade = -2 18 | SET verilogsim = false 19 | SET vhdlsim = true 20 | SET workingdirectory = ./tmp/ 21 | 22 | # CRC: cd767547 23 | -------------------------------------------------------------------------------- /Genesys_NES.vhd: -------------------------------------------------------------------------------- 1 | 2 | library IEEE; 3 | use IEEE.STD_LOGIC_1164.all; 4 | use IEEE.NUMERIC_STD.all; 5 | 6 | use work.NES_Pack.all; 7 | 8 | entity Genesys_NES is 9 | port ( 10 | CLK : in std_logic; -- input clock, 100 MHz. 11 | RSTN : in std_logic; 12 | 13 | HDMIHSync : out std_logic; 14 | HDMIVSync : out std_logic; 15 | HDMIDE : out std_logic; 16 | HDMICLKP : out std_logic; 17 | HDMICLKN : out std_logic; 18 | HDMID : out std_logic_vector(11 downto 0); 19 | HDMISCL : inout std_logic; 20 | HDMISDA : inout std_logic; 21 | HDMIRSTN : out std_logic; 22 | 23 | AUDCLK : in STD_LOGIC; 24 | AUDSDI : in STD_LOGIC; 25 | AUDSDO : out STD_LOGIC; 26 | AUDSYNC : out STD_LOGIC; 27 | AUDRST : out STD_LOGIC; 28 | 29 | JA : inout std_logic_vector(7 downto 0); 30 | JB : inout std_logic_vector(7 downto 0); 31 | 32 | BTN : in std_logic_vector(6 downto 0); 33 | SW : in std_logic_vector(7 downto 0); 34 | LED : out std_logic_vector(7 downto 0) 35 | 36 | ); 37 | end Genesys_NES; 38 | 39 | architecture Behavioral of Genesys_NES is 40 | component NES_Mainboard is 41 | port ( 42 | clk : in std_logic; -- approximation to NES mainboard clock 21.47727 43 | rstn : in std_logic; 44 | 45 | -- Framebuffer output 46 | FB_Address : out unsigned(15 downto 0); -- linear index in 256x240 pixel framebuffer 47 | FB_Color : out std_logic_vector(5 downto 0); -- Palette index of current color 48 | FB_DE : out std_logic; -- True when PPU is writing to the framebuffer 49 | 50 | APU_PCM : out std_logic_vector(7 downto 0); 51 | 52 | -- Controller input 53 | Controller_Strobe : out std_logic; -- Set shift register in controller with current buttons 54 | Controller1_Clock : out std_logic; -- Shift register by one bit on falling edge 55 | Controller2_Clock : out std_logic; -- Shift register by one bit on falling edge 56 | 57 | Controller1_Data0_N : in std_logic; -- Shift register highest bit 58 | Controller1_Data1_N : in std_logic; -- Not connected in standard controllers 59 | Controller1_Data2_N : in std_logic; -- Not connected in standard controllers 60 | 61 | Controller2_Data0_N : in std_logic; -- Shift register highest bit 62 | Controller2_Data1_N : in std_logic; -- Not connected in standard controllers 63 | Controller2_Data2_N : in std_logic; -- Not connected in standard controllers 64 | 65 | PinWithoutSemicolon : out std_logic 66 | ); 67 | end component; 68 | 69 | component AC97_Output is 70 | port ( 71 | CLK : in std_logic; -- 100 MHz Clock 72 | RSTN : in std_logic; 73 | 74 | -- AC97 Ports 75 | 76 | BITCLK : in STD_LOGIC; 77 | AUDSDI : in STD_LOGIC; 78 | AUDSDO : out STD_LOGIC; 79 | AUDSYNC : out STD_LOGIC; 80 | AUDRST : out STD_LOGIC; 81 | 82 | -- Asynchronous PCM Input 83 | PCM_in_left : in std_logic_vector(15 downto 0); 84 | PCM_in_right: in std_logic_vector(15 downto 0) 85 | ); 86 | end component; 87 | 88 | 89 | 90 | signal NES_Clock : std_logic; 91 | signal TFT_Clock : std_logic; -- 25 MHz 92 | 93 | signal NES_Clock_cnt : unsigned(7 downto 0); 94 | signal TFT_Clock_cnt : unsigned(1 downto 0); 95 | 96 | signal FB_Address : unsigned(15 downto 0); 97 | signal FB_Color : std_logic_vector(5 downto 0); 98 | signal FB_DE : std_logic; 99 | 100 | signal HDMI_FB_Address : std_logic_vector(15 downto 0); 101 | signal HDMI_FB_Color : std_logic_vector(5 downto 0); 102 | 103 | type fb_ram_type is array(65535 downto 0) of std_logic_vector(5 downto 0); 104 | type FramebufferFileType is file of fb_ram_type; 105 | 106 | signal fb_ram : fb_ram_type := (others => "101010"); 107 | 108 | signal APU_PCM : std_logic_vector(7 downto 0); 109 | signal AC97_PCM : std_logic_vector(15 downto 0); 110 | signal PCM_test : std_logic_vector(15 downto 0); 111 | 112 | signal Controller_Strobe : std_logic; 113 | signal Controller1_Clock : std_logic; 114 | signal Controller2_Clock : std_logic; 115 | 116 | signal Controller1_ShiftRegister : std_logic_vector(7 downto 0); 117 | begin 118 | 119 | TFT_Clock <= TFT_Clock_cnt(1); 120 | 121 | CLOCK_DIVIDER : process (CLK) 122 | variable ref : integer; 123 | begin 124 | if rising_edge(clk) then 125 | if RSTN = '0' then 126 | NES_Clock_cnt <= X"00"; 127 | TFT_Clock_cnt <= "00"; 128 | NES_Clock <= '0'; 129 | else 130 | ref := to_integer(unsigned(SW)); 131 | if NES_Clock_cnt = ref then 132 | NES_Clock_cnt <= X"00"; 133 | NES_Clock <= not NES_Clock; 134 | else 135 | NES_Clock_cnt <= NES_Clock_cnt + 1; 136 | end if; 137 | 138 | TFT_Clock_cnt <= TFT_Clock_cnt + 1; 139 | end if; 140 | end if; 141 | end process; 142 | 143 | CONTROLLER_INPUT : process (NES_Clock) 144 | variable Controller1_Clock_delay : std_logic; 145 | begin 146 | if rising_edge(NES_Clock) then 147 | if Controller1_Clock = '1' and Controller1_Clock_delay = '0' then -- detect rising edge 148 | Controller1_ShiftRegister <= JB(3) & Controller1_ShiftRegister(7 downto 1); 149 | end if; 150 | 151 | Controller1_Clock_delay := Controller1_Clock; 152 | end if; 153 | end process; 154 | 155 | JA <= APU_PCM; 156 | JB(1) <= Controller1_Clock; 157 | JB(0) <= Controller_Strobe; 158 | 159 | LED <= Controller1_ShiftRegister; 160 | 161 | 162 | 163 | FRAMEBUFFER_READ : process (TFT_Clock) 164 | begin 165 | if rising_edge(TFT_Clock) then 166 | HDMI_FB_Color <= fb_ram(to_integer(unsigned(HDMI_FB_Address))); 167 | end if; 168 | end process; 169 | 170 | FRAMEBUFFER_WRITE : process (NES_Clock) 171 | begin 172 | if rising_edge(Nes_Clock) then 173 | if FB_DE = '1' then 174 | fb_ram(to_integer(unsigned(FB_Address))) <= FB_Color; 175 | end if; 176 | end if; 177 | end process; 178 | 179 | nes : NES_Mainboard port map ( 180 | clk => NES_Clock, 181 | rstn => RSTN, 182 | 183 | FB_Address => FB_Address, 184 | FB_Color => FB_Color, 185 | FB_DE => FB_DE, 186 | 187 | APU_PCM => APU_PCM, 188 | 189 | Controller_Strobe => Controller_Strobe, 190 | Controller1_Clock => Controller1_Clock, 191 | Controller2_Clock => Controller2_Clock, 192 | 193 | Controller1_Data0_N => JB(3), 194 | Controller1_Data1_N => '1', 195 | Controller1_Data2_N => '1', 196 | Controller2_Data0_N => '1', 197 | Controller2_Data1_N => '1', 198 | Controller2_Data2_N => '1', 199 | 200 | PinWithoutSemicolon => open 201 | ); 202 | 203 | hdmi : HDMIController 204 | port map ( 205 | CLK => TFT_Clock, 206 | RSTN => RSTN, 207 | CLK_25 => TFT_Clock, 208 | HDMIHSync => HDMIHSync, 209 | HDMIVSync => HDMIVSync, 210 | HDMIDE => HDMIDE, 211 | HDMICLKP => HDMICLKP, 212 | HDMICLKN => HDMICLKN, 213 | HDMID => HDMID, 214 | HDMISCL => HDMISCL, 215 | HDMISDA => HDMISDA, 216 | HDMIRSTN => HDMIRSTN, 217 | 218 | FB_Address => HDMI_FB_Address, 219 | FB_Data => HDMI_FB_Color 220 | 221 | ); 222 | 223 | AC97_PCM <= APU_PCM & X"00"; 224 | --LED <= APU_PCM; 225 | 226 | process (CLK) 227 | begin 228 | if rising_edge(CLK) then 229 | --PCM_test <= std_logic_vector(unsigned(PCM_test) + 1); 230 | end if; 231 | end process; 232 | 233 | ac97 : AC97_Output 234 | port map ( 235 | CLK => CLK, 236 | RSTN => RSTN, 237 | BITCLK => AUDCLK, 238 | AUDSDI => AUDSDI, 239 | AUDSDO => AUDSDO, 240 | AUDSYNC => AUDSYNC, 241 | AUDRST => AUDRST, 242 | 243 | PCM_in_left => AC97_PCM, 244 | PCM_in_right => AC97_PCM 245 | ); 246 | 247 | 248 | end Behavioral; 249 | 250 | -------------------------------------------------------------------------------- /HDMI/HDMIController.vhd: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | use ieee.numeric_std.all; 4 | 5 | library UNISIM; 6 | use UNISIM.VComponents.all; 7 | 8 | entity HDMIController is 9 | port ( 10 | CLK : in std_logic; 11 | RSTN : in std_logic; 12 | 13 | CLK_25 : in std_logic; 14 | 15 | HDMIHSync : OUT std_logic; 16 | HDMIVSync : OUT std_logic; 17 | HDMIDE : OUT std_logic; 18 | HDMICLKP : OUT std_logic; 19 | HDMICLKN : OUT std_logic; 20 | HDMID : OUT std_logic_vector(11 downto 0); 21 | HDMISCL : INOUT std_logic; 22 | HDMISDA : INOUT std_logic; 23 | HDMIRSTN : OUT std_logic; 24 | 25 | FB_Address : out std_logic_vector(15 downto 0); 26 | FB_Data : in std_logic_vector(5 downto 0) 27 | 28 | ); 29 | end HDMIController; 30 | 31 | architecture Behavioral of HDMIController is 32 | 33 | component tft_interface 34 | port ( 35 | TFT_Clk : in std_logic; 36 | TFT_Rst : in std_logic; 37 | Bus2IP_Clk : in std_logic; 38 | Bus2IP_Rst : in std_logic; 39 | 40 | HSYNC : in std_logic; 41 | VSYNC : in std_logic; 42 | DE : in std_logic; 43 | RED : in std_logic_vector(5 downto 0); 44 | GREEN : in std_logic_vector(5 downto 0); 45 | BLUE : in std_logic_vector(5 downto 0); 46 | 47 | TFT_HSYNC : out std_logic; 48 | TFT_VSYNC : out std_logic; 49 | TFT_DE : out std_logic; 50 | TFT_VGA_CLK : out std_logic; 51 | TFT_VGA_R : out std_logic_vector(5 downto 0); 52 | TFT_VGA_G : out std_logic_vector(5 downto 0); 53 | TFT_VGA_B : out std_logic_vector(5 downto 0); 54 | 55 | TFT_DVI_CLK_P : out std_logic; 56 | TFT_DVI_CLK_N : out std_logic; 57 | TFT_DVI_DATA : out std_logic_vector(11 downto 0); 58 | 59 | I2C_done : out std_logic; 60 | TFT_IIC_SCL_I : in std_logic; 61 | TFT_IIC_SCL_O : out std_logic; 62 | TFT_IIC_SCL_T : out std_logic; 63 | TFT_IIC_SDA_I : in std_logic; 64 | TFT_IIC_SDA_O : out std_logic; 65 | TFT_IIC_SDA_T : out std_logic; 66 | 67 | IIC_xfer_done : out std_logic; 68 | TFT_iic_xfer : in std_logic; 69 | TFT_iic_reg_addr : in std_logic_vector(0 to 7); 70 | TFT_iic_reg_data : in std_logic_vector(0 to 7) 71 | ); 72 | end component; 73 | 74 | component ColorPalette is 75 | port ( 76 | ColorIndex : in unsigned (5 downto 0); 77 | Red : out STD_LOGIC_VECTOR (5 downto 0); 78 | Green : out STD_LOGIC_VECTOR (5 downto 0); 79 | Blue : out STD_LOGIC_VECTOR (5 downto 0) 80 | ); 81 | end component; 82 | 83 | signal HSYNC : std_logic; 84 | signal VSYNC : std_logic; 85 | signal DE : std_logic; 86 | signal RED : std_logic_vector(5 downto 0); 87 | signal GREEN : std_logic_vector(5 downto 0); 88 | signal BLUE : std_logic_vector(5 downto 0); 89 | 90 | signal TFT_IIC_SCL_I : std_logic; 91 | signal TFT_IIC_SCL_O : std_logic; 92 | signal TFT_IIC_SCL_T : std_logic; 93 | signal TFT_IIC_SDA_I : std_logic; 94 | signal TFT_IIC_SDA_O : std_logic; 95 | signal TFT_IIC_SDA_T : std_logic; 96 | 97 | signal HSYNC_cnt : integer := 0; 98 | signal VSYNC_cnt : integer := 0; 99 | 100 | signal FB_Address_int : integer; 101 | 102 | signal VSYNC_delay : std_logic_vector(0 to 3); 103 | signal RST : std_logic; 104 | 105 | begin 106 | 107 | HDMIRSTN <= RSTN; 108 | RST <= not RSTN; 109 | 110 | 111 | 112 | VSYNC_delay(2) <= '0' when VSYNC_cnt < 2 else '1'; 113 | VSYNC <= VSYNC_delay(0); 114 | 115 | HSYNC <= '0' when HSYNC_cnt < 96 else '1'; 116 | 117 | DE <= '1' when HSYNC_cnt >= 144 and HSYNC_cnt < 784 and VSYNC_cnt >= 33 and VSYNC_cnt < 513 else '0'; 118 | 119 | process (VSYNC_cnt, HSYNC_cnt) 120 | variable addr_int : integer; 121 | variable HPOS, VPOS : integer; 122 | begin 123 | HPOS := (HSYNC_cnt - 144) / 2 - 32; 124 | VPOS := (VSYNC_cnt - 33) / 2; 125 | addr_int := VPOS * 256 + HPOS; 126 | 127 | if HPOS >= 0 and HPOS < 256 and VPOS >= 0 and VPOS < 240 then 128 | FB_Address <= std_logic_vector(to_unsigned(addr_int, FB_Address'length)); 129 | else 130 | FB_Address <= (others => '0'); 131 | end if; 132 | end process; 133 | 134 | process (CLK_25) 135 | begin 136 | if rising_edge(CLK_25) then 137 | VSYNC_delay(0 to 1) <= VSYNC_delay(1 to 2); 138 | end if; 139 | end process; 140 | 141 | process (CLK_25) 142 | begin 143 | if rising_edge(CLK_25) then 144 | if RSTN = '0' then 145 | HSYNC_cnt <= 0; 146 | VSYNC_cnt <= 0; 147 | else 148 | if HSYNC_cnt < 800 - 1 then 149 | HSYNC_cnt <= HSYNC_cnt + 1; 150 | else 151 | HSYNC_cnt <= 0; 152 | if VSYNC_cnt < 525 - 1 then 153 | VSYNC_cnt <= VSYNC_cnt + 1; 154 | else 155 | VSYNC_cnt <= 0; 156 | end if; 157 | end if; 158 | end if; 159 | end if; 160 | end process; 161 | 162 | 163 | IISC_SCL_BUF: IOBUF 164 | port map ( 165 | I => TFT_IIC_SCL_O, 166 | O => TFT_IIC_SCL_I, 167 | T => TFT_IIC_SCL_T, 168 | IO => HDMISCL 169 | ); 170 | 171 | IISC_SDA_BUF: IOBUF 172 | port map ( 173 | I => TFT_IIC_SDA_O, 174 | O => TFT_IIC_SDA_I, 175 | T => TFT_IIC_SDA_T, 176 | IO => HDMISDA 177 | ); 178 | 179 | 180 | interface : tft_interface 181 | port map ( 182 | TFT_Clk => CLK_25, 183 | TFT_Rst => RST, 184 | Bus2IP_Clk => CLK, 185 | Bus2IP_Rst => RST, 186 | HSYNC => HSYNC, 187 | VSYNC => VSYNC, 188 | DE => DE, 189 | RED => RED, 190 | GREEN => GREEN, 191 | BLUE => BLUE, 192 | 193 | 194 | TFT_HSYNC => HDMIHSync, 195 | TFT_VSYNC => HDMIVSync, 196 | TFT_DE => HDMIDE, 197 | 198 | TFT_VGA_CLK => open, 199 | TFT_VGA_R => open, 200 | TFT_VGA_G => open, 201 | TFT_VGA_B => open, 202 | 203 | TFT_DVI_CLK_P => HDMICLKP, 204 | TFT_DVI_CLK_N => HDMICLKN, 205 | TFT_DVI_DATA => HDMID, 206 | 207 | I2C_done => open, 208 | TFT_IIC_SCL_I => TFT_IIC_SCL_I, 209 | TFT_IIC_SCL_O => TFT_IIC_SCL_O, 210 | TFT_IIC_SCL_T => TFT_IIC_SCL_T, 211 | TFT_IIC_SDA_I => TFT_IIC_SDA_I, 212 | TFT_IIC_SDA_O => TFT_IIC_SDA_O, 213 | TFT_IIC_SDA_T => TFT_IIC_SDA_T, 214 | IIC_xfer_done => open, 215 | TFT_iic_xfer => '0', 216 | TFT_iic_reg_addr => "--------", 217 | TFT_iic_reg_data => "--------" 218 | ); 219 | 220 | PALETTE : ColorPalette 221 | port map ( 222 | ColorIndex => unsigned(FB_Data), 223 | Red => RED, 224 | Green => GREEN, 225 | Blue => BLUE 226 | ); 227 | 228 | 229 | end Behavioral; 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /NES_2A03/ClockDivider.vhd: -------------------------------------------------------------------------------- 1 | --Dan Leach 2 | --Clock divider (divide by twelve, seven cycle duty cycle) 3 | --Created 1/03/06 4 | --Initial revision 5 | --Modified 1/27/06 6 | --Changed if statements so that Clk_Out is set after the first input clock 7 | -- cycle. Also added another clause so that the counter is reset after 8 | -- 12 input cycles. 9 | --Set default/initial values of all pins 10 | --Modified sometime since then 11 | --Added PHI2 signal, inverse of PHI1/Clk_Out 12 | --Modified 6/27/06 13 | --Need another signal to use when reading data from the bus 14 | -- this means another signal goes high 2 input clock cycles 15 | -- after Clk_Out_Phi2 goes high 16 | --Modified 7/01/06 17 | --Need another signal for putting the address on the bus shortly after 18 | -- PHI1 (Clk_Out_Phi2) goes high 19 | library ieee; 20 | use ieee.std_logic_1164.all; 21 | use ieee.numeric_std.all; 22 | 23 | 24 | entity Clock_Divider is 25 | port(Clk_In, Reset_N, Enable : in std_logic; 26 | PHI1_CE : out std_logic; 27 | PHI2 : out std_logic; 28 | AddOK_CE : out std_logic; 29 | WriteOK_CE : out std_logic; 30 | ReadOK_CE : out std_logic 31 | ); 32 | end Clock_Divider; 33 | 34 | architecture Behavior of Clock_Divider is 35 | constant Duty : unsigned (3 downto 0) := "0111"; --5 cycle duty cycle 36 | constant Total : unsigned (3 downto 0) := "1011";--"1011"; --12 cycles total 37 | constant WriteOK : unsigned (3 downto 0) := "0010"; --Read 2 cycles into Phi2 38 | constant ReadOK : unsigned (3 downto 0) := "0101"; --Read 3 cycles into Phi2 39 | constant AddOK : unsigned (3 downto 0) := "1000"; --Address valid 1 cycle into Phi1 40 | signal Count_Out_W : unsigned (3 downto 0); 41 | 42 | 43 | begin 44 | 45 | PHI1_CE <= '1' when Count_Out_W = Duty else '0'; 46 | PHI2 <= '1' when Count_Out_W < Duty else '0'; 47 | AddOK_CE <= '1' when Count_Out_W = AddOK else '0'; 48 | ReadOK_CE <= '1' when Count_Out_W = ReadOK else '0'; 49 | WriteOK_CE <= '1' when Count_Out_W = WriteOK else '0'; 50 | 51 | process(Clk_In, Reset_N) 52 | begin 53 | if Reset_N = '0' then 54 | Count_Out_W <= (others => '0'); 55 | elsif rising_edge(Clk_In) then 56 | if Enable = '1' then 57 | if Count_Out_W = Total then 58 | Count_Out_W <= (others => '0'); 59 | else 60 | Count_Out_W <= Count_Out_W + 1; 61 | end if; 62 | end if; 63 | end if; 64 | end process; 65 | 66 | -- 67 | -- Count : process(Clk_In, Reset_N) 68 | -- begin 69 | -- if (Reset_N = '0') then 70 | -- Clk_Out <= '0'; 71 | -- Clk_Out_Phi2 <= '0'; 72 | -- Clk_Out_ReadOK <= '0'; 73 | -- Count_Out_W <= (others => '0'); 74 | -- elsif (falling_edge(Clk_In)) then 75 | -- if (Enable = '1') then 76 | -- case Count_Out_W is 77 | -- when "0000" => 78 | -- Clk_Out <= '0'; 79 | -- Clk_Out_Phi2 <= '1'; 80 | -- Count_Out_W <= Count_Out_W + 1; 81 | -- when WriteOK => 82 | -- Clk_Out_WriteOK <= '1'; 83 | -- Clk_Out_AddOK <= '0'; 84 | -- Count_Out_W <= Count_Out_W + 1; 85 | -- when AddOK => 86 | -- Clk_Out_AddOK <= '1'; 87 | -- Count_Out_W <= Count_Out_W + 1; 88 | -- when Duty => 89 | -- Clk_Out <= '1'; 90 | -- Clk_Out_Phi2 <= '0'; 91 | -- Clk_Out_AddOK <= '0'; 92 | -- Clk_Out_WriteOK <= '0'; 93 | -- Clk_Out_ReadOK <= '0'; 94 | -- Count_Out_W <= Count_Out_W + 1; 95 | -- when ReadOK => 96 | -- Clk_Out_ReadOK <= '1'; 97 | -- Count_Out_W <= Count_Out_W + 1; 98 | -- when Total => 99 | -- Clk_Out <= '1'; 100 | -- Clk_Out_Phi2 <= '0'; 101 | -- 102 | -- Count_Out_W <= (others => '0'); 103 | -- when others => 104 | -- Count_Out_W <= Count_Out_W + 1; 105 | -- end case; 106 | -- end if; 107 | -- end if; 108 | -- end process; 109 | end Behavior; -------------------------------------------------------------------------------- /NES_2A03/DanPack.vhd: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.std_logic_1164.all; 3 | use IEEE.numeric_std.all; 4 | use work.T65_Pack.all; 5 | 6 | package Pack_2A03 is 7 | component T65 8 | port( 9 | Mode : in std_logic_vector(1 downto 0); -- "00" => 6502, "01" => 65C02, "10" => 65C816 10 | Res_n : in std_logic; 11 | Enable : in std_logic; 12 | Clk : in std_logic; 13 | Rdy : in std_logic; 14 | --Abort_n : in std_logic; 15 | IRQ_n : in std_logic; 16 | NMI_n : in std_logic; 17 | SO_n : in std_logic; 18 | R_W_n : out std_logic; 19 | Sync : out std_logic; 20 | ML_n : out std_logic; 21 | VP_n : out std_logic; 22 | VDA : out std_logic; 23 | VPA : out std_logic; 24 | T65Address : out std_logic_vector(15 downto 0); 25 | T65DataIn : in std_logic_vector(7 downto 0); 26 | T65DataOut : out std_logic_vector(7 downto 0) 27 | --Added for debugging 28 | --T65_LCycle : out std_logic_vector(2 downto 0); 29 | --T65_MCycle : out std_logic_vector(2 downto 0); 30 | --T65_InitialReset : out std_logic 31 | ); 32 | end component; 33 | 34 | component Clock_Divider 35 | port(Clk_In, Reset_N, Enable : in std_logic; 36 | PHI1_CE : out std_logic; 37 | PHI2 : out std_logic; 38 | AddOK_CE : out std_logic; 39 | WriteOK_CE : out std_logic; 40 | ReadOK_CE : out std_logic 41 | ); 42 | end component; 43 | 44 | component SRAM 45 | port( 46 | Clock : in std_logic; 47 | ChipSelect_N : in std_logic; 48 | WriteEnable_N : in std_logic; 49 | OutputEnable_N : in std_logic; 50 | Address : in std_logic_vector (10 downto 0); 51 | Data : inout std_logic_vector (7 downto 0) 52 | ); 53 | end component; 54 | 55 | component APU_Main is 56 | port ( 57 | CLK: in std_logic; 58 | RSTN : in std_logic; 59 | PHI2_CE: in std_logic; 60 | RW10: in std_logic; 61 | Address: in std_logic_vector(15 downto 0); 62 | Data_read: out std_logic_vector(7 downto 0); 63 | Data_write: in std_logic_vector(7 downto 0); 64 | Interrupt: out std_logic; 65 | PCM_out: out std_logic_vector(7 downto 0) 66 | ); 67 | end component; 68 | end Pack_2A03; 69 | -------------------------------------------------------------------------------- /NES_2A03/Dan_2A03.vhd: -------------------------------------------------------------------------------- 1 | --Dan Leach 2 | --2A03 for NES 3 | --Created 1/03/06 4 | --Initial revision 5 | library ieee; 6 | use ieee.std_logic_1164.all; 7 | use ieee.numeric_std.all; 8 | use work.Pack_2A03.all; 9 | 10 | entity NES_2A03 is 11 | port ( 12 | Global_Clk : in std_logic; --input clock signal from NES mobo crystal 13 | Reset_N : in std_logic; --External Reset 14 | NMI_N : in std_logic; 15 | IRQ_N : in std_logic; 16 | Data : inout std_logic_vector(7 downto 0); 17 | Address : buffer std_logic_vector(15 downto 0); 18 | RW_10 : buffer std_logic; --low if writing, high if reading 19 | 20 | PHI2 : out std_logic; --Clock Divider Output 21 | 22 | --Controller Outputs 23 | CStrobe : out std_logic; --Controller Strobe Signal 24 | C1R_N : out std_logic; --low when reading controller 1 25 | C2R_N : out std_logic; --low when reading controller 2 26 | 27 | --Audio Outputs 28 | A_Rectangle : out std_logic; --Rectangle Wave Output (Mixed) 29 | A_Combined : out std_logic_vector(7 downto 0); --Triangle, Noise, And PCM (DPCM) Output 30 | 31 | --The following three signals represent the status of an internal register 32 | -- used in accessing the expansion port 33 | W_4016_1 : out std_logic; 34 | W_4016_2 : out std_logic; 35 | 36 | --Debugging 37 | --LCycle : out std_logic_vector(2 downto 0); 38 | --MCycle : out std_logic_vector(2 downto 0); 39 | --InitialReset : out std_logic; 40 | AddOKDebug : out std_logic; 41 | ReadOKDebug : out std_logic; 42 | WriteOKDebug : out std_logic; 43 | SRAMChipSelect_NDebug : out std_logic; 44 | SRAMWriteEnable_NDebug : out std_logic; 45 | SRAMOutputEnable_NDebug :out std_logic; 46 | SRAMReading : out std_logic; 47 | SRAMWriting : out std_logic 48 | ); 49 | end NES_2A03; 50 | 51 | architecture Behavioral of NES_2A03 is 52 | --Clock Control 53 | signal PHI1_CE : std_logic; 54 | signal PHI2_Internal : std_logic; 55 | 56 | --Internal Status Registers 57 | signal Global_Enable : std_logic; 58 | 59 | --T65 Control 60 | signal T65Enable : std_logic; 61 | signal T65DMADisable : std_logic; 62 | signal T65RW_10 : std_logic := '0'; 63 | signal T65Address : std_logic_vector (15 downto 0); 64 | signal T65DataIn : std_logic_vector(7 downto 0); 65 | signal T65DataOut : std_logic_vector(7 downto 0); 66 | 67 | --DMA 68 | signal TransferCount : unsigned (9 downto 0); 69 | signal TransferAddress : unsigned (15 downto 0); 70 | signal DMAEnable : std_logic; 71 | signal DMARW_10 : std_logic := '0'; 72 | signal DMAAddress : std_logic_vector (15 downto 0) := "0000000000000000"; 73 | signal DMADataIn : std_logic_vector(7 downto 0); 74 | signal DMADataOut : std_logic_vector(7 downto 0); 75 | signal DMAContinue : std_logic; 76 | signal DMADelay : unsigned (2 downto 0); 77 | 78 | --Bus Control 79 | signal BusControl : std_logic; 80 | signal RW_10Signal : std_logic; 81 | signal AddressSignal : std_logic_vector (15 downto 0); 82 | signal DataInSignal : std_logic_vector (7 downto 0); 83 | signal DataOutSignal : std_logic_vector (7 downto 0); 84 | 85 | --Bus Timing 86 | signal AddOKSignal : std_logic := '0'; 87 | signal ReadOKSignal : std_logic := '0'; 88 | signal WriteOKSignal : std_logic := '0'; 89 | 90 | --Memory 91 | signal SRAMWriteSignal : std_logic; 92 | signal SRAM_Reading : std_logic; 93 | signal SRAM_Writing : std_logic; 94 | signal SRAM_CS_N : std_logic; 95 | 96 | --Controllers 97 | signal ControllerOnePoll : std_logic := '0'; 98 | signal ControllerTwoPoll : std_logic := '0'; 99 | 100 | begin 101 | --more or less constant assignments 102 | Global_Enable <= '1'; 103 | PHI2 <= PHI2_Internal; 104 | 105 | T65Enable <= PHI1_CE and not T65DMADisable; 106 | 107 | SRAMWriteSignal <= not ReadOKSignal when RW_10 = '0' else '1'; 108 | SRAM_CS_N <= not PHI2_Internal or (Address(15) or Address(14) or Address(13)); 109 | 110 | --to be implemented later 111 | A_Rectangle <= '0'; 112 | --A_Combined <= '0'; 113 | W_4016_1 <= 'Z'; 114 | W_4016_2 <= 'Z'; 115 | 116 | --Debugging 117 | WriteOKDebug <= WriteOKSignal; 118 | AddOKDebug <= AddOKSignal; 119 | ReadOKDebug <= ReadOKSignal; 120 | 121 | SRAMWriteEnable_NDebug <= SRAMWriteSignal; 122 | SRAMOutputEnable_NDebug <= '0'; 123 | SRAMChipSelect_NDebug <= (not PHI2_Internal) or (Address(15) or Address(14) or Address(13)); 124 | 125 | SRAMReading <= SRAM_Reading; 126 | SRAMWriting <= SRAM_Writing; 127 | 128 | --Controllers 129 | ControllerOnePoll <= '1' when Address = x"4016" else '0'; 130 | ControllerTwoPoll <= '1' when Address = x"4017" else '0'; 131 | C1R_N <= not (ControllerOnePoll and RW_10 and PHI2_Internal); 132 | C2R_N <= not (ControllerTwoPoll and RW_10 and PHI2_Internal); 133 | 134 | 135 | process (Global_Clk) 136 | begin 137 | if rising_edge(Global_Clk) then 138 | if Address = X"4016" and RW_10 = '0' and ReadOKSignal = '1' then 139 | CStrobe <= Data(0); 140 | end if; 141 | end if; 142 | end process; 143 | 144 | 145 | RW_10Signal <= T65RW_10 when BusControl = '0' else DMARW_10; 146 | AddressSignal <= T65Address when BusControl = '0' else DMAAddress; 147 | 148 | 149 | T65DataIn <= DataInSignal when BusControl = '0' else "01010101"; --???? 150 | DMADataIn <= DataInSignal when BusControl = '1' else "01010101"; 151 | 152 | with BusControl select DataOutSignal <= 153 | T65DataOut when '0', 154 | DMADataOut when others; 155 | 156 | --Bus stuff 157 | process (Reset_N, Global_Clk) 158 | begin 159 | if (Reset_N = '0') then 160 | DMAEnable <= '0'; 161 | Address <= x"0000"; 162 | elsif rising_edge(Global_Clk) then 163 | if AddOKSignal = '1' then 164 | RW_10 <= RW_10Signal; 165 | Address <= AddressSignal; 166 | if (T65Address = x"4014") then 167 | DMAEnable <= '1'; 168 | else 169 | DMAEnable <= '0'; 170 | end if; 171 | end if; 172 | end if; 173 | end process; 174 | 175 | process (Global_Clk) 176 | begin 177 | if rising_edge(Global_Clk) then 178 | if RW_10Signal = '1' then --reading 179 | if (PHI2_Internal = '0') then 180 | Data <= DataInSignal; --might be causing big-ass problems 181 | else 182 | Data <= "ZZZZZZZZ"; 183 | if (ReadOKSignal = '1') then 184 | DataInSignal <= Data; 185 | end if; 186 | end if; 187 | else --writing 188 | if PHI2_Internal = '1' and WriteOKSignal = '1' then 189 | Data <= DataOutSignal; 190 | DataInSignal <= Data; 191 | elsif PHI2_Internal = '0' then 192 | Data <= "ZZZZZZZZ"; 193 | end if; 194 | end if; 195 | end if; 196 | end process; 197 | 198 | DMATransfer : process (Reset_N, Global_Clk) 199 | begin 200 | if (Reset_N = '0') then 201 | TransferCount <= "0000000000"; 202 | TransferAddress <= "0000000000000000"; 203 | DMAContinue <= '0'; 204 | T65DMADisable <= '0'; 205 | BusControl <= '0'; 206 | DMARW_10 <= '1'; 207 | --D 208 | elsif rising_edge(Global_Clk) then --may need to check timing here 209 | if PHI1_CE = '1' then 210 | if (DMAEnable = '1') then 211 | DMAContinue <= '1'; 212 | end if; 213 | if (DMAContinue = '1') then 214 | case TransferCount is 215 | when "0000000000" => 216 | DMAAddress <= T65DataOut & "00000000"; 217 | TransferAddress <= unsigned(T65DataOut & "00000000") + 1; 218 | T65DMADisable <= '1'; 219 | BusControl <= '1'; 220 | TransferCount <= "0000000001"; 221 | when "1000000000" => 222 | T65DMADisable <= '0'; 223 | BusControl <= '0'; 224 | TransferCount <= "0000000000"; 225 | DMAContinue <= '0'; 226 | DMARW_10 <= '1'; --shit dude, it's amazing anything happened without this 227 | when others => 228 | T65DMADisable <= '1'; 229 | BusControl <= '1'; 230 | TransferCount <= TransferCount + 1; 231 | case TransferCount(0) is 232 | when '0' => 233 | DMARW_10 <= '1'; 234 | DMAAddress <= std_logic_vector(TransferAddress); 235 | TransferAddress <= TransferAddress + 1; 236 | when others => 237 | DMARW_10 <= '0'; 238 | DMAAddress <= x"2004"; 239 | DMADataOut <= DMADataIn; 240 | end case; 241 | end case; 242 | end if; 243 | end if; 244 | end if; 245 | end process DMATransfer; 246 | 247 | --Port Maps 248 | Clk_Div_12 : Clock_Divider 249 | port map ( 250 | Clk_In => Global_Clk, 251 | Reset_N => Reset_N, 252 | Enable => Global_Enable, 253 | PHI1_CE => PHI1_CE, 254 | PHI2 => PHI2_Internal, 255 | AddOK_CE => AddOKSignal, 256 | WriteOK_CE => WriteOKSignal, 257 | ReadOK_CE => ReadOKSignal 258 | ); 259 | 260 | NES_2A03 : T65 261 | port map ( 262 | Mode => "00", 263 | Res_n => Reset_N, 264 | Enable => T65Enable, 265 | Clk => Global_Clk, 266 | Rdy => '1', --used for single-cycle execution, not used in 2A03 267 | --Abort_n => '0', --not used at all 268 | IRQ_n => IRQ_N, 269 | NMI_n => NMI_N, 270 | SO_n => '1', --Set Overflow, not used by NES 271 | R_W_n => T65RW_10, 272 | Sync => open, --used for single-cycle execution, not used in 2A03 273 | ML_n => open, --used in 65C816 274 | VP_n => open, --used in 65C816 275 | VDA => open, --used in 65C816 276 | VPA => open, --used in 65C816 277 | T65Address => T65Address, 278 | T65DataIn => T65DataIn, 279 | T65DataOut => T65DataOut 280 | ); 281 | 282 | CPU_RAM : SRAM 283 | port map ( 284 | Clock => Global_Clk, 285 | ChipSelect_N => SRAM_CS_N, 286 | WriteEnable_N => SRAMWriteSignal, 287 | OutputEnable_N => "not"(RW_10), 288 | Address => Address (10 downto 0), 289 | Data => Data 290 | ); 291 | 292 | APU : APU_Main 293 | port map ( 294 | CLK => Global_Clk, 295 | RSTN => Reset_N, 296 | PHI2_CE => ReadOKSignal, 297 | RW10 => RW_10, 298 | Address => Address, 299 | --Data_read => APU_Data, 300 | Data_read => open, 301 | Data_write => Data, 302 | Interrupt => open, 303 | PCM_out => A_Combined 304 | ); 305 | end; 306 | -------------------------------------------------------------------------------- /NES_2A03/SRAM.vhd: -------------------------------------------------------------------------------- 1 | --Dan Leach 2 | --2K SRAM for NES 3 | --Created 9/10/06 4 | -- Initial revision 5 | -- For simplicity, as on the NES itself, the ChipSelect line is used as a clock (Phi2). 6 | -- This effectively makes the RAM synchronous and thus simpler to implement. 7 | library ieee; 8 | use ieee.std_logic_1164.all; 9 | use ieee.numeric_std.all; 10 | 11 | entity SRAM is 12 | port( 13 | Clock : in std_logic; 14 | ChipSelect_N : in std_logic; 15 | WriteEnable_N : in std_logic; 16 | OutputEnable_N : in std_logic; 17 | Address : in std_logic_vector (10 downto 0); 18 | Data : inout std_logic_vector (7 downto 0) 19 | ); 20 | end SRAM; 21 | 22 | architecture Behavioral of SRAM is 23 | -- Declare Memory type 24 | type Memory is array(0 to 2047) of std_logic_vector (7 downto 0); 25 | signal CPUMemory : Memory := (others => (others => 'U')); 26 | 27 | signal Data_out : std_logic_vector (7 downto 0); 28 | 29 | begin 30 | 31 | Data <= "ZZZZZZZZ" when ChipSelect_N = '1' or WriteEnable_N = '0' or OutputEnable_N = '1' else Data_out; 32 | -- 33 | -- process (Clock) 34 | -- begin 35 | -- if rising_edge(Clock) then 36 | -- WriteEnable_d <= WriteEnable_N; 37 | -- if ChipSelect_N = '0' and OutputEnable_N = '0' then 38 | -- if WriteEnable_N = '0' and WriteEnable_d = '1' then 39 | -- CPUMemory(to_integer(unsigned(Address))) <= Data; 40 | -- else 41 | -- Data_out <= CPUMemory(to_integer(unsigned(Address))); 42 | -- end if; 43 | -- else 44 | -- end if; 45 | -- end if; 46 | -- end process; 47 | process (Clock) 48 | begin 49 | if rising_edge(Clock) then 50 | if WriteEnable_N = '0' and ChipSelect_N = '0' then 51 | CPUMemory(to_integer(unsigned(Address))) <= Data; 52 | end if; 53 | 54 | Data_out <= CPUMemory(to_integer(unsigned(Address))); 55 | end if; 56 | end process; 57 | 58 | end Behavioral; 59 | -------------------------------------------------------------------------------- /NES_2A03/T65_ALU.vhd: -------------------------------------------------------------------------------- 1 | -- **** 2 | -- T65(b) core. In an effort to merge and maintain bug fixes .... 3 | -- 4 | -- 5 | -- Ver 300 Bugfixes by ehenciak added 6 | -- MikeJ March 2005 7 | -- Latest version from www.fpgaarcade.com (original www.opencores.org) 8 | -- 9 | -- **** 10 | -- 11 | -- 6502 compatible microprocessor core 12 | -- 13 | -- Version : 0245 14 | -- 15 | -- Copyright (c) 2002 Daniel Wallner (jesus@opencores.org) 16 | -- 17 | -- All rights reserved 18 | -- 19 | -- Redistribution and use in source and synthezised forms, with or without 20 | -- modification, are permitted provided that the following conditions are met: 21 | -- 22 | -- Redistributions of source code must retain the above copyright notice, 23 | -- this list of conditions and the following disclaimer. 24 | -- 25 | -- Redistributions in synthesized form must reproduce the above copyright 26 | -- notice, this list of conditions and the following disclaimer in the 27 | -- documentation and/or other materials provided with the distribution. 28 | -- 29 | -- Neither the name of the author nor the names of other contributors may 30 | -- be used to endorse or promote products derived from this software without 31 | -- specific prior written permission. 32 | -- 33 | -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 34 | -- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 35 | -- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 36 | -- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 37 | -- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 38 | -- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | -- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | -- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 41 | -- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 42 | -- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 43 | -- POSSIBILITY OF SUCH DAMAGE. 44 | -- 45 | -- Please report bugs to the author, but before you do so, please 46 | -- make sure that this is not a derivative work and that 47 | -- you have the latest version of this file. 48 | -- 49 | -- The latest version of this file can be found at: 50 | -- http://www.opencores.org/cvsweb.shtml/t65/ 51 | -- 52 | -- Limitations : 53 | -- 54 | -- File history : 55 | -- 56 | -- 0245 : First version 57 | -- 58 | 59 | library IEEE; 60 | use IEEE.std_logic_1164.all; 61 | use IEEE.numeric_std.all; 62 | use work.T65_Pack.all; 63 | 64 | entity T65_ALU is 65 | port( 66 | Mode : in std_logic_vector(1 downto 0); -- "00" => 6502, "01" => 65C02, "10" => 65816 67 | Op : in std_logic_vector(3 downto 0); 68 | BusA : in std_logic_vector(7 downto 0); 69 | BusB : in std_logic_vector(7 downto 0); 70 | P_In : in std_logic_vector(7 downto 0); 71 | P_Out : out std_logic_vector(7 downto 0); 72 | Q : out std_logic_vector(7 downto 0) 73 | ); 74 | end T65_ALU; 75 | 76 | architecture rtl of T65_ALU is 77 | 78 | -- AddSub variables (temporary signals) 79 | signal ADC_Z : std_logic; 80 | signal ADC_C : std_logic; 81 | signal ADC_V : std_logic; 82 | signal ADC_N : std_logic; 83 | signal ADC_Q : std_logic_vector(7 downto 0); 84 | signal SBC_Z : std_logic; 85 | signal SBC_C : std_logic; 86 | signal SBC_V : std_logic; 87 | signal SBC_N : std_logic; 88 | signal SBC_Q : std_logic_vector(7 downto 0); 89 | 90 | begin 91 | 92 | process (P_In, BusA, BusB) --determines flag status after addition, as well as result 93 | variable AL : unsigned(6 downto 0); 94 | variable AH : unsigned(6 downto 0); 95 | variable C : std_logic; 96 | begin 97 | AL := resize(unsigned(BusA(3 downto 0) & P_In(Flag_C)), 7) + resize(unsigned(BusB(3 downto 0) & "1"), 7); 98 | AH := resize(unsigned(BusA(7 downto 4) & AL(5)), 7) + resize(unsigned(BusB(7 downto 4) & "1"), 7); 99 | 100 | -- pragma translate_off 101 | if is_x(std_logic_vector(AL)) then AL := "0000000"; end if; --if AL cannot be determined it is set to 0 102 | if is_x(std_logic_vector(AH)) then AH := "0000000"; end if; --if AL cannot be determined it is set to 0 103 | -- pragma translate_on 104 | -- if both the high-order nybble and the low-order nybble are 0, then the zero flag is set 105 | if AL(4 downto 1) = 0 and AH(4 downto 1) = 0 then 106 | ADC_Z <= '1'; 107 | else 108 | ADC_Z <= '0'; 109 | end if; 110 | -- BCD Mode, not used in NES 111 | -- if AL(5 downto 1) > 9 and P_In(Flag_D) = '1' then 112 | -- AL(6 downto 1) := AL(6 downto 1) + 6; 113 | -- end if; 114 | 115 | C := AL(6) or AL(5); 116 | AH := resize(unsigned(BusA(7 downto 4) & C), 7) + resize(unsigned(BusB(7 downto 4) & "1"), 7); 117 | 118 | ADC_N <= AH(4); 119 | ADC_V <= (AH(4) xor BusA(7)) and not (BusA(7) xor BusB(7)); 120 | 121 | -- pragma translate_off 122 | if is_x(std_logic_vector(AH)) then AH := "0000000"; end if; 123 | -- pragma translate_on 124 | -- BCD Mode, not used in NES 125 | -- if AH(5 downto 1) > 9 and P_In(Flag_D) = '1' then 126 | -- AH(6 downto 1) := AH(6 downto 1) + 6; 127 | -- end if; 128 | 129 | ADC_C <= AH(6) or AH(5); 130 | 131 | ADC_Q <= std_logic_vector(AH(4 downto 1) & AL(4 downto 1)); 132 | end process; 133 | 134 | process (Op, P_In, BusA, BusB) --determines flag status after subtraction, as well as result 135 | variable AL : unsigned(6 downto 0); 136 | variable AH : unsigned(5 downto 0); 137 | variable C : std_logic; 138 | begin 139 | C := P_In(Flag_C) or not Op(0); 140 | AL := resize(unsigned(BusA(3 downto 0) & C), 7) - resize(unsigned(BusB(3 downto 0) & "1"), 6); 141 | AH := resize(unsigned(BusA(7 downto 4) & "0"), 6) - resize(unsigned(BusB(7 downto 4) & AL(5)), 6); 142 | 143 | -- pragma translate_off 144 | if is_x(std_logic_vector(AL)) then AL := "0000000"; end if; 145 | if is_x(std_logic_vector(AH)) then AH := "000000"; end if; 146 | -- pragma translate_on 147 | 148 | if AL(4 downto 1) = 0 and AH(4 downto 1) = 0 then 149 | SBC_Z <= '1'; 150 | else 151 | SBC_Z <= '0'; 152 | end if; 153 | 154 | SBC_C <= not AH(5); 155 | SBC_V <= (AH(4) xor BusA(7)) and (BusA(7) xor BusB(7)); 156 | SBC_N <= AH(4); 157 | -- BCD Mode, not used in NES 158 | -- if P_In(Flag_D) = '1' then 159 | -- if AL(5) = '1' then 160 | -- AL(5 downto 1) := AL(5 downto 1) - 6; 161 | -- end if; 162 | -- AH := resize(unsigned(BusA(7 downto 4) & "0"), 6) - resize(unsigned(BusB(7 downto 4) & AL(6)), 6); 163 | -- if AH(5) = '1' then 164 | -- AH(5 downto 1) := AH(5 downto 1) - 6; 165 | -- end if; 166 | -- end if; 167 | 168 | SBC_Q <= std_logic_vector(AH(4 downto 1) & AL(4 downto 1)); 169 | end process; 170 | 171 | process (Op, P_In, BusA, BusB, 172 | ADC_Z, ADC_C, ADC_V, ADC_N, ADC_Q, 173 | SBC_Z, SBC_C, SBC_V, SBC_N, SBC_Q) 174 | variable Q_t : std_logic_vector(7 downto 0); 175 | begin 176 | -- ORA, AND, EOR, ADC, NOP, LD, CMP, SBC 177 | -- ASL, ROL, LSR, ROR, BIT, LD, DEC, INC 178 | P_Out <= P_In; 179 | Q_t := BusA; 180 | case Op(3 downto 0) is 181 | when "0000" => 182 | -- ORA 183 | Q_t := BusA or BusB; 184 | when "0001" => 185 | -- AND 186 | Q_t := BusA and BusB; 187 | when "0010" => 188 | -- EOR 189 | Q_t := BusA xor BusB; 190 | when "0011" => 191 | -- ADC 192 | P_Out(Flag_V) <= ADC_V; 193 | P_Out(Flag_C) <= ADC_C; 194 | Q_t := ADC_Q; 195 | when "0101" | "1101" => 196 | -- LDA 197 | when "0110" => 198 | -- CMP 199 | P_Out(Flag_C) <= SBC_C; 200 | when "0111" => 201 | -- SBC 202 | P_Out(Flag_V) <= SBC_V; 203 | P_Out(Flag_C) <= SBC_C; 204 | Q_t := SBC_Q; 205 | when "1000" => 206 | -- ASL 207 | Q_t := BusA(6 downto 0) & "0"; 208 | P_Out(Flag_C) <= BusA(7); 209 | when "1001" => 210 | -- ROL 211 | Q_t := BusA(6 downto 0) & P_In(Flag_C); 212 | P_Out(Flag_C) <= BusA(7); 213 | when "1010" => 214 | -- LSR 215 | Q_t := "0" & BusA(7 downto 1); 216 | P_Out(Flag_C) <= BusA(0); 217 | when "1011" => 218 | -- ROR 219 | Q_t := P_In(Flag_C) & BusA(7 downto 1); 220 | P_Out(Flag_C) <= BusA(0); 221 | when "1100" => 222 | -- BIT 223 | P_Out(Flag_V) <= BusB(6); 224 | when "1110" => 225 | -- DEC 226 | Q_t := std_logic_vector(unsigned(BusA) - 1); 227 | when "1111" => 228 | -- INC 229 | Q_t := std_logic_vector(unsigned(BusA) + 1); 230 | when others => 231 | end case; 232 | 233 | case Op(3 downto 0) is 234 | when "0011" => 235 | P_Out(Flag_N) <= ADC_N; 236 | P_Out(Flag_Z) <= ADC_Z; 237 | when "0110" | "0111" => 238 | P_Out(Flag_N) <= SBC_N; 239 | P_Out(Flag_Z) <= SBC_Z; 240 | when "0100" => 241 | when "1100" => 242 | P_Out(Flag_N) <= BusB(7); 243 | if (BusA and BusB) = "00000000" then 244 | P_Out(Flag_Z) <= '1'; 245 | else 246 | P_Out(Flag_Z) <= '0'; 247 | end if; 248 | when others => 249 | P_Out(Flag_N) <= Q_t(7); 250 | if Q_t = "00000000" then 251 | P_Out(Flag_Z) <= '1'; 252 | else 253 | P_Out(Flag_Z) <= '0'; 254 | end if; 255 | end case; 256 | 257 | Q <= Q_t; 258 | end process; 259 | 260 | end; 261 | -------------------------------------------------------------------------------- /NES_2A03/T65_Pack.vhd: -------------------------------------------------------------------------------- 1 | -- **** 2 | -- T65(b) core. In an effort to merge and maintain bug fixes .... 3 | -- 4 | -- 5 | -- Ver 300 Bugfixes by ehenciak added 6 | -- MikeJ March 2005 7 | -- Latest version from www.fpgaarcade.com (original www.opencores.org) 8 | -- 9 | -- **** 10 | -- 11 | -- 65xx compatible microprocessor core 12 | -- 13 | -- Version : 0246 14 | -- 15 | -- Copyright (c) 2002 Daniel Wallner (jesus@opencores.org) 16 | -- 17 | -- All rights reserved 18 | -- 19 | -- Redistribution and use in source and synthezised forms, with or without 20 | -- modification, are permitted provided that the following conditions are met: 21 | -- 22 | -- Redistributions of source code must retain the above copyright notice, 23 | -- this list of conditions and the following disclaimer. 24 | -- 25 | -- Redistributions in synthesized form must reproduce the above copyright 26 | -- notice, this list of conditions and the following disclaimer in the 27 | -- documentation and/or other materials provided with the distribution. 28 | -- 29 | -- Neither the name of the author nor the names of other contributors may 30 | -- be used to endorse or promote products derived from this software without 31 | -- specific prior written permission. 32 | -- 33 | -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 34 | -- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 35 | -- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 36 | -- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 37 | -- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 38 | -- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | -- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | -- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 41 | -- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 42 | -- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 43 | -- POSSIBILITY OF SUCH DAMAGE. 44 | -- 45 | -- Please report bugs to the author, but before you do so, please 46 | -- make sure that this is not a derivative work and that 47 | -- you have the latest version of this file. 48 | -- 49 | -- The latest version of this file can be found at: 50 | -- http://www.opencores.org/cvsweb.shtml/t65/ 51 | -- 52 | -- Limitations : 53 | -- 54 | -- File history : 55 | -- 56 | 57 | library IEEE; 58 | use IEEE.std_logic_1164.all; 59 | 60 | package T65_Pack is 61 | --flag enumeration 62 | constant Flag_C : integer := 0; 63 | constant Flag_Z : integer := 1; 64 | constant Flag_I : integer := 2; 65 | constant Flag_D : integer := 3; 66 | constant Flag_B : integer := 4; 67 | constant Flag_1 : integer := 5; 68 | constant Flag_V : integer := 6; 69 | constant Flag_N : integer := 7; 70 | 71 | component T65_MCode 72 | port( 73 | Mode : in std_logic_vector(1 downto 0); -- "00" => 6502, "01" => 65C02, "10" => 65816 74 | IR : in std_logic_vector(7 downto 0); -- instruction opcode 75 | MCycle : in std_logic_vector(2 downto 0); -- number of executed cycles for current instruction 76 | P : in std_logic_vector(7 downto 0); -- processor status 77 | LCycle : out std_logic_vector(2 downto 0); -- number of cycles instruction requires on original 6502 78 | ALU_Op : out std_logic_vector(3 downto 0); -- specifies ALU operation to perform 79 | Set_BusA_To : out std_logic_vector(2 downto 0); -- DI,A,X,Y,S,P 80 | Set_Addr_To : out std_logic_vector(1 downto 0); -- PC Adder,S,AD,BA 81 | Write_Data : out std_logic_vector(2 downto 0); -- DL,A,X,Y,S,P,PCL,PCH 82 | Jump : out std_logic_vector(1 downto 0); -- PC,++,DIDL,Rel 83 | BAAdd : out std_logic_vector(1 downto 0); -- None,DB Inc,BA Add,BA Adj 84 | BreakAtNA : out std_logic; 85 | ADAdd : out std_logic; 86 | AddY : out std_logic; 87 | PCAdd : out std_logic; 88 | Inc_S : out std_logic; 89 | Dec_S : out std_logic; 90 | LDA : out std_logic; 91 | LDP : out std_logic; 92 | LDX : out std_logic; 93 | LDY : out std_logic; 94 | LDS : out std_logic; 95 | LDDI : out std_logic; 96 | LDALU : out std_logic; 97 | LDAD : out std_logic; 98 | LDBAL : out std_logic; 99 | LDBAH : out std_logic; 100 | SaveP : out std_logic; 101 | Write : out std_logic 102 | ); 103 | end component; 104 | 105 | component T65_ALU 106 | port( 107 | Mode : in std_logic_vector(1 downto 0); -- "00" => 6502, "01" => 65C02, "10" => 65C816 108 | Op : in std_logic_vector(3 downto 0); 109 | BusA : in std_logic_vector(7 downto 0); 110 | BusB : in std_logic_vector(7 downto 0); 111 | P_In : in std_logic_vector(7 downto 0); 112 | P_Out : out std_logic_vector(7 downto 0); 113 | Q : out std_logic_vector(7 downto 0) 114 | ); 115 | end component; 116 | 117 | end; 118 | -------------------------------------------------------------------------------- /NES_Mainboard.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | use work.NES_Pack.all; 5 | 6 | entity NES_Mainboard is 7 | port ( 8 | clk : in std_logic; -- approximation to NES mainboard clock 21.47727 MHz 9 | rstn : in std_logic; 10 | 11 | -- Framebuffer output 12 | FB_Address : out unsigned(15 downto 0); -- linear index in 256x240 pixel framebuffer 13 | FB_Color : out std_logic_vector(5 downto 0); -- Palette index of current color 14 | FB_DE : out std_logic; -- True when PPU is writing to the framebuffer 15 | 16 | APU_PCM : out std_logic_vector(7 downto 0); 17 | 18 | -- Controller input 19 | Controller_Strobe : out std_logic; -- Set shift register in controller with current buttons 20 | Controller1_Clock : out std_logic; -- Shift register by one bit on falling edge 21 | Controller2_Clock : out std_logic; -- Shift register by one bit on falling edge 22 | 23 | Controller1_Data0_N : in std_logic; -- Shift register highest bit 24 | Controller1_Data1_N : in std_logic; -- Not connected in standard controllers 25 | Controller1_Data2_N : in std_logic; -- Not connected in standard controllers 26 | 27 | Controller2_Data0_N : in std_logic; -- Shift register highest bit 28 | Controller2_Data1_N : in std_logic; -- Not connected in standard controllers 29 | Controller2_Data2_N : in std_logic; -- Not connected in standard controllers 30 | 31 | PinWithoutSemicolon : out std_logic 32 | ); 33 | end NES_Mainboard; 34 | 35 | architecture arch of NES_Mainboard is 36 | 37 | -- CPU Interrupt connected to PPU 38 | signal VBlank_NMI_n : std_logic; 39 | 40 | -- CPU Bus 41 | 42 | signal CPU_Address : std_logic_vector(15 downto 0); 43 | signal CPU_Data : std_logic_vector(7 downto 0); 44 | signal CPU_RW : std_logic; 45 | signal CPU_PHI2 : std_logic; -- High when CPU_Data is valid 46 | 47 | -- CPU Bus slave ports 48 | 49 | signal PPU_CPU_Data : std_logic_vector(7 downto 0); 50 | signal PRG_Data : std_logic_vector(7 downto 0); 51 | signal CPU_PPU_CS_n : std_logic; 52 | 53 | -- ChipSelect lines 54 | signal CPU_PRG_CS_n : std_logic; 55 | 56 | -- PPU Memory Bus 57 | signal CHR_Address : unsigned(13 downto 0); 58 | signal CHR_Data : std_logic_vector(7 downto 0); 59 | 60 | signal CPU_Controller1Read_N : std_logic; 61 | signal CPU_Controller2Read_N : std_logic; 62 | 63 | begin 64 | 65 | -- Access PPU in memory range 0x2000 to 0x2007 66 | CPU_PPU_CS_n <= '0' when CPU_Address(15 downto 3) = "0010000000000" and CPU_PHI2 = '1' else '1'; 67 | 68 | -- Access Program ROM in range 0x8000 to 0xFFFF 69 | CPU_PRG_CS_n <= '0' when CPU_Address(15) = '1' and CPU_PHI2 = '1' else '1'; 70 | 71 | -- Output inverting buffer is controlled by read access on reg $4016, e.g. Controller1Read_N 72 | Controller1_Clock <= not CPU_PHI2 when CPU_Controller1Read_N = '0' else '1'; 73 | 74 | CPU_BUS_MUX : process(CPU_RW, PPU_CPU_Data, PRG_Data, CPU_PPU_CS_n, CPU_PRG_CS_n, CPU_Address, CPU_Controller1Read_N, CPU_Controller2Read_N) 75 | begin 76 | if CPU_RW = '1' then 77 | if CPU_PPU_CS_n = '0' then 78 | CPU_Data <= PPU_CPU_Data; 79 | elsif CPU_PRG_CS_n = '0' then 80 | CPU_Data <= PRG_Data; 81 | elsif CPU_Controller1Read_N = '0' then 82 | CPU_Data <= "01000" & not Controller1_Data2_N & not Controller1_Data1_N & not Controller1_Data0_N; 83 | elsif CPU_Controller2Read_N = '0' then 84 | CPU_Data <= "01000" & not Controller2_Data2_N & not Controller2_Data1_N & not Controller2_Data0_N; 85 | else 86 | CPU_Data <= (others => 'Z'); 87 | end if; 88 | else 89 | CPU_Data <= (others => 'Z'); 90 | end if; 91 | end process; 92 | 93 | 94 | CPU: NES_2A03 95 | port map ( 96 | Global_Clk => clk, 97 | Reset_N => rstn, 98 | NMI_N => VBlank_NMI_n, 99 | IRQ_N => '1', 100 | 101 | Data => CPU_Data, 102 | Address => CPU_Address, 103 | RW_10 => CPU_RW, 104 | 105 | PHI2 => CPU_PHI2, 106 | 107 | CStrobe => Controller_Strobe, 108 | C1R_N => CPU_Controller1Read_N, 109 | C2R_N => CPU_Controller2Read_N, 110 | 111 | A_Rectangle => open, 112 | A_Combined => APU_PCM, 113 | 114 | W_4016_1 => open, 115 | W_4016_2 => open, 116 | 117 | AddOKDebug => open, 118 | ReadOKDebug => open, 119 | WriteOKDebug => open, 120 | SRAMChipSelect_NDebug => open, 121 | SRAMWriteEnable_NDebug => open, 122 | SRAMOutputEnable_NDebug => open, 123 | SRAMReading => open, 124 | SRAMWriting => open 125 | ); 126 | 127 | PPU : NES_2C02 128 | port map ( 129 | clk => clk, 130 | rstn => rstn, 131 | 132 | ChipSelect_n => CPU_PPU_CS_n, 133 | ReadWrite => CPU_RW, 134 | Address => CPU_Address(2 downto 0), 135 | Data_in => CPU_Data, 136 | Data_out => PPU_CPU_Data, 137 | 138 | CHR_Address => CHR_Address, 139 | CHR_Data => CHR_Data, 140 | 141 | VBlank_n => VBlank_NMI_n, 142 | 143 | FB_Address => FB_Address, 144 | FB_Color => FB_Color, 145 | FB_DE => FB_DE 146 | ); 147 | 148 | Cartridge : CartridgeROM 149 | port map ( 150 | clk => clk, 151 | rstn => rstn, 152 | PRG_Address => CPU_Address(14 downto 0), 153 | PRG_Data => PRG_Data, 154 | CHR_Address => CHR_Address, 155 | CHR_Data => CHR_Data 156 | ); 157 | 158 | end architecture; 159 | -------------------------------------------------------------------------------- /NES_Pack.vhd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Entity: NESPack 3 | -- Date:2011-10-25 4 | -- Author: jonathansieber 5 | -- 6 | -- Description ${cursor} 7 | -------------------------------------------------------------------------------- 8 | library ieee; 9 | use ieee.std_logic_1164.all; 10 | use ieee.numeric_std.all; 11 | 12 | package NES_Pack is 13 | 14 | component NES_2A03 is 15 | port ( 16 | Global_Clk : in std_logic; --input clock signal from NES mobo crystal 17 | Reset_N : in std_logic; --External Reset 18 | NMI_N : in std_logic; 19 | IRQ_N : in std_logic; 20 | Data : inout std_logic_vector(7 downto 0); 21 | Address : buffer std_logic_vector(15 downto 0); 22 | RW_10 : buffer std_logic; --low if writing, high if reading 23 | PHI2 : out std_logic; --Clock Divider Output 24 | 25 | --Controller Outputs 26 | CStrobe : out std_logic; --Controller Strobe Signal 27 | C1R_N : out std_logic; --low when reading controller 1 28 | C2R_N : out std_logic; --low when reading controller 2 29 | 30 | --Audio Outputs 31 | A_Rectangle : out std_logic; --Rectangle Wave Output (Mixed) 32 | A_Combined : out std_logic_vector(7 downto 0); --Triangle, Noise, And PCM (DPCM) Output 33 | 34 | --The following three signals represent the status of an internal register 35 | -- used in accessing the expansion port 36 | W_4016_1 : out std_logic; 37 | W_4016_2 : out std_logic; 38 | 39 | --Debugging 40 | --LCycle : out std_logic_vector(2 downto 0); 41 | --MCycle : out std_logic_vector(2 downto 0); 42 | --InitialReset : out std_logic; 43 | AddOKDebug : out std_logic; 44 | ReadOKDebug : out std_logic; 45 | WriteOKDebug : out std_logic; 46 | SRAMChipSelect_NDebug : out std_logic; 47 | SRAMWriteEnable_NDebug : out std_logic; 48 | SRAMOutputEnable_NDebug :out std_logic; 49 | SRAMReading : out std_logic; 50 | SRAMWriting : out std_logic 51 | ); 52 | end component; 53 | 54 | component NES_2C02 is 55 | port ( 56 | clk : in std_logic; -- input clock, 5,37 MHz. 57 | rstn : in std_logic; 58 | 59 | -- CPU Bus 60 | ChipSelect_n : in std_logic; 61 | ReadWrite : in std_logic; -- Write to PPU on 0 62 | Address : in std_logic_vector(2 downto 0); 63 | Data_in : in std_logic_vector(7 downto 0); 64 | Data_out : out std_logic_vector(7 downto 0); 65 | 66 | -- VRAM/VROM bus 67 | CHR_Address : out unsigned(13 downto 0); 68 | CHR_Data : in std_logic_vector(7 downto 0); 69 | 70 | VBlank_n : out std_logic; -- Tied to the CPU's Non-Maskable Interrupt (NMI) 71 | 72 | -- Framebuffer output 73 | FB_Address : out unsigned(15 downto 0); -- linear index in 256x240 pixel framebuffer 74 | FB_Color : out std_logic_vector(5 downto 0); -- Palette index of current color 75 | FB_DE : out std_logic -- True when PPU is writing to the framebuffer 76 | ); 77 | end component; 78 | 79 | component CartridgeROM is 80 | port ( 81 | clk : in std_logic; -- input clock, xx MHz. 82 | rstn : in std_logic; 83 | 84 | PRG_Address : in std_logic_vector(14 downto 0); 85 | PRG_Data : out std_logic_vector(7 downto 0); 86 | 87 | CHR_Address : in unsigned(13 downto 0); 88 | CHR_Data : out std_logic_vector(7 downto 0) 89 | ); 90 | end component; 91 | 92 | component HDMIController is 93 | port ( 94 | CLK : in std_logic; 95 | RSTN : in std_logic; 96 | 97 | CLK_25 : in std_logic; 98 | 99 | HDMIHSync : OUT std_logic; 100 | HDMIVSync : OUT std_logic; 101 | HDMIDE : OUT std_logic; 102 | HDMICLKP : OUT std_logic; 103 | HDMICLKN : OUT std_logic; 104 | HDMID : OUT std_logic_vector(11 downto 0); 105 | HDMISCL : INOUT std_logic; 106 | HDMISDA : INOUT std_logic; 107 | HDMIRSTN : OUT std_logic; 108 | 109 | FB_Address : out std_logic_vector(15 downto 0); 110 | FB_Data : in std_logic_vector(5 downto 0) 111 | 112 | ); 113 | end component; 114 | end; -------------------------------------------------------------------------------- /PPU/Loopy_Scrolling.vhd: -------------------------------------------------------------------------------- 1 | -- Scrolling and PPU Address registers 2 | 3 | -- The name Loopy refers to the nickname of the first person in the NES Community to accurately document this registers 4 | -- To save chip are, the PPU reused the counter registers used for Background Tiles as address register for CPU accesses. 5 | -- This can be utilized to change the scrolling mid-screen, which is used by many games, for example to implement a status bar. 6 | 7 | -- For more information visit http://wiki.nesdev.com/w/index.php/The_skinny_on_NES_scrolling 8 | 9 | 10 | library ieee; 11 | use ieee.std_logic_1164.all; 12 | use ieee.numeric_std.all; 13 | 14 | entity Loopy_Scrolling is 15 | port( 16 | clk : in std_logic; 17 | CE : in std_logic; 18 | rst : in std_logic; 19 | 20 | Loopy_t : in unsigned(14 downto 0); -- Temporary register driven by CPU Port 21 | 22 | Loopy_v : out unsigned(14 downto 0); -- Counting register 23 | 24 | -- Control lines 25 | 26 | ResetXCounter : in std_logic; -- Load initial scroll values 27 | ResetYCounter : in std_logic; 28 | 29 | IncXScroll : in std_logic; -- Increment coarse X, at every 8 pixels during scanline rendering 30 | IncYScroll : in std_logic; -- Increment Y, at scanline dot #256 31 | 32 | LoadAddress : in std_logic; -- Copy t to v, for CPU VRAM Access 33 | IncAddress : in std_logic; -- Increment Address during $2007 VRAM access 34 | AddressStep : in std_logic -- Increment in steps of 32 instead of 1 35 | ); 36 | end entity Loopy_Scrolling; 37 | 38 | architecture RTL of Loopy_Scrolling is 39 | 40 | -- counter layout of the loopy registers 41 | -- 42 | -- yyy NN YYYYY XXXXX 43 | -- ||| || ||||| +++++-- coarse X scroll 44 | -- ||| || +++++-------- coarse Y scroll 45 | -- ||| ++-------------- nametable select 46 | -- +++----------------- fine Y scroll 47 | 48 | signal Loopy : unsigned(14 downto 0) := (others => '0'); -- Internal output register 49 | 50 | alias FineYScroll : unsigned(2 downto 0) is Loopy(14 downto 12); 51 | alias YNametable : std_logic is Loopy(11); 52 | alias XNametable : std_logic is Loopy(10); 53 | alias CoarseYScroll : unsigned(4 downto 0) is Loopy(9 downto 5); 54 | alias CoarseXScroll : unsigned(4 downto 0) is Loopy(4 downto 0); 55 | 56 | begin 57 | 58 | Loopy_v <= Loopy; 59 | 60 | process(clk, rst) is 61 | variable sum : unsigned(7 downto 0); 62 | begin 63 | if rst = '0' then 64 | Loopy <= (others => '0'); 65 | elsif rising_edge(clk) and CE = '1' then 66 | if LoadAddress = '1' then 67 | Loopy <= Loopy_t; 68 | elsif ResetXCounter = '1' then 69 | XNametable <= Loopy_t(10); 70 | CoarseXScroll <= Loopy_t(4 downto 0); 71 | elsif ResetYCounter = '1' then 72 | FineYScroll <= Loopy_t(14 downto 12); 73 | YNametable <= Loopy_t(11); 74 | CoarseYScroll <= Loopy_t(9 downto 5); 75 | elsif IncXScroll = '1' then 76 | sum := "00" & ((XNameTable & CoarseXScroll) + 1); 77 | XNameTable <= sum(5); 78 | CoarseXScroll <= sum (4 downto 0); 79 | elsif IncYScroll = '1' then 80 | if CoarseYScroll = 29 and FineYScroll = 31 then 81 | -- Coarse Y acts as a divide-by-30 counter 82 | YNametable <= not YNametable; 83 | CoarseYScroll <= "00000"; 84 | FineYScroll <= "000"; 85 | else 86 | sum := (CoarseYScroll & FineYScroll) + 1; 87 | CoarseYScroll <= sum(7 downto 3); 88 | FineYScroll <= sum(2 downto 0); 89 | end if; 90 | elsif IncAddress = '1' and AddressStep = '0' then 91 | Loopy <= Loopy + 1; 92 | elsif IncAddress = '1' and AddressStep = '1' then 93 | Loopy <= Loopy + 32; 94 | end if; 95 | end if; 96 | end process; 97 | 98 | 99 | end architecture RTL; 100 | -------------------------------------------------------------------------------- /PPU/PPU.vhd: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | 4 | library ieee; 5 | use ieee.std_logic_1164.all; 6 | use ieee.numeric_std.all; 7 | use work.PPU_Pack.all; 8 | 9 | entity NES_2C02 is 10 | port( 11 | clk : in std_logic; -- approximation to NES mainboard clock 21.47727 12 | rstn : in std_logic; 13 | 14 | -- CPU Bus 15 | ChipSelect_n : in std_logic; 16 | ReadWrite : in std_logic; -- Write to PPU on 0 17 | Address : in std_logic_vector(2 downto 0); 18 | Data_in : in std_logic_vector(7 downto 0); 19 | Data_out : out std_logic_vector(7 downto 0); 20 | 21 | -- VRAM/VROM bus 22 | CHR_Address : out unsigned(13 downto 0); 23 | CHR_Data : in std_logic_vector(7 downto 0); 24 | 25 | VBlank_n : out std_logic; -- Tied to the CPU's Non-Maskable Interrupt (NMI) 26 | 27 | -- Framebuffer output 28 | FB_Address : out unsigned(15 downto 0); -- linear index in 256x240 pixel framebuffer 29 | FB_Color : out std_logic_vector(5 downto 0); -- Palette index of current color 30 | FB_DE : out std_logic -- True when PPU is writing to the framebuffer 31 | 32 | ); 33 | end NES_2C02; 34 | 35 | architecture arch of NES_2C02 is 36 | 37 | -- Internal H/V Counters 38 | signal HPOS : integer range 0 to 340 := 0; 39 | signal VPOS : integer range 0 to 261 := 0; 40 | 41 | -- Nes Clock / 4 Clock divider 42 | signal CE_cnt : unsigned(1 downto 0) := "00"; 43 | signal CE : std_logic; 44 | 45 | -- Specifies the scrolling offset within an 8 bit block 46 | signal Fine_HPOS : integer range 0 to 7; 47 | 48 | 49 | signal VBlankFlag : std_logic := '0'; 50 | signal HitSpriteFlag : std_logic := '0'; 51 | 52 | signal Status_2000 : std_logic_vector(7 downto 0) := "00000000"; 53 | signal Status_2001 : std_logic_vector(7 downto 0) := "00000010"; 54 | 55 | signal Data_in_d : std_logic_vector(7 downto 0) := "00000000"; 56 | signal CPUPortDir : std_logic; 57 | 58 | signal ChipSelect_delay : std_logic; 59 | 60 | 61 | -- Internal Muxer outputs for read access on the PPU Memory Bus 62 | signal PPU_Address : unsigned(13 downto 0); 63 | signal PPU_Data : std_logic_vector(7 downto 0); 64 | 65 | -- This signal does not exist in the original hardware, which relies on bus capacitance 66 | signal CPUVRAM_WriteData : std_logic_vector(7 downto 0); 67 | signal CPUVRAM_Read : std_logic; 68 | signal CPUVRAM_Write : std_logic; 69 | 70 | type VRAMType is array (2047 downto 0) of std_logic_vector(7 downto 0); 71 | type PaletteRAMType is array (31 downto 0) of std_logic_vector(5 downto 0); 72 | 73 | signal VRAM : VRAMType := (others => "00000000"); 74 | signal VRAM_Data : std_logic_vector(7 downto 0); 75 | 76 | signal PaletteRAM : PaletteRAMType := ( 77 | 0 => "000000", 1 => "000001", 2 => "000010", 3 => "000011", 78 | 4 => "000100", 5 => "000101", 6 => "000110", 7 => "000111", 79 | 8 => "001000", 9 => "001001", 10 => "001010", 11 => "001011", 80 | 12 => "001100", 13 => "001101", 14 => "001110", 15 => "001111", 81 | 16 => "010000", 17 => "010001", 18 => "010010", 19 => "010011", 82 | 20 => "010100", 21 => "010101", 22 => "010110", 23 => "010111", 83 | 24 => "011000", 25 => "011001", 26 => "011010", 27 => "011011", 84 | 28 => "011100", 29 => "011101", 30 => "011110", 31 => "011111" 85 | ); 86 | 87 | signal SpriteVRAM_Address : unsigned(13 downto 0); 88 | signal SpriteRAM_Address : unsigned(7 downto 0) := X"00"; 89 | signal SpriteRAM_Data_in : std_logic_vector(7 downto 0); 90 | signal SpriteRAM_Data_out : std_logic_vector(7 downto 0); 91 | signal SpriteRAM_WriteEnable : std_logic; 92 | 93 | signal SpriteColor : unsigned(3 downto 0); 94 | signal SpriteForegroundPriority : std_logic; 95 | signal SpriteIsPrimary : std_logic; 96 | signal SpriteOverflowFlag : std_logic; 97 | 98 | signal TileVRAM_Address : unsigned(13 downto 0); 99 | 100 | signal TileColor : unsigned(3 downto 0); 101 | 102 | 103 | signal FineXScrolling : unsigned(2 downto 0) := "000"; 104 | signal EnableTileRendering : std_logic; 105 | 106 | signal Loopy_t : unsigned(14 downto 0) := (others => '0'); 107 | signal Loopy_v : unsigned(14 downto 0); 108 | signal ResetXCounter : std_logic; 109 | signal ResetYCounter : std_logic; 110 | signal IncXScroll : std_logic; 111 | signal IncYScroll : std_logic; 112 | signal LoadAddress : std_logic; 113 | signal IncAddress : std_logic; 114 | signal AddressStep : std_logic; 115 | 116 | begin 117 | CHR_Address <= PPU_Address; 118 | 119 | VBlank_n <= VBlankFlag nand Status_2000(7); -- Check on flag and VBlank Enable 120 | 121 | 122 | Fine_HPOS <= HPOS mod 8; 123 | 124 | CE <= '1' when CE_cnt = 0 else '0'; 125 | 126 | process(clk) 127 | begin 128 | if rising_edge(clk) then 129 | CE_cnt <= CE_cnt + 1; 130 | end if; 131 | end process; 132 | 133 | process(clk) 134 | begin 135 | if rising_edge(clk) and CE = '1' then 136 | if HPOS < 341 - 1 then 137 | HPOS <= HPOS + 1; 138 | else 139 | HPOS <= 0; 140 | if VPOS < 262 - 1 then 141 | VPOS <= VPOS + 1; 142 | else 143 | VPOS <= 0; 144 | end if; 145 | end if; 146 | end if; 147 | end process; 148 | 149 | process(clk) 150 | variable color : integer range 0 to 31; 151 | begin 152 | if rising_edge(clk) and CE = '1' then 153 | FB_DE <= '0'; 154 | if HPOS >= 0 and HPOS < 256 and VPOS < 240 then 155 | FB_DE <= '1'; 156 | FB_Address <= to_unsigned(HPOS + VPOS * 256, FB_Address'length); 157 | 158 | if (SpriteForegroundPriority = '0' or TileColor(1 downto 0) = "00") and SpriteColor(1 downto 0) /= "00" then 159 | color := to_integer(SpriteColor) + 16; 160 | elsif TileColor(1 downto 0) /= "00" then 161 | color := to_integer(TileColor); 162 | else 163 | color := 16; 164 | end if; 165 | 166 | FB_Color <= PaletteRAM(color); 167 | 168 | --if SpritesFound > 0 then 169 | -- if SpriteCache(0).x - HPOS < 8 then FB_Color <= "101111"; end if; 170 | --end if; 171 | 172 | 173 | if VPOS >= 230 then 174 | FB_Color <= PaletteRAM(HPOS / 8); 175 | end if; 176 | 177 | else 178 | FB_Color <= "000000"; 179 | end if; 180 | end if; 181 | end process; 182 | 183 | CPU_PORT : process(clk) 184 | variable PPUDATA_read : std_logic_vector(7 downto 0); 185 | begin 186 | if rising_edge(clk) then 187 | if CE = '1' and ChipSelect_N = '1' then 188 | if HPOS >= 0 and HPOS < 3 and VPOS = 240 then -- Start VBlank period 189 | VBlankFlag <= '1'; 190 | elsif HPOS >= 0 and HPOS < 3 and VPOS = 261 then -- End VBlank Period 191 | VBlankFlag <= '0'; 192 | HitSpriteFlag <= '0'; 193 | end if; 194 | 195 | -- Hack: Increment sprite RAM address after write here, to avoid off-by-one condition 196 | if SpriteRAM_WriteEnable = '1' then 197 | SpriteRAM_Address <= SpriteRAM_Address + 1; 198 | end if; 199 | 200 | CPUVRAM_Write <= '0'; 201 | CPUVRAM_Read <= '0'; 202 | SpriteRAM_WriteEnable <= '0'; 203 | 204 | IncAddress <= '0'; 205 | AddressStep <= '0'; 206 | LoadAddress <= '0'; 207 | 208 | if CPUVRAM_Read = '1' or CPUVRAM_Write = '1' then 209 | IncAddress <= '1'; 210 | AddressStep <= Status_2000(2); 211 | 212 | if CPUVRAM_Read = '1' then 213 | PPUDATA_read := PPU_Data; 214 | end if; 215 | end if; 216 | 217 | -- Check for Sprite 0 collision 218 | if TileColor(1 downto 0) /= "00" and SpriteColor(1 downto 0) /= "00" and SpriteIsPrimary = '1' then 219 | HitSpriteFlag <= '1'; 220 | end if; 221 | end if; 222 | 223 | ChipSelect_delay <= ChipSelect_n; 224 | Data_in_d <= Data_in; 225 | 226 | -- Do reads on low CS, and writes on rising edge 227 | if ChipSelect_n = '1' and ChipSelect_delay = '0' then 228 | if ReadWrite = '0' then 229 | if Address = "000" then 230 | --Status_2000 <= Data_in_d; 231 | Status_2000 <= Data_in_d(7 downto 2) & "00"; 232 | Loopy_t(11 downto 10) <= unsigned(Data_in_d(1 downto 0)); 233 | elsif Address = "001" then 234 | Status_2001 <= Data_in_d; 235 | elsif Address = "011" then 236 | SpriteRAM_Address <= unsigned(Data_in_d); 237 | elsif Address = "100" then 238 | SpriteRAM_Data_in <= Data_in_d; 239 | SpriteRAM_WriteEnable <= '1'; 240 | elsif Address = "101" then 241 | if CPUPortDir = '0' then 242 | FineXScrolling <= unsigned(Data_in_d(2 downto 0)); 243 | Loopy_t(4 downto 0) <= unsigned(Data_in_d(7 downto 3)); 244 | else 245 | Loopy_t(14 downto 12) <= unsigned(Data_in_d(2 downto 0)); 246 | Loopy_t(9 downto 5) <= unsigned(Data_in_d(7 downto 3)); 247 | end if; 248 | CPUPortDir <= not CPUPortDir; 249 | elsif Address = "110" then 250 | if CPUPortDir = '0' then 251 | Loopy_t(14 downto 8) <= "0" & unsigned(Data_in_d(5 downto 0)); 252 | else 253 | Loopy_t(7 downto 0) <= unsigned(Data_in_d); 254 | LoadAddress <= '1'; 255 | -- Loading to Loopy_v happens in Loopy_control process 256 | end if; 257 | CPUPortDir <= not CPUPortDir; 258 | elsif Address = "111" then 259 | CPUVRAM_Write <= '1'; 260 | CPUVRAM_WriteData <= Data_in_d; 261 | 262 | -- Palette RAM is not actual RAM, just directly accessed registers, so implement it here 263 | if Loopy_v(14 downto 8) = X"3F" then 264 | PaletteRAM(to_integer(Loopy_v(4 downto 0))) <= Data_in_d(5 downto 0); 265 | end if; 266 | end if; 267 | elsif Address = "010" then 268 | VBlankFlag <= '0'; -- Reset flag at the end of read period 269 | CPUPortDir <= '0'; -- Reset 16 bit register selector 270 | end if; 271 | elsif ChipSelect_delay = '1' and ChipSelect_n = '0' and ReadWrite = '1' then 272 | if Address = "000" then 273 | Data_out <= Status_2000; 274 | elsif Address = "001" then 275 | Data_out <= Status_2001; 276 | elsif Address = "010" then 277 | Data_out <= (6 => HitSpriteFlag, 7 => VBlankFlag, others => '0'); 278 | --Data_out <= (6 => HitSpriteFlag, 7 => '1', others => '0'); 279 | elsif Address = "100" then 280 | Data_out <= SpriteRAM_Data_out; 281 | SpriteRAM_Address <= SpriteRAM_Address + 1; 282 | elsif Address = "111" then 283 | Data_out <= PPUDATA_read; 284 | CPUVRAM_Read <= '1'; 285 | if Loopy_v(13 downto 8) = X"3F" then 286 | Data_out <= "00" & PaletteRAM(to_integer(Loopy_v(4 downto 0))); 287 | end if; 288 | else 289 | Data_out <= (others => 'X'); -- This should be a write only register 290 | end if; 291 | end if; 292 | end if; 293 | end process; 294 | 295 | PPU_ADDRESS_MUXER : process(CPUVRAM_Read, CPUVRAM_Write, Loopy_v, TileVRAM_Address, SpriteVRAM_Address, HPOS) 296 | begin 297 | if CPUVRAM_Read = '1' then 298 | PPU_Address <= Loopy_v(13 downto 0); 299 | elsif CPUVRAM_Write = '1' then 300 | PPU_Address <= Loopy_v(13 downto 0); 301 | elsif HPOS < 256 or HPOS >= 320 then 302 | PPU_Address <= TileVRAM_Address; 303 | else 304 | PPU_Address <= SpriteVRAM_Address; 305 | end if; 306 | end process; 307 | 308 | PPU_DATA_MUXER : process(PPU_Address, VRAM_Data, PaletteRAM, CHR_Data) 309 | begin 310 | -- The cartridge has tri-state access to the address lines A10/A11, 311 | -- so it can either provide additional 2k of SRAM, or tie them to 0 312 | -- to mirror the address range of the upper nametables to the lower ones 313 | 314 | -- Super Mario Brothers selects vertical mirroring (A11 tied down), 315 | -- so thats what we are doing here for now 316 | if PPU_Address(13 downto 12) = "10" then -- VRAM 317 | PPU_Data <= VRAM_Data; 318 | else 319 | -- Default to external PPU Data 320 | PPU_Data <= CHR_Data; 321 | end if; 322 | end process; 323 | 324 | -- The nametable VRAM was an extern SRAM IC in the NES, 325 | -- here it is implemented internally with BRAM 326 | 327 | INTERNAL_VRAM : process(clk) 328 | begin 329 | if rising_edge(clk) then 330 | if CE = '1' and CPUVRAM_Write = '1' and PPU_Address(13 downto 12) = "10" then 331 | VRAM(to_integer(PPU_Address(10 downto 0))) <= CPUVRAM_WriteData; 332 | end if; 333 | VRAM_Data <= VRAM(to_integer(PPU_Address(10 downto 0))); 334 | end if; 335 | end process; 336 | 337 | SPRITE_SEL : SpriteSelector 338 | port map( 339 | CLK => CLK, 340 | CE => CE, 341 | RSTN => RSTN, 342 | HPOS => HPOS, 343 | VPOS => VPOS, 344 | PatternTableAddressOffset => Status_2000(3), 345 | SpriteColor => SpriteColor, 346 | SpriteForegroundPriority => SpriteForegroundPriority, 347 | SpriteIsPrimary => SpriteIsPrimary, 348 | SpriteOverflowFlag => SpriteOverflowFlag, 349 | VRAM_Address => SpriteVRAM_Address, 350 | VRAM_Data => PPU_Data, 351 | SpriteRAM_Address => SpriteRAM_Address, 352 | SpriteRAM_Data_in => SpriteRAM_Data_in, 353 | SpriteRAM_Data_out => SpriteRAM_Data_out, 354 | SpriteRAM_WriteEnable => SpriteRAM_WriteEnable 355 | ); 356 | 357 | TILE_FETCHER : TileFetcher 358 | port map ( 359 | CLK => CLK, 360 | CE => CE, 361 | RSTN => RSTN, 362 | Loopy_v => Loopy_v, 363 | FineXScrolling => FineXScrolling, 364 | EnableRendering => EnableTileRendering, 365 | PatternTableAddressOffset => Status_2000(4), 366 | Fine_HPOS => Fine_HPOS, 367 | VRAM_Address => TileVRAM_Address, 368 | VRAM_Data => PPU_Data, 369 | TileColor => TileColor); 370 | 371 | 372 | 373 | 374 | 375 | Loopy_Scrolling_1 : Loopy_Scrolling 376 | port map ( 377 | clk => CLK, 378 | CE => CE, 379 | rst => RSTN, 380 | Loopy_t => Loopy_t, 381 | Loopy_v => Loopy_v, 382 | ResetXCounter => ResetXCounter, 383 | ResetYCounter => ResetYCounter, 384 | IncXScroll => IncXScroll, 385 | IncYScroll => IncYScroll, 386 | LoadAddress => LoadAddress, 387 | IncAddress => IncAddress, 388 | AddressStep => AddressStep); 389 | 390 | Loopy_control : process (HPOS, VPOS, Address, ReadWrite, ChipSelect_N, Status_2001) 391 | begin 392 | ResetXCounter <= '0'; 393 | ResetYCounter <= '0'; 394 | IncXScroll <= '0'; 395 | IncYScroll <= '0'; 396 | 397 | EnableTileRendering <= '0'; 398 | 399 | if Status_2001(3) = '1' then 400 | if VPOS = 261 then 401 | if HPOS >= 280 and HPOS < 304 then 402 | ResetYCounter <= '1'; 403 | end if; 404 | elsif VPOS < 240 then 405 | if (HPOS >= 320 and HPOS < 336) or (HPOS >= 0 and HPOS < 256) then 406 | EnableTileRendering <= '1'; 407 | if HPOS mod 8 = 7 then 408 | IncXScroll <= '1'; 409 | end if; 410 | elsif HPOS = 256 then 411 | IncYScroll <= '1'; 412 | elsif HPOS = 257 then 413 | ResetXCounter <= '1'; 414 | end if; 415 | end if; 416 | end if; 417 | end process Loopy_Control; 418 | 419 | 420 | end arch; 421 | -------------------------------------------------------------------------------- /PPU/PPU_Pack.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | 6 | package PPU_Pack is 7 | component TileFetcher 8 | port ( 9 | CLK : in std_logic; 10 | CE : in std_logic; 11 | RSTN : in std_logic; 12 | Loopy_v : in unsigned(14 downto 0); 13 | FineXScrolling : in unsigned(2 downto 0); 14 | EnableRendering : in std_logic; 15 | PatternTableAddressOffset : in std_logic; 16 | Fine_HPOS : in integer range 0 to 7; 17 | VRAM_Address : out unsigned(13 downto 0); 18 | VRAM_Data : in std_logic_vector(7 downto 0); 19 | TileColor : out unsigned(3 downto 0)); 20 | end component; 21 | 22 | component SpriteSelector is 23 | port ( 24 | CLK : in std_logic; 25 | CE : in std_logic; 26 | RSTN : in std_logic; 27 | 28 | HPOS : in integer range 0 to 340; 29 | VPOS : in integer range 0 to 261; 30 | 31 | PatternTableAddressOffset : in std_logic; 32 | 33 | SpriteColor : out unsigned(3 downto 0); 34 | SpriteForegroundPriority : out std_logic; 35 | SpriteIsPrimary : out std_logic; 36 | 37 | SpriteOverflowFlag : out std_logic; 38 | 39 | VRAM_Address : out unsigned(13 downto 0); 40 | VRAM_Data : in std_logic_vector(7 downto 0); 41 | 42 | SpriteRAM_Address : in unsigned(7 downto 0); 43 | SpriteRAM_Data_in : in std_logic_vector(7 downto 0); 44 | SpriteRAM_Data_out : out std_logic_vector(7 downto 0); 45 | SpriteRAM_WriteEnable : in std_logic 46 | ); 47 | end component; 48 | 49 | component Loopy_Scrolling 50 | port ( 51 | clk : in std_logic; 52 | CE : in std_logic; 53 | rst : in std_logic; 54 | Loopy_t : in unsigned(14 downto 0); 55 | Loopy_v : out unsigned(14 downto 0); 56 | ResetXCounter : in std_logic; 57 | ResetYCounter : in std_logic; 58 | IncXScroll : in std_logic; 59 | IncYScroll : in std_logic; 60 | LoadAddress : in std_logic; 61 | IncAddress : in std_logic; 62 | AddressStep : in std_logic); 63 | end component; 64 | end package; 65 | -------------------------------------------------------------------------------- /PPU/SpriteSelector.vhd: -------------------------------------------------------------------------------- 1 | 2 | library ieee; 3 | use ieee.std_logic_1164.all; 4 | use ieee.numeric_std.all; 5 | 6 | entity SpriteSelector is 7 | port ( 8 | CLK : in std_logic; 9 | CE : in std_logic; 10 | RSTN : in std_logic; 11 | 12 | HPOS : in integer range 0 to 340; 13 | VPOS : in integer range 0 to 261; 14 | 15 | PatternTableAddressOffset : in std_logic; 16 | 17 | -- Selector output 18 | SpriteColor : out unsigned(3 downto 0); -- Palette index of sprite at current VPOS/HPOS pixel position 19 | SpriteForegroundPriority : out std_logic; -- When '0', Sprite is only drawn when background is transparent ("00" Color) 20 | SpriteIsPrimary : out std_logic; -- Is '1' when the current output results from object #0, used for collision detection flag 21 | 22 | SpriteOverflowFlag : out std_logic; -- When more than 8 Sprites are detected on a scanline, this flag is set until the next VBlank period 23 | 24 | VRAM_Address : out unsigned(13 downto 0) := (others => '0'); 25 | VRAM_Data : in std_logic_vector(7 downto 0); 26 | 27 | SpriteRAM_Address : in unsigned(7 downto 0); 28 | SpriteRAM_Data_in : in std_logic_vector(7 downto 0); 29 | SpriteRAM_Data_out : out std_logic_vector(7 downto 0); 30 | SpriteRAM_WriteEnable : in std_logic 31 | ); 32 | end SpriteSelector; 33 | 34 | 35 | architecture arch of SpriteSelector is 36 | 37 | -- Sprite RAM, also called (primary) OAM (Object Attribute Memory) is organized in 4 bytes per sprite, 38 | -- so 64 Sprites totalling 256 bytes can be described at a time. 39 | -- Memory layout of a Sprite Entry: 40 | -- RAM(SpriteNum * 4 + 0) = Y position, evaluated one scanline before the sprite is rendered 41 | -- RAM(SpriteNum * 4 + 1) = Tile Index 42 | -- RAM(SpriteNum * 4 + 2) = (7 => Y-Flip, 6 => X-Flip, 5 => BGPriority, (1 downto 0) => ColorPalette) 43 | -- RAM(SpriteNum * 4 + 3) = X position 44 | 45 | 46 | -- Each scanline, while memory access is reserved to the Background Tile Pipeline, the Sprite Selector 47 | -- selects up to 8 sprites from the OAM to be drawn in the next scanline and stores the necessary 48 | -- information in the "sprite temporary buffer". 49 | -- 50 | -- After this period, the Sprite Selector gains read access to the PPU Memory bus and reads the 51 | -- corresponding tile patterns and writes them to the "secondary" OAM. 52 | 53 | 54 | -- The TempLineBuffer stores the result of the sprite evaluation phase. The MEMFETCH process 55 | -- reads 2 bytes from the Pattern Table, optionally flips them, and writes them to the Secondary OAM 56 | -- along with the remaining attributes. 57 | 58 | type TempLineBufferEntry is record 59 | patternIndex : unsigned(7 downto 0); 60 | ydiff : unsigned(7 downto 0); 61 | x : unsigned(7 downto 0); 62 | palette : unsigned(1 downto 0); 63 | xflip : std_logic; 64 | foreground : std_logic; 65 | primary : std_logic; 66 | end record; 67 | 68 | constant TempLineBufferDefault : TempLineBufferEntry := ( 69 | patternIndex => X"FF", ydiff => X"FF", x => X"FF", 70 | palette => "11", others => '1' 71 | ); 72 | 73 | type TempLineBufferType is array(7 downto 0) of TempLineBufferEntry; 74 | signal TempLineBuffer : TempLineBufferType := (others => TempLineBufferDefault); 75 | 76 | 77 | -- This Datatype corresponds to the "secondary OAM" 78 | type LineBufferEntry is record 79 | x : unsigned(7 downto 0); 80 | pattern0 : unsigned(7 downto 0); 81 | pattern1 : unsigned(7 downto 0); 82 | palette : unsigned(1 downto 0); 83 | foreground : std_logic; 84 | primary : std_logic; 85 | end record; 86 | 87 | constant LineBufferDefault : LineBufferEntry := ( 88 | x => X"00", pattern0 => X"00", pattern1 => X"00", 89 | palette => "00", foreground => '0', primary => '0' 90 | ); 91 | 92 | type LineBufferType is array(7 downto 0) of LineBufferEntry; 93 | signal SpriteLineBuffer : LineBufferType := (others => LineBufferDefault); 94 | 95 | 96 | type SpriteRAMType is array(255 downto 0) of std_logic_vector(7 downto 0); 97 | signal SpriteRAM : SpriteRAMType := ( 98 | 0 => X"30", 1 => X"23", 2 => "11111111", 3 => X"30", 99 | 4 => X"30", 5 => X"33", 6 => "11111111", 7 => X"50", 100 | 8 => X"50", 9 => X"25", 10 => "11111111", 11 => X"50", 101 | 12 => X"70", 13 => X"55", 14 => "11111111", 16 => X"30", 102 | others => X"FF"); 103 | 104 | signal NumSpritesFound : integer range 0 to 8 := 0; 105 | 106 | -- reverse Function used to implement the X-Flip feature 107 | function reverse(p: unsigned) return unsigned is 108 | variable temp: unsigned(p'reverse_range); 109 | variable result: unsigned(p'range); 110 | begin 111 | for i in p'range loop 112 | temp(i) := p(i); 113 | end loop; 114 | result := temp; 115 | return result; 116 | end; 117 | 118 | begin 119 | 120 | SPRITE_RAM_PORT_A : process (clk) 121 | begin 122 | if rising_edge(clk) and CE = '1' then 123 | SpriteRAM_Data_out <= SpriteRAM(to_integer(SpriteRAM_Address)); 124 | 125 | if (SpriteRAM_WriteEnable = '1') then 126 | SpriteRAM(to_integer(SpriteRAM_Address)) <= SpriteRAM_Data_in; 127 | end if; 128 | end if; 129 | end process; 130 | 131 | -- The Line Buffer contains up to 8 sprites, select the first one with non-zero color 132 | PIXEL_MUX : process (SpriteLineBuffer, HPOS) 133 | variable sprite : LineBufferEntry; 134 | variable xpos : integer; 135 | variable patternColor : unsigned(1 downto 0); 136 | begin 137 | SpriteColor <= "0000"; 138 | SpriteForegroundPriority <= '1'; 139 | SpriteIsPrimary <= '0'; 140 | 141 | for i in 7 downto 0 loop -- Loop backwards to prioritize the first entry, as it is written last 142 | sprite := SpriteLineBuffer(i); 143 | xpos := to_integer(sprite.x); 144 | if sprite.x = 0 then 145 | patternColor := sprite.pattern1(0) & sprite.pattern0(0); 146 | 147 | if patternColor /= "00" then 148 | SpriteColor <= unsigned(sprite.palette & patternColor); 149 | SpriteForegroundPriority <= sprite.foreground; 150 | SpriteIsPrimary <= sprite.primary; 151 | end if; 152 | end if; 153 | end loop; 154 | end process; 155 | 156 | 157 | SPRITE_LOOKUP : process (clk, rstn) 158 | variable attributeByte : std_logic_vector(7 downto 0); 159 | variable CurrentSpriteIndex : integer; 160 | begin 161 | if rstn = '0' then 162 | --SpriteCache <= (others => (others => (others => '0'))); 163 | --CurrentSpriteIndex <= 0; 164 | elsif rising_edge(clk) and CE = '1' then 165 | 166 | if VPOS = 240 and HPOS = 0 then 167 | SpriteOverflowFlag <= '0'; 168 | end if; 169 | 170 | if HPOS = 340 then 171 | NumSpritesFound <= 0; 172 | TempLineBuffer <= (others => TempLineBufferDefault); 173 | CurrentSpriteIndex := 0; 174 | elsif HPOS < 256 and VPOS < 240 and NumSpritesFound < 8 then 175 | -- Sprite Lookup phase (8 out of 64 selection) 176 | 177 | CurrentSpriteIndex := HPOS / 4; 178 | 179 | case HPOS mod 4 is 180 | when 0 => 181 | TempLineBuffer(NumSpritesFound).ydiff <= VPOS - unsigned(SpriteRAM(CurrentSpriteIndex * 4)); 182 | when 1 => 183 | if TempLineBuffer(NumSpritesFound).ydiff < 8 then 184 | TempLineBuffer(NumSpritesFound).patternIndex <= unsigned(SpriteRAM(CurrentSpriteIndex * 4 + 1)); 185 | end if; 186 | when 2 => 187 | if TempLineBuffer(NumSpritesFound).ydiff < 8 then 188 | attributeByte := SpriteRAM(CurrentSpriteIndex * 4 + 2); 189 | TempLineBuffer(NumSpritesFound).palette <= unsigned(attributeByte(1 downto 0)); 190 | TempLineBuffer(NumSpritesFound).foreground <= attributeByte(5); 191 | TempLineBuffer(NumSpritesFound).xflip <= attributeByte(6); 192 | TempLineBuffer(NumSpritesFound).primary <= '0'; 193 | if CurrentSpriteIndex = 0 then 194 | TempLineBuffer(NumSpritesFound).primary <= '1'; 195 | end if; 196 | end if; 197 | when 3 => 198 | if TempLineBuffer(NumSpritesFound).ydiff < 8 then 199 | TempLineBuffer(NumSpritesFound).x <= unsigned(SpriteRAM(CurrentSpriteIndex * 4 + 3)); 200 | NumSpritesFound <= NumSpritesFound + 1; 201 | --CurrentSpriteIndex <= CurrentSpriteIndex + 1; 202 | end if; 203 | when others => 204 | end case; 205 | 206 | end if; 207 | end if; 208 | end process; 209 | 210 | 211 | SPRITE_MEMFETCH : process (clk) 212 | variable currentSprite : integer range 0 to 7; 213 | variable patternAddress : unsigned(13 downto 0); 214 | variable fetchedByte : unsigned(7 downto 0); 215 | begin 216 | if rising_edge(clk) and CE = '1' then 217 | if HPOS >= 0 and HPOS < 256 then 218 | for i in 7 downto 0 loop 219 | if SpriteLineBuffer(i).x > 0 then 220 | SpriteLineBuffer(i).x <= SpriteLineBuffer(i).x - 1; 221 | else 222 | SpriteLineBuffer(i).pattern0 <= SpriteLineBuffer(i).pattern0 srl 1; 223 | SpriteLineBuffer(i).pattern1 <= SpriteLineBuffer(i).pattern1 srl 1; 224 | end if; 225 | end loop; 226 | elsif HPOS >= 256 and HPOS < 288 then 227 | currentSprite := (HPOS - 256) / 4; 228 | patternAddress := "0" & PatternTableAddressOffset & 229 | TempLineBuffer(currentSprite).patternIndex & (TempLineBuffer(currentSprite).ydiff(3 downto 0)); 230 | 231 | if currentSprite < NumSpritesFound then 232 | case HPOS mod 4 is 233 | when 0 => 234 | VRAM_Address <= patternAddress; 235 | when 1 => 236 | fetchedByte := unsigned(VRAM_Data); 237 | if TempLineBuffer(currentSprite).xflip = '0' then 238 | fetchedByte := reverse(fetchedByte); 239 | end if; 240 | 241 | SpriteLineBuffer(currentSprite).pattern0 <= fetchedByte; 242 | 243 | VRAM_Address <= patternAddress + 8; 244 | when 2 => 245 | fetchedByte := unsigned(VRAM_Data); 246 | if TempLineBuffer(currentSprite).xflip = '0' then 247 | fetchedByte := reverse(fetchedByte); 248 | end if; 249 | 250 | SpriteLineBuffer(currentSprite).pattern1 <= fetchedByte; 251 | SpriteLineBuffer(currentSprite).x <= TempLineBuffer(currentSprite).x; 252 | SpriteLineBuffer(currentSprite).palette <= TempLineBuffer(currentSprite).palette; 253 | SpriteLineBuffer(currentSprite).primary <= TempLineBuffer(currentSprite).primary; 254 | SpriteLineBuffer(currentSprite).foreground <= TempLineBuffer(currentSprite).foreground; 255 | 256 | when others => 257 | end case; 258 | end if; 259 | 260 | end if; 261 | 262 | end if; 263 | end process; 264 | end arch; 265 | -------------------------------------------------------------------------------- /PPU/TileFetcher.vhd: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.all; 3 | 4 | use IEEE.NUMERIC_STD.all; 5 | 6 | entity TileFetcher is 7 | port( 8 | CLK : in std_logic; 9 | CE : in std_logic; 10 | RSTN : in std_logic; 11 | 12 | Loopy_v : in unsigned(14 downto 0); 13 | FineXScrolling : in unsigned(2 downto 0); 14 | 15 | EnableRendering : in std_logic; 16 | PatternTableAddressOffset : in std_logic; 17 | 18 | Fine_HPOS : integer range 0 to 7; 19 | 20 | VRAM_Address : out unsigned(13 downto 0); 21 | VRAM_Data : in std_logic_vector(7 downto 0); 22 | 23 | TileColor : out unsigned(3 downto 0) 24 | ); 25 | end TileFetcher; 26 | 27 | architecture Behavioral of TileFetcher is 28 | signal TilePattern0 : std_logic_vector(15 downto 0) := (others => '0'); 29 | signal TilePattern1 : std_logic_vector(15 downto 0) := (others => '0'); 30 | signal TileAttribute0 : unsigned(7 downto 0) := (others => '0'); 31 | signal TileAttribute1 : unsigned(7 downto 0) := (others => '0'); 32 | 33 | signal AttributeLatch0 : std_logic; 34 | signal AttributeLatch1 : std_logic; 35 | 36 | signal NextPattern0 : std_logic_vector(7 downto 0); 37 | signal NextPattern1 : std_logic_vector(7 downto 0); 38 | signal NextAttribute0 : std_logic; 39 | signal NextAttribute1 : std_logic; 40 | 41 | 42 | begin 43 | process(TileAttribute0, TileAttribute1, TilePattern0, TilePattern1) 44 | variable attr_offset, pattern_offset : integer; 45 | begin 46 | attr_offset := 7 - to_integer(FineXScrolling); 47 | pattern_offset := 15 - to_integer(FineXScrolling); 48 | 49 | TileColor <= TileAttribute1(attr_offset) & TileAttribute0(attr_offset) & 50 | TilePattern1(pattern_offset) & TilePattern0(pattern_offset); 51 | end process; 52 | 53 | SHIFT_REGS : process(CE, clk) 54 | begin 55 | if rising_edge(clk) and CE = '1' then 56 | if EnableRendering = '1' then 57 | TilePattern0 <= TilePattern0(14 downto 0) & "-"; 58 | TilePattern1 <= TilePattern1(14 downto 0) & "-"; 59 | 60 | if Fine_HPOS mod 8 = 0 then 61 | TilePattern0(7 downto 0) <= NextPattern0; 62 | TilePattern1(7 downto 0) <= NextPattern1; 63 | AttributeLatch0 <= NextAttribute0; 64 | AttributeLatch1 <= NextAttribute1; 65 | end if; 66 | 67 | TileAttribute0 <= TileAttribute0(6 downto 0) & AttributeLatch0; 68 | TileAttribute1 <= TileAttribute1(6 downto 0) & AttributeLatch1; 69 | end if; 70 | end if; 71 | end process; 72 | 73 | PREFETCH : process(CE, clk, rstn) 74 | -- This register is sometimes called PAR (Picture Address Register), 75 | -- in the 2C02 related patent document 76 | variable NextTileName : unsigned(7 downto 0); 77 | 78 | -- Helper variables 79 | variable attr_offset : unsigned(2 downto 0); 80 | variable aoi : integer range 0 to 7; 81 | begin 82 | if rstn = '0' then 83 | VRAM_Address <= (others => '0'); 84 | elsif rising_edge(clk) and CE = '1' then 85 | case Fine_HPOS mod 8 is 86 | when 0 => 87 | VRAM_Address <= "10" & Loopy_v(11 downto 0); 88 | when 1 => 89 | NextTileName := unsigned(VRAM_Data); 90 | when 2 => 91 | VRAM_Address <= "10" & Loopy_v(11 downto 10) & "1111" & Loopy_v(9 downto 7) & Loopy_v(4 downto 2); 92 | when 3 => 93 | attr_offset := Loopy_v(6) & Loopy_v(1) & "0"; 94 | aoi := to_integer(attr_offset); 95 | NextAttribute0 <= VRAM_Data(aoi); 96 | NextAttribute1 <= VRAM_Data(aoi + 1); 97 | when 4 => 98 | VRAM_Address <= "0" & PatternTableAddressOffset & NextTileName & "0" & Loopy_v(14 downto 12); 99 | when 5 => 100 | NextPattern0 <= VRAM_Data; 101 | when 6 => 102 | VRAM_Address <= "0" & PatternTableAddressOffset & NextTileName & "1" & Loopy_v(14 downto 12); 103 | when 7 => 104 | NextPattern1 <= VRAM_Data; 105 | -- TilePattern1 <= VRAM_Data & TilePattern1(15 downto 8); 106 | -- TilePattern0 <= NextTilePattern0 & TilePattern0(15 downto 8); 107 | -- TileAttribute <= NextTileAttribute; 108 | when others => 109 | end case; 110 | end if; 111 | end process; 112 | 113 | end Behavioral; 114 | 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NESFPGA 2 | ======= 3 | 4 | This project implements a simplified version of the 5 | Nintendo Entertainment System on an FPGA, that can execute Super Mario Bros. 6 | 7 | Getting Started 8 | --------------- 9 | 10 | I used the Digilent Genesys FPGA board, which is currently the only board this 11 | project works on. 12 | Sadly, you will need a version of Xilinx ISE software to synthesize this project. 13 | I used version 14.5, the free Webpack Edition should be fine. 14 | 15 | Create a new project, and first include the top module Genesys_NES.vhd, 16 | to see what other files are missing. Don't forget the constraints file nes_top.ucf. 17 | Then add every .vhd and .v file, except the TestBenches, and CartridgeROM*.vhd 18 | 19 | ### Cartridge 20 | 21 | CartridgeROM.vhd reads the ROM data from .dat files in the directory roms/. 22 | The default filenames are 'roms/smb_chr.dat' and 'roms/smb_prg.dat'. 23 | These files are already shipped with the project, but you can generate them 24 | with tools/romconv.py. Beware, this project only supports 32 KiB ROMs. 25 | 26 | XST should support the ROM inferrence style in CartridgeROM.vhd, but this is rather slow. 27 | For faster synthesis, use CartridgeROM_Coregen.vhd and include 28 | the appropriate .ngc files from the Coregen directory. 29 | This directory also includes the Coregen project files, and romconv.py can be 30 | modified to generate the necessary .coe files from .nes ROMs. 31 | 32 | 33 | ### MyHDL Part 34 | 35 | Since the APU is programmed in MyHDL, it needs to be converted to VHDL or Verilog 36 | code for synthesis. apu_convert.py does this for you, but you will need to install 37 | MyHDL (at least version 0.7). 38 | Alternatively, for synthesis you can just use the pregenerated file APU/APU_Main.v. 39 | 40 | 41 | Directory Overview 42 | ------------------ 43 | 44 | 45 | NES_2A03 - The modified CPU from Dan Leach's NES-On-a-FPGA project 46 | PPU - Implementation of the NES 2C02 47 | APU - MyHDL code and testbench for the APU 48 | 49 | TestBenches - Various VHDL based testbenches 50 | tools - Tools for generating ROM helper files, and framebuffer viewer 51 | roms - ROM files in .nes and converted form 52 | 53 | HDMI - Chrontel CH7301C interface from xps_tft 54 | ac97 - The 3rd party AC97 module with my wrapper 55 | 56 | synaesthesia - First attempt at a ISE-independent build system... Ignore for now 57 | 58 | 59 | Flashing the FPGA board 60 | ----------------------- 61 | 62 | To persistently save the bistream on the FPGA boards flash RAM, you 63 | need to use Xilinx iMPact. These are the settings i used to generate 64 | the MCS file: 65 | 66 | - Parallel BPI Single Boot 67 | - Virtex 5 / 32M 68 | - 16 bit width 69 | - 28F256P30 (not 512, the genesys schematic is lying) 70 | 71 | 72 | Testbenches 73 | ----------- 74 | 75 | ### NES_Framebuffer_TB.vhd 76 | 77 | This is the testbench for simulating everything down from NES_Mainboard. 78 | But beware, you will need a fast and expensive simulator for this to be useful. 79 | 80 | It writes the framebuffer data to fbdump_top.out. 81 | The FBView tool in tools/fbview can be used to view this file. 82 | It includes a build script, but you will need a working gcc and the SDL 83 | library to compile it. 84 | 85 | The testbench also includes a primitive way for simulating controller pad inputs 86 | in the process CONTROLLER_INPUT. It uses 1 for not pressed and 0 for pressed. 87 | From left to right, the button mapping is "Right, Left, Down, Up, Start, Select, B, A" 88 | 89 | ### apu_tb.py 90 | 91 | Unlike the other testbenches, this is found in the APU directory. 92 | To use it, you will need Py65. Use these commands to get 93 | and configure python to find it: 94 | 95 | $ cd APU 96 | $ git clone https://github.com/mnaberez/py65 97 | $ export PYTHONPATH=py65 98 | 99 | To start simulating the first song of the SMB nsf, use this command: 100 | 101 | $ python apu_tb.py smb.nsf 0 102 | 103 | It will write the file smb-0.wav to the output directory. 104 | 105 | I recommend you to acquire a recent version of PyPy, and use it instead 106 | of standard CPython, as it speeds up the simulation by orders of magnitude 107 | -------------------------------------------------------------------------------- /TestBenches/DummySound_TB.vhd: -------------------------------------------------------------------------------- 1 | LIBRARY ieee; 2 | USE ieee.std_logic_1164.ALL; 3 | USE ieee.numeric_std.ALL; 4 | 5 | use STD.textio.ALL; 6 | 7 | ENTITY DummySound_TB IS 8 | END DummySound_TB; 9 | 10 | ARCHITECTURE behavior OF DummySound_TB IS 11 | 12 | COMPONENT DummySound 13 | PORT( 14 | CLK : IN std_logic; 15 | CE : IN std_logic; 16 | RSTN : IN std_logic; 17 | 18 | PCM_4BIT_OUT : out signed(3 downto 0) 19 | 20 | 21 | -- VRAM_Address : OUT unsigned(13 downto 0); 22 | -- VRAM_Data : IN std_logic_vector(7 downto 0); 23 | ); 24 | END COMPONENT; 25 | 26 | 27 | --Outputs 28 | signal DUMMY_PCM : unsigned(3 downto 0) := X"0"; 29 | 30 | -- Clock period definitions 31 | constant AC97_CLK_period : time := 20.833333333333332 us; -- 48 kHz 32 | constant CLK_period : time := 46.560848137510206 ns; -- 21.477272 MhZ 33 | 34 | constant APU_clkdiv : integer := 2 * 12; -- Half CPU Clock 35 | 36 | signal AC97_CLK : std_logic := '0'; 37 | 38 | signal CLK : std_logic := '0'; 39 | signal APU_CE : std_logic := '0'; 40 | signal RSTN : std_logic := '0'; 41 | 42 | signal APU_CE_cnt : integer := 0; 43 | 44 | BEGIN 45 | 46 | 47 | APU_CE <= '1' when APU_CE_cnt = 0 else '0'; 48 | 49 | CLK <= not CLK after CLK_period; 50 | AC97_CLK <= not AC97_CLK after AC97_CLK_period; 51 | RSTN <= '1' after 100 ns; 52 | 53 | process (clk) 54 | begin 55 | if rising_edge(clk) then 56 | if APU_CE_cnt = APU_clkdiv - 1 then 57 | APU_CE_cnt <= 0; 58 | else 59 | APU_CE_cnt <= APU_CE_cnt + 1; 60 | end if; 61 | end if; 62 | end process; 63 | 64 | -- uut: APU_Pulse PORT MAP ( 65 | -- CLK => CLK, 66 | -- CE => CE, 67 | -- RSTN => RSTN 68 | -- ); 69 | -- 70 | 71 | APU_PULSE : process(CLK) 72 | variable note : unsigned(11 downto 0) := X"0FD"; 73 | variable sequencer : std_logic_vector(7 downto 0) := "11110000"; 74 | variable timer : integer := 0; 75 | begin 76 | if rising_edge(CLK) and APU_CE = '1' then 77 | if timer = 0 then 78 | timer := to_integer(note); 79 | sequencer := sequencer(6 downto 0) & sequencer(7); 80 | DUMMY_PCM <= X"0"; 81 | if sequencer(0) = '1' then DUMMY_PCM <= X"F"; end if; 82 | else 83 | timer := timer - 1; 84 | end if; 85 | end if; 86 | end process; 87 | 88 | 89 | WRITE_OUTPUT_proc : process(AC97_CLK) 90 | type AudioFileType is file of character; 91 | file outfile : AudioFileType open WRITE_MODE is "audio.dump"; 92 | variable num_samples : integer := 0; 93 | begin 94 | if rising_edge(AC97_CLK) then 95 | write(outfile, character'val(to_integer(DUMMY_PCM & "0000"))); 96 | -- write(outfile, character'val(num_samples)); 97 | 98 | num_samples := num_samples + 1; 99 | 100 | assert num_samples mod 48000 /= 0 report "Wrote 1000 ms" severity note; 101 | 102 | 103 | end if; 104 | end process; 105 | 106 | END; 107 | -------------------------------------------------------------------------------- /TestBenches/NES_Framebuffer_TB.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | use STD.textio.all; 5 | 6 | entity NES_Framebuffer_TB is 7 | end entity; 8 | 9 | architecture arch of NES_Framebuffer_TB is 10 | 11 | component NES_Mainboard is 12 | port ( 13 | clk : in std_logic; -- approximation to NES mainboard clock 21.47727 14 | rstn : in std_logic; 15 | 16 | -- Framebuffer output 17 | FB_Address : out unsigned(15 downto 0); -- linear index in 256x240 pixel framebuffer 18 | FB_Color : out std_logic_vector(5 downto 0); -- Palette index of current color 19 | FB_DE : out std_logic; -- True when PPU is writing to the framebuffer 20 | 21 | -- Controller input 22 | Controller_Strobe : out std_logic; -- Set shift register in controller with current buttons 23 | Controller1_Clock : out std_logic; -- Shift register by one bit on falling edge 24 | Controller2_Clock : out std_logic; -- Shift register by one bit on falling edge 25 | 26 | Controller1_Data0_N : in std_logic; -- Shift register highest bit 27 | Controller1_Data1_N : in std_logic; -- Not connected in standard controllers 28 | Controller1_Data2_N : in std_logic; -- Not connected in standard controllers 29 | 30 | Controller2_Data0_N : in std_logic; -- Shift register highest bit 31 | Controller2_Data1_N : in std_logic; -- Not connected in standard controllers 32 | Controller2_Data2_N : in std_logic; -- Not connected in standard controllers 33 | 34 | PinWithoutSemicolon : out std_logic 35 | ); 36 | end component; 37 | 38 | signal NES_Clock : std_logic; 39 | signal NES_Reset : std_logic; 40 | 41 | signal FB_Address : unsigned(15 downto 0); 42 | signal FB_Color : std_logic_vector(5 downto 0); 43 | signal FB_DE : std_logic; 44 | 45 | signal Controller_Strobe : std_logic; 46 | signal Controller1_Clock : std_logic; 47 | signal Controller2_Clock : std_logic; 48 | 49 | signal Controller1_ShiftRegister : std_logic_vector(7 downto 0); 50 | 51 | constant fb_size : integer := 340 * 261 - 1; 52 | type fb_ram_type is array(fb_size downto 0) of std_logic_vector(5 downto 0); 53 | type FramebufferFileType is file of fb_ram_type; 54 | 55 | signal fb_ram : fb_ram_type := (others => "101010"); 56 | 57 | signal FrameCount : integer := 0; 58 | 59 | constant CLK_period : time := 1 us / 21.47727; 60 | 61 | begin 62 | 63 | uut : NES_Mainboard port map ( 64 | clk => NES_Clock, 65 | rstn => NES_Reset, 66 | 67 | FB_Address => FB_Address, 68 | FB_Color => FB_Color, 69 | FB_DE => FB_DE, 70 | 71 | Controller_Strobe => Controller_Strobe, 72 | Controller1_Clock => Controller1_Clock, 73 | Controller2_Clock => Controller2_Clock, 74 | 75 | Controller1_Data0_N => Controller1_ShiftRegister(0), 76 | Controller1_Data1_N => '0', 77 | Controller1_Data2_N => '0', 78 | Controller2_Data0_N => '0', 79 | Controller2_Data1_N => '0', 80 | Controller2_Data2_N => '0', 81 | 82 | PinWithoutSemicolon => open 83 | ); 84 | 85 | 86 | -- Clock process definitions 87 | CLK_process :process 88 | begin 89 | NES_Clock <= '0'; 90 | wait for CLK_period/2; 91 | NES_Clock <= '1'; 92 | wait for CLK_period/2; 93 | end process; 94 | 95 | 96 | -- Stimulus process 97 | stim_proc: process 98 | begin 99 | NES_Reset <= '0'; 100 | wait for 1 ms; 101 | NES_Reset <= '1'; 102 | wait; 103 | end process; 104 | 105 | CONTROLLER_INPUT: process (Controller_Strobe, Controller1_Clock) 106 | begin 107 | if falling_edge(Controller_Strobe) then 108 | case FrameCount is 109 | when 10 => 110 | -- Controller1_ShiftRegister <= "00000000"; 111 | Controller1_ShiftRegister <= "11111011"; 112 | when 13 => 113 | -- Controller1_ShiftRegister <= "00000000"; 114 | Controller1_ShiftRegister <= "11111011"; 115 | when 16 => 116 | -- Controller1_ShiftRegister <= "00000000"; 117 | Controller1_ShiftRegister <= "11111110"; 118 | when 35 => 119 | Controller1_ShiftRegister <= "11111110"; 120 | when 52 => 121 | Controller1_ShiftRegister <= "11111110"; 122 | when 70 => 123 | Controller1_ShiftRegister <= "11111110"; 124 | when others => 125 | Controller1_ShiftRegister <= "11111111"; 126 | end case; 127 | elsif Controller_Strobe = '0' and rising_edge(Controller1_Clock) then 128 | Controller1_ShiftRegister <= "1" & Controller1_ShiftRegister(7 downto 1); 129 | end if; 130 | end process; 131 | 132 | FB_WRITE_proc : process (NES_Clock) 133 | variable v, h : integer; 134 | begin 135 | if rising_edge(Nes_Clock) then 136 | if FB_DE = '1' then 137 | -- fb_ram stores the full 340x261 range, but the PPU only outputs 138 | -- the visible range 256x240. For this reason, we recompute the address 139 | -- here: 140 | 141 | v := to_integer(FB_Address / 256); 142 | h := to_integer(FB_Address mod 256) + 42; 143 | 144 | fb_ram(v * 340 + h) <= FB_Color; 145 | end if; 146 | end if; 147 | end process; 148 | 149 | FB_DUMP_proc : process (FB_Address, FB_DE) 150 | file my_output : FramebufferFileType open WRITE_MODE is "fbdump_top.out"; 151 | variable my_line : LINE; 152 | variable my_output_line : LINE; 153 | begin 154 | if rising_edge(FB_DE) and FB_Address = X"0000" then 155 | write(my_line, string'("writing file")); 156 | writeline(output, my_line); 157 | write(my_output, fb_ram); 158 | FrameCount <= FrameCount + 1; 159 | end if; 160 | end process; 161 | 162 | end architecture arch; -------------------------------------------------------------------------------- /TestBenches/PPU_tb.vhd: -------------------------------------------------------------------------------- 1 | 2 | LIBRARY ieee; 3 | USE ieee.std_logic_1164.ALL; 4 | USE ieee.numeric_std.ALL; 5 | 6 | 7 | use IEEE.std_logic_textio.all; 8 | use STD.textio.all; 9 | 10 | ENTITY PPU_tb IS 11 | END PPU_tb; 12 | 13 | ARCHITECTURE behavior OF PPU_tb IS 14 | 15 | component NES_2C02 is 16 | port ( 17 | clk : in std_logic; -- input clock, 5,37 MHz. 18 | rstn : in std_logic; 19 | 20 | -- CPU Bus 21 | ChipSelect_n : in std_logic; 22 | ReadWrite : in std_logic; -- Write to PPU on 0 23 | Address : in std_logic_vector(2 downto 0); 24 | Data_in : in std_logic_vector(7 downto 0); 25 | Data_out : out std_logic_vector(7 downto 0); 26 | 27 | -- VRAM/VROM bus 28 | CHR_Address : out unsigned(13 downto 0); 29 | CHR_Data : in std_logic_vector(7 downto 0); 30 | 31 | VBlank_n : out std_logic; -- Tied to the CPU's Non-Maskable Interrupt (NMI) 32 | 33 | -- Framebuffer output 34 | FB_Address : out std_logic_vector(15 downto 0); -- linear index in 256x240 pixel framebuffer 35 | FB_Color : out std_logic_vector(5 downto 0); -- Palette index of current color 36 | FB_DE : out std_logic -- True when PPU is writing to the framebuffer 37 | ); 38 | end component; 39 | 40 | 41 | component CartridgeROM is 42 | port ( 43 | clk : in std_logic; -- input clock, xx MHz. 44 | rstn : in std_logic; 45 | 46 | PRG_Address : in std_logic_vector(14 downto 0); 47 | PRG_Data : out std_logic_vector(7 downto 0); 48 | 49 | CHR_Address : in unsigned(13 downto 0); 50 | CHR_Data : out std_logic_vector(7 downto 0) 51 | ); 52 | end component; 53 | 54 | --Inputs 55 | signal clk : std_logic := '0'; 56 | signal rstn : std_logic := '0'; 57 | signal ChipSelect_n : std_logic := '0'; 58 | signal ReadWrite : std_logic := '0'; 59 | signal Address : std_logic_vector(2 downto 0) := (others => '0'); 60 | signal Data_in : std_logic_vector(7 downto 0) := (others => '0'); 61 | signal CHR_Data : std_logic_vector(7 downto 0) := (others => '0'); 62 | 63 | --Outputs 64 | signal Data_out : std_logic_vector(7 downto 0); 65 | signal CHR_Address : unsigned(13 downto 0); 66 | signal VBlank_n : std_logic; 67 | signal FB_Address : std_logic_vector(15 downto 0); 68 | signal FB_Color : std_logic_vector(5 downto 0); 69 | signal FB_DE : std_logic; 70 | 71 | type fb_ram_type is array(65535 downto 0) of std_logic_vector(5 downto 0); 72 | 73 | signal fb_ram : fb_ram_type; 74 | 75 | -- Clock period definitions 76 | constant clk_period : time := 10 ns; 77 | 78 | type FramebufferFileType is file of fb_ram_type; 79 | 80 | BEGIN 81 | 82 | -- Instantiate the Unit Under Test (UUT) 83 | uut: NES_2C02 PORT MAP ( 84 | clk => clk, 85 | rstn => rstn, 86 | ChipSelect_n => ChipSelect_n, 87 | ReadWrite => ReadWrite, 88 | Address => Address, 89 | Data_in => Data_in, 90 | Data_out => Data_out, 91 | CHR_Address => CHR_Address, 92 | CHR_Data => CHR_Data, 93 | VBlank_n => VBlank_n, 94 | FB_Address => FB_Address, 95 | FB_Color => FB_Color, 96 | FB_DE => FB_DE 97 | ); 98 | 99 | mem: CartridgeROM 100 | port map ( 101 | clk => clk, 102 | rstn => rstn, 103 | PRG_Address => (others => '0'), 104 | PRG_Data => open, 105 | CHR_Address => CHR_Address, 106 | CHR_Data => CHR_Data 107 | ); 108 | 109 | process (clk) 110 | begin 111 | if rising_edge(clk) then 112 | if FB_DE = '1' then 113 | fb_ram(to_integer(unsigned(FB_Address))) <= FB_Color; 114 | end if; 115 | end if; 116 | end process; 117 | 118 | FB_DUMP: process (VBlank_n) 119 | file my_output : FramebufferFileType open WRITE_MODE is "file_io.out"; 120 | -- above declaration should be in architecture declarations for multiple 121 | variable my_line : LINE; 122 | variable my_output_line : LINE; 123 | begin 124 | if falling_edge(VBlank_n) then 125 | write(my_line, string'("writing file")); 126 | writeline(output, my_line); 127 | write(my_output, fb_ram); 128 | -- writeline(my_output, my_output_line); 129 | end if; 130 | end process; 131 | 132 | -- Clock process definitions 133 | clk_process :process 134 | begin 135 | clk <= '0'; 136 | wait for clk_period/2; 137 | clk <= '1'; 138 | wait for clk_period/2; 139 | end process; 140 | 141 | 142 | -- Stimulus process 143 | stim_proc: process 144 | begin 145 | rstn <= '0'; 146 | wait for 100 ns; 147 | rstn <= '1'; 148 | 149 | wait for clk_period*10; 150 | 151 | -- Enable VBlank Interrupt in Register $2000 152 | Address <= "000"; 153 | Data_in <= "10000000"; 154 | ReadWrite <= '0'; 155 | 156 | wait for clk_period*3; 157 | 158 | ChipSelect_n <= '1'; 159 | 160 | wait for clk_period*3; 161 | ReadWrite <= '1'; 162 | wait; 163 | end process; 164 | 165 | END; 166 | -------------------------------------------------------------------------------- /TestBenches/SpriteSelector_TB.vhd: -------------------------------------------------------------------------------- 1 | LIBRARY ieee; 2 | USE ieee.std_logic_1164.ALL; 3 | USE ieee.numeric_std.ALL; 4 | 5 | use STD.textio.ALL; 6 | 7 | use work.NES_Pack.all; 8 | 9 | ENTITY SpriteSelector_TB IS 10 | END SpriteSelector_TB; 11 | 12 | ARCHITECTURE behavior OF SpriteSelector_TB IS 13 | 14 | COMPONENT SpriteSelector 15 | PORT( 16 | CLK : IN std_logic; 17 | CE : IN std_logic; 18 | RSTN : IN std_logic; 19 | HPOS : IN integer; 20 | VPOS : IN integer; 21 | PatternTableAddressOffset : IN std_logic; 22 | SpriteColor : OUT unsigned(3 downto 0); 23 | SpriteForegroundPriority : OUT std_logic; 24 | SpriteIsPrimary : OUT std_logic; 25 | SpriteOverflowFlag : OUT std_logic; 26 | VRAM_Address : OUT unsigned(13 downto 0); 27 | VRAM_Data : IN std_logic_vector(7 downto 0); 28 | SpriteRAM_Address : IN unsigned(7 downto 0); 29 | SpriteRAM_Data_in : IN std_logic_vector(7 downto 0); 30 | SpriteRAM_Data_out : OUT std_logic_vector(7 downto 0); 31 | SpriteRAM_WriteEnable : IN std_logic 32 | ); 33 | END COMPONENT; 34 | 35 | 36 | --Inputs 37 | signal PatternTableAddressOffset : std_logic := '0'; 38 | signal VRAM_Data : std_logic_vector(7 downto 0) := (others => '0'); 39 | signal SpriteRAM_Address : unsigned(7 downto 0) := (others => '0'); 40 | signal SpriteRAM_Data_in : std_logic_vector(7 downto 0) := (others => '0'); 41 | signal SpriteRAM_WriteEnable : std_logic := '0'; 42 | 43 | --Outputs 44 | signal SpriteColor : unsigned(3 downto 0); 45 | signal SpriteForegroundPriority : std_logic; 46 | signal SpriteIsPrimary : std_logic; 47 | signal SpriteOverflowFlag : std_logic; 48 | signal VRAM_Address : unsigned(13 downto 0); 49 | signal SpriteRAM_Data_out : std_logic_vector(7 downto 0); 50 | 51 | -- Clock period definitions 52 | constant CLK_period : time := 10 ns; 53 | 54 | constant fb_size : integer := 340 * 261 - 1; 55 | type fb_ram_type is array(fb_size downto 0) of std_logic_vector(5 downto 0); 56 | type FramebufferFileType is file of fb_ram_type; 57 | signal fb_ram : fb_ram_type := (others => "101010"); 58 | 59 | 60 | signal CLK : std_logic := '0'; 61 | signal CE : std_logic := '0'; 62 | signal RSTN : std_logic := '0'; 63 | 64 | signal HPOS : integer range -42 to 298; -- negative values mark the HBlank period 65 | signal VPOS : integer range 0 to 261; -- 240 to 261 is the VBlank period 66 | 67 | signal CE_cnt : integer := 0; 68 | 69 | BEGIN 70 | 71 | CE <= '1' when CE_cnt = 0 else '0'; 72 | 73 | CLK_proc : process 74 | begin 75 | CLK <= '0'; 76 | wait for CLK_period / 2; 77 | CLK <= '1'; 78 | wait for CLK_period / 2; 79 | end process; 80 | 81 | process (clk) 82 | begin 83 | if rising_edge(clk) then 84 | if CE_cnt = 3 then 85 | CE_cnt <= 0; 86 | else 87 | CE_cnt <= CE_cnt + 1; 88 | end if; 89 | end if; 90 | end process; 91 | 92 | -- Instantiate the Unit Under Test (UUT) 93 | uut: SpriteSelector PORT MAP ( 94 | CLK => CLK, 95 | CE => CE, 96 | RSTN => RSTN, 97 | HPOS => HPOS, 98 | VPOS => VPOS, 99 | PatternTableAddressOffset => PatternTableAddressOffset, 100 | SpriteColor => SpriteColor, 101 | SpriteForegroundPriority => SpriteForegroundPriority, 102 | SpriteIsPrimary => SpriteIsPrimary, 103 | SpriteOverflowFlag => SpriteOverflowFlag, 104 | VRAM_Address => VRAM_Address, 105 | VRAM_Data => VRAM_Data, 106 | SpriteRAM_Address => SpriteRAM_Address, 107 | SpriteRAM_Data_in => SpriteRAM_Data_in, 108 | SpriteRAM_Data_out => SpriteRAM_Data_out, 109 | SpriteRAM_WriteEnable => SpriteRAM_WriteEnable 110 | ); 111 | 112 | Cartridge : CartridgeROM 113 | port map ( 114 | clk => clk, 115 | rstn => rstn, 116 | PRG_Address => (others => '0'), 117 | PRG_Data => open, 118 | CHR_Address => VRAM_Address, 119 | CHR_Data => VRAM_Data 120 | ); 121 | 122 | process (clk) 123 | begin 124 | if rising_edge(clk) and CE = '1' then 125 | if RSTN = '0' then 126 | HPOS <= -42; 127 | VPOS <= 0; 128 | else 129 | if HPOS = 297 then 130 | HPOS <= -42; 131 | if VPOS = 260 then 132 | VPOS <= 0; 133 | else 134 | VPOS <= VPOS + 1; 135 | end if; 136 | else 137 | HPOS <= HPOS + 1; 138 | end if; 139 | end if; 140 | end if; 141 | end process; 142 | 143 | -- Stimulus process 144 | stim_proc: process 145 | begin 146 | SpriteRAM_Address <= (others => '0'); 147 | SpriteRAM_Data_in <= (others => '0'); 148 | SpriteRAM_WriteEnable <= '0'; 149 | 150 | RSTN <= '0'; 151 | wait for 100 ns; 152 | 153 | wait for CLK_period*10; 154 | 155 | 156 | RSTN <= '1'; 157 | 158 | SpriteRAM_WriteEnable <= '1'; 159 | 160 | for i in 0 to 127 loop 161 | SpriteRAM_Address <= to_unsigned(i, 8); 162 | 163 | case i mod 4 is 164 | when 0 => 165 | SpriteRAM_Data_in <= std_logic_vector(to_unsigned(10 + (i / 8) * 8, 8)); 166 | when 1 => 167 | SpriteRAM_Data_in <= std_logic_vector(to_unsigned(i / 4, 8)); 168 | -- SpriteRAM_Data_in <= X"00"; 169 | when 2 => 170 | SpriteRAM_Data_in <= "11111111"; 171 | when 3 => 172 | SpriteRAM_Data_in <= std_logic_vector(to_unsigned(30 + ((i / 4 + 1) mod 2) * 8 , 8)); 173 | when others => 174 | end case; 175 | 176 | -- Also write the primary sprite used by Super Mario Brothers for synchronization purposes 177 | if i = 0 then 178 | SpriteRAM_Data_in <= X"18"; 179 | elsif i = 1 then 180 | SpriteRAM_Data_in <= X"FF"; 181 | elsif i = 2 then 182 | SpriteRAM_Data_in <= X"23"; 183 | elsif i = 3 then 184 | SpriteRAM_Data_in <= X"58"; 185 | end if; 186 | 187 | wait for CLK_period * 4; 188 | end loop; 189 | SpriteRAM_WriteEnable <= '0'; 190 | 191 | wait; 192 | end process; 193 | 194 | FB_WRITE_proc : process (clk) 195 | variable fbaddr : integer; 196 | begin 197 | if rising_edge(clk) and CE = '1' then 198 | fbaddr := (HPOS + 42) + VPOS * 340; 199 | fb_ram(fbaddr) <= "00" & std_logic_vector(SpriteColor); 200 | 201 | -- Draw a border for the visible part of the screen 202 | if HPOS = -1 or HPOS = 256 or VPOS = 240 then 203 | fb_ram(fbaddr) <= "111010"; 204 | end if; 205 | end if; 206 | end process; 207 | 208 | FB_DUMP_proc : process (clk) 209 | file my_output : FramebufferFileType open WRITE_MODE is "spritetb.out"; 210 | variable my_line : LINE; 211 | variable my_output_line : LINE; 212 | begin 213 | if rising_edge(clk) and CE = '1' and HPOS = 297 and VPOS = 260 then 214 | write(my_line, string'("writing file")); 215 | writeline(output, my_line); 216 | write(my_output, fb_ram); 217 | end if; 218 | end process; 219 | 220 | END; 221 | -------------------------------------------------------------------------------- /TestBenches/nes_top_tb.vhd: -------------------------------------------------------------------------------- 1 | LIBRARY ieee; 2 | USE ieee.std_logic_1164.ALL; 3 | 4 | -- Uncomment the following library declaration if using 5 | -- arithmetic functions with Signed or Unsigned values 6 | --USE ieee.numeric_std.ALL; 7 | 8 | ENTITY nes_top_tb IS 9 | END nes_top_tb; 10 | 11 | ARCHITECTURE behavior OF nes_top_tb IS 12 | 13 | -- Component Declaration for the Unit Under Test (UUT) 14 | 15 | COMPONENT nes_top 16 | PORT( 17 | CLK : IN std_logic; 18 | RSTN : IN std_logic; 19 | HDMIHSync : OUT std_logic; 20 | HDMIVSync : OUT std_logic; 21 | HDMIDE : OUT std_logic; 22 | HDMICLKP : OUT std_logic; 23 | HDMICLKN : OUT std_logic; 24 | HDMID : OUT std_logic_vector(11 downto 0); 25 | HDMISCL : INOUT std_logic; 26 | HDMISDA : INOUT std_logic; 27 | HDMIRSTN : OUT std_logic; 28 | -- LED : OUT std_logic_vector(0 to 7); 29 | BTN : IN std_logic_vector(0 to 6) 30 | ); 31 | END COMPONENT; 32 | 33 | 34 | --Inputs 35 | signal CLK : std_logic := '0'; 36 | signal RSTN : std_logic := '0'; 37 | signal BTN : std_logic_vector(0 to 6) := (others => '0'); 38 | 39 | --BiDirs 40 | signal HDMISCL : std_logic; 41 | signal HDMISDA : std_logic; 42 | 43 | --Outputs 44 | signal HDMIHSync : std_logic; 45 | signal HDMIVSync : std_logic; 46 | signal HDMIDE : std_logic; 47 | signal HDMICLKP : std_logic; 48 | signal HDMICLKN : std_logic; 49 | signal HDMID : std_logic_vector(11 downto 0); 50 | signal HDMIRSTN : std_logic; 51 | signal LED : std_logic_vector(0 to 7); 52 | 53 | -- Clock period definitions 54 | constant CLK_period : time := 10 ns; 55 | 56 | BEGIN 57 | 58 | -- Instantiate the Unit Under Test (UUT) 59 | uut: nes_top PORT MAP ( 60 | CLK => CLK, 61 | RSTN => RSTN, 62 | HDMIHSync => HDMIHSync, 63 | HDMIVSync => HDMIVSync, 64 | HDMIDE => HDMIDE, 65 | HDMICLKP => HDMICLKP, 66 | HDMICLKN => HDMICLKN, 67 | HDMID => HDMID, 68 | HDMISCL => HDMISCL, 69 | HDMISDA => HDMISDA, 70 | HDMIRSTN => HDMIRSTN, 71 | -- LED => LED, 72 | BTN => BTN 73 | ); 74 | 75 | -- Clock process definitions 76 | CLK_process :process 77 | begin 78 | CLK <= '0'; 79 | wait for CLK_period/2; 80 | CLK <= '1'; 81 | wait for CLK_period/2; 82 | end process; 83 | 84 | 85 | -- Stimulus process 86 | stim_proc: process 87 | begin 88 | RSTN <= '0'; 89 | -- hold reset state for 100 ns. 90 | wait for 100 ns; 91 | 92 | wait for CLK_period*10; 93 | 94 | RSTN <= '1'; 95 | 96 | -- insert stimulus here 97 | 98 | wait; 99 | end process; 100 | 101 | END; 102 | -------------------------------------------------------------------------------- /ac97/Talkthrough_Parts.vhd: -------------------------------------------------------------------------------- 1 | --//////////Package file for AC97 Talkthrough //////////////////////////-- 2 | -- *********************************************************************** 3 | -- FileName: Talkthrough_Parts.vhd 4 | -- FPGA: Xilinx Spartan 6 5 | -- IDE: Xilinx ISE 13.1 6 | -- 7 | -- HDL IS PROVIDED "AS IS." DIGI-KEY EXPRESSLY DISCLAIMS ANY 8 | -- WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT 9 | -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | -- PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL DIGI-KEY 11 | -- BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL 12 | -- DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR EQUIPMENT, COST OF 13 | -- PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS 14 | -- BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), 15 | -- ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER SIMILAR COSTS. 16 | -- DIGI-KEY ALSO DISCLAIMS ANY LIABILITY FOR PATENT OR COPYRIGHT 17 | -- INFRINGEMENT. 18 | -- 19 | -- Version History 20 | -- Version 1.0 12/06/2011 Tony Storey 21 | -- Initial Public Release 22 | 23 | 24 | library IEEE; 25 | use IEEE.STD_LOGIC_1164.all; 26 | use IEEE.numeric_std.all; 27 | 28 | 29 | 30 | package Spar6_Parts is 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --/////////////////////// AC97 Driver //////////////////////////////////-- 39 | 40 | 41 | 42 | component ac97 43 | port ( 44 | n_reset : in std_logic; 45 | clk : in std_logic; 46 | -- ac97 interface signals 47 | ac97_sdata_out : out std_logic; -- ac97 output to SDATA_IN 48 | ac97_sdata_in : in std_logic; -- ac97 input from SDATA_OUT 49 | ac97_sync : out std_logic; -- SYNC signal to ac97 50 | ac97_bitclk : in std_logic; -- 12.288 MHz clock from ac97 51 | ac97_n_reset : out std_logic; -- ac97 reset for initialization [active low] 52 | ac97_ready_sig : out std_logic; -- pulse for one cycle 53 | L_out : in std_logic_vector(17 downto 0); -- lt chan output from ADC 54 | R_out : in std_logic_vector(17 downto 0); -- rt chan output from ADC 55 | L_in : out std_logic_vector(17 downto 0); -- lt chan input to DAC 56 | R_in : out std_logic_vector(17 downto 0); -- rt chan input to DAC 57 | latching_cmd : in std_logic; 58 | cmd_addr : in std_logic_vector(7 downto 0); -- cmd address coming in from ac97cmd state machine 59 | cmd_data : in std_logic_vector(15 downto 0) -- cmd data coming in from ac97cmd state machine 60 | ); 61 | end component; 62 | 63 | 64 | --/////////////// STATE MACHINE TO CONFIGURE THE AC97 ///////////////////////////-- 65 | 66 | component ac97cmd 67 | port ( 68 | clk : in std_logic; 69 | ready : in std_logic; 70 | cmd_addr : out std_logic_vector(7 downto 0); 71 | cmd_data : out std_logic_vector(15 downto 0); 72 | latching_cmd : out std_logic; 73 | volume : in std_logic_vector(4 downto 0); 74 | source : in std_logic_vector(2 downto 0) 75 | ); 76 | end component; 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | end Spar6_Parts; 86 | 87 | 88 | 89 | 90 | ------------------------------------------------------------------------------------- 91 | --//////////////////////////// COMPONENTS /////////////////////////////////////////-- 92 | ------------------------------------------------------------------------------------- 93 | 94 | 95 | 96 | --/////////////////////// AC97 CONTROLLER //////////////////////////////////-- 97 | 98 | 99 | 100 | 101 | library ieee; 102 | use ieee.std_logic_1164.all; 103 | use ieee.numeric_std.all; 104 | 105 | 106 | entity ac97 is 107 | port ( 108 | n_reset : in std_logic; 109 | clk : in std_logic; 110 | -- ac97 interface signals 111 | ac97_sdata_out : out std_logic; -- ac97 output to SDATA_IN 112 | ac97_sdata_in : in std_logic; -- ac97 input from SDATA_OUT 113 | ac97_sync : out std_logic; -- SYNC signal to ac97 114 | ac97_bitclk : in std_logic; -- 12.288 MHz clock from ac97 115 | ac97_n_reset : out std_logic; -- ac97 reset for initialization [active low] 116 | ac97_ready_sig : out std_logic; -- pulse for one cycle 117 | L_out : in std_logic_vector(17 downto 0); -- lt chan output from ADC 118 | R_out : in std_logic_vector(17 downto 0); -- rt chan output from ADC 119 | L_in : out std_logic_vector(17 downto 0); -- lt chan input to DAC 120 | R_in : out std_logic_vector(17 downto 0); -- rt chan input to DAC 121 | latching_cmd : in std_logic; 122 | cmd_addr : in std_logic_vector(7 downto 0); -- cmd address coming in from ac97cmd state machine 123 | cmd_data : in std_logic_vector(15 downto 0)); -- cmd data coming in from ac97cmd state machine 124 | end ac97; 125 | 126 | 127 | architecture arch of ac97 is 128 | 129 | 130 | signal Q1, Q2 : std_logic; -- signals to deliver one cycle pulse at specified time 131 | signal bit_count : std_logic_vector(7 downto 0); -- counter for aligning slots 132 | signal rst_counter : integer range 0 to 4097; -- counter to set ac97_reset high for ac97 init 133 | 134 | signal latch_cmd_addr : std_logic_vector(19 downto 0); -- signals to latch in registers and commands 135 | signal latch_cmd_data : std_logic_vector(19 downto 0); 136 | 137 | signal latch_left_data : std_logic_vector(19 downto 0); 138 | signal latch_right_data : std_logic_vector(19 downto 0); 139 | 140 | signal left_data : std_logic_vector(19 downto 0); 141 | signal right_data : std_logic_vector(19 downto 0); 142 | signal left_in_data : std_logic_vector(19 downto 0); 143 | signal right_in_data : std_logic_vector(19 downto 0); 144 | 145 | 146 | begin 147 | 148 | -- concat for 18 bit usage can concat further for 16 bit use 149 | -- by using <& "0000"> and 150 | ------------------------------------------------------------------------------------- 151 | left_data <= L_out & "00"; 152 | right_data <= R_out & "00"; 153 | 154 | L_in <= left_in_data(19 downto 2); 155 | R_in <= right_in_data(19 downto 2); 156 | 157 | 158 | -- Delay for ac97_reset signal, clk = 100MHz 159 | -- delay 10ns * 37.89 us for active low reset on init 160 | -------------------------------------------------------------------------------------- 161 | process (clk, n_reset) 162 | begin 163 | if (clk'event and clk = '1') then 164 | if n_reset = '0' then 165 | rst_counter <= 0; 166 | ac97_n_reset <= '0'; 167 | elsif rst_counter = 3789 then 168 | ac97_n_reset <= '1'; 169 | rst_counter <= 0; 170 | else 171 | rst_counter <= rst_counter + 1; 172 | end if; 173 | end if; 174 | end process; 175 | 176 | 177 | -- This process generates a single clkcycle pulse 178 | -- to get configuration data from the ac97cmd FSM 179 | -- and lets the user know when a sample is ready 180 | --------------------------------------------------------------------------------------- 181 | process (clk, n_reset, bit_count) 182 | begin 183 | if(clk'event and clk = '1') then 184 | Q2 <= Q1; 185 | if(bit_count = "00000000") then 186 | Q1 <= '0'; 187 | Q2 <= '0'; 188 | elsif(bit_count >= "10000001") then 189 | Q1 <= '1'; 190 | end if; 191 | ac97_ready_sig <= Q1 and not Q2; 192 | end if; 193 | end process; 194 | 195 | 196 | -- ac97-link frame is 256 cycles 197 | -- [slot0], [slot1], [slot2], [slot3], [slot4], [slot5] ... [slot9], [slot10], [slot11], [slot12] 198 | -- slot 0 [tag phase] is 16 cycles slot1:12 are 20 cycles so 16 + 12 * 20 = 256 cycles 199 | -- ac97 link output frame [frame going out] 200 | --------------------------------------------------------------------------------------- 201 | process (n_reset, bit_count, ac97_bitclk) 202 | begin 203 | 204 | if(n_reset = '0') then -- active low reset 205 | bit_count <= "00000000"; -- starts bit count at 0 206 | elsif (ac97_bitclk'event and ac97_bitclk = '1') then -- rising edge of ac97_bitclk 207 | 208 | if bit_count = "11111111" then -- Generate sync signal for ac97 209 | ac97_sync <= '1'; -- at bitcnt = 255 210 | 211 | end if; 212 | 213 | if bit_count = "00001111" then -- at bitcnt = 15 214 | ac97_sync <= '0'; 215 | end if; 216 | 217 | 218 | -- At the end of each frame the user data is latched in 219 | if bit_count = "11111111" then 220 | latch_cmd_addr <= cmd_addr & "000000000000"; 221 | latch_cmd_data <= cmd_data & "0000"; 222 | latch_left_data <= left_data; 223 | latch_right_data <= right_data; 224 | end if; 225 | -- Tag phase 226 | if (bit_count >= "00000000") and (bit_count <= "00001111") then -- bit count 0 to 15 227 | -- Slot 0 : Tag Phase 228 | case bit_count is -- Can create input signals to verify on tag phase 229 | when "00000000" => ac97_sdata_out <= '1'; -- AC Link Interface ready 230 | when "00000001" => ac97_sdata_out <= latching_cmd; -- Vaild Status Adress or Slot request 231 | when "00000010" => ac97_sdata_out <= '1'; -- Valid Status data 232 | when "00000011" => ac97_sdata_out <= '1'; -- Valid PCM Data (Left ADC) 233 | when "00000100" => ac97_sdata_out <= '1'; -- Valid PCM Data (Right ADC) 234 | when others => ac97_sdata_out <= '0'; 235 | end case; 236 | -- starting at slot 1 add 20 bit counts each time 237 | elsif (bit_count >= "00010000") and (bit_count <= "00100011") then -- bit count 16 to 35 238 | -- Slot 1 : Command address (8-bits, left justified) 239 | 240 | if latching_cmd = '1' then 241 | ac97_sdata_out <= latch_cmd_addr(35 - to_integer(unsigned(bit_count))); 242 | else 243 | ac97_sdata_out <= '0'; 244 | end if; 245 | 246 | elsif (bit_count >= "00100100") and (bit_count <= "00110111") then -- bit count 36 to 55 247 | -- Slot 2 : Command data (16-bits, left justified) 248 | 249 | if latching_cmd = '1' then 250 | ac97_sdata_out <= latch_cmd_data(55 - to_integer(unsigned(bit_count))); 251 | else 252 | ac97_sdata_out <= '0'; 253 | end if; 254 | 255 | elsif ((bit_count >= "00111000") and (bit_count <= "01001011")) then -- bit count 56 to 75 256 | 257 | -- Slot 3 : left channel 258 | 259 | ac97_sdata_out <= latch_left_data(19); -- send out bits and rotate through 20 bit word 260 | 261 | latch_left_data <= latch_left_data(18 downto 0) & latch_left_data(19); 262 | 263 | elsif ((bit_count >= "01001100") and (bit_count <= "01011111")) then -- bit count 76 to 95 264 | -- Slot 4 : right channel 265 | 266 | ac97_sdata_out <= latch_right_data(95 - to_integer(unsigned(bit_count))); 267 | 268 | else 269 | ac97_sdata_out <= '0'; 270 | end if; 271 | 272 | -- incriment bit counter 273 | bit_count <= std_logic_vector(unsigned(bit_count) + 1); 274 | end if; 275 | end process; 276 | 277 | -- ac97 link input frame [frame coming in] 278 | --------------------------------------------------------------------------- 279 | process (ac97_bitclk) 280 | begin 281 | if (ac97_bitclk'event and ac97_bitclk = '0') then -- clock on falling edge of bitclk 282 | if (bit_count >= "00111001") and (bit_count <= "01001100") then -- from 115 to 76 283 | -- Slot 3 : left channel 284 | left_in_data <= left_in_data(18 downto 0) & ac97_sdata_in; -- concat incoming bits on end 285 | elsif (bit_count >= "01001101") and (bit_count <= "01100000") then -- from 77 to 96 286 | -- Slot 4 : right channel 287 | right_in_data <= right_in_data(18 downto 0) & ac97_sdata_in; -- concat incoming bits on end 288 | end if; 289 | end if; 290 | end process; 291 | 292 | end arch; 293 | 294 | 295 | --/////////////// STATE MACHINE TO CONFIGURE THE AC97 ///////////////////////////-- 296 | 297 | 298 | 299 | library ieee; 300 | use ieee.std_logic_1164.all; 301 | use ieee.numeric_std.all; 302 | 303 | -- one can add extra inputs and signals to control the input to LM4550 (ac97) register map below see volume and source for example 304 | entity ac97cmd is 305 | port ( 306 | clk : in std_logic; 307 | ac97_ready_sig : in std_logic; 308 | cmd_addr : out std_logic_vector(7 downto 0); 309 | cmd_data : out std_logic_vector(15 downto 0); 310 | latching_cmd : out std_logic; 311 | volume : in std_logic_vector(4 downto 0); -- input for encoder for volume control 0->31 312 | source : in std_logic_vector(2 downto 0)); -- 000 = Mic, 100=LineIn 313 | end ac97cmd; 314 | 315 | 316 | 317 | architecture arch of ac97cmd is 318 | signal cmd : std_logic_vector(23 downto 0); 319 | signal atten : std_logic_vector(4 downto 0); -- used to set atn in 04h ML4:0/MR4:0 320 | type state_type is (S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11); 321 | signal cur_state, next_state : state_type; 322 | begin 323 | -- parse command from data 324 | cmd_addr <= cmd(23 downto 16); 325 | cmd_data <= cmd(15 downto 0); 326 | atten <= std_logic_vector(31 - unsigned(volume)); -- convert vol to attenuation 327 | 328 | -- USED TO DETERMINE IF THE REGISTER ADDRESS IS VALID 329 | -- one can add more with select statments with output signals to do more error checking 330 | --------------------------------------------------------------------------------------------- 331 | with cmd(23 downto 16) select 332 | latching_cmd <= 333 | '1' when X"02" | X"04" | X"06" | X"0A" | X"0C" | X"0E" | X"10" | X"12" | X"14" | 334 | X"16" | X"18" | X"1A" | X"1C" | X"20" | X"22" | X"24" | X"26" | X"28" | 335 | X"2A" | X"2C" | X"32" | X"5A" | X"74" | X"7A" | X"7C" | X"7E" | X"80", 336 | '0' when others; 337 | 338 | 339 | -- go through states based on input pulses from ac97 ready signal 340 | ------------------------------------------------------------------------------------------ 341 | process(clk, next_state, cur_state) 342 | begin 343 | 344 | if(clk'event and clk = '1') then 345 | if ac97_ready_sig = '1' then 346 | cur_state <= next_state; 347 | end if; 348 | end if; 349 | end process; 350 | 351 | 352 | -- use state machine to configure controller 353 | -- refer to register map on LM4550 data sheet 354 | -- signals and input busses can be added to control 355 | -- the AC97 codec refer to the source and volume to see how 356 | -- first part is address, second part after _ is command 357 | -- states and input signals can be added for real time configuration of 358 | -- any ac97 register 359 | ------------------------------------------------------------------------------------------- 360 | process (next_state, cur_state, atten, source) 361 | begin 362 | 363 | case cur_state is 364 | when S0 => 365 | cmd <= X"02_8000"; -- master volume 0 0000->0dB atten, 1 1111->46.5dB atten 366 | next_state <= S2; 367 | when S1 => 368 | cmd <= X"04" & "000" & atten & "000" & atten; -- headphone volume 369 | next_state <= S4; 370 | when S2 => 371 | cmd <= X"0A_0000"; -- Set pc_beep volume 372 | next_state <= S11; 373 | when S3 => 374 | cmd <= X"0E_8048"; -- Mic Volume set to gain of +20db 375 | next_state <= S10; 376 | when S4 => 377 | cmd <= X"18_0808"; -- PCM volume 378 | next_state <= S6; 379 | when S5 => 380 | cmd <= X"1A" & "00000" & source & "00000" & source; -- Record select reg 000->Mic, 001->CD in l/r, 010->Video in l/r, 011->aux in l/r 381 | next_state <= S7; -- 100->line_in l/r, 101->stereo mix, 110->mono mix, 111->phone input 382 | when S6 => 383 | cmd <= X"1C_0F0F"; -- Record gain set to max (22.5dB gain) 384 | next_state <= S8; 385 | when S7 => 386 | cmd <= X"20_8000"; -- PCM out path 3D audio bypassed 387 | next_state <= S0; 388 | when S8 => 389 | cmd <= X"2C_BB80"; -- DAC rate 48 KHz, can be set to 1F40 = 8Khz, 2B11 = 11.025KHz, 3E80 = 16KHz, 390 | next_state <= S5; -- 5622 = 22.05KHz, AC44 = 44.1KHz, BB80 = 48KHz 391 | when S9 => 392 | cmd <= X"32_BB80"; -- ADC rate 48 KHz, can be set to 1F40 = 8Khz, 2B11 = 11.025KHz, 3E80 = 16KHz, 393 | next_state <= S3; -- 5622 = 22.05KHz, AC44 = 44.1KHz, BB80 = 48KHz 394 | when S10 => 395 | cmd <= X"80_0000"; 396 | next_state <= S9; 397 | when S11 => 398 | cmd <= X"80_0000"; 399 | next_state <= S1; 400 | end case; 401 | 402 | end process; 403 | 404 | end arch; 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | -------------------------------------------------------------------------------- /ac97/ac97_top.vhd: -------------------------------------------------------------------------------- 1 | -- Wrapper module for AC97 output from PCM lines 2 | -- 3 | 4 | library IEEE; 5 | use IEEE.STD_LOGIC_1164.ALL; 6 | 7 | -- Uncomment the following library declaration if using 8 | -- arithmetic functions with Signed or Unsigned values 9 | use IEEE.NUMERIC_STD.ALL; 10 | 11 | use work.Spar6_Parts.all; 12 | 13 | entity ac97_output is 14 | port ( 15 | CLK : in std_logic; -- 100 MHz Clock 16 | RSTN : in std_logic; 17 | 18 | -- AC97 Ports 19 | 20 | BITCLK : in STD_LOGIC; 21 | AUDSDI : in STD_LOGIC; 22 | AUDSDO : out STD_LOGIC; 23 | AUDSYNC : out STD_LOGIC; 24 | AUDRST : out STD_LOGIC; 25 | 26 | -- Asynchronous PCM Input 27 | PCM_in_left : in std_logic_vector(15 downto 0); 28 | PCM_in_right: in std_logic_vector(15 downto 0) 29 | ); 30 | end ac97_output; 31 | 32 | -- /////////////////////////////////////////////// 33 | -- // ATTENTION!!! // 34 | -- // - - - - - - - - - - - - - - - - - - - - - // 35 | -- // // 36 | -- // The following code is copy-pasted crap // 37 | -- // // 38 | -- // (In your own interest) // 39 | -- // DO NOT READ!!! // 40 | -- /////////////////////////////////////////////// 41 | 42 | architecture Behavioral of ac97_output is 43 | 44 | signal L_bus, R_bus, L_bus_out, R_bus_out : std_logic_vector(17 downto 0); 45 | signal cmd_addr : std_logic_vector(7 downto 0); 46 | signal cmd_data : std_logic_vector(15 downto 0); 47 | signal ready : std_logic; 48 | signal latching_cmd : std_logic; 49 | 50 | constant VOLUME : std_logic_vector(4 downto 0) := "01011"; 51 | constant SOURCE : std_logic_vector(2 downto 0) := "000"; 52 | 53 | begin 54 | -- INSTANTIATE BOTH THE MAIN DRIVER AND AC97 CHIP CONFIGURATION STATE-MACHINE 55 | ------------------------------------------------------------------------------- 56 | ac97_cont0 : entity work.ac97(arch) 57 | port map(n_reset => rstn, clk => clk, ac97_sdata_out => AUDSDO, ac97_sdata_in => AUDSDI, latching_cmd => latching_cmd , 58 | ac97_sync => AUDSYNC, ac97_bitclk => BITCLK, ac97_n_reset => AUDRST, ac97_ready_sig => ready, 59 | L_out => L_bus, R_out => R_bus, L_in => L_bus_out, R_in => R_bus_out, cmd_addr => cmd_addr, cmd_data => cmd_data); 60 | 61 | ac97cmd_cont0 : entity work.ac97cmd(arch) 62 | port map (clk => clk, ac97_ready_sig => ready, cmd_addr => cmd_addr, cmd_data => cmd_data, volume => VOLUME, 63 | source => SOURCE, latching_cmd => latching_cmd); 64 | 65 | -- Latch output back into input for Talkthrough testing 66 | -- this process can be replaced with a user component for signal processing 67 | ------------------------------------------------------------------------------- 68 | 69 | process ( clk, rstn, L_bus_out, R_bus_out, PCM_in_left, PCM_in_right) 70 | begin 71 | if (clk'event and clk = '1') then 72 | if rstn = '0' then 73 | L_bus <= (others => '0'); 74 | R_bus <= (others => '0'); 75 | elsif(ready = '1') then 76 | L_bus <= std_logic_vector(PCM_in_left) & "00"; 77 | R_bus <= std_logic_vector(PCM_in_right) & "00"; 78 | end if; 79 | end if; 80 | end process; 81 | 82 | end Behavioral; 83 | 84 | 85 | -------------------------------------------------------------------------------- /doc/implementation-issues.txt: -------------------------------------------------------------------------------- 1 | - Tristate vs. Multiplexer design 2 | - Tristate are not implemented in FPGA hardware nowadays, but for compatibility reasons, automatic 3 | transformation to mux busses are well-supported in synthesizer, allthough not recommended 4 | - Dan Leach's 6502 core was a tristate design 5 | - Bad coding leads to undefined simulation states and possible mismatches 6 | - Multiplexer design in new code 7 | - Some NES Components have an external discrete address decoder. This makes sense to combine with a muxer design, because the decoding logic lands in one hierarchical block 8 | - Input registers and APU are placed and decoded inside the CPU chip, but can equally be distinct devices on the memory bus. Only drawback is that the APU needs a connection to the internal CPU clock 9 | - Muxes imply a different design, because address decoding is moved outside of the device. 10 | -------------------------------------------------------------------------------- /doc/myhdl-findings.txt: -------------------------------------------------------------------------------- 1 | Conversion does not yet support class attribute signals 2 | 3 | http://referaat.cs.utwente.nl/conference/17/paper/7344/comparing-hardware-description-languages.pdf 4 | 5 | google scholar more on myhdl 6 | -------------------------------------------------------------------------------- /doc/teaching.doc: -------------------------------------------------------------------------------- 1 | possible goals to achieve in a lab: 2 | 3 | write a controller automation in myhdl for debugging 4 | 5 | implement an apu feature like sweep 6 | 7 | modify an existing mapper infrastructure to run a new game 8 | 9 | implement a video scaler 10 | 11 | implement a missing apu feature like sweep 12 | -------------------------------------------------------------------------------- /doc/todo.txt: -------------------------------------------------------------------------------- 1 | Fix various synthesis warnings 2 | SpriteOverflow Flag is apparently unused 3 | 4 | APU Details 5 | Pulse Sweep (Candidate for student implementation) 6 | Status Registers 7 | Interrupts 8 | DMC 9 | 10 | Graphic Glitches: 11 | Sprite Y Flip 12 | Wrong attributes in second column 13 | Border Glitch / Nicer blending 14 | 15 | 16 | 17 | Interesting Demonstration tool: put some graphic/audio feature enables to switches 18 | Halt and single frame stepping 19 | Advanced: Manual Scrolling with joypad 20 | 21 | Infrastructure for mapper support 22 | Idea: Use mappers for design partitioning and partial reconfiguration 23 | 24 | 25 | Documentation 26 | Write build instructions 27 | Simplify things like py65 PYTHONPATH 28 | Write some comments 29 | 30 | Overview of each module / c&p into thesis 31 | -------------------------------------------------------------------------------- /doc/tutorial-genesys.txt: -------------------------------------------------------------------------------- 1 | WRITE IT!!! 2 | 3 | target flash settings: 4 | - Parallel BPI Single Boot 5 | - Virtex 5 / 32M 6 | - 16 bit width 7 | - 28F256P30 (not 512, schematic is lying) 8 | -------------------------------------------------------------------------------- /nes_top.ucf: -------------------------------------------------------------------------------- 1 | # This file is a general .ucf for Genesys rev C board 2 | # To use it in a project: 3 | # - remove or comment the lines corresponding to unused pins 4 | # - rename the used signals according to the project 5 | # clock pin for Genesys rev C board 6 | #TIMESPEC TS_clk = PERIOD clk 100000 kHz; 7 | NET "clk" LOC = "AG18"; # Bank = 4, Pin name = IO_L6P_GC_4, Type = GCLK, Sch name = GCLK0 8 | NET "rstn" LOC = "E7" |IOSTANDARD = LVCMOS25 |PULLUP; 9 | 10 | # onboard HDMI 11 | NET "HDMIHSYNC" LOC = "H8"; # Bank = 20, Pin name = IO_L3N_20, Sch name = HDMI-H 12 | NET "HDMIVSYNC" LOC = "F13"; # Bank = 20, Pin name = IO_L19P_20, Sch name = HDMI-V 13 | NET "HDMIDE" LOC = "V10"; # Bank = 18, Pin name = IO_L19P_18, Sch name = HDMI-DE 14 | NET "HDMICLKP" LOC = "K11"; # Bank = 20, Pin name = IO_L5P_20, Sch name = HDMI-CLK_P 15 | NET "HDMICLKN" LOC = "J11"; # Bank = 20, Pin name = IO_L5N_20, Sch name = HDMI-CLK_N 16 | NET "HDMID<0>" LOC = "G10"; # Bank = 20, Pin name = IO_L2N_20, Sch name = HDMI-D0 17 | NET "HDMID<1>" LOC = "G8"; # Bank = 20, Pin name = IO_L3P_20, Sch name = HDMI-D1 18 | NET "HDMID<2>" LOC = "B12"; # Bank = 20, Pin name = IO_L8N_CC_20, Sch name = HDMI-D2 19 | NET "HDMID<3>" LOC = "D12"; # Bank = 20, Pin name = IO_L6P_20, Sch name = HDMI-D3 20 | NET "HDMID<4>" LOC = "C12"; # Bank = 20, Pin name = IO_L6N_20, Sch name = HDMI-D4 21 | NET "HDMID<5>" LOC = "D11"; # Bank = 20, Pin name = IO_L4P_20, Sch name = HDMI-D5 22 | NET "HDMID<6>" LOC = "F10"; # Bank = 20, Pin name = IO_L2P_20, Sch name = HDMI-D6 23 | NET "HDMID<7>" LOC = "D10"; # Bank = 20, Pin name = IO_L4N_VREF_20, Sch name = HDMI-D7 24 | NET "HDMID<8>" LOC = "E9"; # Bank = 20, Pin name = IO_L0P_20, Sch name = HDMI-D8 25 | NET "HDMID<9>" LOC = "F9"; # Bank = 20, Pin name = IO_L1P_20, Sch name = HDMI-D9 26 | NET "HDMID<10>" LOC = "E8"; # Bank = 20, Pin name = IO_L0N_20, Sch name = HDMI-D10 27 | NET "HDMID<11>" LOC = "F8"; # Bank = 20, Pin name = IO_L1N_20, Sch name = HDMI-D11 28 | NET "HDMISCL" LOC = "U8"; # Bank = 18, Pin name = IO_L17N_18, Sch name = I2C-SCL 29 | NET "HDMISDA" LOC = "V8"; # Bank = 18, Pin name = IO_L17P_18, Sch name = I2C-SDA 30 | #NET "HDMIHPINT" LOC = "W9"; # Bank = 18, Pin name = IO_L15N_18, Sch name = HDMI-HPINT 31 | NET "HDMIRSTN" LOC = "AF23"; # Bank = 2, Pin name = IO_L1P_CC_A25_2, Sch name = HDMI-RST 32 | 33 | 34 | NET "AUDCLK" LOC = "AH17"; # Bank = 4, Pin name = IO_L7P_GC_VRN_4, Sch name = AUD-BIT-CLK 35 | NET "AUDSDI" LOC = "AE18"; # Bank = 4, Pin name = IO_L8N_CC_GC_4, Sch name = AUD-SDI 36 | NET "AUDSDO" LOC = "AG20"; # Bank = 4, Pin name = IO_L4N_GC_VREF_4, Sch name = AUD-SDO 37 | NET "AUDSYNC" LOC = "J9"; # Bank = 20, Pin name = IO_L9N_CC_20, Sch name = AUD-SYNC 38 | NET "AUDRST" LOC = "E12"; # Bank = 20, Pin name = IO_L17P_20, Sch name = AUD-RESET 39 | 40 | 41 | NET "Led<0>" LOC = "AG8"; # Bank = 22, Pin name = IO_L18P_22, Sch name = LD0 42 | NET "Led<1>" LOC = "AH8"; # Bank = 22, Pin name = IO_L18N_22, Sch name = LD1 43 | NET "Led<2>" LOC = "AH9"; # Bank = 22, Pin name = IO_L17P_22, Sch name = LD2 44 | NET "Led<3>" LOC = "AG10"; # Bank = 22, Pin name = IO_L19P_22, Sch name = LD3 45 | NET "Led<4>" LOC = "AH10"; # Bank = 22, Pin name = IO_L17N_22, Sch name = LD4 46 | NET "Led<5>" LOC = "AG11"; # Bank = 22, Pin name = IO_L19N_22, Sch name = LD5 47 | NET "Led<6>" LOC = "AF11"; # Bank = 22, Pin name = IO_L16P_22, Sch name = LD6 48 | NET "Led<7>" LOC = "AE11"; # Bank = 22, Pin name = IO_L16N_22, Sch name = LD7 49 | 50 | NET "BTN<0>" LOC = "G6"; # Bank = 12, Pin name = IO_L17P_12, Sch name = BTN0 51 | NET "BTN<1>" LOC = "G7"; # Bank = 12, Pin name = IO_L17N_12, Sch name = BTN1 52 | NET "BTN<2>" LOC = "E6"; # Bank = 12, Pin name = IO_L19P_12, Sch name = BTNU 53 | NET "BTN<3>" LOC = "J17"; # Bank = 3, Pin name = IO_L4N_GC_VREF_3, Sch name = BTNR 54 | NET "BTN<4>" LOC = "H15"; # Bank = 3, Pin name = IO_L6N_GC_3, Sch name = BTND 55 | NET "BTN<5>" LOC = "K19"; # Bank = 3, Pin name = IO_L5N_GC_3, Sch name = BTNL 56 | NET "BTN<6>" LOC = "J21"; # Bank = 3, Pin name = IO_L7N_GC_3, Sch name = BTNS 57 | 58 | # onBoard SWITCHES 59 | NET "sw<0>" LOC = "J19"; # Bank = 3, Pin name = IO_L3N_GC_3, Sch name = SW0 60 | NET "sw<1>" LOC = "L18"; # Bank = 3, Pin name = IO_L1N_CC_GC_3, Sch name = SW1 61 | NET "sw<2>" LOC = "K18"; # Bank = 3, Pin name = IO_L3P_GC_3, Sch name = SW2 62 | NET "sw<3>" LOC = "H18"; # Bank = 3, Pin name = IO_L0N_CC_GC_3, Sch name = SW3 63 | NET "sw<4>" LOC = "H17"; # Bank = 3, Pin name = IO_L0P_CC_GC_3, Sch name = SW4 64 | NET "sw<5>" LOC = "K17"; # Bank = 3, Pin name = IO_L1P_CC_GC_3, Sch name = SW5 65 | NET "sw<6>" LOC = "G16"; # Bank = 3, Pin name = IO_L2N_GC_VRP_3, Sch name = SW6 66 | NET "sw<7>" LOC = "G15"; # Bank = 3, Pin name = IO_L2P_GC_VRN_3, Sch name = SW7 67 | 68 | 69 | # PMOD Connectors 70 | NET "JA<0>" LOC = "AD11"; # BANK = 22, Pin name = IO_L10N_CC_22, Sch name = JA1 71 | NET "JA<1>" LOC = "AD9"; # BANK = 22, Pin name = IO_L9N_CC_22, Sch name = JA2 72 | NET "JA<2>" LOC = "AM13"; # BANK = 22, Pin name = IO_L2N_22, Sch name = JA3 73 | NET "JA<3>" LOC = "AM12"; # BANK = 22, Pin name = IO_L6P_22, Sch name = JA4 74 | NET "JA<4>" LOC = "AD10"; # BANK = 22, Pin name = IO_L10P_CC_22, Sch name = JA7 75 | NET "JA<5>" LOC = "AE8"; # BANK = 22, Pin name = IO_L9P_CC_22, Sch name = JA8 76 | NET "JA<6>" LOC = "AF10"; # BANK = 22, Pin name = IO_L14N_VREF_22, Sch name = JA9 77 | NET "JA<7>" LOC = "AJ11"; # BANK = 22, Pin name = IO_L11N_CC_22, Sch name = JA10 78 | 79 | NET "JB<0>" LOC = "AE9"; # BANK = 22, Pin name = IO_L12N_VRP_22, Sch name = JB1 80 | NET "JB<1>" LOC = "AC8"; # BANK = 22, Pin name = IO_L5P_22, Sch name = JB2 81 | NET "JB<2>" LOC = "AB10"; # BANK = 22, Pin name = IO_L1P_22, Sch name = JB3 82 | NET "JB<3>" LOC = "AC9"; # BANK = 22, Pin name = IO_L7N_22, Sch name = JB4 83 | NET "JB<4>" LOC = "AF8"; # BANK = 22, Pin name = IO_L12P_VRN_22, Sch name = JB7 84 | NET "JB<5>" LOC = "AB8"; # BANK = 22, Pin name = IO_L5N_22, Sch name = JB8 85 | NET "JB<6>" LOC = "AA10"; # BANK = 22, Pin name = IO_L1N_22, Sch name = JB9 86 | NET "JB<7>" LOC = "AA9"; # BANK = 22, Pin name = IO_L3N_22, Sch name = JB10 87 | 88 | # NET "JC<0>" LOC = "AL11"; # BANK = 22, Pin name = IO_L8P_CC_22, Sch name = JC1 89 | # NET "JC<1>" LOC = "AJ10"; # BANK = 22, Pin name = IO_L15N_22, Sch name = JC2 90 | # NET "JC<2>" LOC = "AK9"; # BANK = 22, Pin name = IO_L13N_22, Sch name = JC3 91 | # NET "JC<3>" LOC = "AF9"; # BANK = 22, Pin name = IO_L14P_22, Sch name = JC4 92 | # NET "JC<4>" LOC = "AK11"; # BANK = 22, Pin name = IO_L11P_CC_22, Sch name = JC7 93 | # NET "JC<5>" LOC = "AC10"; # BANK = 22, Pin name = IO_L7P_22, Sch name = JC8 94 | # NET "JC<6>" LOC = "AJ9"; # BANK = 22, Pin name = IO_L15P_22, Sch name = JC9 95 | # NET "JC<7>" LOC = "AA8"; # BANK = 22, Pin name = IO_L3P_22, Sch name = JC10 96 | # 97 | # NET "JD<0>" LOC = "AN14"; # BANK = 22, Pin name = IO_L0P_22, Sch name = JD1 98 | # NET "JD<1>" LOC = "AN13"; # BANK = 22, Pin name = IO_L2P_22, Sch name = JD2 99 | # NET "JD<2>" LOC = "AP12"; # BANK = 22, Pin name = IO_L4P_22, Sch name = JD3 100 | # NET "JD<3>" LOC = "AL10"; # BANK = 22, Pin name = IO_L8N_CC_22, Sch name = JD4 101 | # NET "JD<4>" LOC = "AP14"; # BANK = 22, Pin name = IO_L0N_22, Sch name = JD7 102 | # NET "JD<5>" LOC = "AN12"; # BANK = 22, Pin name = IO_L4N_VREF_22, Sch name = JD8 103 | # NET "JD<6>" LOC = "AM11"; # BANK = 22, Pin name = IO_L6N_22, Sch name = JD9 104 | # NET "JD<7>" LOC = "AK8"; # BANK = 22, Pin name = IO_L13P_22, Sch name = JD10 105 | 106 | #Created by Constraints Editor (xc5vlx50t-ff1136-2) - 2011/11/11 107 | NET "CLK" TNM_NET = CLK; 108 | TIMESPEC TS_CLK = PERIOD "CLK" 10 ns HIGH 50%; 109 | 110 | -------------------------------------------------------------------------------- /roms/fpgasmb.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strfry/nesfpga/5059f4b2394c47a59518675b1a63adfc50ff1b63/roms/fpgasmb.nes -------------------------------------------------------------------------------- /roms/smb.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strfry/nesfpga/5059f4b2394c47a59518675b1a63adfc50ff1b63/roms/smb.nes -------------------------------------------------------------------------------- /roms/stress.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strfry/nesfpga/5059f4b2394c47a59518675b1a63adfc50ff1b63/roms/stress.nes -------------------------------------------------------------------------------- /synaesthesia/genesys.xst: -------------------------------------------------------------------------------- 1 | -p xc5vlx50t-ff1136-2 2 | 3 | # Disable iobufs for submodules 4 | 5 | -iobuf NO -bufg 0 -iob false 6 | 7 | # Optimization Level 8 | 9 | -opt_level 0 10 | 11 | 12 | # Other Options 13 | 14 | -power NO 15 | -iuc YES 16 | -keep_hierarchy No 17 | -netlist_hierarchy As_Optimized 18 | -rtlview Yes 19 | -glob_opt AllClockNets 20 | -read_cores NO 21 | -write_timing_constraints NO 22 | -cross_clock_analysis NO 23 | -hierarchy_separator / 24 | -bus_delimiter <> 25 | -case Maintain 26 | -slice_utilization_ratio 100 27 | -bram_utilization_ratio 100 28 | -dsp_utilization_ratio 100 29 | -lc Off 30 | -reduce_control_sets Off 31 | -verilog2001 YES 32 | -fsm_extract YES -fsm_encoding Auto 33 | -safe_implementation No 34 | -fsm_style LUT 35 | -ram_extract Yes 36 | -ram_style Auto 37 | -rom_extract Yes 38 | -mux_style Auto 39 | -decoder_extract YES 40 | -priority_extract Yes 41 | -shreg_extract YES 42 | -shift_extract YES 43 | -xor_collapse YES 44 | -rom_style Auto 45 | -auto_bram_packing NO 46 | -mux_extract Yes 47 | -resource_sharing YES 48 | -async_to_sync NO 49 | -use_dsp48 Auto 50 | -max_fanout 100000 51 | -register_duplication YES 52 | -register_balancing No 53 | -slice_packing YES 54 | -optimize_primitives NO 55 | -use_clock_enable Auto 56 | -use_sync_set Auto 57 | -use_sync_reset Auto 58 | -equivalent_register_removal YES 59 | -slice_utilization_ratio_maxmargin 5 60 | 61 | #-bufg 32 62 | #-iobuf YES 63 | #-iob Auto 64 | -------------------------------------------------------------------------------- /synaesthesia/run_xst.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Workaround to launch xst manually 4 | # 5 | 6 | XILINX_HOME=/opt/cad/xilinx/xilinx-13.1-64b/ISE_DS/ISE 7 | 8 | # echo run -ifn watchvhd.vhd -ifmt VHDL -ofn watchvhd.ngc -ofmt NGC -p xcv50-bg256-6 -opt_mode Speed 9 | 10 | # Workaround for random runtime linker errors. 11 | # Debugging stuff like this is not exactly fun. 12 | # Seriously Xilinx, fuck you guys!. 13 | 14 | FUBAR=$FUBAR:$XILINX_HOME/lib/lin64/libboost_serialization-gcc41-mt-p-1_38.so.1.38.0 15 | FUBAR=$FUBAR:$XILINX_HOME/lib/lin64/libAntlr.so 16 | FUBAR=$FUBAR:$XILINX_HOME/lib/lin64/libXst2_CoreData.so 17 | 18 | # Oh, and XST reads on stdin for more what should be command line parameters 19 | CONFIG=$(grep -E ^[^\#] $XST_CONFIG) 20 | 21 | #XST_PARAMS="$@ "$(grep -E ^[^\#] $XST_CONFIG) 22 | 23 | # And set a temp dir to box up all the crap XST generates 24 | 25 | DIRSET_CMD='set -tmpdir "xst/projnav.tmp"\nset -xsthdpdir "xst"\n' 26 | CONFIG=$(grep -E ^[^\#] $XST_CONFIG) 27 | 28 | 29 | export LD_PRELOAD=$FUBAR 30 | echo -e $DIRSET_CMD run $CONFIG $@ | xst 31 | -------------------------------------------------------------------------------- /synaesthesia/synthesis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Set up Xilinx environment variable 4 | 5 | XILINX_SETTINGS_PATH = "/opt/Xilinx/14.5/ISE_DS/settings64.sh" 6 | 7 | def shell_source(script): 8 | """Sometime you want to emulate the action of "source" in bash, 9 | settings some environment variables. Here is a way to do it.""" 10 | import subprocess, os 11 | pipe = subprocess.Popen(". %s; env" % script, stdout=subprocess.PIPE, shell=True) 12 | output = pipe.communicate()[0] 13 | print output 14 | env = zip((line.split("=", 1) for line in output.splitlines())) 15 | os.environ.update(env) 16 | 17 | #shell_source(XILINX_SETTINGS_PATH) 18 | 19 | from xilinx import Fpga, Xilinx 20 | 21 | # Specify UCF-like pin mappings 22 | 23 | fpga = Fpga() 24 | fpga.ucf_file = "nes_top.ucf" 25 | fpga.setDevice('virtex5', 'xc5vlx50t', 'ff1136', '-2') 26 | 27 | fpga.setPin("clk", "AG18") 28 | fpga.setPin("rstn", "E7") 29 | fpga.setPin("HDMIHSYNC", "H8") 30 | fpga.setPin("HDMIVSYNC", "F13") 31 | fpga.setPin("HDMIDE", "V10") 32 | fpga.setPin("HDMICLKP", "K11") 33 | fpga.setPin("HDMICLKN", "J11") 34 | fpga.setPin("HDMID<0>", "G10") 35 | fpga.setPin("HDMID<1>", "G8") 36 | fpga.setPin("HDMID<2>", "B12") 37 | fpga.setPin("HDMID<3>", "D12") 38 | fpga.setPin("HDMID<4>", "C12") 39 | fpga.setPin("HDMID<5>", "D11") 40 | fpga.setPin("HDMID<6>", "F10") 41 | fpga.setPin("HDMID<7>", "D10") 42 | fpga.setPin("HDMID<8>", "E9") 43 | fpga.setPin("HDMID<9>", "F9") 44 | fpga.setPin("HDMID<10>", "E8") 45 | fpga.setPin("HDMID<11>", "F8") 46 | fpga.setPin("HDMISCL", "U8") 47 | fpga.setPin("HDMISDA", "V8") 48 | fpga.setPin("HDMIRSTN", "AF23") 49 | fpga.setPin("AUDCLK", "AH17") 50 | fpga.setPin("AUDSDI", "AE18") 51 | fpga.setPin("AUDSDO", "AG20") 52 | fpga.setPin("AUDSYNC", "J9") 53 | fpga.setPin("AUDRST", "E12") 54 | fpga.setPin("Led<0>", "AG8") 55 | fpga.setPin("Led<1>", "AH8") 56 | fpga.setPin("Led<2>", "AH9") 57 | fpga.setPin("Led<3>", "AG10") 58 | fpga.setPin("Led<4>", "AH10") 59 | fpga.setPin("Led<5>", "AG11") 60 | fpga.setPin("Led<6>", "AF11") 61 | fpga.setPin("Led<7>", "AE11") 62 | fpga.setPin("BTN<0>", "G6") 63 | fpga.setPin("BTN<1>", "G7") 64 | fpga.setPin("BTN<2>", "E6") 65 | fpga.setPin("BTN<3>", "J17") 66 | fpga.setPin("BTN<4>", "H15") 67 | fpga.setPin("BTN<5>", "K19") 68 | fpga.setPin("BTN<6>", "J21") 69 | fpga.setPin("sw<0>", "J19") 70 | fpga.setPin("sw<1>", "L18") 71 | fpga.setPin("sw<2>", "K18") 72 | fpga.setPin("sw<3>", "H18") 73 | fpga.setPin("sw<4>", "H17") 74 | fpga.setPin("sw<5>", "K17") 75 | fpga.setPin("sw<6>", "G16") 76 | fpga.setPin("sw<7>", "G15") 77 | fpga.setPin("JA<0>", "AD11") 78 | fpga.setPin("JA<1>", "AD9") 79 | fpga.setPin("JA<2>", "AM13") 80 | fpga.setPin("JA<3>", "AM12") 81 | fpga.setPin("JA<4>", "AD10") 82 | fpga.setPin("JA<5>", "AE8") 83 | fpga.setPin("JA<6>", "AF10") 84 | fpga.setPin("JA<7>", "AJ11") 85 | fpga.setPin("JB<0>", "AE9") 86 | fpga.setPin("JB<1>", "AC8") 87 | fpga.setPin("JB<2>", "AB10") 88 | fpga.setPin("JB<3>", "AC9") 89 | fpga.setPin("JB<4>", "AF8") 90 | fpga.setPin("JB<5>", "AB8") 91 | fpga.setPin("JB<6>", "AA10") 92 | fpga.setPin("JB<7>", "AA9") 93 | 94 | print fpga 95 | 96 | # Specify build files 97 | 98 | _ = '../' 99 | 100 | imp = Xilinx('build', 'Genesys_NES') 101 | imp.setFpga(fpga) 102 | 103 | # Add HDL Files 104 | 105 | # Top File for Digilent Genesys Board 106 | imp.addHdl(_ + './Genesys_NES.vhd') 107 | 108 | # NES Core 109 | imp.addHdl(_ + './NES_Pack.vhd') 110 | imp.addHdl(_ + './NES_Mainboard.vhd') 111 | 112 | # Cartridge Module for coregen files 113 | imp.addHdl(_ + 'CartridgeROM_Coregen.vhd') 114 | 115 | # Coregen files for Super Mario 116 | imp.addHdl(_ + 'Coregen/chr_rom_smb.ngc') 117 | imp.addHdl(_ + 'Coregen/prg_rom_smb.ngc') 118 | 119 | # NES CPU 120 | imp.addHdl(_ + './NES_2A03/T65.vhd') 121 | imp.addHdl(_ + './NES_2A03/SRAM.vhd') 122 | imp.addHdl(_ + './NES_2A03/T65_ALU.vhd') 123 | imp.addHdl(_ + './NES_2A03/Dan_2A03.vhd') 124 | imp.addHdl(_ + './NES_2A03/T65_MCode.vhd') 125 | imp.addHdl(_ + './NES_2A03/T65_Pack.vhd') 126 | imp.addHdl(_ + './NES_2A03/ClockDivider.vhd') 127 | imp.addHdl(_ + './NES_2A03/DanPack.vhd') 128 | 129 | # NES APU 130 | 131 | imp.addHdl(_ + 'myhdl/APU_Main.v') 132 | 133 | # NES PPU 134 | 135 | imp.addHdl(_ + './PPU/PPU.vhd') 136 | imp.addHdl(_ + './PPU/TileFetcher.vhd') 137 | imp.addHdl(_ + './PPU/PPU_Pack.vhd') 138 | imp.addHdl(_ + './PPU/Loopy_Scrolling.vhd') 139 | imp.addHdl(_ + './PPU/SpriteSelector.vhd') 140 | 141 | # HDMI Output 142 | imp.addHdl(_ + './HDMI/HDMIController.vhd') 143 | imp.addHdl(_ + './HDMI/tft_interface.v') 144 | imp.addHdl(_ + 'HDMI/HDMIController.vhd') 145 | imp.addHdl(_ + 'HDMI/iic_init.v') 146 | imp.addHdl(_ + './ColorPalette.vhd') 147 | 148 | # AC97 Audio Output 149 | 150 | imp.addHdl(_ + './ac97/ac97_top.vhd') 151 | imp.addHdl(_ + './ac97/Talkthrough_Parts.vhd') 152 | 153 | imp.createTcl(target = 'Generate Programming File') 154 | 155 | # Run 156 | imp.run() 157 | -------------------------------------------------------------------------------- /synaesthesia/vhd2ngc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./run_xst.sh run -ifn $1 -ifmt VHDL -ofn $2 -ofmt NGC 3 | -------------------------------------------------------------------------------- /synaesthesia/xilinx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import popen2 4 | import sys, os 5 | from time import gmtime, strftime 6 | 7 | 8 | ####################################################################### 9 | # 10 | # 11 | class Xilinx(object): 12 | '''Python classe to run implementation tools 13 | 14 | TODO: make it modular to allow other implementation tools 15 | ''' 16 | def __init__(self, path=None, top_name=None): 17 | self.path = '.' 18 | if path is not None: 19 | # TODO: add some code to have path separator all the same 20 | # either windows or linux, depending on what system it runs 21 | self.path = path 22 | self.fpga = None 23 | self.tcl_name = None 24 | self.top_name = None 25 | if top_name: 26 | if isinstance(top_name, str): 27 | self.top_name = top_name 28 | self.tcl_name = top_name + '.tcl' 29 | else: 30 | raise TypeError('top_name needs to be string') 31 | self.hdl_fileL = [] 32 | 33 | # start with the text string for the TCL script 34 | self.tcl_script = '#\n#\n# ISE implementation script\n' 35 | date_time = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) 36 | self.tcl_script += '# create: %s\n'%date_time 37 | self.tcl_script += '# by: %s\n'%os.path.basename(sys.argv[0]) 38 | self.tcl_script += '#\n#\n' 39 | 40 | def addHdl(self, files): 41 | '''Add HDL files to the project 42 | files : string or list of strings 43 | ''' 44 | if isinstance(files, str): 45 | self.hdl_fileL.append(files) 46 | 47 | elif isinstance(files, (list, tuple)): 48 | 49 | for f in files: 50 | if not isinstance(f, str): 51 | raise TypeError('List or Tuple entry needs to be a string') 52 | self.hdl_fileL.append(f) 53 | 54 | else: 55 | raise TypeError('files not string or list of strings') 56 | 57 | def createTcl(self, target, filename=None): 58 | '''Creat the TCL script 59 | ''' 60 | if filename: 61 | fn = os.path.join(self.path, filename) 62 | else: 63 | fn = os.path.join(self.path, self.tcl_name) 64 | 65 | self.tcl_script += '# set compile directory:\n' 66 | self.tcl_script += 'set compile_directory %s\n'%self.path 67 | if self.top_name: 68 | self.tcl_script += 'set top_name %s\n'%self.top_name 69 | if self.hdl_fileL: 70 | self.tcl_script += '# input source files:\n' 71 | self.tcl_script += 'set hdl_files [ list \\\n' 72 | for f in self.hdl_fileL: 73 | self.tcl_script += ' '*17 74 | self.tcl_script += '%s \\\n'%f 75 | self.tcl_script += ']\n' 76 | 77 | if self.fpga: 78 | if not self.fpga.ucf_file: 79 | if not self.top_name: 80 | raise ValueError('No Top Name set') 81 | self.fpga.createUcf(self.top_name + '.ucf') 82 | 83 | self.tcl_script += '# set ucf file:\n' 84 | self.tcl_script += 'set constraints_file %s\n'% self.fpga.ucf_file 85 | 86 | self.tcl_script += '# set Project:\n' 87 | self.tcl_script += 'set proj %s\n'% self.top_name 88 | 89 | self.tcl_script += '# change to the directory:\n' 90 | self.tcl_script += 'cd %s\n'% self.path 91 | 92 | # test whether ise project file exits 93 | f = os.path.join(self.path, self.top_name + '.ise') 94 | pj_fn = self.top_name + '.ise' 95 | if os.path.exists(f): 96 | os.remove(f) 97 | self.tcl_script += '# set variables:\n' 98 | self.tcl_script += 'project new %s\n'%pj_fn 99 | if self.fpga.family: 100 | self.tcl_script += 'project set family %s\n'%self.fpga.family 101 | self.tcl_script += 'project set device %s\n'%self.fpga.device 102 | self.tcl_script += 'project set package %s\n'%self.fpga.package 103 | self.tcl_script += 'project set speed %s\n'%self.fpga.speed 104 | 105 | # add the hdl files 106 | self.tcl_script += '# add hdl files:\n' 107 | for hdl_file in self.hdl_fileL: 108 | self.tcl_script += 'xfile add %s\n'%hdl_file 109 | 110 | self.tcl_script += '# test if set_source_directory is set:\n' 111 | self.tcl_script += 'if { ! [catch {set source_directory' 112 | self.tcl_script += ' $source_directory}]} {\n' 113 | self.tcl_script += ' project set "Macro Search Path"\n' 114 | self.tcl_script += ' $source_directory -process Translate\n' 115 | self.tcl_script += '}\n' 116 | 117 | 118 | # run the implementation 119 | self.tcl_script += '# run the implementation:\n' 120 | self.tcl_script += 'process run "' + target + '"\n' 121 | # close the project 122 | self.tcl_script += '# close the project:\n' 123 | self.tcl_script += 'project close\n' 124 | 125 | 126 | fid = open(fn, 'w') 127 | fid.write(self.tcl_script) 128 | fid.close() 129 | 130 | 131 | 132 | 133 | 134 | def run(self, filename=None): 135 | '''Run the created TCL script 136 | ''' 137 | 138 | # Workaround: Delete .xise file 139 | try: 140 | os.unlink(self.path + '/' + self.top_name + '.xise') 141 | except: 142 | pass 143 | 144 | 145 | if filename: 146 | tcl_name = filename 147 | else: 148 | tcl_name = os.path.join(self.path, self.tcl_name) 149 | 150 | if not os.path.exists(self.path): 151 | os.mkdir(self.path) 152 | 153 | # self.fpga.createUcf() 154 | cmd = 'xtclsh ' + tcl_name 155 | print 'running command: ', cmd 156 | 157 | import subprocess 158 | subprocess.call(cmd, shell=True) 159 | 160 | # r, w, e = popen2.popen3(cmd) 161 | # print 'errors: ', e.readlines() 162 | # print 'reply: ', r.readlines() 163 | # r.close() 164 | # e.close() 165 | # w.close() 166 | 167 | def setFpga(self, fpga): 168 | self.fpga = fpga 169 | 170 | ####################################################################### 171 | # 172 | # 173 | class Fpga(object): 174 | def __init__(self, path=None): 175 | self.path= '.' 176 | if path is not None: 177 | self.path = path 178 | self.ucfD = {} 179 | self.ucf_file = None 180 | self.family = None 181 | self.device = None 182 | self.package = None 183 | self.speed = None 184 | 185 | def setPin(self, net, pin, iostandard=None, slew=None, drive=None): 186 | if iostandard is None and slew is None and drive is None: 187 | self.ucfD[pin] = [net, None] 188 | else: 189 | self.ucfD[pin] = [net, [iostandard, slew, drive]] 190 | 191 | 192 | def setDevice(self, family, device, package, speed): 193 | 194 | if not isinstance(family, str): 195 | raise TypeError('"Family" needs to be a string') 196 | if not isinstance(device, str): 197 | raise TypeError('"Device" needs to be a string') 198 | if not isinstance(speed, str): 199 | raise TypeError('"Speed" needs to be a string') 200 | if not isinstance(package, str): 201 | raise TypeError('"Package" needs to be a string') 202 | 203 | self.family = family 204 | self.device = device 205 | self.package = package 206 | self.speed = speed 207 | 208 | 209 | 210 | def createUcf(self, filename='my.ucf'): 211 | self.ucf_file = os.path.join(self.path, filename) 212 | 213 | str = '# UCF file automatically create by "%s"\n'%os.path.basename(sys.argv[0]) 214 | str += '#\n' 215 | for key, value in self.ucfD.items(): 216 | str += 'NET "%s" LOC = "%s"'%(value[0], key) 217 | if value[1] is not None: 218 | if value[1][0] is not None: 219 | str += ' | IOSTANDARD = %s'%value[1][0] 220 | if value[1][1] is not None: 221 | str += ' | SLEW = %s'%value[1][1] 222 | if value[1][2] is not None: 223 | str += ' | DRIVE = %s'%value[1][2] 224 | 225 | str += ' ;\n' 226 | 227 | str += '#\n' 228 | 229 | fid = open(self.ucf_file, 'w') 230 | fid.write(str) 231 | fid.close() 232 | 233 | def __repr__(self): 234 | s = 'FPGA:\n' 235 | if self.family: 236 | s += 'Family: %s\n'% self.family 237 | s += 'Device: %s\n'% self.device 238 | s += 'Package: %s\n'% self.package 239 | s += 'Speed: %s\n'% self.speed 240 | return s 241 | -------------------------------------------------------------------------------- /thesis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strfry/nesfpga/5059f4b2394c47a59518675b1a63adfc50ff1b63/thesis.pdf -------------------------------------------------------------------------------- /tools/cartridge.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | from file_rom import file_rom 4 | 5 | def str2bin(s): 6 | return tuple(map(lambda c: ord(c), s)) 7 | 8 | rom_file = file("smb.nes") 9 | rom_header = rom_file.read(16) 10 | rom_prg = str2bin(rom_file.read(2**15)) 11 | rom_chr = str2bin(rom_file.read(2**13)) 12 | 13 | def CartridgeROM( 14 | CLK, 15 | RSTN, 16 | PRG_Address, 17 | PRG_Data, 18 | CHR_Address, 19 | CHR_Data 20 | ): 21 | 22 | PRG = file_rom(CLK, PRG_Address, PRG_Data, rom_prg) 23 | CHR = file_rom(CLK, CHR_Address, CHR_Data, rom_chr) 24 | 25 | return PRG, CHR 26 | 27 | 28 | 29 | def convert(): 30 | clk = Signal(bool()) 31 | rstn = Signal(bool()) 32 | prg_address = Signal(intbv()[15:0]) 33 | prg_data = Signal(intbv()[8:0]) 34 | chr_address = Signal(intbv()[14:0]) 35 | chr_data = Signal(intbv()[8:0]) 36 | 37 | toVerilog(CartridgeROM, clk, rstn, prg_address, prg_data, chr_address, chr_data) 38 | 39 | 40 | 41 | convert() 42 | -------------------------------------------------------------------------------- /tools/fbview/SDLMain.h: -------------------------------------------------------------------------------- 1 | /* SDLMain.m - main entry point for our Cocoa-ized SDL app 2 | Initial Version: Darrell Walisser 3 | Non-NIB-Code & other changes: Max Horn 4 | 5 | Feel free to customize this file to suit your needs 6 | */ 7 | 8 | #ifndef _SDLMain_h_ 9 | #define _SDLMain_h_ 10 | 11 | #import 12 | 13 | @interface SDLMain : NSObject 14 | @end 15 | 16 | #endif /* _SDLMain_h_ */ 17 | -------------------------------------------------------------------------------- /tools/fbview/SDLMain.m: -------------------------------------------------------------------------------- 1 | /* SDLMain.m - main entry point for our Cocoa-ized SDL app 2 | Initial Version: Darrell Walisser 3 | Non-NIB-Code & other changes: Max Horn 4 | 5 | Feel free to customize this file to suit your needs 6 | */ 7 | 8 | #include "SDL/SDL.h" 9 | #include "SDLMain.h" 10 | #include /* for MAXPATHLEN */ 11 | #include 12 | 13 | /* For some reaon, Apple removed setAppleMenu from the headers in 10.4, 14 | but the method still is there and works. To avoid warnings, we declare 15 | it ourselves here. */ 16 | @interface NSApplication(SDL_Missing_Methods) 17 | - (void)setAppleMenu:(NSMenu *)menu; 18 | @end 19 | 20 | /* Use this flag to determine whether we use SDLMain.nib or not */ 21 | #define SDL_USE_NIB_FILE 0 22 | 23 | /* Use this flag to determine whether we use CPS (docking) or not */ 24 | #define SDL_USE_CPS 1 25 | #ifdef SDL_USE_CPS 26 | /* Portions of CPS.h */ 27 | typedef struct CPSProcessSerNum 28 | { 29 | UInt32 lo; 30 | UInt32 hi; 31 | } CPSProcessSerNum; 32 | 33 | extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); 34 | extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); 35 | extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); 36 | 37 | #endif /* SDL_USE_CPS */ 38 | 39 | static int gArgc; 40 | static char **gArgv; 41 | static BOOL gFinderLaunch; 42 | static BOOL gCalledAppMainline = FALSE; 43 | 44 | static NSString *getApplicationName(void) 45 | { 46 | const NSDictionary *dict; 47 | NSString *appName = 0; 48 | 49 | /* Determine the application name */ 50 | dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); 51 | if (dict) 52 | appName = [dict objectForKey: @"CFBundleName"]; 53 | 54 | if (![appName length]) 55 | appName = [[NSProcessInfo processInfo] processName]; 56 | 57 | return appName; 58 | } 59 | 60 | #if SDL_USE_NIB_FILE 61 | /* A helper category for NSString */ 62 | @interface NSString (ReplaceSubString) 63 | - (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; 64 | @end 65 | #endif 66 | 67 | @interface NSApplication (SDLApplication) 68 | @end 69 | 70 | @implementation NSApplication (SDLApplication) 71 | /* Invoked from the Quit menu item */ 72 | - (void)terminate:(id)sender 73 | { 74 | /* Post a SDL_QUIT event */ 75 | SDL_Event event; 76 | event.type = SDL_QUIT; 77 | SDL_PushEvent(&event); 78 | } 79 | @end 80 | 81 | /* The main class of the application, the application's delegate */ 82 | @implementation SDLMain 83 | 84 | /* Set the working directory to the .app's parent directory */ 85 | - (void) setupWorkingDirectory:(BOOL)shouldChdir 86 | { 87 | if (shouldChdir) 88 | { 89 | char parentdir[MAXPATHLEN]; 90 | CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); 91 | CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); 92 | if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { 93 | chdir(parentdir); /* chdir to the binary app's parent */ 94 | } 95 | CFRelease(url); 96 | CFRelease(url2); 97 | } 98 | } 99 | 100 | #if SDL_USE_NIB_FILE 101 | 102 | /* Fix menu to contain the real app name instead of "SDL App" */ 103 | - (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName 104 | { 105 | NSRange aRange; 106 | NSEnumerator *enumerator; 107 | NSMenuItem *menuItem; 108 | 109 | aRange = [[aMenu title] rangeOfString:@"SDL App"]; 110 | if (aRange.length != 0) 111 | [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; 112 | 113 | enumerator = [[aMenu itemArray] objectEnumerator]; 114 | while ((menuItem = [enumerator nextObject])) 115 | { 116 | aRange = [[menuItem title] rangeOfString:@"SDL App"]; 117 | if (aRange.length != 0) 118 | [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; 119 | if ([menuItem hasSubmenu]) 120 | [self fixMenu:[menuItem submenu] withAppName:appName]; 121 | } 122 | } 123 | 124 | #else 125 | 126 | static void setApplicationMenu(void) 127 | { 128 | /* warning: this code is very odd */ 129 | NSMenu *appleMenu; 130 | NSMenuItem *menuItem; 131 | NSString *title; 132 | NSString *appName; 133 | 134 | appName = getApplicationName(); 135 | appleMenu = [[NSMenu alloc] initWithTitle:@""]; 136 | 137 | /* Add menu items */ 138 | title = [@"About " stringByAppendingString:appName]; 139 | [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; 140 | 141 | [appleMenu addItem:[NSMenuItem separatorItem]]; 142 | 143 | title = [@"Hide " stringByAppendingString:appName]; 144 | [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; 145 | 146 | menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; 147 | [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 148 | 149 | [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; 150 | 151 | [appleMenu addItem:[NSMenuItem separatorItem]]; 152 | 153 | title = [@"Quit " stringByAppendingString:appName]; 154 | [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; 155 | 156 | 157 | /* Put menu into the menubar */ 158 | menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; 159 | [menuItem setSubmenu:appleMenu]; 160 | [[NSApp mainMenu] addItem:menuItem]; 161 | 162 | /* Tell the application object that this is now the application menu */ 163 | [NSApp setAppleMenu:appleMenu]; 164 | 165 | /* Finally give up our references to the objects */ 166 | [appleMenu release]; 167 | [menuItem release]; 168 | } 169 | 170 | /* Create a window menu */ 171 | static void setupWindowMenu(void) 172 | { 173 | NSMenu *windowMenu; 174 | NSMenuItem *windowMenuItem; 175 | NSMenuItem *menuItem; 176 | 177 | windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; 178 | 179 | /* "Minimize" item */ 180 | menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; 181 | [windowMenu addItem:menuItem]; 182 | [menuItem release]; 183 | 184 | /* Put menu into the menubar */ 185 | windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; 186 | [windowMenuItem setSubmenu:windowMenu]; 187 | [[NSApp mainMenu] addItem:windowMenuItem]; 188 | 189 | /* Tell the application object that this is now the window menu */ 190 | [NSApp setWindowsMenu:windowMenu]; 191 | 192 | /* Finally give up our references to the objects */ 193 | [windowMenu release]; 194 | [windowMenuItem release]; 195 | } 196 | 197 | /* Replacement for NSApplicationMain */ 198 | static void CustomApplicationMain (int argc, char **argv) 199 | { 200 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 201 | SDLMain *sdlMain; 202 | 203 | /* Ensure the application object is initialised */ 204 | [NSApplication sharedApplication]; 205 | 206 | #ifdef SDL_USE_CPS 207 | { 208 | CPSProcessSerNum PSN; 209 | /* Tell the dock about us */ 210 | if (!CPSGetCurrentProcess(&PSN)) 211 | if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) 212 | if (!CPSSetFrontProcess(&PSN)) 213 | [NSApplication sharedApplication]; 214 | } 215 | #endif /* SDL_USE_CPS */ 216 | 217 | /* Set up the menubar */ 218 | [NSApp setMainMenu:[[NSMenu alloc] init]]; 219 | setApplicationMenu(); 220 | setupWindowMenu(); 221 | 222 | /* Create SDLMain and make it the app delegate */ 223 | sdlMain = [[SDLMain alloc] init]; 224 | [NSApp setDelegate:sdlMain]; 225 | 226 | /* Start the main event loop */ 227 | [NSApp run]; 228 | 229 | [sdlMain release]; 230 | [pool release]; 231 | } 232 | 233 | #endif 234 | 235 | 236 | /* 237 | * Catch document open requests...this lets us notice files when the app 238 | * was launched by double-clicking a document, or when a document was 239 | * dragged/dropped on the app's icon. You need to have a 240 | * CFBundleDocumentsType section in your Info.plist to get this message, 241 | * apparently. 242 | * 243 | * Files are added to gArgv, so to the app, they'll look like command line 244 | * arguments. Previously, apps launched from the finder had nothing but 245 | * an argv[0]. 246 | * 247 | * This message may be received multiple times to open several docs on launch. 248 | * 249 | * This message is ignored once the app's mainline has been called. 250 | */ 251 | - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename 252 | { 253 | const char *temparg; 254 | size_t arglen; 255 | char *arg; 256 | char **newargv; 257 | 258 | if (!gFinderLaunch) /* MacOS is passing command line args. */ 259 | return FALSE; 260 | 261 | if (gCalledAppMainline) /* app has started, ignore this document. */ 262 | return FALSE; 263 | 264 | temparg = [filename UTF8String]; 265 | arglen = SDL_strlen(temparg) + 1; 266 | arg = (char *) SDL_malloc(arglen); 267 | if (arg == NULL) 268 | return FALSE; 269 | 270 | newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); 271 | if (newargv == NULL) 272 | { 273 | SDL_free(arg); 274 | return FALSE; 275 | } 276 | gArgv = newargv; 277 | 278 | SDL_strlcpy(arg, temparg, arglen); 279 | gArgv[gArgc++] = arg; 280 | gArgv[gArgc] = NULL; 281 | return TRUE; 282 | } 283 | 284 | 285 | /* Called when the internal event loop has just started running */ 286 | - (void) applicationDidFinishLaunching: (NSNotification *) note 287 | { 288 | int status; 289 | 290 | /* Set the working directory to the .app's parent directory */ 291 | [self setupWorkingDirectory:gFinderLaunch]; 292 | 293 | #if SDL_USE_NIB_FILE 294 | /* Set the main menu to contain the real app name instead of "SDL App" */ 295 | [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; 296 | #endif 297 | 298 | /* Hand off to main application code */ 299 | gCalledAppMainline = TRUE; 300 | status = SDL_main (gArgc, gArgv); 301 | 302 | /* We're done, thank you for playing */ 303 | exit(status); 304 | } 305 | @end 306 | 307 | 308 | @implementation NSString (ReplaceSubString) 309 | 310 | - (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString 311 | { 312 | unsigned int bufferSize; 313 | unsigned int selfLen = [self length]; 314 | unsigned int aStringLen = [aString length]; 315 | unichar *buffer; 316 | NSRange localRange; 317 | NSString *result; 318 | 319 | bufferSize = selfLen + aStringLen - aRange.length; 320 | buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); 321 | 322 | /* Get first part into buffer */ 323 | localRange.location = 0; 324 | localRange.length = aRange.location; 325 | [self getCharacters:buffer range:localRange]; 326 | 327 | /* Get middle part into buffer */ 328 | localRange.location = 0; 329 | localRange.length = aStringLen; 330 | [aString getCharacters:(buffer+aRange.location) range:localRange]; 331 | 332 | /* Get last part into buffer */ 333 | localRange.location = aRange.location + aRange.length; 334 | localRange.length = selfLen - localRange.location; 335 | [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; 336 | 337 | /* Build output string */ 338 | result = [NSString stringWithCharacters:buffer length:bufferSize]; 339 | 340 | NSDeallocateMemoryPages(buffer, bufferSize); 341 | 342 | return result; 343 | } 344 | 345 | @end 346 | 347 | 348 | 349 | #ifdef main 350 | # undef main 351 | #endif 352 | 353 | 354 | /* Main entry point to executable - should *not* be SDL_main! */ 355 | int main (int argc, char **argv) 356 | { 357 | /* Copy the arguments into a global variable */ 358 | /* This is passed if we are launched by double-clicking */ 359 | if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { 360 | gArgv = (char **) SDL_malloc(sizeof (char *) * 2); 361 | gArgv[0] = argv[0]; 362 | gArgv[1] = NULL; 363 | gArgc = 1; 364 | gFinderLaunch = YES; 365 | } else { 366 | int i; 367 | gArgc = argc; 368 | gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); 369 | for (i = 0; i <= argc; i++) 370 | gArgv[i] = argv[i]; 371 | gFinderLaunch = NO; 372 | } 373 | 374 | #if SDL_USE_NIB_FILE 375 | NSApplicationMain (argc, argv); 376 | #else 377 | CustomApplicationMain (argc, argv); 378 | #endif 379 | return 0; 380 | } 381 | 382 | -------------------------------------------------------------------------------- /tools/fbview/build.sh: -------------------------------------------------------------------------------- 1 | g++ fbview.cpp SDLMain.m -framework SDL -framework Cocoa -g 2 | -------------------------------------------------------------------------------- /tools/fbview/fbview.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | //#undef main 7 | 8 | #define WIDTH 340 9 | #define HEIGHT 261 10 | 11 | int color_palette[] = { 12 | 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400, 13 | 0x503000, 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000, 14 | 0xBCBCBC, 0x0078F8, 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10, 15 | 0xAC7C00, 0x00B800, 0x00A800, 0x00A844, 0x008888, 0x000000, 0x000000, 0x000000, 16 | 0xF8F8F8, 0x3CBCFC, 0x6888FC, 0x9878F8, 0xF878F8, 0xF85898, 0xF87858, 0xFCA044, 17 | 0xF8B800, 0xB8F818, 0x58D854, 0x58F898, 0x00E8D8, 0x787878, 0x000000, 0x000000, 18 | 0xFCFCFC, 0xA4E4FC, 0xB8B8F8, 0xD8B8F8, 0xF8B8F8, 0xF8A4C0, 0xF0D0B0, 0xFCE0A8, 19 | 0xF8D878, 0xD8F878, 0xB8F8B8, 0xB8F8D8, 0x00FCFC, 0xF8D8F8, 0x000000, 0x000000 20 | 21 | }; 22 | 23 | std::vector g_Frames; 24 | 25 | int g_currentFrame = 0; 26 | 27 | 28 | // GHDL seems to have the same binary format as ModelSim, but with an additional header 29 | void probe_header(FILE* file) 30 | { 31 | char* line = 0; 32 | size_t len; 33 | getline(&line, &len, file); 34 | 35 | if (line && strstr(line, "GHDL-BINARY-FILE")) { 36 | getline(&line, &len, file); 37 | } else { 38 | rewind(file); 39 | } 40 | 41 | free(line); 42 | } 43 | 44 | void load_frames(const char* filename) 45 | { 46 | FILE* file = fopen(filename, "rb"); 47 | 48 | if (!file) { 49 | perror("fopen"); 50 | exit(1); 51 | } 52 | 53 | probe_header(file); 54 | 55 | int img_len = WIDTH * HEIGHT * 6; 56 | 57 | char img_buf[img_len]; 58 | 59 | while (fread(img_buf, 1, img_len, file) == img_len) { 60 | puts("found image"); 61 | 62 | SDL_Surface* surface = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT, 32, 0xff0000, 0xff00, 0xff, 0); 63 | SDL_LockSurface(surface); 64 | 65 | for (int y = 0; y < HEIGHT; y++) { 66 | for (int x = 0; x < WIDTH; x++) { 67 | int pixelValue = 0; 68 | for (int bit = 0; bit < 6; bit++) { 69 | switch (img_buf[(y * WIDTH + x) * 6 + bit]) { 70 | // case 0: 71 | // TODO: Handle Unknown/Invalid bit value 72 | // break; 73 | case 2: 74 | // Zero - do nothing 75 | break; 76 | case 3: 77 | pixelValue += (1 << (5 - bit)); 78 | break; 79 | default: 80 | printf("Unknown bit value %x\n", img_buf[(y * WIDTH + x) * 6 + bit]); 81 | } 82 | } 83 | // printf("Computed Pixel Value %x\n", pixelValue); 84 | int* pixels = (int*)surface->pixels; 85 | // pixels[y * surface->w + x] = pixelValue; 86 | pixels[(surface->h - 1 - y) * surface->w + (surface->w - x - 1)] = color_palette[pixelValue]; 87 | } 88 | } 89 | 90 | SDL_UnlockSurface(surface); 91 | g_Frames.push_back(surface); 92 | } 93 | 94 | } 95 | 96 | void draw() { 97 | // SDL_ 98 | SDL_Surface* video = SDL_GetVideoSurface(); 99 | 100 | SDL_BlitSurface(g_Frames[g_currentFrame], 0, video, 0); 101 | } 102 | 103 | void updateTitle() { 104 | char title[256]; 105 | snprintf(title, 256, "Frame %d of %lu", g_currentFrame + 1, g_Frames.size()); 106 | SDL_WM_SetCaption(title, 0); 107 | } 108 | 109 | int main(int argc, char* argv[]) 110 | { 111 | if (argc < 2) { 112 | puts("Usage: fbview simdump.out"); 113 | exit(1); 114 | } 115 | 116 | SDL_Surface *screen; 117 | SDL_Event event; 118 | 119 | if (SDL_Init(SDL_INIT_VIDEO) < 0 ) return 1; 120 | 121 | SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); 122 | 123 | if (!(screen = SDL_SetVideoMode(WIDTH, HEIGHT, 0, SDL_SWSURFACE | SDL_DOUBLEBUF))) 124 | { 125 | SDL_Quit(); 126 | return 1; 127 | } 128 | 129 | load_frames(argv[1]); 130 | 131 | int lastTime = SDL_GetTicks(); 132 | 133 | int running = 1; 134 | while(running) 135 | { 136 | 137 | while(SDL_PollEvent(&event)) 138 | { 139 | switch (event.type) 140 | { 141 | case SDL_QUIT: 142 | running = 0; 143 | break; 144 | case SDL_KEYDOWN: 145 | switch (event.key.keysym.sym) { 146 | case SDLK_q: 147 | running = 0; 148 | break; 149 | case SDLK_LEFT: 150 | if (g_currentFrame > 0) { 151 | g_currentFrame--; 152 | } else { 153 | g_currentFrame = g_Frames.size() - 1; 154 | } 155 | updateTitle(); 156 | break; 157 | case SDLK_RIGHT: 158 | if (g_currentFrame < g_Frames.size() - 1) { 159 | g_currentFrame++; 160 | } else { 161 | g_currentFrame = 0; 162 | } 163 | 164 | updateTitle(); 165 | break; 166 | } 167 | } 168 | 169 | } 170 | 171 | 172 | int curTime = SDL_GetTicks(); 173 | 174 | float dt = float(curTime - lastTime) / 1000.0; 175 | lastTime = curTime; 176 | draw(); 177 | SDL_Flip(screen); 178 | 179 | SDL_Delay(15); 180 | 181 | 182 | } 183 | SDL_Quit(); 184 | 185 | return 0; 186 | } 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /tools/file_rom.py: -------------------------------------------------------------------------------- 1 | from myhdl import * 2 | 3 | rom = tuple(map(lambda c: ord(c), file("smb.nes").read())) 4 | rom = rom[:8192] 5 | 6 | def file_rom(clk, address, data, CONTENT): 7 | 8 | @always(clk.posedge) 9 | def logic(): 10 | data.next = CONTENT[int(address)] 11 | 12 | return logic 13 | 14 | -------------------------------------------------------------------------------- /tools/romconv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | filename = sys.argv[1] 6 | basename = filename.split(".")[0] 7 | 8 | f = file(filename) 9 | header = f.read(4) 10 | assert(header == "NES\x1a") 11 | prg_size = ord(f.read(1)) 12 | chr_size = ord(f.read(1)) 13 | f.read(10) 14 | 15 | def write_coe(name, bytes): 16 | fo = file(name, "w") 17 | fo.write("memory_initialization_radix=16;\nmemory_initialization_vector=\n") 18 | for i in range(0, bytes - 1): 19 | fo.write(hex(ord(f.read(1)))[2:] + ",\n") 20 | fo.write(hex(ord(f.read(1)))[2:] + ";\n") 21 | 22 | def write_dat(name, bytes): 23 | fo = file(name, "w") 24 | for i in range(0, bytes): 25 | byte = f.read(1) 26 | fo.write('{0:08b}\n'.format(ord(byte))) 27 | 28 | 29 | write_dat(basename + "_prg.dat", 16384 * prg_size) 30 | #write_coe(basename + "_chr.coe", 8192 * chr_size) 31 | 32 | write_dat(basename + "_chr.dat", 8192 * chr_size) 33 | --------------------------------------------------------------------------------