New CP/M-80 computer: Z80, SD cards, VGA, USB keyboard and power... Teensy 4.1

I've been working almost (no, not almost, ...) obsessively getting the fZ80 reliable. I had a huge random-memory-corrupt error, a true heisenbug, that I finally traced solidly to interaction between the IntervalTimer object and something in the FlexIO2VGA DMA and/or interrupt. I spent a month getting it first repeatable, then tracking it down.

It only happens on MP/M, though both CP/M and MP/M used that IntervalTimer, for a simple 50 Hz tick.

The immediate trigger was a full screen rewrite, after re-sizing windows, presumably the 50 Hz tick, then the z80 executed some random byte because an array read, after bank logic, array [index] or b= *ptr, simply returns the wrong value.

Here's the thing: on MP/M the "Z80" is running in 64K allocated in DMARAM, and 146K in ram1 (four 54K banks, one 16K fixed in DMARAM, etc). (I've used the full megabyte of the machine; about 400K for the VGA, 210 K for MP/M's four banks, etc.)

So VGA DMA is doing it's thing, IntervalTimer is interrupting at 50 Hz, and the main CPU task loop is doing high-speed large block writes into DMARAM, from ram1, while an interleaved task loop is doing high speed read and write to DMARAM. KABOOM! on thr 50Hz tick -- a byte read from DMARAM returns the wrong value.

I'm solving the Z80 50 Hz tick through other means (software timer sync'ed with one-second from the RTC for long term stability).

The bug was so nasty... I was doubting sanity and my skills. Nailed it though.

It's a big blob of complexity to reproduce, I'll snapshot that revision and maybe you can reproduce it.


MEANWHILE, lol, I've got the 120 page book done, minus some tweaks I'm making to the resize-screen keys, aesthetic stuff, the two-piece aluminum chassis top arriving hopefully today. Attached is what the base looks like. Working on a sticker for the front panel. Gonna have a dozen to sell.[/I]
WOW!! Sounds like maybe playing with interrupt priorities might shed some light on the problem or help. Just a thought if you have not tried yet. Other than that I'll play with it and see what happens. I've been in those nasty bug swatting situations that seemed to take forever to find and then one day you find it and it seems so obvious you think you have wasted all that time on something so simple to fix. But with some of these bugs and with all of the inter reactions between hardware and software you are lucky to catch it at all o_O When it is available I will download the latest fZ80 software and test. Nice work...
 
Sure you want flat black? I use Montana paints, the graffiti folk use them. Amazing colors. Here's the choice, lol.


I've been thinking something bright, and period-incorrect. Vivid red, Kalani orange, Electra violet, Valley green... I'll want to pick one or two colors for the dozen, but I also have some colors laying around: black, white, Bozai green (looks "surplus"), Kalani orange. Lol flat black works.

The top cover will be painted, the base aluminum with a clear coat. Rubber feet on bottom not shown here.
How about Vivid Red:D
 
I was going to say that bug sounds like a cache issue... then realized the original VGA class code didn't actually have any cache handling included in it....
It's probably better to use arm_dcache_flush() rather than arm_dcache_flush_delete(), the data only needs to be flushed from the cache to memory rather than flushed and evicted, it's not going to be updated by hardware other than CPU so is safe to stay in the cache and it eliminates the risk of throwing away other data if the specified size is somehow larger than the actual size of the framebuffer.
 
I was going to say that bug sounds like a cache issue... then realized the original VGA class code didn't actually have any cache handling included in it....
It's probably better to use arm_dcache_flush() rather than arm_dcache_flush_delete(), the data only needs to be flushed from the cache to memory rather than flushed and evicted, it's not going to be updated by hardware other than CPU so is safe to stay in the cache and it eliminates the risk of throwing away other data if the specified size is somehow larger than the actual size of the framebuffer.
Thanks for the tip @jmarsh. So instead of:
Code:
  arm_dcache_flush_delete(s_frameBuffer[frameBufferIndex], fb_height*_pitch);
Change it to:
Code:
  arm_dcache_flush(s_frameBuffer[frameBufferIndex], fb_height*_pitch);

Will try it out and see if there are any side effects. @tomicdesu In the VGA_4bit_T4.cpp file look for:
Code:
//==========================================
// Write frame buffer to VGA memory. (DMA)
//==========================================
void FlexIO2VGA::fbUpdate(bool wait) {
  arm_dcache_flush_delete(s_frameBuffer[frameBufferIndex], fb_height*_pitch);  <------ this line to be changed.
  set_next_buffer(s_frameBuffer[frameBufferIndex], _pitch, wait);
//frameBufferIndex ^=1;
}
 
OK will do!

Oh, I don't actually *understand* the VGA/DMA code! lol. Honestly I haven't tried; I worked around it; I have a ton of krap to do to get out from under this grown-insanely project! But I will not forget it.

