{"id":615,"date":"2026-09-22T06:54:18","date_gmt":"2026-09-22T06:54:18","guid":{"rendered":"https:\/\/www.r3tr0.net\/?p=615"},"modified":"2026-09-22T06:54:18","modified_gmt":"2026-09-22T06:54:18","slug":"wondermca-the-smartdrive-ghost-4-mb-of-extra-ram-a-cpld-that-reprograms-itself-and-a-server-95-running-doom","status":"publish","type":"post","link":"https:\/\/www.r3tr0.net\/index.php\/2026\/09\/22\/wondermca-the-smartdrive-ghost-4-mb-of-extra-ram-a-cpld-that-reprograms-itself-and-a-server-95-running-doom\/","title":{"rendered":"WonderMCA &#8211; The SmartDrive ghost, 4 MB of extra RAM, a CPLD that reprograms itself, and a Server 95 running DOOM"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><h2>Where we were<\/h2>\n<p>Since the summer WonderMCA has worked on every PS\/2 I own\u2026 as long as you\ndid not look too closely at the 386DX machines. On the 286 and 386SX boxes\n(8550Z, 8555, 8556, 8557) the card was solid. On the Model 70 (8570), the\nsame card, the same firmware and the same CPLD produced a slow drip of\nproblems that never pointed at anything:<\/p>\n<ul>\n<li>at power-on the BIOS sometimes said <strong>\u201cMemory change: Reset the RAM\nConfig\u201d<\/strong>, meaning the card\u2019s configuration block had been changed behind\nits back;<\/li>\n<li><code class=\"\" data-line=\"\">DIR C:<\/code> could reboot the machine;<\/li>\n<li>loading a 67 KB <code class=\"\" data-line=\"\">.EXE<\/code> from the emulated floppy rebooted the PC before the\nprogram even started.<\/li>\n<\/ul>\n<p>This post is the story of the last few weeks. It covers five things that\nturned out to be linked more than I expected:<\/p>\n<ol>\n<li>finding the real cause of the 8570 instability (spoiler: SmartDrive, and\nfour address lines the firmware never looked at);<\/li>\n<li>a new feature: <strong>4 MB of extended memory (XMS)<\/strong> served from the card\u2019s\nPSRAM. Slower than the planar\u2019s own RAM, but it works;<\/li>\n<li><strong>updating the CPLD from the microSD card<\/strong>: the RP2350 now does the JTAG\nitself, so future updates no longer need a cable and a Windows PC;<\/li>\n<li>validating the <strong>rev 8 board<\/strong>, including the long hunt for the USB\npanics;<\/li>\n<li>the <strong>Server 95 (9595)<\/strong>, which now runs DOOM after a DMA arbitration\nproblem was solved.<\/li>\n<\/ol>\n<p>It is a long one. Grab a coffee.<\/p>\n<hr>\n<h2>A quick map of the card<\/h2>\n<p>You need a picture of the card to follow the rest, so here is one.<\/p>\n<pre><code class=\"\" data-line=\"\">                     MCA edge connector\n   A0-A23   D0-D15   \/ADL \/CMD S0 S1 M\/IO   CHRDY  ARB0-3 \/PREEMPT \/BURST\n      |        |           |                  ^         ^\n      |        |           |                  |         |\n      |        |           v                  |         |\n      |        |     +--------------------------------------+\n      |        |     |  ATF1508AS CPLD                      |\n      |        |     |  - address decode (all 24 bits)      |\n      |        |     |  - CHRDY: &quot;wait, I am not ready&quot;     |\n      |        |     |  - card-selected feedback, DS16      |\n      |        |     |  - DMA arbitration                   |\n      |        |     +--------------------------------------+\n      |        |              ^  RP_REL (&quot;release the cycle&quot;)\n      v        v              |\n   +-------------+     +---------------------------------------+\n   | 74LVC257    |----&gt;|  RP2350B                              |\n   | address mux |     |   Core 1: the bus handler (assembly), |\n   | (A_AB pin   |     |           serves every cycle          |\n   |  selects    |     |   Core 0: the emulated devices        |\n   |  which half)|     |           (disk, Sound Blaster, USB,  |\n   +-------------+     |            network, screenpad...)     |\n                       +---------------------------------------+\n                          |        |        |        |\n                       8 MB     microSD   USB host  WiFi \/ W5500\n                       PSRAM\n<\/code><\/pre>\n<p>The important idea is that <strong>two chips decide whether a bus cycle belongs to\nthe card<\/strong>. The CPLD is fast and decodes the address in hardware: it tells the\nPS\/2 \u201cthis is mine, wait for me\u201d by pulling CHRDY. The RP2350\u2019s Core 1 runs a\ntight assembly loop that watches for the start of every cycle (\/ADL), reads the\naddress through the mux and serves the data. When it has finished it pulses\nRP_REL so the CPLD lets the cycle end.<\/p>\n<p>Two decoders. Keep that in mind.<\/p>\n<hr>\n<h2>Part 1: the SmartDrive ghost<\/h2>\n<h3>Two months of wrong answers<\/h3>\n<p>The 8570 problems started in July. Here is what I tried, in order. I am\nlisting it because every item seemed perfectly reasonable at the time.<\/p>\n<table>\n<thead>\n<tr>\n<th>When<\/th>\n<th>Theory<\/th>\n<th>What I did<\/th>\n<th>Result<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>July<\/td>\n<td>The Model 70 reserves <code class=\"\" data-line=\"\">D7000<\/code><\/td>\n<td>Moved the DPRAM window from <code class=\"\" data-line=\"\">D6000<\/code> to <code class=\"\" data-line=\"\">D5000<\/code><\/td>\n<td>The hang moved somewhere else<\/td>\n<\/tr>\n<tr>\n<td>July<\/td>\n<td>The Model 80 claims <code class=\"\" data-line=\"\">D6000<\/code><\/td>\n<td>Shrank the ROM, moved the window to <code class=\"\" data-line=\"\">D4000<\/code><\/td>\n<td>\u201cMemory change\u201d at POST instead<\/td>\n<\/tr>\n<tr>\n<td>Aug\u2013Sep<\/td>\n<td>386DX bus timing: dword splits, data steering, a late DS16<\/td>\n<td>Timing proposals, logic analyser traces<\/td>\n<td>Nothing conclusive<\/td>\n<\/tr>\n<tr>\n<td>Sep<\/td>\n<td>Stale address from the mux<\/td>\n<td>CPLD rev 101, timed CHRDY on memory cycles<\/td>\n<td>Built, never needed<\/td>\n<\/tr>\n<tr>\n<td>Sep<\/td>\n<td>Something writes the config block<\/td>\n<td>A \u201ccanary\u201d in the firmware + x86 tools: <code class=\"\" data-line=\"\">cfgdump<\/code>, <code class=\"\" data-line=\"\">memstres<\/code>, <code class=\"\" data-line=\"\">dwtest<\/code>, <code class=\"\" data-line=\"\">latchtst<\/code>, <code class=\"\" data-line=\"\">diskrd<\/code>, <code class=\"\" data-line=\"\">made24<\/code><\/td>\n<td><strong>The first real evidence<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>On 10 September I withdrew the two July \u201cSOLVED\u201d entries in the development log\npublicly: the PS\/2 does not reserve anything at <code class=\"\" data-line=\"\">D6000<\/code> or <code class=\"\" data-line=\"\">D7000<\/code>. Every\nwindow move had simply changed <em>which<\/em> part of the DPRAM got damaged. The good news is that I thought I had bad memory RAM and of course it was not the case, I was way more subtle and it was my mistake.<\/p>\n<h3>The canary sings<\/h3>\n<p>The canary was a 768-byte shadow copy of the configuration block, compared\ncontinuously, printing every byte that changed. On the 8570 it caught the damage\nin the act:<\/p>\n<ul>\n<li>right after <strong>POST 52\/53<\/strong> (the planar\u2019s memory test above 1 MB): 50 bytes\nchanged, in 50 runs of exactly one byte, every one on a 4-byte grid, holding\ntest patterns <code class=\"\" data-line=\"\">55<\/code>, <code class=\"\" data-line=\"\">AA<\/code>, <code class=\"\" data-line=\"\">00<\/code>;<\/li>\n<li>in DOS, during disk reads: bytes on a 4-byte grid holding <strong>sector data<\/strong>.<\/li>\n<\/ul>\n<p>One byte in four, always on a doubleword grid, always holding data that was\nbeing moved around somewhere else in memory.<\/p>\n<h3>The one-minute test I should have done first<\/h3>\n<p>On 11 September I booted the 8570 clean (F5: no HIMEM, no SmartDrive) and\ntried again.<\/p>\n<ul>\n<li><strong>Clean boot: no problem.<\/strong> <code class=\"\" data-line=\"\">DIR C:<\/code> fine, <code class=\"\" data-line=\"\">.EXE<\/code> loads fine.<\/li>\n<li><strong>HIMEM alone: fine.<\/strong><\/li>\n<li><strong>HIMEM + SmartDrive: the problems come back.<\/strong><\/li>\n<\/ul>\n<p>Weeks of timing theories, and the trigger was a disk cache.<\/p>\n<h3>What SmartDrive does, and why it hurts<\/h3>\n<p>SmartDrive keeps its cache in extended memory, above 1 MB, starting just after\nthe HMA at about <code class=\"\" data-line=\"\">0x110000<\/code>. A cache of 800 KB or more therefore covers\n<code class=\"\" data-line=\"\">0x1D4000\u20130x1D5FFF<\/code>.<\/p>\n<p>Now look at that address: <code class=\"\" data-line=\"\">0x1D4000<\/code> is <strong>1 MB + <code class=\"\" data-line=\"\">D4000<\/code><\/strong>. And <code class=\"\" data-line=\"\">D4000<\/code> is\nexactly where WonderMCA\u2019s DPRAM (with the config block) lives.<\/p>\n<pre><code class=\"\" data-line=\"\">   Physical memory on the 8570\n\n   0x000000 +------------------------+\n            | conventional 640 KB    |\n   0x0A0000 +------------------------+\n            | video, ROMs            |\n   0x0D0000 +------------------------+  &lt;-- WonderMCA ROM   D0000-D3FFF\n   0x0D4000 |########################|  &lt;-- WonderMCA DPRAM D4000-D5FFF\n   0x0D6000 +------------------------+      (config block, disk buffers)\n            | ...                    |\n   0x100000 +------------------------+  1 MB\n            | HMA                    |\n   0x110000 +------------------------+\n            | SmartDrive XMS cache   |\n            |                        |\n   0x1D4000 |########################|  &lt;-- 1 MB + D4000: SAME LOW 20 BITS\n   0x1D6000 |                        |      as the DPRAM window\n            |                        |\n            +------------------------+\n<\/code><\/pre>\n<h3>Two decoders, two widths<\/h3>\n<p>This is where \u201ctwo decoders\u201d matters. <strong>I finally found it on the night of\n11 September.<\/strong><\/p>\n<ul>\n<li>The <strong>CPLD<\/strong> decodes all 24 address bits. For <code class=\"\" data-line=\"\">0x1D4000<\/code> it correctly says\n\u201cnot mine\u201d: no CHRDY, no card-selected feedback. The planar\u2019s real memory\nowns that cycle.<\/li>\n<li>The <strong>Core 1 handler<\/strong> built its page index from <strong>A12\u2013A19 only<\/strong>. That is a\n1 MB address space. So for Core 1, <code class=\"\" data-line=\"\">0x1D4000<\/code> and <code class=\"\" data-line=\"\">0x0D4000<\/code> were the same\npage, <code class=\"\" data-line=\"\">0xD4<\/code>.<\/li>\n<\/ul>\n<pre><code class=\"\" data-line=\"\">   Address bit:  23 22 21 20 | 19 18 17 16 15 14 13 12 | 11 ........ 0\n                 ------------+-------------------------+--------------\n   CPLD decode:  [  used   ] | [        used         ] | [   used    ]\n   Core 1 page:  [ IGNORED ] | [   page index 0-255  ] | [ offset    ]\n\n   0x0D4000 = 0000 | 1101 0100 | ...   -&gt;  page 0xD4  -&gt; DPRAM   (correct)\n   0x1D4000 = 0001 | 1101 0100 | ...   -&gt;  page 0xD4  -&gt; DPRAM   (WRONG)\n                ^\n                A20 set, but Core 1 never looked at it\n<\/code><\/pre>\n<p>So every time SmartDrive wrote its cache at <code class=\"\" data-line=\"\">0x1D4000<\/code>, Core 1 <em>also<\/em> stored\nthe data into the DPRAM. On reads it drove the bus while the planar\u2019s own RAM\nwas answering too. Because the CPLD had not claimed the cycle, there was no\nCHRDY: the planar ran the cycles at full memory speed and Core 1 caught only\nthe first byte of each doubleword. That is exactly the <strong>one byte in four<\/strong> the\ncanary saw.<\/p>\n<p>It also explains the rest:<\/p>\n<ul>\n<li><strong>Why POST 52\/53:<\/strong> the planar\u2019s memory test walks the same 1 MB alias with\n<code class=\"\" data-line=\"\">55<\/code>\/<code class=\"\" data-line=\"\">AA<\/code> patterns.<\/li>\n<li><strong>Why reboots:<\/strong> SmartDrive served executables from a cache the card had\nbeen corrupting. The PC ran garbage.<\/li>\n<li><strong>Why only the 8570:<\/strong> on the 386SX and 286 machines, memory above 1 MB\nstays on the planar and never reaches the channel. On the 386DX Model 70 it\ndoes.<\/li>\n<\/ul>\n<p>The <code class=\"\" data-line=\"\">made24<\/code> tool confirmed it: with A20 verified ON, a write at <code class=\"\" data-line=\"\">0x1D4000<\/code>\ncame back at <code class=\"\" data-line=\"\">D400:1800<\/code>.<\/p>\n<h3>The fix: two instructions<\/h3>\n<p>A20\u2013A23 were already on the mux, on GPIO 24\u201327, and read by the same poll as\nA12\u2013A19. The fix is a test before the page lookup in the memory read and memory\nwrite paths:<\/p>\n<pre><code class=\"\" data-line=\"\">    tst   ADDR, #0x0F000000     @ any of A20..A23 set?\n    bne   7f                    @ yes: not ours, leave the cycle alone\n    ubfx  ...                   @ page index from A12..A19, as before\n<\/code><\/pre>\n<p>That costs two cycles, taken from settle time the handler was already\nspending. On 12 September the 8570 ran HIMEM + DOS=HIGH + SmartDrive with no\nconfig corruption and no reboot. An arc that had started in July closed with\ntwo instructions.<\/p>\n<p><strong>Lesson:<\/strong> when a fault only shows under a particular software stack, boot\nwithout that stack before doing anything else. That test takes one minute. I\nspent weeks on bus timing for a bug that lived in the memory-manager layer.<\/p>\n<p>During all that period, I was also hunting a mouse (COM2) emulation issue (chassis freezing) on particular game like DUNE or WARCRAFT. In the end, it was already the same HMA issue with RP2350 core1 taking the wrong address.<\/p>\n<p>Why this bug, it was my fault, I blindly ported the code from the famous Picomem from FreddyV. FreddyV helped me doing this port and explained me how to manage MEMW and MEMR with tight timing but in the end I did not challenge the fact that ISA bus is only 0-19 Address line wide where the PS\/2 is 0-23 Wide (considering that everything above is left aside with Made24 signal).<\/p>\n<hr>\n<h2>Part 2: 4 MB of extended memory from the PSRAM<\/h2>\n<h3>Why<\/h3>\n<p>Many PS\/2s have 1 or 2 MB of RAM, and DOOM wants 4 MB, this is the case for the 8550 and 8550Z without any slot expansion capabilities. The card carries an 8 MB PSRAM chip. Could WonderMCA serve some of it as <strong>real, bus-mapped\nextended memory<\/strong>? Not an emulated XMS driver but actual RAM that the POST\ncounts and HIMEM uses, like a memory card?<\/p>\n<h3>The problem: PSRAM is slow<\/h3>\n<p>Serving a memory read from the RP2350\u2019s internal SRAM (the ROM, the DPRAM)\nleaves about <strong>80 ns<\/strong> between the start of the cycle and the moment the data\nmust be on the bus. One byte from the QSPI PSRAM takes <strong>150\u2013250 ns<\/strong>. It\ncannot fit.<\/p>\n<p>The MCA bus has an answer: <strong>CHRDY<\/strong>. A slave that is not ready pulls CHRDY\nlow and the planar waits. WonderMCA already did this for I\/O cycles. The new\npart is doing it for memory, on a window above the planar\u2019s RAM:<\/p>\n<pre><code class=\"\" data-line=\"\">   Ordinary memory read (ROM \/ DPRAM, from SRAM): no wait\n\n   \/ADL   \u203e\u203e\u203e\\__\/\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\n   \/CMD   \u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\\______________\/\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\n   CHRDY  \u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e   always ready\n   DATA   ----------------&lt;  byte  &gt;----------\n                  |&lt;- ~80 ns -&gt;|\n\n\n   Held memory read (XMS, from PSRAM)\n\n   \/ADL   \u203e\u203e\u203e\\__\/\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\n   \/CMD   \u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\\__________________________________\/\u203e\u203e\u203e\u203e\u203e\n   CHRDY  \u203e\u203e\u203e\u203e\u203e\u203e\\___________________________\/\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e\u203e   CPLD says &quot;wait&quot;\n   PSRAM              [== fetch 150-250 ns ==]\n   DATA   ------------------------------&lt;  byte  &gt;---------\n   RP_REL ________________________________\/\u203e\\_____________   release = LAST\n                                              ^\n                           Core 1 must be back at the poll\n                           before the next cycle starts\n<\/code><\/pre>\n<h3>The window has to start above the planar\u2019s RAM<\/h3>\n<p>A PS\/2 with 1 MB keeps 640 KB low and <strong>remaps the other 384 KB<\/strong> to\n<code class=\"\" data-line=\"\">0x100000\u20130x15FFFF<\/code>. Claim any cycle there and the planar\u2019s memory test fails\n(POST 201). So the window starts at the planar\u2019s top, and <strong>the CPLD has to do\nthat skip itself<\/strong>: any CHRDY on planar RAM breaks it, and doing the skip in\nfirmware only was not enough. The start is set with two DIP switches (CPLD rev\n123):<\/p>\n<table>\n<thead>\n<tr>\n<th>SW_CFG3 : SW_CFG4<\/th>\n<th>Planar RAM<\/th>\n<th>Card window<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>off : on<\/td>\n<td>1 MB<\/td>\n<td><code class=\"\" data-line=\"\">0x160000<\/code> + 4 MB<\/td>\n<\/tr>\n<tr>\n<td>on : on<\/td>\n<td>2 MB<\/td>\n<td><code class=\"\" data-line=\"\">0x240000<\/code> + 4 MB<\/td>\n<\/tr>\n<tr>\n<td>on : off<\/td>\n<td>4 MB<\/td>\n<td><code class=\"\" data-line=\"\">0x440000<\/code> + 4 MB<\/td>\n<\/tr>\n<tr>\n<td>off : off<\/td>\n<td>\u2014<\/td>\n<td>XMS off<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The same code goes into <code class=\"\" data-line=\"\">config.txt<\/code> as <code class=\"\" data-line=\"\">XMS 1<\/code>, <code class=\"\" data-line=\"\">XMS 2<\/code> or <code class=\"\" data-line=\"\">XMS 4<\/code>, because\nthe firmware cannot read those switches.<\/p>\n<p>The Model 70 had a trap: with 2 MB it reports 1920 KB, not 2048. It keeps\n128 KB for itself. The rule became: planar top = n MB + <code class=\"\" data-line=\"\">0x40000<\/code>.<\/p>\n<h3>Everything that went wrong, and the rules it left behind<\/h3>\n<p>XMS took about three days and roughly twenty CPLD revisions (105 to 123). Each\nfailure left a rule behind:<\/p>\n<ol>\n<li>\n<p><strong>The init was dead code.<\/strong> On the first test all cycles were declined. The\ninit call sat in a code path that the WM10 board never reaches, so the\ncompiler dropped it.\n<em>Rule: anything the WM10 needs must be on the live path. Check the ELF.<\/em><\/p>\n<\/li>\n<li>\n<p><strong>The second byte of every word read vanished.<\/strong> After releasing a cycle,\nCore 1 spent about 100 ns on counters and tracing before it got back to\nwatching \/ADL. The PS\/2 starts the second byte of a word 10\u2013100 ns after\nthe first one ends.\n<em>Rule: <strong>release is the last instruction.<\/strong> Do the bookkeeping while the\ncycle is still held, since that time is free.<\/em><\/p>\n<\/li>\n<li>\n<p><strong>Data shifted by one byte in streams.<\/strong> The stub read A0\u2013A11 at T+21, after\nthe page table lookup. By then the planar had already put the <em>next<\/em>\naddress on the bus.\n<em>Rule: sample the bus address in the T+17\u2026T+21 window. Holding the cycle\nbuys time for everything except the address.<\/em><\/p>\n<\/li>\n<li>\n<p><strong>POST stalled at 704, 832 or 960 KB.<\/strong> An address transient at a\nboundary (<code class=\"\" data-line=\"\">0xFFFFF<\/code> \u2192 <code class=\"\" data-line=\"\">0x100000<\/code>) matched the window decode for a few\nnanoseconds and latched CHRDY for a cycle nobody would ever release.\n<em>Rule: every self-holding CHRDY latch needs its hold gated by a decode\nsampled at \/ADL (the <code class=\"\" data-line=\"\">XM_LAT<\/code> and <code class=\"\" data-line=\"\">IO_LAT<\/code> guards).<\/em><\/p>\n<\/li>\n<li>\n<p><strong>\u201cIt works when tracing is on.\u201d<\/strong> A detector for a stuck bus sampled \/CMD\nonce per millisecond. During the POST sweep over XMS almost every cycle is\nheld, so 50 \u201casserted\u201d samples in a row happened by chance, and the\n\u201crecovery\u201d then broke a live transfer.\n<em>Rule: never conclude \u201cbus stuck\u201d from sparse samples of a level signal.<\/em><\/p>\n<\/li>\n<li>\n<p><strong>The 8570 would not boot at all.<\/strong> A self-test in the Core 1 init drove\n<code class=\"\" data-line=\"\">0xAA<\/code> onto the data bus while the planar was still coming out of reset.\nOn the 8550Z the bus is idle at that moment, so it never showed there.<\/p>\n<\/li>\n<li>\n<p><strong>DOOM: \u201cInternal stack overflow. System halted.\u201d<\/strong> Moving a single\nheartbeat counter increment to <em>after<\/em> the release made Core 1 late for the\nnext cycle often enough to corrupt DOOM\u2019s stack. One store, in the wrong\nplace. (Rule 2 again.)<\/p>\n<\/li>\n<\/ol>\n<h3>Results<\/h3>\n<p>On the 8550Z the POST counted <strong>4736 KB<\/strong> (640 KB + 4096 KB). On the 8570\nwith a 4 MB planar it counted <strong>8064 KB<\/strong> (3968 KB + 4096 KB), and HIMEM\naccepted it. XMSTEST, a tool I wrote for this, runs ten suites of patterns\nthrough the BIOS block move and passes on both machines.<\/p>\n<p>Then I added <strong>16-bit transfers<\/strong>: one bus cycle per word instead of two byte\ncycles. XMSBENCH measures throughput with the BIOS block move (INT 15h, 87h),\n8570, window at <code class=\"\" data-line=\"\">0x240000<\/code>, in KB\/s:<\/p>\n<table>\n<thead>\n<tr>\n<th>Transfer<\/th>\n<th style=\"text-align:right\">8-bit card<\/th>\n<th style=\"text-align:right\">16-bit card<\/th>\n<th style=\"text-align:right\">Gain<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Write, even address<\/td>\n<td style=\"text-align:right\">1033<\/td>\n<td style=\"text-align:right\"><strong>1814<\/strong><\/td>\n<td style=\"text-align:right\">+76%<\/td>\n<\/tr>\n<tr>\n<td>Read, even address<\/td>\n<td style=\"text-align:right\">995<\/td>\n<td style=\"text-align:right\"><strong>1814<\/strong><\/td>\n<td style=\"text-align:right\">+82%<\/td>\n<\/tr>\n<tr>\n<td>Write, odd address (byte lanes)<\/td>\n<td style=\"text-align:right\">969<\/td>\n<td style=\"text-align:right\">1014<\/td>\n<td style=\"text-align:right\"><\/td>\n<\/tr>\n<tr>\n<td>Read, odd address (byte lanes)<\/td>\n<td style=\"text-align:right\">896<\/td>\n<td style=\"text-align:right\">928<\/td>\n<td style=\"text-align:right\"><\/td>\n<\/tr>\n<tr>\n<td>Reference: conventional \u2192 conventional<\/td>\n<td style=\"text-align:right\">3548<\/td>\n<td style=\"text-align:right\">3548<\/td>\n<td style=\"text-align:right\"><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>So the card\u2019s XMS runs at about <strong>half the speed of the planar\u2019s own RAM<\/strong>.\nThat is slow for memory and fine for what it is used for: HIMEM, SmartDrive,\nDOS extenders, DOOM\u2019s level data. DOOM runs on the 8570 with XMS, Sound Blaster\nand the USB keyboard all enabled.<\/p>\n<p>One honest note on optimisation: I tried DMA prefetching from the PSRAM. It\nmeasured <em>slower<\/em> (1616 vs 1718 KB\/s), so it is switched off. The numbers\ndecide.<\/p>\n<p>The 16-bit build had a surprise of its own. It froze on an I\/O cycle, just\nlike an old snooping bug. The cause was a register allocation detail of GCC\u2019s\n<code class=\"\" data-line=\"\">asm goto<\/code>: an output written early in the assembly shared its register with\nan input that was still needed. One <code class=\"\" data-line=\"\">&amp;<\/code> fixed it (<code class=\"\" data-line=\"\">&quot;=&amp;l&quot;<\/code>, \u201cearly clobber\u201d).<\/p>\n<hr>\n<h2>Part 3: the CPLD reprograms itself<\/h2>\n<h3>Why<\/h3>\n<p>Until now, every CPLD update meant a JTAG cable, the ATMISP software on a\nWindows PC, and taking the card out of the machine. That is fine for me and a\nbarrier for anyone else who owns a WonderMCA. The RP2350 is right next to the\nCPLD with GPIOs to spare, so why not let it do the JTAG itself?<\/p>\n<h3>How it works<\/h3>\n<pre><code class=\"\" data-line=\"\">   microSD                RP2350                        ATF1508AS\n   0:\/UPGRADE\/            bit-banged JTAG               CPLD\n   +----------------+     +------------------+   SW1   +---------+\n   | cpld_erase.svf |----&gt;| libxsvf player   |---TCK--&gt;|         |\n   | cpld_upgrade_  |     |                  |   SW2   |  JTAG   |\n   |   123.svf      |     | GPIO 42 = TCK    |---TMS--&gt;|  TAP    |\n   | cpld_verif.svf |     | GPIO 43 = TMS    |   SW3   |         |\n   +----------------+     | GPIO 47 = TDI    |---TDI--&gt;|         |\n                          | GPIO 46 = TDO    |&lt;--TDO---|         |\n                          +------------------+   SW4   +---------+\n                                   |              (via a buffer)\n                                   v\n                          screenpad: progress bar, LEDs, result\n<\/code><\/pre>\n<ul>\n<li><strong>ATMISP<\/strong> produces standard SVF files: erase, program, verify. Copy them\ninto <code class=\"\" data-line=\"\">UPGRADE<\/code> on the microSD. The <code class=\"\" data-line=\"\">_123<\/code> in the name is the CPLD revision,\nshown everywhere during the update.<\/li>\n<li>At boot, the RP2350 plays them into the CPLD using <strong>libxsvf<\/strong> (Clifford\nWolf\u2019s SVF\/XSVF player), clocking TCK at about 250 kHz from four GPIOs.<\/li>\n<li>The screenpad shows a progress bar with the yellow LED lit, then the result\n(revision, IDCODE, \u201cPOWER CYCLE NOW\u201d). The board LED blinks during the\nupdate and finishes with three long flashes.<\/li>\n<li>With <code class=\"\" data-line=\"\">CPLD_UPDATE_AUTO 0<\/code> in <code class=\"\" data-line=\"\">config.txt<\/code>, the screenpad lists the files\nand you play them one at a time by hand.<\/li>\n<\/ul>\n<h3>The DIP switches, and why they matter so much<\/h3>\n<p>The RP2350 does not have four spare pins. <strong>TCK and TMS share GPIO 42 and 43\nwith IRQ6 and IRQ7.<\/strong> Four DIP switches select which one each pin connects to.<\/p>\n<p>That creates a real danger. If the bus handler were running with the switches\nin the JTAG position, every IRQ6 or IRQ7 the card raised would clock the\nCPLD\u2019s JTAG state machine with a random TMS. So:<\/p>\n<pre><code class=\"\" data-line=\"\">   At power-on, BEFORE the bus handler starts:\n\n   read the switch positions from the pull resistors\n            |\n            +-- none set ---------&gt; normal boot\n            |\n            +-- all set ----------&gt; bus handler NOT started\n            |                       -&gt; CPLD update\n            |\n            +-- some set, some not -&gt; STOP. Red LED, switch\n                                       positions on the screenpad.\n                                       The card can do neither job.\n<\/code><\/pre>\n<p>A partial setting now halts the card deliberately. The first version let it\nboot on with no bus handler: alive, answering nothing. That is the worst kind\nof failure to diagnose.<\/p>\n<h3>The difficulties<\/h3>\n<ul>\n<li>\n<p><strong>TDO came back inverted.<\/strong> The first IDCODE read was <code class=\"\" data-line=\"\">FEAF7FC0<\/code> where the\nfile expected <code class=\"\" data-line=\"\">0150803F<\/code>: the exact bitwise complement. The board has an\ninverting buffer on TDO. The JTAG standard requires bit 0 of every IDCODE to\nbe 1, so the firmware checks that bit and corrects the polarity by itself.<\/p>\n<\/li>\n<li>\n<p><strong>\u201cOut of memory\u201d halfway through programming.<\/strong> libxsvf reallocates its\nbuffers constantly, shrinking and growing them in turn, which fragmented the\nRP2350\u2019s heap until an allocation failed. The fix keeps one grow-only buffer\nper purpose.<\/p>\n<\/li>\n<li>\n<p><strong>Files not found.<\/strong> My own file was named <code class=\"\" data-line=\"\">cpld_ugrade_116.svf<\/code>. The\nfirmware now uses its own case-insensitive matcher, lists the whole folder\nand accepts that misspelling with a note.<\/p>\n<\/li>\n<li>\n<p><strong>SW4 cannot be seen.<\/strong> The switch positions are read by fighting each\nexternal pull resistor with the RP2350\u2019s internal pull. That works for TCK,\nTMS and TDI. TDO sits behind a buffer that drives the pin whichever way the\nswitch is set. My first test said \u201coff\u201d when it was on, and my second said\n\u201con\u201d when it was off. The honest answer is that SW4 cannot be read, so the\nfirmware no longer judges it. A disconnected TDO shows up anyway: the IDCODE\nread fails and nothing is written.<\/p>\n<\/li>\n<\/ul>\n<h3>Don\u2019t lock the door behind you<\/h3>\n<p>The ATF1508\u2019s JTAG pins can also be used as ordinary I\/O. If a CPLD image ever\nwent out with JTAG disabled, or with the security fuse set, that card could\nnever be reprogrammed again, by this feature or by a cable. The <code class=\"\" data-line=\"\">.fit<\/code> report\nmust say <code class=\"\" data-line=\"\">JTAG = ON<\/code> and <code class=\"\" data-line=\"\">Security_mode = OFF<\/code>. I checked the current build,\nand the rule is now written into the reference document.<\/p>\n<h3>Status<\/h3>\n<p>The JTAG path reads the correct IDCODE, the SVF player runs, and programming\nreached the row writes before hitting the heap problem, which is now fixed.\nThe first complete erase \u2192 program \u2192 verify run on the bench is the next step.<\/p>\n<hr>\n<h2>Part 4: board rev 8 and the USB panics<\/h2>\n<p>Everything above ran on <strong>board rev 8<\/strong>: RP2350B, ATF1508AS CPLD, 74LVC257\naddress mux, 8 MB QSPI PSRAM, microSD, USB host, W5500 Ethernet, WiFi, RTC,\nand the screenpad (OLED + keypad + four LEDs). Validating rev 8 meant, above\nall, making USB reliable.<\/p>\n<h3>Panic 1: \u201cep was already available\u201d, and a voltage<\/h3>\n<p>Sustained USB mass-storage reads panicked, but <strong>only while Core 1 was\nserving the MCA bus<\/strong>. With Core 1 idle, the same stick read 73 MB flawlessly.<\/p>\n<p>That load dependence pointed straight at Core 1, and I spent a long time\nthere: dummy Core 1, poll-only Core 1, per-cycle-type stubs, code in flash\nversus SRAM, every bus-priority setting. All of it chased symptoms.<\/p>\n<h3>Panic 2: the orphaned AVAIL bit<\/h3>\n<p>The USB Ethernet dongles brought a new panic. The host scheduler shares one\nhardware endpoint between several transfers and aborts a transfer that keeps\nanswering NAK. When that abort races a packet that <em>is<\/em> arriving, the buffer\u2019s\n\u201cavailable\u201d bit is left set with nobody to clear it. The stock code could only\nwait and then panic.<\/p>\n<h3>Problem 3: exactly 50% packet loss<\/h3>\n<p>DHCP failed on most boots, and one ping in two was lost. One aborted transfer\nleft the host\u2019s data toggle (the DATA0\/DATA1 sequence bit) one step behind the\ndongle. From then on, every other packet arrived with the \u201cwrong\u201d toggle and\nwas dropped, until re-enumeration.<\/p>\n<h3>Problem 4: double buffering<\/h3>\n<p>The host\u2019s bulk IN double buffering dropped the second buffer when a short\npacket arrived. It is forced to single buffering now.<\/p>\n<p>All these USB issues had the same cause, the rev 5 of the board was a 2 layer PCB with small traces below the RP2350 crystal. This design was really not smart as the crystal was creating false edge on the rest of the PCB. Moving to 4 PCB layer and creating a clean zone around the main crystal solved the issue after endless days of debugging<\/p>\n<hr>\n<h2>Part 5: the Server 95 runs DOOM<\/h2>\n<h3>01291300<\/h3>\n<p>The IBM PS\/2 Server 95 (9595) is the fastest machine in my collection: a\n486DX2 at 66 MHz. WonderMCA booted on it, but DMA sound stopped with\n<strong>error 01291300<\/strong>.<\/p>\n<p>IBM\u2019s Hardware Maintenance Manual calls 01291300 a \u201ccache error\u201d. It is not.\nIBM\u2019s own field service tip database (the RETAIN tips) says it is a <strong>bus\nmaster arbitration error<\/strong>. It also says two things that made everything\nclear:<\/p>\n<ul>\n<li>On PS\/2 systems at 66 MHz and above, adapters with marginal DMA timing time\nout. IBM\u2019s fix for its own Multiprotocol Adapter was a <strong>hardware rework of\nthe adapter<\/strong>, not of the system.<\/li>\n<li>The 9595\u2019s <strong>High Speed Parallel Port arbitrates<\/strong> on the bus (by default at\nshared level 7) and is a known cause of 01291300. The standard remedy is to\ndisable the arbitrable serial and parallel ports in the configuration.<\/li>\n<\/ul>\n<h3>How MCA DMA arbitration works (very short version)<\/h3>\n<p>WonderMCA is a DMA slave. To play a Sound Blaster sample it asks for the bus;\nthe planar\u2019s DMA controller then moves the byte on its behalf.<\/p>\n<pre><code class=\"\" data-line=\"\">   card wants a byte        arbitration phase             grant phase\n   -----------------&gt;  \/PREEMPT low               ARB\/-GNT low: winner owns bus\n                       ARB\/-GNT high              planar DMA: MEMR from RAM,\n                       every requester drives     then IOW to the card\n                       its level on ARB0-3\n                       (wire-AND: lowest wins)\n\n   \/BURST  -- tells the arbiter &quot;I am doing a burst, don&#039;t take the bus back&quot;\n              WonderMCA never bursts, so it must NEVER pull this low\n<\/code><\/pre>\n<p>Two things were wrong on the 9595:<\/p>\n<ol>\n<li><strong>The parallel port was competing.<\/strong> With the High Speed Parallel Port\ndisabled in the configuration, the arbitration conflict went away.<\/li>\n<li><strong>\/BURST was left to chance.<\/strong> The CPLD pin wired to MCA \/BURST was declared\nbut had no equation, so its state was whatever the fitter chose. A \/BURST\nstuck low tells the arbiter a burst is in progress, which is precisely an\narbitration error. The smaller PS\/2s do not care. The Server 95\u2019s arbiter\ndoes. CPLD rev 104 releases \/BURST permanently (open-collector, high\nimpedance), the same way the reference Sound Blaster design <code class=\"\" data-line=\"\">mcsb.v<\/code> does.<\/li>\n<\/ol>\n<p>With both fixes Prince of Persia played with sound on the 9595, and now DOOM\nruns too.<\/p>\n<hr>\n<h2>What I take away from these weeks<\/h2>\n<ul>\n<li><strong>Boot clean before theorising.<\/strong> One F5 test would have saved weeks on\nSmartDrive.<\/li>\n<li><strong>Two decoders must agree.<\/strong> The CPLD and the RP2350 each decide whether a\ncycle is the card\u2019s. If they disagree by even one address bit, you get\nghosts.<\/li>\n<li><strong>Release last.<\/strong> Anything Core 1 does after freeing a bus cycle is time\nstolen from the next one: a lost byte, a DOOM stack overflow.<\/li>\n<li><strong>A lower voltage that fixes a bug means power, not logic.<\/strong><\/li>\n<li><strong>Don\u2019t trust a pin you can\u2019t see.<\/strong> If the hardware cannot tell you\nsomething (SW4), say so and prove it another way.<\/li>\n<li><strong>The fastest machine finds the sloppiest signal.<\/strong> An undriven \/BURST was\nharmless on six chassis and fatal on the seventh.<\/li>\n<li><strong>Read IBM\u2019s field notes.<\/strong> The RETAIN tips knew about 01291300 thirty years\nago.<\/li>\n<li><strong>Measure.<\/strong> The prefetch \u201coptimisation\u201d was slower. Only the benchmark told\nme.<\/li>\n<\/ul>\n<h2>What\u2019s next<\/h2>\n<p>Beta testing opening \ud83d\ude09<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[20,43],"tags":[33,32,34],"class_list":["post-615","post","type-post","status-publish","format-standard","hentry","category-ibm-ps-2","category-wondermca","tag-ibm","tag-mca","tag-ps-2"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts\/615","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/comments?post=615"}],"version-history":[{"count":2,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts\/615\/revisions"}],"predecessor-version":[{"id":619,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts\/615\/revisions\/619"}],"wp:attachment":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/media?parent=615"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/categories?post=615"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/tags?post=615"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}