Desultorily rummaging through the code...
RSTs
There are only a few RSTs implemented in this unit. Some are covered with FF and so don't do anything (well, they'll reboot since FF is coincidentally 'RST 0').
RST 0 starts the party with 'ld sp, C800h' and 'jp boot_100'. Seems OK, except boot_100 immediately changes it's mind to 'ld sp, C7ffh'; lol. Incidentally c800 is ostensibly more sane since SP is pre-decrement. So C7ffh wastes the last byte. But this is a fun system.
RST 8, 10, and 18 do something. RST 20, 28, 30, and 38 are in FF padding. That padding continues out to the NMI 66h which I already describe it's special-ness. It is immediately followed by one of these:
0082 nullsub_7: 0082 C9 ret
"One of these" because there are nearly 40 such empty functions throughout the code. So surely there's one within a relative jump range if you need it! Joking aside, this is giving me the vibe that at least some of this code is machine-generated -- i.e. compiled or some other generation tool. It was the 80s. Throwing out redundant code is the linker's job, and there were business selling fancy linkers that had the smarts to do that. Phar Lap is one that comes to mind. I haven't discerned an obvious calling convention, though.
RST 18 is the easiest to understand. I simply vectors to an instruction indexed by A, 0-4.
Function 01 was the first easiest as I was able to use my recent experience with the RTC to see that it was reading the clock and updating the values in RAM as BCD. It confused me for a moment at the end where it OR'd all the values on top of each other and then masked that with 0f and conditionally invoked sub_DF9, until I realized that it was just checking for midnight. So sub_DF9 does things at midnight. It's only checking hours and minutes, so this needs to be invoked at least once a minute or it will miss it. Also, it needs to be invoked not more than once a minute unless the midnight tasks are idempotent.
RST 8 and 10 are more involved. They operate on blocks of data. The blocks have the form:
00 code 8-bit 01 param1 16-bit 03 param2 16-bit 04 ... data
RST 8 expects the block to be referenced through IX
RST 10 does a little more, expecting a sequence of blocks in HL, and then dispatching them via RST 8.
What is amusing is that most of the code does not invoke these functions via the RST instruction. Rather, they make the traditional 3-byte call to the underlying implementation. There are no invocations of RST 8, and only one of RST 10. All the invocations of rst 8 are direct (OK, there's only 3) and 24 direct invocations of rst 10. So why bother? Again, it seems to be more likely some machine generated code -- humans don't code this way.
Tasks
[Nigel] made a little headway getting past the memory test failure, but is stuck in a new loop. His instruction trace ended up looping around here:
...
01FC 3E C0 ld a, 0C0h ; IM2 vector base is C000h
01FE ED 47 ld i, a
0200 ED 5E im 2
0202 FB ei
0203 again_203:
0203 AF xor a
0204 32 62 C0 ld (byte_C062), a ; XXX index into function table @ C040
0207 next_207:
0207 3A 62 C0 ld a, (byte_C062) ; XXX index into function table @ C040
020A 07 rlca
020B 06 00 ld b, 0
020D 4F ld c, a
020E 21 40 C0 ld hl, word_C040 ; XXX an array of 16 fxnptrs indexed by C062
... stuff
022E 3C inc a
022F FE 08 cp 8
0231 28 D0 jr z, again_203
0233 32 62 C0 ld (byte_C062), a ; XXX index into function table @ C040
0236 18 CF jr next_207
...
Turns out this is exactly where it should be. This is the main() loop! What it does is whizz through a list of function pointers, invoking them, and then repeating that forever.
The first task is mainTask00_default_2C3, and that is the one that check for the 'do warm boot' flag set by the NMI that we looked at yesterday!
A curiousity is that there is room for 16 tasks, but only ever are three used, and the first one doesn't change. So it's over-provisioned relative to something like:
for (;;) {
defaultTask();
myTask1();
myTask2();
}
This oddity is not a code generation thing, this means that some sort of library was used that was intended to be more general purpose. I don't know if it was in-house or 3rd party.
It's also interesting because the ISRs for CTCb1 and CTCb2 have a similar 'task list' design (though they only execute one pass at a time; not an infinite loop. They are also over provisioned supporting up to 8 tasks though only 3 are used.
Text Renderer
There appear to be several 'screens' in memory, of dimension 32x16 (one is at E400, but there are a couple more). This board has no video, of course, so it presumably is shipping it over to the MCU2 board for actual display.
There is a rendering function I named 'renderDescText_11F9' which renders what I am calling 'described text'. (yes, concocting naming is not my talent). The 'description' is a header of:- row
- column
- length
- text...
So cross-referencing all the calls to 11F9h, I was able to find and annotate all the fixed strings in the application that are rendered via this method. That soaked up a bit of the unexplored area -- not a lot, but a little.
I left a lot of the text as hex because I just don't know what those code points look like on the screen. I don't have the actual character generator rom dump, but [Nigel] suspected that it would be the same as some other Japanese models and he gave me one from a Seiko unit. I made a tool to render the binary into a shape. E.g.:
char 0xa6 (166)
00 00
01 00
02 fe #######
03 02 #
04 02 #
05 fe #######
06 02 #
07 04 #
08 08 #
09 30 ##
10 00
11 00
etc. So perhaps I will make another tool to take the binary string and render it into a bitmap that I can feed into google goggles or something to translate the text. E.g.:
ISRs
There's a bunch of interrupt sources on this board, mostly from the CTCs. Moreover, the handlers are dynamically changed during program execution. I was looking at one (isrCTCa1_624F) which does some port I/O and I stumbled across a code sequence:
6280 E6 7F and 7Fh
6282 EA 87 62 jp pe, loc_6287
Which was striking to me because that is a real parity check. The Z80 flag P/V is used for both parity and overflow (and also another function during block compare). Most of the time you're using it for the other functions, but contextually we know that we mean parity here because of the logical operation that precedes it. So is this part of the bit-banged serial routine? Surrounding context:
...
627D 3A 0E C9 ld a, (byte_C90E)
6280 E6 7F and 7Fh
6282 EA 87 62 jp pe, loc_6287
6285 F6 80 or 80h
6287 loc_6287:
6287 32 13 C9 ld (byte_C913), a
628A 3E 08 ld a, 8
628C 32 14 C9 ld (byte_C914), a
628F DB 66 in a, (66h)
6291 E6 7F and 7Fh
6293 D3 66 out (66h), a
6295 21 BB 62 ld hl, 62BBh
6298 22 02 C0 ld (word_C002), hl
629B loc_629B:
629B E1 pop hl
629C F1 pop af
629D FB ei
629E ED 4D reti
So we get a byte, mask off the most significant bit, and check for even parity. If it's not even, set the msb, which will make it even, otherwise skip that because it's already even. Then we stow that somewhere. Then we store 8 somewhere. Then we set one of the 8255 PPI port's bit 7 low. Then we load an address of a *new* isr to handle the *next* interrupt. And then we beat it.
So this does seem to be part of the machine for bit-banged serial out. Serial out is a little easier to interpret because you just clock the data out at the bit timing. (Serial in is more tricky because it's async.)
The line protocol is hard-coded as E-7-2. Not to my taste, but oh well, I didn't write this.
I did more rummaging and found the time constant for the timer, and knowing this one is one tick per bit, I worked out that this timer is clocked at the full system speed of 4 MHz. It supports two bit rate options of 300 bps and 200 bps. 200 is a new one on me.
OK, enough for the day.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.