I've snapshotted a version of my code that has the problem, and I'm fairly sure if I add the IntervalTimer thingie back in to the current version that it will fail, modulo heisenberg aspects. It bugs me! If I can't figure it out I'll try to reduce it to something that is repeatably testable without one of my boards under the teensy.
 
Yeah it's very weird. Now CP/M doesn't do this! And the Teensy code is the same! CP/M also has a (very simple) timer tick. ANd it can run multi-window, or not, so that code isn't it. This is why I was so baffled! I couldn't see how it wasn't MP/M related, which means 8080 code, or D.R.s code, etc.

The difference: MP/M is using all of RAM, including all of the DMAMEM -- CP/M uses only one 64K static array as Z80 address space.

MP/M uses up to 210K of ram spread between two arrays, one in ram1, the other in DMAMEM, and each Z80 memory-fetch or -write runs through an "MMU" subroutine that calcs the effective address (bank and Common vs banked) and hammers those two arrays quite quickly.

This is why I was exploring stack size issues; I'm using a lot of memory:

Code:
Memory Usage on Teensy 4.1:
  FLASH: code:155324, data:43392, headers:9152   free for files:7918596
   RAM1: variables:334112, code:150792, padding:13048   free for local variables:26336
   RAM2: variables:484000  free for malloc/new:40288

That memory usage, probably not common, has got to be it (famous last words...)
 
I wrote this up for myself, to go into the source. But it tells the tale above. linearly.

At the moment I'm using a very stripped out version of VGA4BIT, which I'd done initially looking for bugs that might write into my Z80 banks in the same RAM2 memory, like out of bounds array reference (it is C, after all! lol). Of course I found none, your code is tight and clean.

Anyway here's what I've put into my source. I'll revisit this in the future.

Code:
THE BIG INTERRUPT/MEMORY CORRUPT PROBLEM
06 nov 2025

  For a solid two months I chased a heisenbug, the symptom of which
  was Z80 execution of random instructions. The entire time the error
  was related, somehow, to VGA screen drawing. This was narrowed down
  to full-screen or mostly full- writes, eg. scrolling. It finally
  reduced to a repeatable alternating between two console 0 resizes
  that would trigger the fault ("Z80 halt" or other bad-instruction)
  about 50% of the time.
  
  Massive effort was spent in the emulator (it was solid, no errors
  found; @davidly's !), and my window scrolling (array index+1 errors
  abound, lol), testing rendered that solid; every single runtime
  index array use was bound-checked and reported error.

  After window bounding done, which also resulted in rewrites that
  reduced complexity and increased speed, I found most of my display
  complexity was in cursor management (VGA4BIT handled it well for the
  screen; but fz80 has four windows...) so this entailed some 
  substantial changes to VGA4BIT (see below).

  Z80 interrupts were clearly involved, from the start. With interrupts
  disabled (via RTC Z80 port, from the Z80 side) it never failed.
  But hundreds of hours of test-hammering, using two MP/M machines 
  running four test programs designed to poke at weaknesses, 
  many tens of millions of Z80 interrupts, revealed not one single 
  error (found and fixed an, my, IFF2 error). Interrupt behavior 
  was testably correct.

  The breakthrough came when I got a repeatable fault trigger: 
  window-resize keys such that I could quickly alternate between
  one size and another in console0. The error, when it happened, 
  occurred only after Con's REDRAWTIMER fired; but all of the code
  under that tested out impeccably.

  In exasperation I commented-out the RTC IntervalTimer -- faults
  stopped, 100%. It was now impossible to induce a fault -- modulo
  heisenbug, which this was (nearly any change to the code induced
  change in the manifestation of the fault). 

  But now it was testable, simply. IntervalTimer running, faults.
  Not running, fault. 

  Also, none of this happens, not even once, on CP/M. Only MP/M.
  But the fault manifests on the Teensy, not Z80. Same emulator,
  same windows, same interrupts; the CP/M :: MP/M distinction 
  is entirely within the emulation.

  Except memory usage.
  

WTF HYPOTHESIS

  CP/M runs entirely within one single 64K static byte array.

  MP/M consumed 210K of memory, and since I've used "all" memory,
  the four MP/M banks are split across RAM1 and RAM2, ram1, 
  and "DMAMEM".

  MP/M uses large amounts of RAM in the same physical memory
  as the DMA accessed VGA display.

  A lot of my testing was related to this adjacency of Z80 banks
  and the DMA-accessed VGA arrays. 

  I assume this is related. Add in something-something interrupts.
  

