Jump to content
Sign in to follow this  
youki

Causes of VRAM Corruption ?

Recommended Posts

Hi,

 

What are all the possible causes of VRAM corruption on the colecovision.

 

On my current i project after a little modification i did , the VRAM corrupts after a while.

 

Does this occurs when a nmi is trigger at the moment you are accessing the VDP to put or read a char for instance?

 

Is there other possible causes?

 

What would be the "best pratices" to avoid this kind of glitches?

 

Thanks

Share this post


Link to post
Share on other sites

I just use a bit flag that's always at $7000 for any routines that write to VRAM so you can test BIT X, (IY+$00) without having to load anything. The NMI does an EX AF, AF' to preserve flags then checks the flag, if it's set it jumps to the end. After the VRAM write you remove the flag and do a IN A, (BF) to acknowledge the interrupt. That cleared up all my VRAM corruption problems. I used a MSX decompression routine and had to use this method or half the time it would decompress garbage data since it would get interrupted in the middle of writes by the NMI.

Share this post


Link to post
Share on other sites

This is probably because of how interrupts work with the VDP.

 

If you are trying to write bytes to the VRAM, you give it the two halves of the address separately followed by the data. If you read the VDP status register during this, it will screw up the process. Same for reading VRAM.

 

On MSX and SG-1000, the VDP is a maskable interrupt, so you can prevent this easily.

 

But some brainiac at Coleco put it on NMI, which is rather stupid, since you need to read the VDP status register to acknowledge the interrupt so you can get another one. This is the number one problem with converting games from other systems.

 

What you need is some way to tell your interrupt handler to not read the VDP status register when you're about to write to VRAM, but then you need to read it somewhere else.

 

 

In RAM you need two flags: a "lockout" flag during VDP access, and an "NMI happened" flag. If you're not using IY for anything important, you can use BIT/SET/RES (IY+nn), etc. to manipulate them without using other registers.

 

Start by clearing both flags, then set the lockout flag whenever you are about to use the VDP and clear it when you are done. After clearing it, test the "NMI happened" flag. If it's set, read the VDP status register.

 

Inside the NMI routine, if the lockout flag is NOT set, read the status register right away. Then always set the "NMI happened" flag.

 

 

Black Onyx wasn't much of a problem because it had only three or four routines to write data to the VDP.

 

When I was porting Girl's Garden, it would write the VDP address, do some stuff, then poke a few bytes of data to the VDP. It would use EI and DI instructions all over the place. Then it would use the HALT instruction to wait for the next interrupt! That made things a lot more tricky, and I had to make EI, DI, and HALT into macros. And because it used so many of them, I made the macros do RST instructions to go to the messy interrupt code.

Share this post


Link to post
Share on other sites
On MSX and SG-1000, the VDP is a maskable interrupt, so you can prevent this easily.

 

But some brainiac at Coleco put it on NMI, which is rather stupid, since you need to read the VDP status register to acknowledge the interrupt so you can get another one. This is the number one problem with converting games from other systems.

 

When I first got into Coleco work, seeing this assertion (written elsewhere by others!) really threw me, because I took it at face value. I came from the TI-99/4A where it's also maskable on the CPU. So I did all the little workarounds for being unable to predict when the interrupt would occur (what worked best was doing all my VDP access immediately after VBLANK, and making sure it could all be done in less than one frame).

 

But it's actually not strictly true. You CAN prevent interrupts from occurring any time you like. You can't mask it on the Z80, but you CAN mask it on the VDP. I think since every other system lets you mask on the CPU, we all forget about that bit?

 

Bit 2 in VDP Register 1 has the VDP enable bit. When cleared, the VDP will not assert the interrupt pin on vertical blank. When you set this bit again, if the interrupt bit in the status register is set, then the interrupt pin is asserted.

 

With this knowledge, you can delay the interrupt as long as you need to. You can also do away with the interrupt, and poll the status register, if you prefer - note that reading the status register will still clear the interrupt bit.

 

So if that's useful, this is the code that I'm using, patterned after the rest of the library that I was using.

 

// enables and disables the vblank interrupt so we can run safely 
void cv_vdpout(const unsigned char reg, const unsigned char data1);
extern uint8_t cv_vdpreg[2];	// in CV lib

void cv_set_vint_active(bool active) {
cv_vdpout(0x1, cv_vdpreg[1] = (active ? 0x20 : 0x0) | (cv_vdpreg[1] & ~0x20));
}

 

Note this library has a cache of VDP registers 0 and 1 named cv_vdpreg[], and has a function named cv_vdpout() used to set the register, but it should be pretty easy to adapt it to another lib. I've been moving to my own code lately just to make it a little more familiar to my TI background. ;)

 

All my code does is take the cached version of VDP register 1, mask out bit 0x20 (VDP Interrupt enable), and then if you pass in 'active' as true, ORs the value in. It then saves the result back in the VDP register cache, and passes it to cv_vdpout, which writes the resulting value to the register.

 

