The Sega Master System’s graphics chip was designed to be an upgrade to the TMS9918A that its predecessor the SG-1000 used. The result is mostly but not 100% compatible—the biggest visible difference when running SG-1000 code on an SMS is that the colors are different and mostly dimmer. This is a result of trying to pack the TMS’s deeply wacky palette (including a “light red” that could not even be properly represented in a composite video signal!) into the SMS’s more systematic 6-bit RGB palette. Beyond that, however, the SMS is a very credible superset of its predecessor not just on the video chip but across the board.
This rarely mattered, though—Sega’s own documentation for the Master System basically did not admit that the backwards-compatibility modes existed at all and just …
The Sega Master System’s graphics chip was designed to be an upgrade to the TMS9918A that its predecessor the SG-1000 used. The result is mostly but not 100% compatible—the biggest visible difference when running SG-1000 code on an SMS is that the colors are different and mostly dimmer. This is a result of trying to pack the TMS’s deeply wacky palette (including a “light red” that could not even be properly represented in a composite video signal!) into the SMS’s more systematic 6-bit RGB palette. Beyond that, however, the SMS is a very credible superset of its predecessor not just on the video chip but across the board.
This rarely mattered, though—Sega’s own documentation for the Master System basically did not admit that the backwards-compatibility modes existed at all and just instructed developers to configure the VDP in its most modern mode. We, however, have already looked at the 4 graphics modes offered by the TMS9918A, both from the standpoint of a casual user and from the standpoint of an assembly language programmer. We know where we’ve been, so we may more clearly see how far Sega took it.
One of the particular things Sega did was dramatically simplify the intended programming interface. Where the SG-1000 had four different graphics modes, numbered 0 through 3, essentially all Master System software was written using the new Mode 4. They would repeat this decision four years later, when the Genesis/Mega Drive would retain Mode 4 for playing old Master System games but would do all of its own work in a single mode they named “Mode 5.”
My plan is to cover this new graphics mode over two posts. In this post, I will outline the overall capabilities of the chip and how it is programmed, much like my guide to the TMS9918A linked above. Next week, I’ll walk through two sample programs where we put first the VDP and then the entire system through its paces.
AN IMPORTANT TERMINOLOGY NOTE: I have encountered some inconsistencies when describing the TMS’s graphics modes and control bits. I am following TI in naming the graphics modes 0,1,2,3 as Graphics 1, Text, Multicolor, and Graphics 2, but I am following everybody else in treating “bit 0” as the least significant bit and “bit 7” as the most. When looking in debuggers in your own work, pay more attention to bit positions than to precise names like “M2 mode bit.”
Communicating With the VDP
The SMS’s VDP appears to the system much like the SG-1000’s TMS9918A. The core interface is two bidirectional 8-bit communication ports: a Control/Address/Status Port at $BF and a Data Port at $BE. There is also an interrupt signal mapped to the IRQ. Through these ports we have write-only access to 11 control registers, read-only access to a status register, read/write access to 16KB of VRAM, and write-only access to 32 bytes of color RAM (CRAM). The protocol for working with all of these is as follows:
- To read the status register, simply read the VDP control port. This also clears the status register and acknowledges any pending video interrupts. If you wish to consult the value of this multiple times in a frame, be sure to cache it somewhere in memory.
- To write a control register, write the new 8-bit value for that register to the control port, then add 128 (
$80) to the number 0-10 of the register you wish to write and write that value as well. - To read from VRAM, write the 14-bit address of the start of your data block to the control port low byte first. (The top two bits must be zero, so this value will be in the
$0000–$3FFFrange.) You may then read your data block a byte at a time out of the data port. - To write to VRAM, add the value 16384 (
$4000) to the address that starts your destination block, and write that value (in the range$4000–$7FFF) to the control port low byte first. You may then write your data block a byte at a time into the data port. - To write to CRAM, add the value 49152 (
$C000) to the address that starts your destination block, and write that value (in the range$C000–$C01F) to the control port low byte first. You may then write your data block a byte at a time into the data port.
During blanking periods, or when the display is not active, there are no timing restrictions on any of these; we may write either port as fast as we can chain together OUT instructions. While the display is active, accesses to the data port must be 29 cycles apart, and when reading VRAM a similar gap must separate the last write to the control port and the first read from the data port. Allowing 4 cycles for the actual signal to be stable, and subtracting the 12 cycles of the usual OUT (nn),A instruction, such instructions should be separated by at least 21 cycles worth of code. This is not usually a burdensome restriction, but it does mean that we cannot use the bulk-transfer instructions outside of VBLANK.
(The previous paragraph contradicts Sega’s official manual, which states that there is a 16-cycle delay required between writes during the blanking period. This is almost certainly not the case; some hardware tests from 2014 put the numbers as more like 3 cycles in blanking and 23 without, but the confidence those testers had in their results made me uneasy. The timings I give above are just slightly more generous to the programmer than the TMS9918A’s more solidly-documented requirements.)
Canonical VRAM Layout
The TMS9918A arranged VRAM into individual tables to hold various kinds of graphics data, and it offered considerable flexibility in laying them out. A big part of that is that it wanted to remain basically functional even when only 4KB of VRAM was available on the system. The Master System has no such concerns, and so while many tables are still relocatable in Mode 4, you are ultimately going to be using all of VRAM for it and there was a canonical layout provided by both Sega’s documentation and the system initialization code.
- The Pattern Table defines the shapes of the tile graphics. It holds 512 32-byte characters, covering the full 16KB of VRAM. Background tiles in theory get the full run of all of VRAM, but in practice some of this space has to be used for other data.
- The Sprite Pattern Table only holds 256 32-byte chracters, and thus in practice is half of the full pattern table. Under default configuration, it uses the first 256 entries of the Pattern Table, covering the first 8KB of RAM.
- The Name Table is a 32×28 grid of 16-bit values, occupying
$3800–$3EFFin VRAM. This defines the background tiles in the display. - The Sprite Attribute Table takes up the last 256 bytes of VRAM (
$3F00–$3FFF). This defines the relevant information about the sprites on the screen.
The TMS9918A had rough equivalents for all of these (plus an additional Color Table that Mode 4 doesn’t need thanks to dedicated CRAM) and five of its eight registers (registers 2-6) were dedicated to defining their locations. In Mode 4 we simply set registers 2-5 to $FF and register 6 to $FB and this gives us the canonical layout above.
The VDP Registers
The VDP offers one read-only status register and eleven write-only control registers. We’ll look at these in turn and compare them to its predecessor.
The Status Register
In Mode 4, the status register has three bits with meaningful values.
- Bit 7, the most significant bit, is set if the vertical interrupt has fired.
- Bit 6 is set if some sprites were truncated due to too many being on a scanline.
- Bit 5 is set if any two sprites have collided anywhere on the screen.
These descriptions could just as well describe the status register of the TMS9918A as well, but there are some differences in interpretation:
- The frame interrupt is no longer the only IRQ source, so the interrupt processor may well find that Bit 7 isn’t 1. In practice, the bit is 0 on a raster interrupt and 1 on a frame interrupt.
- Bit 6 now fires when there are nine or more sprites on a raster line instead of five.
- Bits 4-0 no longer hold meaningful data; the TMS9918A would use this space to stash which sprite got redacted when bit 6 was set, but Mode 4 has 64 sprites instead of 32 and that’s not enough space.
VDP Mode Registers
The original 8 registers from the TMS9918A are still here, with three newcomers as well. We’ve already seen registers 2 through 6; they locate the various tables in VRAM and they have stock values in Mode 4. The two mode control registers, 0 and 1, have been dramatically expanded and a few bits mean something different in Mode 4. Some of these bits haven’t changed, or at least we can pretend they haven’t changed:
- Register 0 bit 0 still controls mixing graphics with an external signal. The SMS cannot do this so this bit must always be zero.
- Register 1 bit 7 used to switch between 4KB and 16KB memory models. Mode 4 demands 16KB and this bit must always be 1.
- Register 1 bit 6 is still the display-enable bit. Turn this off when doing bulk VRAM initialization and turn it on when it’s time for the display to be visible.
- Register 1 bit 5 still enables the VBLANK interrupt.
- Register 1 bit 1 still makes sprites multi-tile. This behaves slightly differently in mode 4; large sprites are made of only two tiles instead of four, arranged with the first sprite above the second.
- Register 1 bit 0 used to magnify sprites. Mode 4 doesn’t support this and this bit must always be zero.
The remaining defined bits on the TMS9918A were the M1/M2/M3 bits that controlled the screen mode. The master system adds an M4 mode bit and in Mode 4 these mode bits mean something else:
- Register 0 bit 2 is the Mode 4 selector bit. If this is on, we are in Mode 4, so it effectively should always be on.
- Register 1 bit 3 changes from 192 pixel rows to 224. If this is set we will use the entire 32×28 name table for our display, though we also will likely have overscan issues.
- Register 1 bit 4 changes from 192 pixel rows to 240. I’m not really sure how this works, but it does seem like it will require moving the Sprite Attribute Table out from its canonical location to make room for it.
- Register 0 bit 1 enables the previous two bits. Without this set, the display is forced to 192 pixel rows.
The remaining five bits in register 0 control behavior that is unique to Mode 4 and the SMS.
- Register 0 bit 3 shifts every sprite 8 pixels to the left. This is like the TMS9918A’s “early clock” bit but it affects every sprite instead of individual ones.
- Register 0 bit 4 enables raster interrupts. It’s possible to get IRQs mid-frame on particular scanlines, and this bit has to be on for that to happen.
- Register 0 bit 5 blanks the leftmost column of the display. Since there is no “wiggle room” in the horizontal part of the display—all 32 columns of the name table are used—this gives us a buffer to let us scroll sprites smoothly off the left side of the screen and to draw the next column of a horizontally-scrolling map. With this capability, there’s generally very little call for the “shift every sprite 8 pixels left” option.
- Register 0 bit 6 puts a status window at the top of the screen. The two top rows of the name table are immune to scrolling with this bit set, allowing an easy status window with no raster trickery.
- Register 0 bit 7 puts a status window on the right of the screen. The right eight columns of the name table are immune to scrolling with this bit set, allowing a status window of the sort we otherwise never see in the 8-bit world.
The remaining VDP Registers
There are still four registers left, and they hold byte-level values:
- Register 7 used to hold the foreground/background colors in Mode 1 (Text Mode), and its background color also defined the border color and what “transparent” background tiles would be in the graphics modes. In Mode 4, the “background” color (the least significant 4 bits) defines the border color. This color is taken from from the last 16 colors defined in CRAM.
- Register 8 holds the horizontal scroll value. Since the name table and the display are both 256 pixels wide, unless the leftmost 8 pixels are being blanked, the entire width of the background is still on-screen, just rotated.
- Register 9 holds the vertical scroll value. The name table is 28 rows long compared to the display’s 24, so we have more wiggle room here. The C64 suffered from utterly wild anomalies regarding mid-frame changes of vertical scroll—seriously, the glitches surrounding it are so comprehensive that I struggled to find a single article to link there—and the NES scrollers retained some glitches into the 1990s, so this is potentially a fraught register to mess with. Sega addressed this problem with a sledgehammer: this value is locked into place in the vertical blanking period and any writes to it are ignored until the next frame.
- Register 10 controls mid-screen interrupts. Writing a value into this register will count out that many scanlines starting from the start of the frame and trigger an IRQ at that point. It then reloads the value to try again, neatly splitting the screen into a series of equally-spaced lanes. Values larger than the screen disable these mid-screen interrupts.
Initial Settings
The Master System had a brief boot ROM that ran some code during power-on. Its main job was to figure out which ROM slot the cartridge was in and transfer control to it, but it did also pre-load the VDP registers with sensible values. You’d want to replicate those writes yourself during startup, though, since the boot ROM did not return on RESET. Here are the values for each register:
- Register 0:
$36. We’re in Mode 4, raster interrupts and vertical screen expansion are both permitted, and we’re blanking the leftmost column. - Register 1:
$A0. 8×8 sprites, a 192-scanline display, the display is disabled, and VBLANK interrupts are on (though will not happen since interrupts should have been disabled during boot). - Registers 2-5:
$FF. This does most of the work of setting up the VRAM layout. - Register 6:
$FB. This does the rest of that work. - Register 7:
$00. The border color is palette 1, color 0. - Registers 8-9:
$00. The screen is unscrolled. - Register 10:
$FF. No raster interrupts.
Palettes
The Master System offers 64 colors, expressed in binary as a 6-bit RGB value 00BBGGRR. Mode 4 provides two 16-color palettes in CRAM drawn from this gamut. Palette 0, in locations 0-15, is usable only by background tiles. Palette 1, in locations 16-31, is also usable by background tiles but is also the only palette that sprites or the border may use.
CRAM is write-only from the CPU’s standpoint so if you need to keep track of color values at runtime, mirror them in the main RAM.
Patterns
Sprites and backgrounds alike are made out of 8×8 16-color tiles. In Mode 4, patterns can be anywhere in VRAM at any time, but in the normal configuration, the background has 448 tiles available in the range $0000–$37FF and sprites can use the first 256 of those.
Each tile takes up 32 bytes of space, and is represented as four interleaved bitplanes: each row is four bytes of data, with each byte representing one bit of the color code in an 8-pixel row. The least-significant bit appears first, so, for example, a pixel row that has the colors 1 through 8 left to right would look like this:
10101010 = $AA
01100110 = $66
00011110 = $17
00000001 = $01
--------
12345678
A tile that repeated this pattern for each row would repeat the sequence for each row:
defb $aa,$66,$17,$01,$aa,$66,$17,$01,...
or equivalently:
defd $011766aa,$011766aa,$011766aa,...
This is different from what we saw on the Genesis (which would encode each pixel four bits at a time in chunks, with something more like DEFD $12345678) or on the NES, which lays down one bitplane for the entire character before moving on to the next.
Name Tables
The name table defines the background tiles in the 32×28 internal screen. Two bytes are assigned per tile, for a total of 32×28×2 = 1,792 bytes. Each 16-bit little-endian value is organized as follows:
- The bottom nine bits are the tile number. 512 tiles at 32 bytes per tile covers the entire 16KB of VRAM; patterns can come from anywhere, as far as the name table is concerned.
- The
$200and$400bits flips tiles horizontally or vertically when set, respectively. - The
$800bit selects the palette to use for this tile; if 0 it uses the first 16 colors, and if 1 it uses the last 16 colors (the same ones the sprites and border uses). - The
$1000bit sets sprite priority; if this bit is set the sprites treat this tile as foreground instead of background. Color 0 is “transparent” in the sense that sprites are visible behind it, but Color 0 from the relevant palette will still show up if there’s no sprite in the way. This differs from the TMS9918, where “transparent” colors in the background tiles would let the border color shine through, and from the NES, where color 0 in every palette is aliased to the same color. - The top three bits are reserved for programmer use, allowing the developer to tag tiles in VRAM as desired.
Sprites
Sprites notionally have their own pattern table independent of the background tiles, but in practice since background tiles reach through all of VRAM and the Sprite Pattern Table only has 256 entries and starts at either $0000 or $8000, in the default configuration sprites use the same pattern table as the backgrounds but are restricted to the first 256 entries.
Some aspects of sprite display are globally configured through the control registers; the important one is the $02 bit of Register 1, which if set causes sprites to be 8×16 instead of 8×8. The default global configuration for the rest of sprite operations is generally fine as-is.
Per-sprite information is held in the Sprite Attribute Table, which in the default configuration is placed at the final 256 bytes of VRAM, at $3F00. There are three bytes of data per sprite, and this data is organized a little awkwardly:
- The first 64 bytes of the table are every Y coordinate for each of the 64 sprites, in order. The Master System inherits the quirk of the TMS9918A where the top row of the display is actually Y coordinate -1 (
$FF), not 0. - The next 64 bytes are unused.
- The last 128 bytes of the table hold the rest of the data, collated by sprite this time. Even bytes hold the X coordinates, and odd bytes hold the pattern number for the first pattern of the sprite. (so,
$3F80holds sprite 0’s X coordinate,$3F81holds its pattern number,$3F82holds sprite 1’s X coordinate, and so on up to$3FFFholding sprite 63’s pattern number.)
In 8×16 sprite mode, pattern numbers must be even. This specifies the pattern that will be the topmost tile, with the subsequent tile pattern being then drawn as the bottom half.
Much like on the TMS9918A, a sprite with a Y coordinate of 208 ($D0) not only is not drawn but also halts all further sprite processing.
Partially visible sprites are pretty straightforward. Sprites may be scrolled vertically on and off the screen by passing values that make it only partially visible; a sprite doesn’t become completely invisible until its coordinate reaches 191 ($BF) or -17 ($EF). Any value between those is invisible—just watch out for the sprite list terminator value at $D0. Sprites may be scrolled horizontally off the screen by setting X values from $F9 through $FF (scrolling off the right side) or with values from $01 through $07 (hiding part of the sprite behind the force-blanked first column of the screen). The Early Clock bit interferes with this and doesn’t give you full freedom of movement the way blanking the first column does, and you’ll likely want that blanking for scrolling anyway.
Putting It to Use
This has been another “Reference Card” kind of post, much like the TMS9918A guide was. As a result, there’s a lot of bulleted lists but not a real sense of how it all comes together into a single display. Next time, we will work through some sample programs to put the whole system through its paces.