THE SOLUTION

  Not entirely satisfiying... but reliable. Z80 timer tick, 50 Hz
  20 mS, is done with a software timer in Dev's loop(). Because SD
  card can block for 15 mS, this loses time; but task-switch ints
  lost amount to nothing because the task is blocking on SD/disk
  (by MP/M design). So the only problem was TOD, the ONESECOND tick.
  That was repaired by having the timer tick read the RTC device
  one-second register, and generate the XDOS ONESECOND flag event
  on one-second transitions. Short term jitter is terrible; that 
  amounts to nothing, and long-term stability is excellent.
  

CHANGES TO VGA4BIT

  VGA4BIT is kinda the center around which fZ80 revolves; without it
  there would be no fZ80.

  As this project scaled up screen performance and complexity 
  became an issue; the "automatic" cursor became a curse (lol) 
  and so I turned it off and rolled my own and complexity
  plummeted. 

  But when I got into this "interrupt" problem I suspected 
  everything. I cloned VGA4BIT and started chopping, hoping for 
  a nice dumb array-out-of-bounds error (it's C, after all!)
  Alas (lol!) the code seemed flawless. This work coincided
  with stress-testing Con.h (H19Out and the window manager)
  and I found a lot of performance increase in the reduced
  code... so that chopping resulted in me retaining the
  feature-reduced version of VGA4BIT as SR-VGA_4BIT_T4-MINIMAL
  in the SRResources library.

  SR-VGA_4BIT_T4-MINIMAL dropped everything that was not
  write-char-to-RAM; cursors and its overhead, gone. 
  write() now takes (row, col) argument for every write.
  Cursor is entirely done in Con:loop() and is a dozen lines.

  Though I'd done it initially as throw-away debug/exploration
  the performance increase was so massive I decided to 
  keep it.
 
I am just now playing with it. Still reading the manual and getting used to MP/M. I think you have it locked down. That kind of memory usage is to close to borderline stack trashing. I'll wait for updates:)
 
Memory Usage on Teensy 4.1: FLASH: code:155324, data:43392, headers:9152 free for files:7918596 RAM1: variables:334112, code:150792, padding:13048 free for local variables:26336
If 20K of the 150K code could be made FLASH resident it would result in another 32K block of free RAM1 reducing that wasted padding:13048

There is a 32K code cache - that should hold all of that most used code, if not found in startup one time code, and would still run at full speed.
 
Aargh the release has a bad MP/M XIOS in it, interrupts off (a test copy). Will fix today. Sorry for that. Will let you know.
Man, you just can't seem to get a break o_O I am setting up the Arduino fZ80.6 for compiling right now. I looked at your stripped down version if VGA_4bit_T4. Whatever it takes to stabilize the code;)
 
OK! I uploaded the correct released files and fixed some broken links. The upgrade process, for all the changes, is just re-flash, copy the contents of one of the bundles onto your A: drive, SYSBOOT the new CBIOS.HEX, and press reset.

Fixed up the project page. The manual, a nice PDF, is preliminary but essentially done (contents correct).
 
Last edited:
Man, you just can't seem to get a break o_O I am setting up the Arduino fZ80.6 for compiling right now. I looked at your stripped down version if VGA_4bit_T4. Whatever it takes to stabilize the code;)

Yeah, sorry to undo all your work!! Except for the three-arg write, nothing I've changed is incompatible. I just needed to get out from under this bug. It's now quite repeatable so I can revisit it later when.

fZ80 doesn't use as much stack as "modern" code -- I've written embedded crap for so long I tend to have extremely conservative techniques for memory management. Malloc used only during setup, and only small stuff, I never release memory ever, big buffers are static. Automatics are small. Predictability and stability are more important! Its only running one program.
 
Last edited:
Yeah, sorry to undo all your work!! Except for the three-arg write, nothing I've changed is incompatible. I just needed to get out from under this bug. It's now quite repeatable so I can revisit it later when.

fZ80 doesn't use as much stack as "modern" code -- I've written embedded crap for so long I tend to have extremely conservative techniques for memory management. Malloc used only during set, and only small stuff, I never release memory ever, big buffers are static. Automatics are small. Predictability and stability are more important! Its only running one program.
That's good to hear. No worries about vga_4bit_t4 lib. It's meant to be user usable no matter how it is configured :D CP/M and MP/M are very much text oriented anyway. I'll download and test and then play with it. Hopefully this is the last of the bug swatting...
 
If the error only happens after a particular interrupt handler is executed you could try saving the stack location when that handler gets executed (just stash the address of a local variable into a global), to see how deep it is when the bug occurs.
 
OK! I uploaded the correct released files and fixed some broken links. The upgrade process, for all the changes, is just re-flash, copy the contents of one of the bundles onto your A: drive, SYSBOOT the new CBIOS.HEX, and press reset.

Fixed up the project page. The manual, a nice PDF, is preliminary but essentially done (contents correct).
The size of the CBIOS.HEX file is showing 0 bytes after copying CPMDISK.SYS to the A: drive?
 
Back
Top