Note, of course, that if you are using any BIOS or library functions that need to run on the vertical blank, that disabling the interrupt may interfere with those.

 

hth!

Edited by Tursi

Share this post


Link to post
Share on other sites

Sure, you could disable the interrupt enable in the VDP, but then you could miss a vertical retrace. The longer it's disabled, the better chance of missing a retrace. Then your frame timing will be off, and your game will lag randomly.

 

This is cool for something like you'd write in BASIC, but not for an action game.

Share this post


Link to post
Share on other sites

Sure, you could disable the interrupt enable in the VDP, but then you could miss a vertical retrace. The longer it's disabled, the better chance of missing a retrace. Then your frame timing will be off, and your game will lag randomly.

 

This is cool for something like you'd write in BASIC, but not for an action game.

 

That's also true for disabling it on the CPU, as you noted in your previous post:

 

On MSX and SG-1000, the VDP is a maskable interrupt, so you can prevent this easily.

 

You don't miss the retrace at all. It will remain pending until you read the status register. It is possible to miss an entire frame, but it is regardless of where in the chain you mask it.

 

Given that this is what most other systems do, and working with it for better than 25 years, as well as using it in my ColecoVision Super Space Acer port, I can assure you that it works just fine for action games.

Share this post


Link to post
Share on other sites

My previous post was a bit snippy, and I realize you may have misinterpreted my information.

 

We're actually talking about several different issues here now, in terms of your reply. We're talking about avoiding the problems caused by the NMI interrupting VDP access, we're talking about delaying the VDP interrupt so that access is safe, and we're talking about missing VDP interrupts.

 

So first we should talk about how the interrupt system works in this configuration.

 

The TMS99x8A datasheet gives this description of the interrupt out line:

 

The VDP INT output pin is used to generate an interrupt at the end of each active-display scan, which is about 1/60 second for the TMS9918A/9928A and 1/50 second for the TMS9929A. The INT output is active when the Interrupt Enable bit (IE) in VDP Register 1 is a 1 and the F bit of the status register is a 1. Interrupts are cleared when the status register is read.

 

The interrupt enable (IE) bit in VDP Register 1 is the bit I was describing above. It's simply a mask bit for the vertical blank status bit (F). The datasheet tells us this about (F):

 

The F status flag in the status register is set to 1 at the end of the raster scan of the last line of the active display. It is reset to a 0 after the status register is read or when the VDP is externally reset. If the Interrupt Enable bit in VDP Register 1 is active (1), the VDP interrupt output (INT) will be active (low) whenever the F status flag is a 1.

 

Note that the status register needs to be read frame by frame in order to clear the interrupt and receive the new interrupt of the next frame.

 

It further notes, on clearing this bit:

 

The status register may be read at any time to test the F, C and 5S status bits. Reading the status register will clear the interrupt flag, F. However, asynchronous reads will cause the frame flag (F) to be reset and therefore missed. Consequently, the status register should be read only when the VDP interrupt is pending.

 

Besides a bit of confusion in calling F both the interrupt flag and the Frame Flag (they mean the same thing here), the important points of this at the VDP is fairly straightforward:

 

1) The VDP F bit is set at the end of each active display (ie: at the beginning of vertical blanking)

2) If the IE bit in VDPReg 1 is set, then F is mapped to the external interrupt line, with '1' making it active

3) The F bit is cleared by reading the status register.

 

Therefore, the VDP asserts the external interrupt only when both IE and F are set. The order in which they are set is irrelevant, so it's okay to set IE after F has been set, it will still assert the interrupt.

 

INT (active low) = IE NAND F

 

Now, the Z80 NMI pin is edge triggered, that is, it only considers an NMI to have occurred when the line changes from inactive (high) to active (low). Therefore, if the VDP keeps the pin low, subsequent NMIs will not be detected. This is what permits the software workaround to function.

 

So now let's cover what happens in the software workaround case. We will assume an interrupted VDP operation.

 

-First, we set our VDP lockout flag. We start working with the VDP.

-An end of frame maliciously occurs, and code execution jumps to the NMI. The NMI code sees the lockout flag, and does not touch the VDP this frame. It sets the NMI happened flag, and returns.

-We finish working with the VDP.

-We check the NMI happened flag, and we see that it was set. We read the VDP status register to clear the interrupt, and the next frame is ready to come in.

-And, finished, we clear the VDP lockout flag.

 

