Close

FINALLY!

A project log for Improbable AVR -> 8088 substitution for PC/XT

Probability this can work: 98%, working well: 50% A LOT of work, and utterly ridiculous.

eric-hertzEric Hertz 02/12/2017 at 04:294 Comments

Seriously, look at the number of experiments...


The signs were all there:

 #warning "Have we considered the AVR input-latch delay...?!"

I like that one the best...

Yahknow, it only reminded me to look into the actual cause of the problem *every time I compiled* since the very beginning. (It's been years, but I still haven't gotten used to the new gcc warning-output... it's too friggin' verbose. Does it really need to show the column of the line that starts with \#warning, then show the \#warning *output* as well?)

------------

So, here's what it boils down to: early-on (when I wrote that warning) I figured there was basically no way in he** this could possibly be less than at least a few instructions... Read the port-input-register, AND it, invert it, set up a loop, etc...

while(!(READY_PIN & READY_MASK)) {};

In reality it compiles to TWO instructions!

sbis  0x16, 3 
rjmp  .-4     
And, technically, the read-input instruction grabs the value stored in its latch during a *previous* clock-cycle.

I had a vague idea it might need to be looked into, but I was *pretty sure* I had it right... but not *certain*.

Then the friggin' ORDEAL with h-retrace seemingly confirming various theories... And boy there were many... Friggin' rabbit-hole.

So, then... this last go-round I had it down...

After the rabbit-hole revealed its true nature, I found a place of utmost-oddity...

Inserting the following 'if(awaitRetrace)' before retraceWait() somehow magically fixed the problem, in its entirety. I mean, the rabbit-hole got me close. Visually I was getting no errors, but it was still taking about 500 retries (out of 2000 bytes) to get it there. But somehow inserting that if-statement before the already-running retraceWait() fixed it 100%.

So, at that point I'd inlined bus88_write() which didn't have any significant effect... maybe the error-rate dropped, slightly, that first-try, but whatever benefit was easily wiped-out by the uncanny results of various seemingly irrelevant changes, such as adding a nop between write and read-back.

But I left it inline, anyhow, thinking maybe it'd reduce the number of instructions between detecting the h-retrace and actually-writing. (In case, maybe, all those pushes/pops, jumps, returns might've taken longer than the actual h-retrace).

So, when I added the if(awaitRetrace), what I'd *planned* on doing was reusing the cga_writeByte() function for two purposes, I'd have two tests: one like I'd been doing--drawing while the screen is refreshing (waiting for retrace), and a new one, where I'd shut down the video-output, and only *then* write the data. Then compare the error-rates of the two. (Thus, in the second case, I'd have to disable retrace-waiting, since there would be no retracing).

That was the idea.

All I did was add an argument for awaitRetrace to cga_writeByte, and add that if-statement. Then called it with my normal code, (I hadn't written-up the stop-output portion yet) expecting it to function *exactly* the same (but figuring that'd be a lot to ask, since merely inserting a nop in random places was enough to cause dramatic changes).

Instead, 100% accurate. For the first time in this entire endeavor.

How the HECK could an if-statement whose argument is 1--wrapping a function which was previously called anyhow--fix the problem?!

So I looked into the assembly-listings side-by-side.

And, sure-enough, the optimizer did its thing. I'll come back to what it did.

Usually I'd write things like bus88_write in assembly to make *certain* the optimizer wouldn't interfere with timing. An easy example is

uint8_t byteB = byteA&0xAA;
PORTA = byteA;
PORTB = byteB;

Which the optimizer might-well compile as, essentially:

PORTA = byteA;
byteA &= 0xAA;
PORTB = byteA;
That way it doesn't have to use an additional register. But, the problem is, now PORTB's write occurs *two* cycles after PORTA's, rather than the *one* intended.

I haven't been in the mood to refresh myself on inline-assembly, lately, so instead I'd been keeping an eye on the assembly-output nearly every time I made a change to the bus88_write() function to make sure that sorta stuff didn't happen. And it didn't.

Until I inlined it... And even *then* it didn't.

Until I added that if-statement around a completely different function.

I'll spare *all* the details, but here's the important bit:

The key-factor is that the inlined h-retrace function, since it now belongs in an if-statement, is moved *below* its place in the C code,and jumped-to. It happens to be placed *exactly* before the inlined write-function's READY-testing routine. (Why there? Dumb-luck again, I guess).

So, the one on the *right* side is the "new" one that works. It has the if-test, and remapped the h-retrace functionality to literally right before the READY testing in bus88_write's functionality. And to get around that, it inserted a jump. And that jump takes two clock-cycles.

Thus, delaying the testing of the READY-pin by two bus-clocks, and thus, apparently, giving the CGA card enough time to output its "NOT READY! WAIT!" Flag.

------------

So the be-all-end-all solution to literally a week's worth of harassment (and over 80 experiments!) is to insert a danged NOP (or two) where I once thought it might be wise to consider inserting a NOP (or two).

-----------

And all that stuff about h-retrace's seemingly fixing the problem at times...? Just dumb luck I guess. Worse than dumb-luck, BAD dumb-luck, until it *finally* sorta redeemed itself for all that harassment, here...

Discussions

Thomas wrote 02/12/2017 at 07:11 point

A most interesting writeup, thanks!
Takeaways:
* know the limitations of your µC architecture when you need to solve problems beyond the limit of the Nyquist criterium (i.e. synchronized systems)
* an optimizing C-compiler isn't the best way to code your way to a solution of such problem. A compiler update may break your code in an unexpected way
* perseverance is the key to success


  Are you sure? yes | no

Eric Hertz wrote 02/13/2017 at 04:42 point

If anyone takes anything away from this experience besides "dude, that guy's crazy" then that's on them.

I've my own set of takeaways, but that probably stems from the fact that for me this is ridiculous-venture number 3741.

Technologically, though, those are some decent takeaways. Wouldn'ta thought of this as a place to consult Nyquist!

  Are you sure? yes | no

Thomas wrote 02/13/2017 at 06:11 point

Synchronized systems are "magical" if done right. However, sometimes the magic not only involves sacrificing chicken, but also a pact with the devil ;-)

  Are you sure? yes | no

Eric Hertz wrote 02/13/2017 at 06:54 point

Oh lordy, I hope the latter didn't happen...

  Are you sure? yes | no