NESe (pronounced "Nessie") is a NES emulator based on the e6502 emulator, also written in C with a focus on speed and portability for use on embedded platforms, especially ARM.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
9.3KB

  1. #include <stdio.h>
  2. #include "ppu.h"
  3. #include "mapper.h"
  4. #include "cart.h"
  5. // TODO: Retain open bus bits?
  6. #define ppu_reg_ctrl (0x2000U)
  7. #define ppu_reg_mask (0x2001U)
  8. #define ppu_reg_status (0x2002U)
  9. #define oam_reg_addr (0x2003U)
  10. #define oam_reg_data (0x2004U)
  11. #define ppu_reg_scroll (0x2005U)
  12. #define ppu_reg_addr (0x2006U)
  13. #define ppu_reg_data (0x2007U)
  14. #define oam_reg_dma (0x4014U)
  15. uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) {
  16. uint8_t val = 0;
  17. if (ppu_reg_status == addr) {
  18. val = ppu->status;
  19. ppu->latch = 0;
  20. } else if (oam_reg_data == addr) {
  21. OAM_LOG("PPU: OAM READ %02x > %02x\n", ppu->oam_addr, val);
  22. val = ((uint8_t*)ppu->oam)[ppu->oam_addr];
  23. } else if (ppu_reg_data == addr) {
  24. if (ppu->addr >= nes_ppu_mem_pal_start) {
  25. PPU_LOG("PPU: PAL READ %04x > %02x\n", ppu->addr, val);
  26. uint8_t pal_addr =
  27. (ppu->addr - nes_ppu_mem_pal_start) &
  28. (nes_ppu_mem_pal_size - 1);
  29. val = ppu->palette[pal_addr];
  30. } else {
  31. val = ppu->data;
  32. if (ppu->addr < nes_ppu_mem_vram_start) {
  33. PPU_LOG("PPU: CHR MEM READ %04x > %02x\n", ppu->addr, val);
  34. ppu->data = nes_chr_read(ppu->mapper,
  35. ppu->map_data,
  36. ppu->addr);
  37. } else if (ppu->addr < nes_ppu_mem_vram_start +
  38. nes_ppu_mem_vram_size) {
  39. VRAM_LOG("PPU: VRAM READ %04x > %02x\n", ppu->addr, val);
  40. ppu->data = nes_vram_read(
  41. ppu->mapper, ppu->map_data,
  42. ppu->addr - nes_ppu_mem_vram_start
  43. );
  44. } else if (ppu->addr < nes_ppu_mem_pal_start) {
  45. PPU_LOG("PPU: BLANK READ %04x > %02x\n", ppu->addr, val);
  46. /*
  47. ppu->data = *(nes_map_chr_addr(ppu->mapper,
  48. ppu->addr));
  49. */
  50. }
  51. }
  52. ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ?
  53. 32 : 1;
  54. }
  55. PPU_LOG("PPU: <-R $%04x %02x\n", addr, val);
  56. return val;
  57. }
  58. // Copy t to v (decoded into scroll_x, control)
  59. static inline void nes_ppu_internal_copy_x(nes_ppu* ppu) {
  60. ppu->control &= ~(1U);
  61. ppu->control |= ((ppu->t >> 10) & 1U);
  62. ppu->scroll_x = ((ppu->t & 0b11111U) << 3) |
  63. (ppu->x & 0b111U);
  64. }
  65. // Copy t to v (decoded into scroll_y, control)
  66. static inline void nes_ppu_internal_copy_y(nes_ppu* ppu) {
  67. ppu->control &= ~(0b10U);
  68. ppu->control |= ((ppu->t >> 10) & 0b10U);
  69. ppu->scroll_y = ((ppu->t & 0x03E0U) >> 2) |
  70. ((ppu->t & 0x7000U) >> 12);
  71. }
  72. void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) {
  73. PPU_LOG("PPU: W-> $%04x %02x\n", addr, val);
  74. if (ppu_reg_ctrl == addr) {
  75. PPU_LOG("PPU: CTRL %02x\n", val);
  76. ppu->control &= ppu_Control_Nametable_Mask;
  77. ppu->control |= (val & ~ppu_Control_Nametable_Mask);
  78. ppu->t &= ~(0xC00U);
  79. ppu->t |= (uint16_t)(val & ppu_Control_Nametable_Mask) << 10;
  80. } else if (oam_reg_addr == addr) {
  81. OAM_LOG("PPU: OAM ADDR %02x\n", val);
  82. ppu->oam_addr = val;
  83. } else if (oam_reg_data == addr) {
  84. OAM_LOG("PPU: OAM %02x < %02x\n", ppu->oam_addr, val);
  85. ((uint8_t*)ppu->oam)[ppu->oam_addr++] = val;
  86. } else if (ppu_reg_mask == addr) {
  87. PPU_LOG("PPU: Mask %02x\n", val);
  88. ppu->mask = val;
  89. } else if (ppu_reg_scroll == addr) {
  90. if (ppu->latch) {
  91. PPU_LOG("PPU: Scroll Y %02x\n", val);
  92. ppu->t &= 0b0000110000011111U;
  93. ppu->t |= (uint16_t)(val & 0b00000111U) << 12;
  94. ppu->t |= (uint16_t)(val & 0b11111000U) << 2;
  95. } else {
  96. PPU_LOG("PPU: Scroll X %02x\n", val);
  97. ppu->t &= ~(0b11111U);
  98. ppu->t |= (val & 0b11111000U) >> 3;
  99. ppu->x = (val & 0b111U);
  100. }
  101. ppu->latch = !ppu->latch;
  102. } else if (ppu_reg_addr == addr) {
  103. VRAM_LOG("PPU: ADDR %02x\n", val);
  104. if (ppu->latch) {
  105. ppu->t &= 0xFF00U;
  106. ppu->t |= val;
  107. ppu->addr = (ppu->t & 0x3FFFU);
  108. VRAM_LOG("PPU: VRAM ADDR %04x\n", ppu->addr);
  109. nes_ppu_internal_copy_x(ppu);
  110. nes_ppu_internal_copy_y(ppu);
  111. PPU_LOG("PPU: Scroll N, X, Y = %d, %d, %d\n",
  112. ppu->control & ppu_Control_Nametable_Mask,
  113. ppu->scroll_x, ppu->scroll_y);
  114. } else {
  115. ppu->t &= 0x00FFU;
  116. ppu->t |= (uint16_t)(0x3FU & val) << 8;
  117. }
  118. ppu->latch = !ppu->latch;
  119. } else if (ppu_reg_data == addr) {
  120. if (ppu->addr >= nes_ppu_mem_size) {
  121. printf("!!! PPU: MEM OOB: %04x\n", ppu->addr);
  122. } else if (ppu->addr >= nes_ppu_mem_pal_start) {
  123. uint8_t pal_addr =
  124. (ppu->addr - nes_ppu_mem_pal_start) &
  125. (nes_ppu_mem_pal_size - 1);
  126. PPU_LOG("PPU: PAL %02x < %02x\n", pal_addr, val);
  127. ppu->palette[pal_addr] = val;
  128. if ((pal_addr & 0b11) == 0) {
  129. ppu->palette[pal_addr & 0xF] = val;
  130. }
  131. } else if (ppu->addr >= nes_ppu_mem_vram_start) {
  132. uint16_t vram_addr = ppu->addr -
  133. nes_ppu_mem_vram_start;
  134. if (vram_addr >= nes_ppu_mem_vram_size) {
  135. printf("!!! PPU: VRAM OOB: %04x\n", vram_addr);
  136. vram_addr &= (nes_ppu_mem_vram_size - 1);
  137. }
  138. #ifdef DEBUG_VRAM
  139. {
  140. int page = vram_addr >> 10;
  141. int loc = vram_addr & 0x3FFU;
  142. if (loc < 960) {
  143. int y = loc / 32;
  144. int x = loc % 32;
  145. printf("PPU: VRAM Page %d @ %2d,%2d : %02x\n",
  146. page, y, x, val);
  147. } else {
  148. printf("PPU: VRAM Attr %2d : %02x\n",
  149. loc - 960, val);
  150. }
  151. }
  152. #endif // DEBUG_VRAM
  153. // printf("PPU: VRAM %04x < %02x\n", vram_addr, val);
  154. nes_vram_write(ppu->mapper, ppu->map_data,
  155. vram_addr, val);
  156. } else {
  157. // PPU_LOG("PPU: CHR MEM WRITE %04x > %02x\n", ppu->addr, val);
  158. nes_chr_write(ppu->mapper, ppu->map_data,
  159. ppu->addr, val);
  160. }
  161. ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ?
  162. 32 : 1;
  163. }
  164. }
  165. void nes_ppu_reset(nes_ppu* ppu) {
  166. ppu->control = 0;
  167. ppu->mask = 0;
  168. ppu->latch = 0;
  169. ppu->scroll_x = 0;
  170. ppu->scroll_y = 0;
  171. ppu->data = 0;
  172. ppu->frame = 0;
  173. ppu->scanline = 0;
  174. ppu->cycle = 0;
  175. }
  176. int nes_ppu_init(nes_ppu* ppu, const nes_cart* cart) {
  177. ppu->mapper = cart->mapper;
  178. ppu->map_data = cart->map_data;
  179. ppu->status = 0;
  180. ppu->oam_addr = 0;
  181. ppu->addr = 0;
  182. ppu->t = 0;
  183. nes_ppu_reset(ppu);
  184. return 0;
  185. }
  186. nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles) {
  187. nes_ppu_Result result = ppu_Result_Running;
  188. int next_cycle = ppu->cycle + cycles;
  189. if ( ppu->scanline < nes_ppu_render &&
  190. ppu->cycle < 257 && next_cycle >= 257) {
  191. nes_ppu_internal_copy_x(ppu);
  192. if (ppu->scanline == 0) {
  193. nes_ppu_internal_copy_y(ppu);
  194. }
  195. }
  196. if ( NULL != ppu->mapper->scanline &&
  197. ppu->scanline < nes_ppu_render &&
  198. (ppu->mask & (ppu_Mask_Back | ppu_Mask_Sprite)) &&
  199. ppu->cycle < 260 && next_cycle >= 260) {
  200. ppu->mapper->scanline(ppu->map_data);
  201. }
  202. ppu->cycle = next_cycle;
  203. while (ppu->cycle >= nes_ppu_dots) {
  204. ppu->cycle -= nes_ppu_dots;
  205. if ( ppu->scanline < nes_ppu_prerender &&
  206. (ppu->frame & 1)) {
  207. // Prerender line is one dot shorter in odd frames
  208. // Fake it by incrementing the cycle in this case
  209. // TODO: Only if actually rendering
  210. ppu->cycle++;
  211. }
  212. ppu->scanline++;
  213. if (ppu->scanline >= nes_ppu_frame) {
  214. ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit);
  215. ppu->hit_line = 0;
  216. ppu->hit_dot = 0;
  217. ppu->scanline = 0;
  218. ppu->frame++;
  219. result = ppu_Result_VBlank_Off;
  220. } else if (ppu->scanline >= nes_ppu_active) {
  221. ppu->status |= ppu_Status_VBlank;
  222. if (ppu->control & ppu_Control_VBlank) {
  223. result = ppu_Result_VBlank_On;
  224. }
  225. } else {
  226. if ( ppu->scanline > nes_ppu_prerender &&
  227. ppu->scanline < nes_ppu_render) {
  228. ppu->scroll_y++;
  229. if (ppu->scroll_y >= nes_ppu_render_h) {
  230. ppu->scroll_y -= nes_ppu_render_h;
  231. ppu->control ^= 0b10;
  232. }
  233. }
  234. result = ppu_Result_Ready;
  235. }
  236. }
  237. if ( ppu->hit_line > 0 &&
  238. ppu->scanline > ppu->hit_line &&
  239. ppu->cycle >= ppu->hit_dot) {
  240. if (!(ppu->status & ppu_Status_Hit)) {
  241. PPU_LOG("PPU: Hit @ %d, %d\n", ppu->hit_line + 1, ppu->hit_dot);
  242. PPU_LOG("(Currently @ %d, %d)\n", ppu->scanline, ppu->cycle);
  243. }
  244. ppu->status |= ppu_Status_Hit;
  245. }
  246. return result;
  247. }