This works, but it takes user code and two flags (which in most people's code will be two bytes). The NMI needs to do extra work, and the main code needs to do extra work every time it touches the VDP. The system operates normally, but with this approach you can't put any VDP work into the NMI function unless it's okay for that code not to execute some frames.

 

An approach that I tried instead was to do all VDP access inside the NMI itself. This works as long as you can afford the RAM to double-buffer things (in particular I double-buffered the sprite table, which makes flicker easier, and I also copied graphics data out of ROM each frame in response to pointers to animate sprites). You must be able to guarantee that you can do all your work in a fixed amount of time - preferably less than one frame, since you are probably still doing game code after you return! This can be difficult to arrange, since the game logic MUST NOT touch the VDP, it can only queue up requests. It looks like this:

 

-Main code is humming along, working only with CPU RAM - buffers and pointers for VDP work may be set but the vDP can not be accessed directly.

-VDP interrupt fires, and the NMI takes over.

-CPU buffers are updated with VDP RAM (usually writing to it). The NMI starts in the vertical blank, so you can use fast writes if you keep it small enough. All pending VDP work is done here or postponed, if it is determined to be too much. (You have to come up with your own way to determine that).

-VDP status register is read to clear the interrupt

-NMI returns

 

So no flags or extra code, but there are restrictions on the main code not being allowed to touch the VDP, and the NMI has a time restriction which must be honored.

 

So what happens on other systems, who mask the VDP interrupt in the CPU? There are two trains of thought I have run across.

 

The first, is to only disable the VDP interrupt for brief periods of time, and only when working on the VDP. For instance, like so:

 

-Main code is running, but wants to talk to the VDP (except status register)

-interrupts are disabled

-VDP is accessed - VDP interrupt line asserts during this code, but CPU has masked it so ignores it

-interrupts are re-enabled

-CPU processes still-active VDP interrupt

 

There's nothing wrong with this, really, except that you have to wrap all VDP access with a couple of extra instructions. But it keeps latency to a minimum without requiring any structure to the code, and on most CPUs the interrupt enable/disable is a single instruction.

 

The other way I've seen is to leave interrupts disabled for all processing, and enable them only briefly during the loop. This is how the TI-99/4A usually writes code. In that case, you get something like this:

 

-Main code is running, is free to talk to VDP at will (except status register)

-VDP interrupt line asserts during loop, but CPU has masked it, so ignores it

-Interrupts are enabled

-CPU processing pending VDP interrupt

-Regardless as to whether an interrupt occurred, interrupts are disabled

-Loop repeats

 

Literally, the enable/disable is two lines of code side-by-side. On the TI it's:

 

LIMI 2 * enable VDP int

LIMI 0 * disable VDP int

 

If an interrupt is pending, it gets its chance to execute only during that window. This is okay, because it's either ready or not, and if it is, it doesn't matter that the slice is small.

 

The downside, again, is that your loop time needs to be fairly consistent so that you get a steady framerate. If you process for 2 frames, then enable the interrupt, you only get one interrupt, so to be steady you must ALWAYS process for 2 frames.

 

For the Coleco, the first option of these two is not possible. We can't mask NMI on the Z80, and it's not safe to temporarily disable interrupts on the 9918, because it takes two writes to do it. You might be interrupted even while you are disabling them.

 

The second option is not too bad, though. It's always safe to turn the interrupts on if they are off already, and if the interrupt fires, then it is going to be safe to turn them back off again. However, there is a tiny race for the case where the interrupt didn't fire.. it might fire as you are turning the interrupts off again. This would only happen if your processing took you close to the edge, and you never synchronize with the vertical blank.

 

All of the above generally ignores synchronization - which is a major reason for using the vertical blank. This helps you get a real-time source for your game. To that end, one of the flags mentioned way above - NMI Happened - is very useful. If you instead change the enable/disable sequence into a "wait for frame", then you can do this very safely (as long as your NMI takes less than a full frame to run). The NMI just needs to set that flag. That sequence looks like this:

 

-Clear NMI Happened

-Enable VDP IE bit in Register 1 to allow interrupts

-Wait for NMI Happened to be set

-Disable VDP IE bit in Register 1 to disable interrupts

-Main code loop continues

 

This is pretty decent. It's safe so long as the NMI is guaranteed to take less than a full frame to execute, because then you can predict when the disable write takes place. It gives the main code freedom to talk to the VDP (except the status register, this is needed to trigger the NMI).

 

If your title needs the status register, you have a couple of options. For one, you can disable interrupts altogether - clear the IE bit and run at will. You can manually check for the F bit in the VDP Status register to provide synchronization. The same loop above will work, but instead of an NMI happened flag, you just poll the status register until F is set.

 

The other way, and a common one, is to cache the VDP Status register in RAM somewhere when the NMI reads it to clear the interrupt.

 

One final approach, that I noticed in the Mario and Zelda disassemblies from the NES, I think is rather clever and something I might try myself. They run the entire game logic on the vertical blank itself -- this is pretty much the opposite of disabling interrupts and polling. The main code loop just spins, waiting for an NMI to fire. When the NMI does fire, it runs through the entire game logic for one frame before clearing the status bit and returning.

 

This has the same limitation of timing - you need to run a relatively consistent timing, ideally under one frame, but it works well, and has no limitations on VDP access at all, while giving you hardware timing without any variables.

 

I'm getting tired and probably rambling - hope that's clear to someone. ;)

  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...