-
20241104 - Shifting Focus to I/O Board
4 days ago • 0 commentsToday I was contacted by [Nigel] asking about interrupt and PIO details, especially regarding interboard communications. [Nigel] is attempting an emulator of the system, but presently it is 'hanging' early in the boot process and hypothesized that the main board is waiting on the io board.
I replied that I had not really looked into that part of the system, figuring that the I/O board would be inscrutable without a schematic, but that I would take a peek.
[Nigel] shared what he knew of the hardware; i.e.port mappings of the various I/O chips. I was impressed with such detail, and as it turns out there was a historical effort to emulate this system which was not completed.
I did an initial disassembly of those ROMs, and found the init code for the CTCs, PIOs, and PPIs. This jibed with Nigel's existing port mapping. (This also saved me some time, because otherwise I'd have to look at the byte sequence and see for which device this made sense. I did that for the main board.)
The memory map is straightforward: 32 KiB ROM followed by 32 KiB RAM.
[Nigel] hypothesized there was some 'shared memory' scheme between the two boards. This was based on comments in the non-functioning prior emulator. I was skeptical of this -- it would certainly be pretty exotic, and just the board shot alone doesn't seem to have the needed support circuitry. But I'm open to being wrong. You have to be open to being wrong when reverse-engineering. You're always wrong until you progressively become right.
-
20241103 - Numbers and Math
4 days ago • 0 commentsAs mentioned before, I wanted to take a closer look at the floating point format used in this machine to compare against Microsoft's.
I had previously found the BASIC dispatch table, and thought it would be good to take a peek at the trig functions, since those involve well known constants that will have a distinctive bit pattern.
I notice that SIN uses several constants. These appear to be 5-byte quantities. This is striking because in the TRS-80 they are either 4 or 8 bytes. So we'll see.
I also notice that COS falls into SIN:
2FDE ; handle BASIC token COS e0h 2FDE bastokCOS_2FDE: 2FDE 21 A1 31 ld hl, byte_31A1 ; XXX const, COS 2FE1 CD EE 27 call sub_27EE 2FE4 CD 0A 2B call sub_2B0A 2FE7 ; handle BASIC token SIN e1h 2FE7 bastokSIN_2FE7: 2FE7 21 97 31 ld hl, byte_3197 ; XXX const, SIN 2FEA CD EE 27 call sub_27EE 2FED CD 69 29 call sub_2969 ...
Considering the trig identity cos ( th ) = sin ( pi/2 - th ), my hunch is that byte_31A1 is the representation of pi/2. (and the subs that follow are possibly 'transfer constant to working register' and 'subtract working registers', but that will be for later exploration.
COS, const byte_31A1 31A1 A3 byte_31A1: db 0A3h 31A2 DA db 0DAh 31A3 0F db 0Fh 31A4 49 db 49h 31A5 80 db 80h
This is interesting because it is similar to Microsoft's float format. They did make a 40-bit format (5 bytes) extended precision for the Commodore PET implementation. Pi/2 in that format is:
81 49 0f da a2
So very similar. There's a difference I don't understand yet in what is ostensibly the 'exponent'. The discrepancy in the least significant bit doesn't concern me, though. The similarity is striking enough that I think the math format is a minor variation Microsoft's. Maybe Sanyo licensed it directly or indirectly.
However, looking at the implementation of SIN, TAN, LOG, and EXP relative to that in the TRS-80, it's clear Sanyo is using different algorithms. The TRS-80's were Taylor series, and the Sanyo's use few coefficients that moreover do not resemble those of the TRS-80's.
TAN is interesting in that it is a short routine:
306D ; handle BASIC token TAN e2h 306D bastokTAN_306D: 306D CD 00 28 call sub_2800 3070 CD DE 2F call bastokCOS_2FDE ; handle BASIC token COS e0h 3073 CD D3 27 call sub_27D3 3076 CD 1D 28 call sub_281D 3079 CD 3A 28 call sub_283A 307C CD E7 2F call bastokSIN_2FE7 ; handle BASIC token SIN e1h 307F CD 57 28 call sub_2857 3082 C3 C8 29 jp sub_29C8
So surely it is computing tan = sin / cos. The end is a jump to sub_29C8 to finish things up, which at the beginning makes a check for zero and invokes 'divide by zero' error, so I think that sub_29C8 is part of the division core. Finding these cores will help me map out the RAM area that doubtlessly has a few internal 'working registers' for doing math stuff.
Also, it's testing the limits of my mental capabilities to keep track of bits and bytes moving through the registers, particularly for this numeric stuff. So I might break down an make a limited purpose emulator so that I can run snippets of code through and see what is the net result. It is definitely not my intent to make a full-featured system emulator, but rather just run some short snippets and see what bit pattern comes out. For instance, taking these embedded constants and converting them to text format would be helpful in understanding the algorithms better. Maybe they're CORDIC, who knows. And it might explain that off-by-one exponent I saw in pi/2.
-
20241101 - RST-y Business
4 days ago • 0 commentsThe previous effort involving the embedded parameters and the similarity to a known Microsoft calling convention diverted me to RSTs. The Microsoft BASIC (at least for the TRS-80) used RST 8 for the 'syntax check' I mentioned.
If you're not already familiar, RST's are single-byte CALL instructions to well-known locations. They were particularly useful in the 8080, whose interrupt mode involved a device providing an instruction to the CPU for its servicing. While you could supply multi-byte instructions, it was easier if they were single byte. Systems designed for Z80 specifically don't usually use that interrupt mode, but the instruction is then used for improving code density.
I had already visited RST 8 that provided the VRAM contention avoidance feature, so time for a closer look at the others.
RST 10
RST 10 synthesizes a notional "CP HL, DE" instruction (which doesn't natively exist), and sets the Z and C flags as one would expect.
0570 ; CP HL, DE; Z ==; C DE>HL 0570 rst10impl_570: 0570 7C ld a, h 0571 92 sub d 0572 C0 ret nz 0573 7D ld a, l 0574 93 sub e 0575 C9 ret
This is striking because the code is identical to that of RST 18 in the TRS-80. So more puzzlin' evidence. At the same time, this implementation is so simple that you might have to work to come up with a different one, and also maybe it was common practice at the time.
RST 18, 20, 28
These are principally provide parser support, ultimately invoking the XXXsyntaxCheck_1D19 function I described in an earlier post. These also add on advancement past whitespace, and 'qualification' of the character that is left pointed-to by HL. By 'qualification' I mean that there is indication if this is at the end of a line, on a statement separator (":"), on a numeric digit, etc.
RST 18 -- skip this character, advance past spaces, and qualify byte
RST 20 -- advance past spaces, and qualify byte
This is the same as RST 18 but without the initial "inc hl" at the start, and is implemented that way.
The implementation is nearly identical to that of the TRS-80 in flags and registers. (The TRS-80 has a few extra cases for vertical tab and tab.)
RST 28 -- test for comma, skip spaces
This is a special application of XXXsyntaxCheck_1D19 where the expected token is a comma. This is used a bunch in parsing parameter lists.
Reflecting on these he similarity once again is strikingly similar to the TRS-80's BASIC design. But there are odd differences. The TRS-80's BASIC used RST 8 as the core 'syntax check' API, yet here XXXsyntaxCheck_1D19 is not directly exposed via RST, so to invoke it you need to make a conventional 3 byte CALL. Similarly, the precious few RSTs you have are used to create slight variants of the same principal call. So it's a curious implementation choice.
RST30
I haven't dug into this one too deeply as it seems to be 'qualify the working register', where 'qualify' in this case means 'figure out data type'. I need to dig deeper into the BASIC implementation to figure out the gory details here.
But once again it is conceptually similar to a TRS-80 BASIC function rst20_ACCTypeTest. We'll see when I dig in.
RST 38 -- IM1, already covered, legacy ISR
Anyway, the similarities to the TRS-80 implementation now make me curious about the details of the floating point format. I would think that would be distinctive if it was original work.
-
20241030 - "Undocumented Instructions" and Puzzlin' Evidence
11/04/2024 at 18:59 • 0 commentsWhile disassembling, I found a sequence the disassembler refused to interpret
24F7 ; XXX maybe LET 87h 24F7 sub_24F7: 24F7 CD 34 21 call sub_2134 24FA CD 14 1D call sub_1D14 24FD D2 D5 3A jp nc, loc_3AD5 2500 55 ld d, l 2501 FD db 0FDh ; XXX ??? wut; undefined function. probably has no effect. 2502 F5 push af ...
In the Z-80 there are a lot of 'undocumented instructions' (that have long since been documented). The Z-80, being binary-compatible with the 8080, added an IX and IY register, and uses a prefix that indicates 'use the IX/IY instead of HL in the following instruction'. This is great, though there are a bunch of code points that do nothing with HL that can be prefixed as well. Some of them are interesting. But many are not. This was one of those cases. The FDh prefix normally means 'use IY instead of HL' but the next instruction involves AF, so the FDh is effectively a NOP. What a weird thing to have in code. A bug? Maybe; it would be benign.
Elsewhere was some other suspicious code:
095F CD 19 1D call sub_1D19 0962 C7 rst 0 ; XXX wut? restart the computer? 0963 2B dec hl ; XXX wut? can't get here?
and
238B CD 19 1D call sub_1D19 238E D2 54 5D jp nc, unk_5D54 ; XXX wut? jumps into text at end of rom
So, it's time for a closer look.
1D19 sub_1D19: 1D19 7E ld a, (hl) 1D1A E3 ex (sp), hl 1D1B BE cp (hl) 1D1C 23 inc hl 1D1D E3 ex (sp), hl 1D1E C2 BB 05 jp nz, showSyntaxError_5BB ; show "Syntax Error" ...
So there's some legerdemain going on with the stack. HL in these BASIC implementation points to the current program byte, usually text. So we're getting that in A, then we exchange the Top-Of-Stack value (which is the return address) with HL. And we compare the program byte in A with the value that is at the return address (which is usually code!). Then we increment that return address and put it back onto the stack.
If the values do not compare, then we go to the 'Syntax Error' routine. Otherwise we continue on doing ... stuff.
OK, that means I need to take a pass through all call sites to sub_1D19 and fixup that a data byte follows, and that normal code disassembly resumes after that byte.
Doing so made things more sane; e.g. the above transformed to:
095F CD 19 1D call XXXsyntaxCheck_1D19; 0962 C7 db 0C7h ; XXX BASIC token ??? 0963 2B dec hl
and
238B CD 19 1D call XXXsyntaxCheck_1D19; 238E D2 db 0D2h ; XXX BASIC token ??? 238F 54 ld d, h 2390 5D ld e, l
And the first one with the FDh prefix called sub_1D14 which is just prior to 1D19 and falls through into it.
1D13 loc_1D13: 1D13 23 inc hl 1D14 ; XXX skip space and syntax check 1D14 sub_1D14: 1D14 7E ld a, (hl) 1D15 FE 20 cp ' ' 1D17 28 FA jr z, loc_1D13 1D19 ; expect @ hl byte following caller; syntax error on fail; skip spacess and get and qualify b Z if end-of-statement (could be nul, ELSE or colon), C if digit 1D19 XXXsyntaxCheck_1D19: 1D19 7E ld a, (hl) 1D1A E3 ex (sp), hl ; the the parameter pointer in hl ...
And that changes the thing that originally piqued my interest to the more sane:
24F7 ; handle BASIC token LET 87h 24F7 bastokLET_24F7: 24F7 CD 34 21 call sub_2134 24FA CD 14 1D call sub_1D14 ; XXX skip space and syntax check 24FD D2 db 0D2h ; basic token '=' 24FE D5 push de 24FF 3A 55 FD ld a, (byte_FD55) ; XXX data type?? 0 = int?, 1 = stringref? 2502 F5 push af ...
Which is much more sane and doesn't involved undocumented instructions -- much less ones that do nothing.
I've seen this method of passing a parameter to a function before: in the TRS-80 BASIC ROM. E.g.:
;NOTE: this is from the TRS-80 ROM; not Cefu 1C96 _impl_rst8_SyntaxCheckAndSkipWhitespace: 1C96 7E ld a, (hl) 1C97 E3 ex (sp), hl ; the the parameter pointer in hl 1C98 BE cp (hl) ; see if the the parameter matches the value at the buffer position that was passed in (HL at time of call) 1C99 23 inc hl ; (the parameter is in the code following the call; asjust return address past it) 1C9A E3 ex (sp), hl ; fixup return address, and restore buffer pointer into HL 1C9B CA 78 1D jp z, _impl_rst10_AdvanceAndSkipWhitespace ; advance past whitespace if we succeeded our syntax check 1C9E C3 97 19 jp errSyntax ; SN ERROR routine.
It's also interesting that the Cefu does not (directly) get to this function via a RST, whereas the TRS-80 does. So maybe time to take a closer look at the RSTs....
-
20241028 - VDG Contention and Ports and System Tick
11/02/2024 at 23:34 • 0 commentsLast time I mentioned some concern about the contention between the Z80 and the 6847 accessing VRAM, since the design does not use the MC6883. I also mentioned that the original IM1 vector seemed to be maintaining a 'system tick' by incrementing a counter by 6 on each interrupt.
In the course of other disassembly I found RST 8 is used a lot and it is pretty short. Functionally, it is a 'LD A, (HL)' that has some gating logic:
14F5 rst8impl_14F5: 14F5 DB 40 in a, (40h) 14F7 E6 10 and 10h 14F9 28 01 jr z, cont_14FC 14FB 76 halt 14FC cont_14FC: 14FC 7E ld a, (hl) 14FD C9 ret
So a bit test from an IO port that skips over a HALT. Hmm. HALT in Z80 is like WFI in other processors -- it stops until an interrupt comes in, and resumes after its servicing. So this 'read' is being gated until an interrupt happens. We know about the IM1 interrupt happening periodically, so on a hunch, could this be the arbitration mechanism? Do not access VRAM until a periodic interrupt comes is? Makes sense if it was the vertical retrace interval because the 6847 would not be accessing RAM then. So the port IO and bit test could possibly be sampling the Vertical Retrace signal, and then proceeding if we are in retrace, and then if not waiting for an interrupt. If that Vertical Retrace signal is also the periodic interrupt, then the HALT would wait for it to happen (no need to sample the bit). Also, Vertical Retrace is indeed a periodic signal that could plausibly be used for a system tick.
Reverse-engineering firmware when you don't have the physical hardware to buzz out can be tricky. One technique is to look at the port I/O and try to figure out which datasheet makes sense for the observed bits or port access pattern. E.g. this was useful in figuring out the PIO because it has several ports and the sequence of init values to configure it made sense for it and not for, say, the sound chip.
Also, the original IM1 routine is chained to from the new IM2 routine handling PIO Port A. And the interrupt mask for it only selects b7. Additionally, there is no steering logic in the ISR for that port, so it pretty clear that the original Vertical Retrace got redirected in to the PIO which taps it for its own periodic handling before thunking over to the original ISR.
In Japan, the TV system is a variant of NTSC and is 60 Hz vertical sync. So that implies the tick count is 60*6 = 360 ticks/second.
Using the 'plausible port access' technique for the other port I/O leads to the current port map of:
00h out printer 40h out misc; cassette data out, motor, printer strobe, graphics mode, a special key 40h in; cassette data in, printer status, vertical retrace 80h-88h in; keyboard matrix c0h out; AY-3-8910; write to PSG c1h out; AY-3-8910; latch reg address c1h in; AY-3-8910; read from PSG The general purpose port A and B on the AY-3-8910 seem to be the joystick ports. They each have 5 bits and are accessed during the handling of the BASIC statements STICK and STRIG. Cefu-specific; unknown, interboard comm? e0h PIO A data e1h PIO A control e2h PIO B data e3h PIO B control
-
20241025 -- BASIC Jump Table, VDG Explorations
10/30/2024 at 12:59 • 2 commentsThings didn't work out quite as perfectly as I had hoped with the jump table. There is a curious break in the keyword list demarcated with an 0xff where usually the next string's length prefix goes, before proceeding with more keywords. Hmm. And the first several of those keywords do not seem to have entries in the jump table. I was able to logically re-sync the rest of the jump table with the rest of the list using the same technique of finding a few functions that made sense (in this case INP and PEEK) and spot-checking others for sanity. So not an altogether bad situation, but one that needs more thought.
Even in the apparent token dispatch routine, there is a check for a maximum of 50, which considerably short of the whole list, so I suspect that the higher tokens are dispatched elsewhere somehow. But they do have table entries. The keywords that do not have entries in the dispatch table are like 'THEN', 'TO', 'STEP', 'FN', the various math operators, etc. The only thing I can guess about them are that they syntactically are not like statements or functions. I don't know if they even have token values. So I quit numbering the higher keywords until I work that out.
It's a bit of a pity since the math operators would have gotten me to the math core quickly, and then I could figure out the variable format and likely the 'working registers' in the data area. Oh well, it will come...
I did spend some time reviewing the MC6847 datasheet. The recommendations are to use a companion device (a MC6883 'Synchronous Address Multiplexer) to arbitrate CPU and VDG access to the VRAM. But this is a big 40-pin package, and none of the board shots of either the Cefucom or the PHC-25 show such a beast. So are they simply doing a Hail Mary pass? This could cause glitchy display (a la TRS-80 Model I). Ugly though it be, it could be done safely from an electrical standpoint since the VDG only ever reads. Still though, bletch...
-
20241024 -- Epiphanies
10/26/2024 at 15:39 • 0 commentsA lot of code sections that are clearly code but as yet unreferenced. A couple short ones were at 24E0 and 24EC. These were short enough for me to have a clue.
24E0 ; XXX similar to 24ec, but with a port 24E0 sub_24E0: 24E0 CD 2C 23 call sub_232C 24E3 D5 push de 24E4 EF rst 28h 24E5 CD 2C 23 call sub_232C 24E8 C1 pop bc 24E9 ED 79 out (c), a 24EB C9 ret
and
24EC ; XXX similar to 24e0, but with and address 24EC sub_24EC: 24EC CD 63 23 call sub_2363 24EF D5 push de 24F0 EF rst 28h 24F1 CD 2C 23 call sub_232C 24F4 D1 pop de 24F5 12 ld (de), a 24F6 C9 ret
I guess because they were short but interesting and there were two of them so similar and because they looked a lot like an OUT and 24EC looks a POKE that I was motivated to search the binary for their addresses. Maybe I could find the call site. I didn't find a call site, but I did find what looked like their addresses in a lump of not-code. ('Not code' because the area did not have the expected density of CD, C3, C9 that you would normally see from calls, jumps, and rets.) So maybe I found a jump table.
The led to a large-ish sequence of bytes starting at 0x0051. Taking these two at a time, did yield a list of addresses that pointed to sane code, most of which was previously unreferenced. I continued with this until the pointers became insane (e.g. pointing in to the RAM area.) This went from 0x51-0x57, meaning 53 16-bit entries.
The start address of this notional table was also unreferenced, so I did a similar search on the binary and found a reference @ 0872, which was in a previously un-disassembled chunk. I'll spare you the surrounding code, but short story is there is an index that is offset by 80h that selects into this table, and seems to eventually synthesize a jump by pushing the value onto the stack with no apparent matching pop before meandering off elsewhere.
Since this table is so large, and because those two functions look so much like the implementation of some specific BASIC statements, I wanted to correlate with a list of keywords found at 02AA. It is a sequence of length prefixed strings like this:
02AA 03 db 3 02AB 45 4E 44 aEnd: .ascii 'END' 02AE 03 db 3 02AF 46 4F 52 aFor: .ascii 'FOR' 02B2 04 db 4 02B3 4E 45 58 54 aNext: .ascii 'NEXT' ...
So I thought it would be interesting to correlate the ordinals of 'OUT' and 'POKE' to those of the presumed jump table. And Lo! and Behold! They do correlate.
So my intuition is that this is a tokenized BASIC, with tokens starting at 80h, and the token indexing this table.
OK, I should stop the blogging and start labelling labelling....
-
20241023 -- A Diversion into the Sanyo PHC-25 ROM
10/25/2024 at 14:13 • 0 commentsI decided to take a peek at the Sanyo ROM, which I was able to find on a fan site [http://www.phc25.com/specstech.htm]. The Sanyo was not successful in the market place, but for some reason has some fans in France.
Disassembling the Sanyo doesn't add much to my endeavor (other than a lot of work transferring over labels and whatnot), but it might give some insight into the hacks the Cefucom performed.
The Sanyo is from a different build version, and so does not binarily compare, alas. Things are scootched a little bit, so I will have to disassemble to text and diff from that standpoint (Z80 code has a lot of absolute addressing).
It is amusing that the 'tail end junk text' exists in both the Cefucom and the Sanyo, but I suspect that is less surprising since it is fairly clear that the Cefucom was built from the binary of the ROM and not the source.
But some things might be helpful, such as cataloging the IO ports. So I can see what are common and what are peculiar to the Cefucom. The common stuff will surely be for things like the printer port, etc, while the differences will be for the Cefucom-specific stuff. I'll not be able to do much with that since I have no schematics.
One other score in my research was on a museum site (https://www.oldcomputr.com/sanyo-phc-25-1982/) which had photos of the Sanyo, including a breakdown of the keyboard. The pictures are crisp enough of the PCB that I might be able to figure out the keyboard matrix from them! We'll see...
I did see that the IM1 ISR is pretty much identical between the two systems. This is not that remarkable except that the ISR seems to be keeping a 'system tick' counter: a 32-bit quantity that is incremented by 6 on every interrupt. So that seems to indicate that both systems are running at the same clock speed (and the Sanyo is documented as 4 MHz). And if I can tract the tick counter to a time$ routine eventually, I should be able to work out what the interrupt rate is by inverting the conversion factor.
There is also some fiddling with port 60h and 61h, which reminds me that I should re-familiarize myself with the 6847 and the AY-3-8910, since it could be involved with either. The values being output and read could give a clue as to which, if either, is involved. (Also, I did find the 'putc' routine, so that should eventually trace down to the video stuff.)
One curiousity is that in both roms, the 'Tape read error' error message seems to be unreferenceable. When looking at the Cefucom, I figured maybe they took out the tape routine, but it seems unreachable in the Sanyo as well. Maybe I'm missing a subtlety, though.
One thing that is notable, though, is that the pch-25 depicted on 'oldcomputr' is appreciably different than the Cefucom and also whatever was for the ROM I got. The depicted pch-25 has 16 KiB RAM, and no sound chip. Yet the Sanyo ROM I have is for 32 KiB and does have the sound routines, similar to the Cephucom. So something seems out-of-sync. The article mentions that the PCH-25 was the high end model, after the PCH-10 and PCH-20, so maybe those board shots are inconsistent with the case shots? Who knows?
-
20241022 -- More disassembly, figured out rom 4
10/25/2024 at 01:35 • 0 commentsDisassembly continues, which means sifting between code and data, establishing references, and collecting info particularly on hardware (e.g. cataloguing all port IO and whatnot.
After inferring that video RAM is 6000h-7800h, this leaves a 2 KiB hole in the memory map, so maybe it goes there? And they just tie A11 low. I don't know why a 4K eprom would be used but maybe it was cheaper than the 2716. The pinout is the same, so the 4K can be put in with no modification.
The disassembly becomes more sane with this ROM added, and now I can see the IM2 setup in it. This suggests that the PIO was added and not present in the Sanyo design, and possibly explains why there is apparent IM1 code. One of the ISRs thunks over to the IM1 (rst38) routine, so maybe the original was an IM1 system, and this was a way of tacking on IM2 while not having to revamp the original code.
On that note, it does seem that this system was built by hacking the original ROM image, rather than recompiling from source. There are some nop'ed out sections, and some code that has no effect. Most amusingly is that the 24 KiB 'main' space has unused area at its end. But it's not filled with FF! It's filled with junk. Junk in this case is some 'random' text that is some assembler source code. It doesn't do or hurt anything, but it is a curious artifact of the build system.
-
20241021b -- Initial Impressions
10/24/2024 at 22:31 • 0 commentsinteresting machine. the paper roll graphic display could make for a cool retro tech display in a moody movie, a la the scene in mr robot with the C64 and Angela.
The first board ('board 1', with the bad battery, it doesn't seem to have a marking on the silkscreen) seems to be I/O, and peripheral to the other board. It has 4 KiB RAM, and 32 KiB ROM, but much of that is data. The admixture of 8255 and Z80PIO is striking. 8255 were cheaper and simpler, so it seems if they used a PIO then they might have done that for a reason. But since 'board 1' seemed to be peripheral, and because I do not have schematics, I dug a little deeper into into 'board 2'.
'Board 2' (silkscreen "MCU02") seems to have a 24 KiB rom system in roms marked 1-3. These are mapped at 0000h-5fffh. 8000h-ffffh is the DRAM. 6000h-77ffh is, I suspect, the static ram in chips marked IC130,137,143. That region is init'ed in two chunks -- one of zeros and the other of spaces. Interestingly, there seem to be parallel chunks in system RAM at e000 and e800. This is striking since the sram should otherwise have read/write access. IC129 is surely the character generator rom, but as it's soldered in I suspect a dump is not forthcoming. No matter, it's not required, but may be useful eventually understand how the Japanese characters are depicted.
There is a 2 KiB hole at 7800h-7fffh. And there is an invocation to 7800h. I'm not yet sure if this is for the ROM cartridge, or for 'rom 4'. 'rom 4' is interesting in that it is 4 KiB, but half is unused. It /could/ fit there. But it also seems to be a boot rom. In the photo, it's close to connector CN109. Maybe it's a 'default' cartridge? (and maybe I'm barking up the wrong tree.)
The MCU02 does not seem to use NMI, but does seem to use INT, and in the Z80 IM1. This is striking, because I do not (yet) see an explicit switch to IM1 in the code of roms 1-3. So maybe rom 4 does that before paging out. Perhaps I should have a word with rom 4...
I did find the BASIC error code display routine, which should make short(ish) work of resolving the display stuff. It’s been a while since I saw a 684x.