21

Please consider the code in Super NES Programming/Initialization Tutorial/Snes Init.

Here is an excerpt:

 stz     $2113   ; Plane 3 scroll x (first 8 bits)
 stz     $2113   ; Plane 3 scroll x (last 3 bits) #$0 - #$07ff

From the comments, this seems intuitive enough. However, when I look at the instruction set reference provided in 6.5 LDA LDX LDY STA STX STY STZ:

STZ store zero into the memory location specified by the operand."

and

Note that no registers are affected by STZ.

From this it seems that STZ with some constant operand should yield the exact same result every time it is executed, so what is the point in executing it with the same operand immediately after the previous instruction?

What is the variable here? Time? 0x2113 is a hardware register, and according to Super NES Programming/SNES Hardware Registers, it is the "BG 4 Horizontal Scroll Offset". This doesn't seem to match the comment in the source for it being "Plane 3 scroll x".

Anyway, does 0x2113 perhaps map to different actual memory/hardware registers depending on time? E.g. at some horizontal scanline the 0x2113 points to a particular horizontal scroll offset?

But even if that were true, I don't see why issuing the instruction only twice like that (and apparently with no code to check timing) would do any good.

Clearly I'm missing something obvious, or there is something weird with the source. I'm pretty certain it is me though, since the source assembles and runs perfectly fine.

4
  • 2
    If the source code comments mean anything, you are writing to a 16-bit register (of which 11 are used) mapped to one byte-size memory location. Thus to get all the bits you have to write twice.
    – RichF
    Jul 21, 2018 at 17:49
  • 2
    Wow, this seems like such an error prone protocol! What happens when such a double write is interrupted? How does the device decide which is the low byte and which the high byte???
    – Erik Eidt
    Jul 22, 2018 at 4:42
  • Remember that the 65C816 has no opcodes dedicated to I/O. Instead I/O is done by reading and writing "memory" addresses that are attached to hardware instead of RAM. That hardware can do basically anything it wants when it is read or written.
    – cjm
    Jan 2, 2021 at 22:36
  • @Erik : I suppose disabling interrupts and then doing a pair of writes is one way to get around any potential "desync".. It may be that display registers on the SNES can only be written during H/VBlank anyway.
    – PaulHK
    Oct 25, 2022 at 7:58

3 Answers 3

24

This is a “write-twice” register, a 16-bit register mapped into a single byte, which takes two 8-bit stores to populate. There are a number of these on the SNES, with varying write orders (high or low byte first). The Super Famicom Development Wiki describes the behaviour of this particular family of registers thus:

Note that these are "write twice" registers, first the low byte is written then the high. Current theory is that writes to the register work like this:

   BGnHOFS = (Current<<8) | (Prev1&~7) | (Prev2&7);
   Prev1 = Current;
   Prev2 = Current;

or

   BGnVOFS = (Current<<8) | Prev1;
   Prev1 = Current;
5
  • Thanks a lot, this was actually the most simple case (only two writes). In other examples there are loops that will write to the same hardware register e.g. 20 times, again, same operand. I can only assume that this will make it write to 20 different actual memory locations, in a similar manner as with a 16-bit register taking two 8-bit stores to populate? This happens e.g. with the OAMDATA (0x2104) when the OAMDATA is being cleared in initialization code. Jul 21, 2018 at 22:17
  • 1
    Yes, OAM data writes (and reads) increment an internal pointer, so successive stores write data to increasing OAM addresses. Jul 21, 2018 at 22:30
  • 4
    If I remember rightly -- it's been 25 years or so -- some of the 16-bit external registers are set up as "write twice to the same address" and others are "write to successive addresses"; the overall impression was that the hardware was designed by a few different teams that didn't communicate well. Jul 21, 2018 at 23:06
  • @RussellBorogove: It seems weird that the CPU/Audio chip supports block-transfer DMA for the OAM, but the PPU can't arrange to use the same address for feeding OAM data as display data and palette data. If the audio subsystem had a more versatile timer function, and the CPU package had included a chip-select output for the PPU, I think it would have been possible to easily modify the 6502 core to allow single-cycle DMA by simply making it so that if the D flag was set and the I flag was clear, the CPU would assert the PPU chip select (which would also be asserted for...
    – supercat
    Jun 21, 2021 at 4:33
  • ...some address range) but treat all fetched bytes as some arbitrary two-byte two-cycle opcode. To perform DMA, push a status register value with D set and I clear, the address of the region to be copied, set a timer to interrupt the CPU after some number of cycles, and execute an RTI. The CPU would then fetch and ignore "instructions" until an interrupt hits.
    – supercat
    Jun 21, 2021 at 4:36
