aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorlonkaars <loek@pipeframe.xyz>2023-02-05 16:49:17 +0100
committerlonkaars <loek@pipeframe.xyz>2023-02-05 16:49:17 +0100
commitbc02cf257fe205c89213334f447d30eb8dc5c5e2 (patch)
treeb2253f94e966d3217a1459fdde0d4fc084aba8f1 /docs
parentdb30b8eacce34d6fc901f50982acade589eecd04 (diff)
add more PPU design
Diffstat (limited to 'docs')
-rw-r--r--docs/ppu.drawio1
-rw-r--r--docs/research.md144
2 files changed, 135 insertions, 10 deletions
diff --git a/docs/ppu.drawio b/docs/ppu.drawio
new file mode 100644
index 0000000..17ea515
--- /dev/null
+++ b/docs/ppu.drawio
@@ -0,0 +1 @@
+<mxfile host="Electron" modified="2023-02-05T15:24:04.957Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36" etag="Sw92OaPQb4kISNLgk6ge" version="20.8.16" type="device" pages="2"><diagram name="level-1" id="DBE48RpXtx8JnoEt3ekA">7Vpdb9owFP01SNsDVb6BxwApndQiBLRbH11iQjaDI8cU2K+f09iExEBIQwma6EMV31xfO8fnXPu6remd+bpHQDB7wi5ENU1x1zW9W9M0w2yx35FhExuaViM2eMR3Y5OaGEb+X8iNCrcufReGKUeKMaJ+kDZO8GIBJzRlA4TgVdptilF61AB4UDKMJgDJ1p++S2f8K0wlsT9A35uJkVWFv5kD4cwN4Qy4eLVj0p2a3iEY0/hpvu5AFGEncIn73R94u50YgQt6Sgdz84PSX3S8VMerV4T7lqM81HmUd4CW/IPbINyEOrPdD3p2TbMQC95+I+zJi54Gg2f+OXQjMCJ4uXBhNIzCPFYzn8JRACbR2xUjBbPN6Byxlsoe5WmLOUBC4XrHxD+jB/EcUrJhLuJtk0PKOaXz5ipZINXittnu4ghHwEnhbUMnuLEHDl0BGDUJxq4fBghEk8ZLGiypjOS3l579vXIs9ROwVC6KpS5hORo/6dq90lKHHRnGzhUQUm/dtRonwHin6JdEUm1IUL4M7ae63e0OJczgwrWjfMlaEwTC0J+kYUpjCl2WJXlnTOgMe3gBkJNY2xC9fQSLUPRZPmUmgHxvwUwITqOVYwkxiPpPEVzzodshJfjPNtNG47J58V1B/3gPCN1ps6YH6QASn0EGSbTK/sITC8/DGKLjNq4SB061D/IgxEsygfmMjeeSn2wj7E5KcxKFzD1CFDYCEaD+e3rb2kcpPt4A++w7k8GMgxwWQWIceL/djSYTSmtKoYxMKL5s2VCMBR8pU7gFkUN4ZNKNdPJSs1tgMX/2EM8g01tMB0+nIaS1rCy3K1dCqc39Su3aY/tKlVpMXMdVfJPeuaRnGtlQjS+SnilNWjVy1JfbpToBGrL+6qPXfkcWX66c0vJLfB4xDrhCf0NKN5z/YElxWr9CfCRmWgl1iIIpTx1aMXWIuMV4X5RhuplO1uZxdh13r45ZlsSshxuzKmaWlaZKI4dZR92rY5Z8Zhg63RutriZhibr51IyV8a+OWC2JWL2h4/Rv1LoeauUUOjn+FRY6isSt9uOzc6PW9VAr5xSf418dtTSZWk7fbj/uIdfFC+jPc+tW4GYKXClU64sKXEsaKacCye1RoTbkS/Xq69vS2jg17+qf0kZJ0hvqITYUJr0h/32hno11JtaXLLpzjiVaGe+r0ZJ8V1R9Rf+fa0mTBJC91iyjpexmdC4tlbtmUHN2mzLe13KFoe27wxg5Y1lKtyPbZY9spnKn7PyoKfpYnxSf2lKOxjnb0a2Q8I67f4U4WDP5v5/YPfnnKd35Bw==</diagram><diagram id="lj51MlK96fuJFRP7cw84" name="level-2">1Zhbs5owEMc/DY9nhouKPnrQY2tr6xw79bGTwgqpgTAxXj99FwkqBtueViu+OMk/t81vF5bVcLx4MxAkjUY8AGbYZrAxnJ5h241mB38zYZsL7ZabC6GgQS5ZR2FCd6BEU6lLGsCiNFFyziRNy6LPkwR8WdKIEHxdnjbjrHxqSkLQhIlPmK5OaSAjdYumedTfAQ2j4mTLVCMxKSYrYRGRgK9PJKdvOJ7gXOateOMBy9gVXPJ1LxdGD4YJSOSfLHhNv3/ytkP4xqMfg9FwKMzZ7snOd1kRtlQX9j5+UPbKbQFB8GUSQLaPaTjP64hKmKTEz0bX6HXUIhkz7FnY1O1Spq5ASNicSMrOAfAYpNjilE3Z+dtyd310QEdJ0Qn7widEuTw87Hukgg0F5g2QHA3S10E3I0TDhGSrQ0hAEMnF/cnVDF1DQ/dM/Hm4x7J/auOUL2gdyJ0HnXtnck2N3AsXUEdy5zF3b3ItjdyY+nIpIDssCOD+xOr2gnM1YpNUIATU8JZIrAbMahZkbT0nvHZH/0Ypg0Hx06PLMLGgJnl6HXbO38Wb5d6KXkdPC9eGF9MgyDa6Cj+7zK+h83Mr+Nm3wlf47zQ5PBK/1r35WRq/L5RBjEwehuGh0PgNxJs9w5ZeO4yxbJISy7WHodj+f5FoD6bcnXhi3vu8m9uyP6L91ZMeiNP3Yw0fXk6WGS2k4HPwOMPvP6eX8CTjM6OMnUlEMfQRGSbxX8CtckrZbRdYV3jkchI/D+KKN2mrgr/zdv7YPdbW+7GTPyic/k8=</diagram></mxfile> \ No newline at end of file
diff --git a/docs/research.md b/docs/research.md
index 7eca454..1c13739 100644
--- a/docs/research.md
+++ b/docs/research.md
@@ -20,36 +20,160 @@ this chip's features and limitations:
- composite output at 256x224 @ 60Hz (NTSC) or 256x240 @ 50Hz (PAL)
- 256 tiles per tilemap (with 8x8 tiles)
-- 2 tilemaps
+- 2 tilemaps (pattern tables)
- 4 palettes per sprite with 64 possible colors
-- 512x480 background palette with scrolling
+- 512x480 background canvas with scrolling
- background scrolling splits
-- 64 total sprites on screen (8 per scanline)
+- 64 total sprites on screen (max 8 per scanline)
- sprites can be drawn before or after the background layer
- PPU control using DMA
- 8K character memory (tilemaps)
-- 2K nametable memory
+- 2K nametable memory (OAM for background tiles)
- 256B object attribute memory (OAM)
- tiles can be flipped using OAM
- no frame buffer
+### Usage
+
+The NES PPU has a lot of capabilities, so here's a quick run-down of how the
+PPU is used by games to produce pictures.
+
+On boot, the NES copies the contents of the so-called CHR-ROM (in game
+cartridge) into the PPU's pattern tables. The NES has two 256-tile tilemaps
+called the pattern tables which store all sprites in the game. By modifying the
+nametable memory directly, the game can control which tile, and which palette
+will be used for any given tile on the background layer. Because the background
+layer only displays tiles in a fixed grid, the nametable memory area is not
+very big.
+
+The same process happens for the foreground sprites, though these can each be
+positioned using screen coordinates, and don't have to conform to any grid. The
+NES also has some limitations on how many sprites can be drawn and how many
+palettes can be used. An important note is that the first color of any palette
+used on any foreground sprite, is treated as the transparency color. Our PPU
+also copies this behavior.
+
+The following is a small section of pseudocode, depicting a program that will
+display a triangle moving in a circle:
+
+```c
+unsigned frame = 0;
+
+void setup() {
+ // copy character rom to PPU
+ memcpy(CHR, PPU->CHR, 0x8000);
+ // character rom contains a sprite of a triangle at index 0
+
+ // set palette index 0 to have 4 random colors
+ PAL[0] = (palette) {
+ PALETTE_COLOR_25,
+ PALETTE_COLOR_F3,
+ PALETTE_COLOR_00,
+ PALETTE_COLOR_3D,
+ };
+ // copy palette data to PPU
+ memcpy(PAL, PPU->PAL, 0x40);
+}
+
+void loop() {
+ frame++; // increment frame counter
+
+ OAM[0] = (sprite) {
+ .x = sin(frame) * 20 + 10, // calculate circle position using frame counter
+ .y = cos(frame) * 20 + 10, // calculate circle position using frame counter
+ .pattern_index = 0, // triangle sprite
+ .palette_index = 0, // palette 0 (see setup)
+ .attributes = PPU_FX_FLIP_H | PPU_FX_FLIP_V, // flip horizontally and vertically
+ };
+
+ memcpy(OAM, PPU->OAM, 0x100); // update PPU with local copy of OAM
+}
+
+int main() {
+ setup();
+ while(1) loop();
+}
+```
+
## Custom PPU
Here's a list of features our PPU should have:
<!-- TODO: expand list with PPU spreadsheet -->
-- 256x240 @ 60Hz VGA output
-- single tilemap with room for 2048 tiles of 8x8 pixels
+- 256x224 @ 60Hz VGA output
+- single tilemap with room for 1024 tiles of 16x16 pixels
- 8 colors per palette, with 4096 possible colors (12-bit color depth)
-- 512x480 background palette with scrolling
-- **NO** background scrolling splits
-- 128 total sprites on screen (**NO** scanline sprite limit)
+- 512x448 background canvas with scrolling
+- NO background scrolling splits
+- 128 total sprites on screen (NO scanline sprite limit)
- sprites are always drawn on top of the background layer
- PPU control using DMA (dual-port asynchronous RAM)
-- tiles can be flipped using OAM
+- tiles can be flipped using FAM or BAM
- no frame buffer
- vertical and horizontal sync output
+Notable differences:
+
+- NES nametable equivalent is called BAM (background attribute register)
+- NES OAM equivalent is called FAM (foreground attribute register)
+- No scanline sprite limit
+
+ Unless not imposing any sprite limit makes the hardware implementation
+ impossible, or much more difficult, this is a restriction that will likely
+ lead to frustrating debugging sessions, so will not be replicated in our
+ custom PPU.
+- Sprites are 16x16
+
+ Most NES games already tile multiple 8x8 tiles together into "metatiles" to
+ create the illusion of larger sprites. This was likely done to save on memory
+ costs as RAM was expensive in the '80s, but since we're running on an FPGA
+ cost is irrelevant.
+- Single 1024 sprite tilemap shared between foreground and background sprites
+
+ The NES OAM registers contain a bit to select which tilemap to use (of two),
+ which effectively expands each tile's index address by one byte. Instead of
+ creating the illusion of two separate memory areas for tiles, having one
+ large tilemap seems like a more sensible solution to indexed tiles.
+- 8 total palettes, with 8 colors each
+
+ More colors is better. Increasing the total palette count is a very memory
+ intensive operation, while increaing the palette color count is likely slower
+ when looking up color values for each pixel on real hardware.
+- Sprites can be positioned paritally off-screen on all screen edges using only
+ the offset bits in the FAM register
+
+ The NES has a separate PPUMASK register to control special color effects, and
+ to shift sprites off the left and top screen edges, as the sprite offsets
+ count from 0. Our PPU's FAM sprite offset bits count from -15, so the sprite
+ can shift past the top and left screen edges, as well as the standard bottom
+ and right edges.
+- No status line register, only V-sync and H-sync outputs are supplied back to
+ CPU
+
+ The NES status line register contains some handy lines, such as a buggy
+ status line for reaching the max sprite count per scanline, and a status line
+ for detecting collisions between background and foreground sprites. Our PPU
+ doesn't have a scanline limit, and all hitbox detection is done in software.
+ Software hacks involving swapping tiles during a screen draw cycle can still
+ be achieved by counting the V-sync and H-sync pulses using interrupts.
+- No background scrolling splits
+
+ This feature allows only part of the background canvas to be scrolled, while
+ another portion stays still. This was used to draw HUD elements on the
+ background layer for displaying things like health bars or score counters.
+ Since we are working with a higher foreground sprite limit, we'll use regular
+ foreground sprites to display HUD elements.
+- Sprites are always drawn on top of the background layer
+
+ Our game doesn't need this capability for any visual effects. Leaving this
+ feature out will lead to a simpler hardware design
+
+### Hardware design schematics
+
+![PPU top-level design](../assets/ppu-level-1.svg)
+
+![PPU level 2 design](../assets/ppu-level-2.svg)
+
[nesppuspecs]: https://www.copetti.org/writings/consoles/nes/
[nesppudocs]: https://www.nesdev.org/wiki/PPU_programmer_reference
[nesppupinout]: https://www.nesdev.org/wiki/PPU_pinout