6

As RichF already mentioned, it's a 16-bit register, which means there are two 8-bit values needed. Usually such a register would be mapped into two addresses. The NES is a bit special here, as it only uses one memory address which results in the need of two consecutive writes with the same address (*1).

The tutorial shows an example by setting the background colour where two bytes are written into $2122, low byte first.

What is the variable here? Time?

Exactly, instead of spreading the halves across a spacial (address) dimension, they are separated temporal (sequence) on the same location. First low byte then high byte. The initialization example shows nicely (*2) that these writes can be mixed in-between other instructions, as long as the sequence of low first, high next is kept.

Anyway, does 0x2113 perhaps map to different actual memory/hardware registers depending on time?

Within the graphics hardware, yes. Depending on the sequence to either 'half' of the full 11-bit register.

E.g. at some horizontal scanline the 0x2113 points to a particular horizontal scroll offset?

No. It's still just a simple register (pair). No complex issue beside saving address space that is.


The whole idea here is that an 8 bit CPU has to use multiple writes to fill any hardware registers with more than 8 bit. At the same time, writing such a register only partial doesn't make much sense (*3). Also, spreading them over multiple addresses won't save code. Therefore arbitrary large registers can be 'hidden' behind a single address to save address space without any negative effect.

Bottom line, 76 registers could be squeezed into 62 ($3E) addresses, keeping the decoding simple.

Additionally, depending on the register it's possible to set up a general logic to buffer all writes (for such wide registers) until the last (here the second) and then commit the whole value at once. From a programing view this is not only handy, but mandatory for registers that change value fast (like counters) to exactly synchronize their start. While this also can be achieved with two addresses, it may save circuitry on the hardware side when handled in a generic fashion (*4).


*1 - It's a bit like with configuring Intel style peripheral chips where a series of control words are written into a single register address for initialisation.

*2 - For example, register $210E vs. $210F

*3 - We're talking panned effects, not fine tuned hacks.

*4 - It's also a classic case for a fox vs. hedgehog design choice, this being the latter.

3

In addition to the other answers, I'd like to explain why write-twice registers exist on the SNES.

The SNES's CPU, a Ricoh 5A22, is 16 bit internally but has an external 8 bit data bus. Thus it can only write 8 bits at a time, meaning it needs two writes to update a 16 bit register.

It's often undesirable to have the possibility of a 16 bit register being half written. In this case it could cause the display to flicker or be momentarily corrupted. Having write-twice registers prevents this happening, by buffering the first write and only copying the whole 16 bit value into the real register on the second write.

Some architectures implement this using two consecutive memory addresses, with writes to one being buffered. The SNES uses a single 8 bit register to save address space, as the chipset only has an 8 bit address bus (256 possible registers) between it and the CPU.

3
  • 1
    Could you clarify about the 8 bit address bus? I suspect you were speaking in the context of the connections between the CPU and the custom chips which has a limited number of address lines used, whereas the full address bus coming out of the CPU is 24 bits. Jul 23, 2018 at 15:09
  • I assume it isn't so much the actual address bus limitations that necessitate this scheme, but wanting to avoid bank switching and still being able to reach all of WRAM and all of the hardware registers from a single bank? Still, it seems like they overdid it with there being a significant amount of unused addresses: en.wikibooks.org/wiki/Super_NES_Programming/… Jul 24, 2018 at 16:51
  • Chances are some of those chips were not developed specifically for the Super Famicom, or were developed by a separate team who just did their best to minimise address space footprint as a matter of course.
    – user
    Jul 25, 2018 at 8:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .