-
20241119 - Setting Up SDCC for the Z-80
11/20/2024 at 17:00 • 3 commentsAs mentioned before, I am attempting to make a custom ROM for the Cefucom PCU to help in reversing the hardware. I have no idea whether the folks that have the actual unit in their possession would be game for such, but I'm continuing on as a mental exercise, and maybe, who knows, they might take me up on it.
I also mentioned that I am going to make my life harder by trying to bring up a C-based environment rather than simply cranking out some bespoke assembler like a professional would do in the real world. To do that, I am going to use SDCC [https://sdcc.sourceforge.net/]. This project has been around for a very long time and realizes a pretty rich C toolchain for old-school CPUs, including the Z-80. It is a labor of love, however, and as such there are some things that can be frustrating. E.g. there is pretty rich documentation, but it often does not explain what I am trying to find out or seems a little out of date. But hey that's what you get for 'free, and uncompensated work'. So we have to soldier on...
If you're familiar with Z80, you'll know that reset is always at address 0000h, that there are special locations for 'restarts' on 8-byte boundaries, that there is location 0066h which is the NMI handler, that IM1 uses rst38h, and that IM2 uses a vector table you put in memory somewhere on a 256-byte boundary. So your code has to honor that stuff. Also you have to do the usual things of informing the tools of your memory map.
I found the process to be quirky, but long story short I did get something working correctly. As to whether this is the orthodox way, I do not know, because I did not see a lot of tribal wisdom during my searches. So I want to document my findings for posterity. (Which is possibly just me, and that's fine, too.)
- The tools are apparently meant to be run by invoking 'sdcc', which seems to be a front end that will select the actual compiler and invoke the tools in some ways. This works, but there are caveats:
- There is a default target processor of 8051 and if you forget to specify, say, -mz80, you will encounter the sadness of inscrutable error messages about pdata sections etc. I'd rather there be no default, but I'm sure that's legacy behaviour because this project started as an 8051 toolchain before it grew legs.
- Another caveat is that sdcc will compile one source file only. So if you need more than one (what project doesn't) then you will need to use -c to 'compile only' and then a separate invocation to link the object files, which are named *.REL. And you will need to specify the target processor again lest you get the inscrutable error messages for a different cpu.
If you compile a single source file as described in the docs:
sdcc -mz80 main.cthe tool will compile that, generate a 'linker script' of sorts, and link with a pre-built crt0.rel and standard library. I will make all sorts of assumptions about your platform, like that you have 32 KiB RAM and 32 KiB ROM, have no interrupts, etc. Maybe you do, maybe you don't.
The tool does emit assembler, but not of the final linked binary. So I disassembled that myself as I went along to truly know what was going on, despite it being a chore. But I'm glad I did because it was clear that out-of-box the tool definitely did not place things were I needed them to be. I'm going to save you the story of the journey, and just provide my end findings.
- You will almost surely want to customize crt0.s. So grab a local copy out of installed toolchain and hack that.
- The default crt0.s puts stub ISRs at all the RSTs. I find this vexing. So I commented all that out.
- You will also set up the stack pointer. This is done right away, and has a hard-coded default at 0000h. Maybe you want it somewhere else. Maybe there's no RAM at top of memory. E.g. I am modelling an existing system with RAM a 8000h-9ffffh. So you will need to fix that. It is not clear if there is a way to do that symbolically rather than hard-coded. Oh, btw, this particular assembler uses the syntax #0xNNNN for constants. That took me a while.
- There's a 'clock' and an 'exit' label that load A and do a RST 8. I don't know what that is or why I would want it in my program by default. I suspect it has to do with the companion 'simulator' tool, but unsure. So I commented the rst's out (but left the terminal halt loop; maybe you'd choose to rst 0).
- There is init code for the initialized and uninitialized data, but it uses symbols that I guess are magically set in sdcc somehow because the assembler complains about undefined symbols. So you will need to add at the top:
.globl ___sdcc_external_startup .globl l__DATA .globl s__DATA .globl l__INITIALIZER .globl s__INITIALIZED .globl s__INITIALIZER
___sdcc_external_startup is a function that indicates that variable init should not be done, that it will be done 'externally'. The default implementation simply returns 'false', so init is always done.
The other symbols appear to be generated by the linker, and I infer that the 's__' means 'start' and 'l__' means length. You only have to declare these, you do not have to set them.
- A quirk seems to be the absence of 'weak' functions, but in lieu of such you apparently can blithely over stamp code simply by placing some on top of it later. I think this is how you override the default ___sdcc_external_startup -- just declare your own later in the linking order. I don't need this, so I didn't explore much more.
That should get you a generally usable crt0.s (with the exception of having to customise the stack pointer per-project).
Next was about placing functions at specific addresses. There is not a mechanism like an 'attribute' that can be applied to a function to do this (there is for data, though; more on that in a moment). But there is a pragma:
#pragma codesegThe thing is that pragma will affect the entire source file. Not just the things that follow. I know this because I tried to use it for my RST 8, just before the function's definition, and the effect was that all the code in that module started there. main() and everything. My actual rst was somewhere far later, lol. So if you need to place your function at a specific address, then you'll need to put that definition in it's own source file, and have that definition the first bit of code. At least as far as I know. So, I can do a RST 8 like this with a separate file:
rst8.c:#pragma codeseg RST8 void rst8 (void) { }
This will now give you linking problems because it doesn't know where _RST8 is. So you will need to define the resulting symbol "_RST8" to the linker via it's 'linker script'. The linker script is really just a list of command line options. The pertinent one here is 'base':
-b _RST8 = 0x0008Now if you check your disassembly you'll see you rst8 stub correctly located, and everything else where expected (namely, crt0 at 0x100, and your code (main) at 0x200. It's a bit of a hassle to require a separate source file just to get the base address applied correctly, but hey, the price is right, yes?
Now for interrupts. There is a little bit of quirkiness in the syntax which I'm sure is that way for legacy reasons of another CPU, but the form looks like this:
void isrCTCa0(void) __critical __interrupt(0);
The '__critical' keyword is optional, and in Z80 world means 'prohibit nesting of interrupts by not generating an EI at the start of the generated code'. In the Z-80 world you generally do not nest unless you are in IM2, so you probably need __critical in most case. '__interrupt(n)' is required for maskable interrupts, and means "end the function with an 'ei, reti' pair instead of the usual 'ret'". The numeric parameter has no material effect for Z-80, but it is required and must be globally unique and can take on the value 0-255. I used 0-5 for mine and 255 for a do-nothing stub implementation.
Other than that the ISR is an ordinary function that can wind up anywhere in code space. This is OK for an IM2 handler. If you are doing IM1, you probably want to have something at 38h specifically, even if just a thunk, so you're going to have to use a separate file just as with the rst 8. Likewise, if you're going to have an NMI handler then you are required to be at 66h.
So I already described how to get the code to be at 66h by creating a separate source file nmi.c with it's implementation:
#pragma codeseg NMI
and add into the linker 'script':
-b _NMI = 0x0066But you also have to use a syntactic hack and omit the interrupt number from the signature:
void isrNMI (void) __critical __interrupt;
By omitting the interrupt number you are semantically telling the compiler that this is an NMI, and it will generate a final 'RETN' instead of the usual 'ei, reti' pair. Quirky!
At this point it might be useful to show my linker 'script'. The file was generated when I first ran sdcc on a trivial source file with main(), and then I customised it. As you can see, it's less of a script and more of a list of command line options passed to the linker.
-mjwx -i diagnosticrom001.ihx -b _CODE = 0x0200 -b _DATA = 0x8000 -b _RST8 = 0x0008 -b _RST10 = 0x0010 -b _RST18 = 0x0018 -b _RST20 = 0x0020 -b _RST28 = 0x0028 -b _RST30 = 0x0030 -b _RST38 = 0x0038 -b _NMI = 0x0066 -k C:\Program Files (x86)\SDCC\bin\..\lib\z80 -l z80 crt0.rel main.rel nmi.rel rst8.rel -e
Notable are the various '-b' options and the list of object files near the bottom. It is documented that order is important, with crt0.rel coming first, and then whatever.
OK, that's enough for an IM0 or IM1 system, but this one is IM2 so we have to set up an interrupt vector table. This isn't as bad, because data does have a means of declaring placement. It uses the '__at' qualifier. E.g. in my case:
void* __at(0x9e00) g_im2Vectors[16] = { isrCTCa0, //00 CTCa-0 isrCTCa1, //02 CTCa-1 ... };
So that wasn't as bad as with placing functions. (A pity __at() does not work there. You know I tried.) What is a little annoying is that there doesn't seem to be 'math' capabilities on these numbers, so like the setting up of the stack pointer in ctr0.s, you will need to be conscientious about updating these spots if you change the memory map as your design evolves. So maybe try to do that up front and leave a little comment to your future self.
Which gets us to the last one: loading the I register. In the Z-80 the base of the interrupt vector table is held in the I register, and so would be 0x9e in this case. We'll need some inline assembler for that. There is an 'old' syntax and a 'new' syntax and I suggest using 'old' because it is block oriented. (The 'new' syntax is apparently for consistency with some other tools' conventions, but is not block oriented.) So in main(), after the hardware is set up, and we're ready to shift the system into 'drive', a code block:
//need inline assembler to setup im2 __asm ld a, #0x9e ; hibyte of address of IM2 vector table ld i, a im 2 ei ; away we go __endasm;
The last thing you'll want to know for Z-80 is about port I/O addressing. This is well-documented, so I'll be brief: you declare a location as a 'special function register'. E.g.:
__sfr __at(0x00) ioCTCa_0;
Now you can simply assign to and from that spot and the compiler will emit the requisite 'in' and 'out' instructions.
After doing all that that you will have a baseline to start coding in C and be less exposed to Z-80 particulars. Bear in mind that you'll need to go through a similar process of customising crt0.s for other runtime scenarios, such as running the context of an OS, as an extension, as an overlay, etc. Maybe they don't have interrupts, maybe they don't fiddle with the stack, maybe..., etc.
Some final comments:
- build tools
I wound up invoking the assembler and linker directly so that I could control what they were doing. The compiler seems to be embedded in the sdcc front end application, so you still have to use that there with the -c option to just compile.
I did this via scripts 'assemble', 'compile', 'link' because I didn't want to add 'make' to my problems just now, but it can surely be used for greater sophistication. - calling conventions
This is C, and locals are on the stack, and there is a frame pointer via the index registers. The calling convention is documented, so you will want to take a peek at that. You can specify to omit the frame pointer if you have other designs on IX and IY. Also, the alternate register set is not used at all, so you have that at your disposal (and responsibility). - initialized variables
The crt0.s does the stuff expected by C for initializing storage. However what I found interesting in SDCC is that it will also initialize data that is not in the default data segment. That is unique in my experience -- on other toolchains you have to do that yourself. What is also distinctive is that it does this not by doing a 'memcpy()' from a chunk of constant, but by emitting code that loads registers and stores values. Know that this can get big, so you might choose to do that yourself. - you can download the sdcc project source to look into the stdlib implementation, and you might need to. (or at least peruse it in the repo via web.) For example, there is a malloc() and free(), but I have no idea how they work or are placed in memory. So that's worth checking out because usually I am not happy with any given toolchain's choices for heap. If you need a heap.
The generated code quality is OK, doubtlessly not better because there is not whole program optimisation during link. So you'll find things like fun things loading a value of zero in a register and then immediately testing if it is zero to make a conditional jump. It's like that because the constant that happened to be zero was not known at code generation time -- it was set at link time. And the same truth applies to the non-zero case, so really the test and conditional jump were never needed. If only it knew... So the result is probably going to be a bit fluffy and you may run into resource shortages sooner than if you hand-assembled.
But hey, coding in C certainly beats coding in assembler if you prioritize your development time over generated code quality (and your sanity over juggling registers). Plus you get multiplication, division, floating point, trig if you really need it, and sprintf, etc. So I'm glad I put forth the effort to scope it out and ramp up, and I am very grateful for the tool's existence.
-
20241116 - Front Panel LEDs and Buttons
11/19/2024 at 13:54 • 0 commentsToday I received from [Nigel] an alternative ROM dump of another instance of the machine's PCU board. This is from a later revision than [Rees'] and I refer to it as the [Tony] version.
There was some concern that Rees' dumps may have suffered bit rot since there were unrepeatable reads of the last ROM 8-2. I did a casual diff and there were over 5,000 differences, so I had to dig in.
At length I found that there were a couple bytes of new code added fairly early in the image, but just that small change causes all the subsequent routines to move out a bit, and so there were a huge number of "benign" differences.
Locating the material change was a bit of a needle-in-a-haystack, but what I found was that [Tony's]
version simply disables 'mode 5' -- the planned-for-but-incomplete 'information' feature. There was also some removal of a section of data lump later in an undecoded witchcraft script, but I suspect that it will be perhaps some menu text. (This system is Japanese, so I can't read it with my eyes. Oh, yes, btw, I'm pretty sure the character encoding is "JIS X 0201".)While the loss of the feature is in some way sad (though the implementation is all still right there, and also you could simply put in the old roms to get it back), the stopper that was added put me on the track of the mode selection menu. What was added was a test for a case of '5', which is ignored. There were existing rejected cases of 0, 7, and 8. So the coincidence of the number, plus the case 7 and 8, but no more, made me think that these numbers directly associate with the front panel buttons. The buttons are soft keys with different meanings in different context, such as when you're using the 'mode 4' tape recorder.
Following along the path of variable-with-the-button number back to the hardware, I was able to piece together with high certainty that the buttons are simple SPST w/pullup on port 68h (8255c-a). I also found the associated key scanner and debounce routine in sub_1A5F, which is invoked from isrCTCb0_1A48 -- the periodic timer ISR.
There is a debounce count of 4. I tried to game out plausible debounce periods (e.g. I usually go for 50 ms myself) backwards to a sensible input clock, and the best solution I could find was 1 MHz. I'm a little disappointed I didn't make it to the system clock of 4 MHz (as I was able to with CTCa-0 and -1), but there are some 7474's on board so maybe there is external division. Just a guess.
As an added bonus, there are also front panel LEDs that light to indicate mode, so I was able to correlate them to another port 65h (8255b-b). That is 5 of the bits, and since there are three more LEDs on the panel (indicating learning progress), I suspect that is the function of those other three. (I don't know enough about the learning system yet to be conclusive.)
So! Two PPI ports fully deciphered, and all without a physical sample. Just puzzling though code. (And OK the operating manual provide critical context.)
But I will hit a limit on what I can decipher from code alone, so I'd really like it if some of the folks with the hardware can do some buzzing-out and whatnot. This gave me one of my kooky ideas:
- I should be able to make a custom firmware that puts the ports in a known state which can easily be verified with a multimeter. E.g. There are three PPIs which I call 'a', 'b', and 'c' solely based on their port number in the code, but what physical chip is that? What is the part designation? Same with the CTCs. (There's only one PIO, so that's unambiguous.)
- I wouldn't presume to configure any of the ports differently than what the actual firmware does in fear of causing some damage (though I doubt it based on the board shots), but each of those PPIs has at least one output port. So notionally I could, say, set b7 high on one device, b6 high on another device, and b5 high on the other.
- This would be a lot faster than buzzing out the decoder logic, which plausibly is at IC 14, 16, 18, possibly with associated nearby SSI logic.
- Similar approach with the CTC's. And if the tester's multimeter had even a rudimentary frequency counter function, then all the better.
Anyway, I don't know if I'll get any takers, but this did send me on a diversion of building a custom firmware for this purpose. I can do that easily enough in assembler for something of this scope, but why not make things more challenging, and see if I can't get a C implementation working?
Oh, incidentally, I think IC 16 is for memory decoding more than port decoding.
-
20241115 - Witchcraft
11/15/2024 at 20:48 • 0 commentsAs mentioned yesterday, there is a section of code that is confusing. There are a few call levels deep with return address manipulation and indirection that obscure the flow of execution. But it needs to be figured out because the mechanism is used with some frequency (16 known instances) and operates on sizeable hunks of embedded data.
4ECC sub_4ECC: 4ECC CD 14 5A call sub_5A14 4ECF 0C db 0Ch ; lump o data 4ED0 0A db 0Ah 4ED1 D7 db 0D7h 4ED2 4E db 4Eh 4ED3 00 db 0 4ED4 C3 db 0C3h 4ED5 E2 db 0E2h ...
The routine eats the return address into HL (which points to the lump o data) and then calls something else, and then jumps to whatever was left in HL.
5A14 sub_5A14: 5A14 E1 pop hl 5A15 CD 19 5A call sub_5A19 5A18 E9 jp (hl)
The 'something else' it calls, calls yet something else, and apparently does an infinite loop. I.e., not returning to the caller to provide HL to which to jump.
5A19 sub_5A19: 5A19 CD 1E 5A call sub_5A1E ; do something with HL pointing to a lump o data 5A1C 18 FB jr sub_5A19 ; infinite loop?
The 'yet something else' it calls gets a code at the start of the lump o data, and increments the pointer past that, and does some checking and mapping of value before invoking my old friend 'sub_5905' which is known to dispatch via a 'subsequent table'.
5A1E sub_5A1E: 5A1E 7E ld a, (hl) 5A1F 47 ld b, a ; remember the code 5A20 23 inc hl ; adjust data pointer to after the code 5A21 FE B0 cp 0B0h 5A23 38 08 jr c, loc_5A2D 5A25 FE C0 cp 0C0h 5A27 3E 10 ld a, 10h 5A29 38 02 jr c, loc_5A2D 5A2B 3E 01 ld a, 1 5A2D loc_5A2D: 5A2D CD 05 59 call sub_5905 5A30 52 5A dw sub_5A52 5A32 56 5A dw sub_5A56 5A34 6C 5A dw sub_5A6C 5A36 71 5A dw sub_5A71 ... 17 of such subroutines
I had seen sub_5905 late last week when I was cataloguing the various direct dispatch tables. The gist being that a table of functions follows the call, and A indexes into that table and finally a jump (effectively) is made to the selected address.
5905 sub_5905: 5905 E3 ex (sp), hl ; HL now has the table address (which followed the call site) 5906 CD 0B 59 call sub_590B ; HL = * ((WORD*)HL) [A] 5909 E3 ex (sp), hl 590A C9 ret ; effectively a jump
Those were simpler times. The 'witchcraft' method uses a similar 'lomp o data follows what looks like a call' technique, but the data is not a simple table. And the interstitial infinite loop adds a twist. My limited mental faculties required that I page out to paper, and kept track of the call stack on the way down to the dispatched functions:
stack:
ret 0 = witchcraft_01, sub_5A19, infinite loop
ret 1 = witchcraft_00, sub_5A14, thunk via HL
on entry:
B = code dispatched upon
HL = pointer into data after the codeSo, it seems this witch knows 17 'spells', and is given a lump of data as an 'incantation' specifying a sequence of spells to cast along with contextual data. So it's time to take a closer look at her spell repertoire.
The first 'spell', referenced by code 0, is brief:
5A52 witchspell00_5A52: 5A52 33 inc sp 5A53 33 inc sp 5A54 C9 ret
By bumping SP twice, we effectively eat a return address. Often times we do that with a pop, but I guess the author did not want to clobber any registers at all and chose the two increments instead. The net effect is to return to the caller's caller, 'ret 1', which gets us past the infinite loop to where we thunk over via HL.
The second spell involves more stuff than I want to dig into right now, and moreover can be entered multiple ways so I'm going to save that for later.
The third 'spell', referenced by code 2, is straightforward:
5A6C witchspell02_5A6C: 5A6C 5E ld e, (hl) 5A6D 23 inc hl 5A6E 56 ld d, (hl) 5A6F 23 inc hl 5A70 C9 ret
So it loads DE (little-endian) from the data pointer, and advances the data pointer past that. Notably it does a ret, so it goes back to 'ret 0', which is the infinite loop. I.e. it will then continue consuming and dispatching spells.
At this point we can start to see a design emerging. The witchcraft mechanism is yet another 'embedded scripting' technique used in this machine.
The fourth spell invokes our old friend the embedded scripting engine:
5A71 witchspell03_5A71: 5A71 D5 push de ; save 5A72 CD 23 41 call impl_rst10_4123 ; run VM program 5A75 D1 pop de ; restore 5A76 C9 ret
I'll need to spend some time with the other 14 spells, because as mentioned there are 16 invocations of this witchcraft that I will need to annotate. But I can already do a short one:
... 51A2 CD 14 5A call witchcraft_00_5A14 ; witchcraft00; process subsequent data block 51A2 ; --------------------------------------------------------------------------- 51A5 02 db 2 ; witchspell02_ 5A6C load DE 51A6 20 C8 dw unk_C820 ; ... with this value 51A8 D1 db 0D1h ; witchspell01_ 5A56, embedded param 11h XXX off_ 5B5D entry 17 51A9 03 db 3 ; witchspell03_ 5A71 run RST10 program 51AA 48 db 48h ; vmop48_4589 - 'bin2bcd' 51AB C8 25 dw 25C8h ; c825 - dest buffer 51AD 81 04 dw 481h ; 8104 - uint16be_t* value 51AF 04 03 dw 304h ; 0403 - 4 digits, blank pad 51B1 03 db 3 ; vmop03_41B1 - *(uint16be_t*)param1 = param2 51B2 C8 29 dw 29C8h ; c829 51B4 14 37 dw 3714h ; 1437 51B6 00 db 0 ; vmop00_end_419E 51B7 00 db 0 ; witchspell00_ 5A52 end incantation and thunk to HL (which is next byte) 51B8 ; --------------------------------------------------------------------------- 51B8 C9 ret ; return from this invocation of witchcraft
So, are we done yet with embedded scripting engines? I'm not so sure. The magic dispatch function that is used by the witchcraft mechanism (for thunking via a subsequent table of function pointers) is referenced in another place:
... 4E54 D2 A7 53 jp nc, sub_53A7 ; A >= 45; too high 4E57 CD 05 59 call doMagicDispatch_5905 ; dispatch (not call) by A with subsequent table 4E5A B4 4E dispatch_4E5A:dw sub_4EB4 4E5C CC 4E dw sub_4ECC 4E5E E1 4E dw sub_4EE1 ...
So there is another mechanism with 45 'opcodes'. ¡Ay, chihuahua!
I apologise for my whimsical naming of 'witchcraft', 'spells', and 'incantations', but that silliness helps to keep me going. I intended to change it to something more pedestrian, but now that I've gone through it all, I'm not sure if I should. I've got to call it something, and there's so much more to decipher.
But it's Friday, and maybe this weekend I will go outside and get some light for a change....
-
20241114 - Virtual Machine Summary
11/14/2024 at 21:54 • 0 commentsYesterday I mentioned this apparent 'virtual machine' embedded in the Cefucom product, and I spent some time figuring how the it do. The machine workings are pretty clear now, and I have done 66 of the opcodes (10 more to figure out). Here's the scoop so far:
Overview
* basic unit of execution is a 'block'
* block structure is:
* opcode
* parameters... Number of parameters is opcode-specific. So a block is between 1 and 7 bytes.
* a 'program' is a series of blocks
* rst 8 is the 'primitive block executive'. It is not typically used directly.
* rst 10 is the 'sequenced block executive', and is the primary way of executing 'programs'
* is BIG-ENDIAN
* has absolute addresses
* indices are 1-relative
* has various functional groups:
* load/store; 8 and 16 bit, references and constants
* memset/memcpy
* arithmetic; addition/subtraction, usually accumulator form (e.g. *parm1 += *param2)
* bitwise; and, or, xor (note, no 'not', though I suppose you can synthesize that from xor)
* shift
* goto
* computed goto (well, 'indexed'; the computation would be done separately)
* if (and if-not)
* next
* 'usr' (call out to an assembly routine)
* 'run' (another program)
* some Cefucom specific opcodes; probably added by the companyother notable aspects
* The C register is used to store flags, where appropriate. 80h = carry, 1 = non-zero/no-carry, 0 = zero. The C register is actively preserved between block execution.
* A couple instrucstion place the result in B or DE, but these are not preserved between blocks. (I need to look more into this when I find a program that invokes them; and maybe there are no instances of such.)
* the RST 10 implementation realizes a few more outside of the dispatch table:
* 7F - NOP
* 7E - exit on no-carry
* 7D - exit on carry
* 7C - exit on non-zero (or carry)
* 7B - exit on zero
* the implementation speculatively loads param1 into HL and param2 into DE, which is useful for most instructions, but some do have less parameters (e.g. END). This is a potential out-of-bounds read, but will not cause problems on this hardware.
* there are two opcodes 62h and 63h which have little-endian parameters param1 and param2, while param3 is big-endian. I think this is a bug, though it might originate from a detail of their build process.
* opcode 43 is also little-endian, which runs a rst 10 program. This is interesting because there is already opcode 3e that does the same thing with a big-endian program pointer, so there must have been felt a special need.The remaining opcodes deal with Cefucom structures which I do not yet understand, so I might put those off for the moment in the interest of making progress.
One vexing thing has been some functions that do some stack manipulation such that the execution is no longer linear. I figured out some of those, like the ones that have a dispatch table that follows a call. But there are some others that have more convoluted shenanigans. I marked those as 'witchcraft' so that I can find them more easily when I come back to them. Perhaps now is the time to look into this witchcraft.
-
20241113 - An Embedded P-Code-esque Virtual Machine?
11/13/2024 at 16:05 • 0 commentsAs mentioned before there are a lot of tables, and even a couple of tables of tables. Some are lists of 'described text' (having a header indicating position), some are indexed dispatch tables, some are double-dispatch, and the others are presently unknown.
I had previously thought the table-of-tables form was a double-dispatch mechanism, but it's not. Rather it's a list
Often they are processed by RST 10, which delegates processing of the elements to RST 8. The elements are variably-sized blocks. They seem to have the structure:uint8_t fxn; uint16le_t param1; uint16le_t param2; ... payload;
(I am coining the term 'uint16le_t' because I have found many places that are big-endian! So elsewhere I use 'uint16be_t' for that.)
Often the blocks are short, like this:
7817 62 byte_7817:db 62h 7818 95 7A dw unk_7A95 781A 11 00 dw 11h 781C 00 db 0
but others can be quite long. I originally thought param1 is a pointer, and params is a length, because the RST 8 code that processed them immediately loads param1 into HL and param2 into DE. Indeed sometimes those are used as pointers and lengths, but in other cases they are not. But for starters I look at the ones at off_7803, which is a list of these things which have just one element in them, and seem to follow the (ptr,length) assumption. The trailing zero is interesting. All the ptrs and lengths worked out sanely when updating the the disassembly.
These blocks are ultimately processed by RST 10. This is a block sequencer, feeding individual blocks to RST 8 (which expects the block pointer in IX). A null function code terminates processing, so that explains the trailing 'db 0' above. So there is no explicit payload length of a block; it depends on function code.
RST 8 dispatches servicing through dispatch_4002, which has 128(!) entries. I went through and labelled each of them like 'fxn00'. Many of them are apparently unimplemented as their slot directs to 'fxn00', which was found to be used as the block sequence terminator. It does have an implementation, though, which is to back up IX by 4. This is interesting because fxn00 will not be dispatched by RST 10, but it would for the ostensibly unimplemented function codes. Turns out that RST is optimistically loading param1 and param2 and incrementing IX just past. So the IX-=4 is to undo that optimistic loading and continue at the byte following the unimplemented function code. So 'ignore unimplemented function code'.
In the end, there are 76 implemented and 52 unimplemented functions.
After labelling, I looked at fxn62h, since that was the the code used in the blocks above. The implementation boggled my mind a bit. It did some sort of queueing into c200, which is structured as 32 8-byte entries. A gave up with that and scrolled through the nearby disassembly casually and found fxn63 just below it, that also did something similar with c200, and then invoked my buddy RST 10 -- the 'block list dispatcher'. So if fxn62 puts it in, and fxn63 takes it out and processes it, this seems evocative of a 'load' and 'run' functionality. Anyway, there was still too many unknowns so I started to look for smaller fish to fry.
Scrolling through I found some shorter ones that I could comprehend, and coincidentally these tended to be the smaller numbered function codes. The first one I found was:
43FD ; XXX 3f: dispatch to param1 (no param2) 43FD fxn3f_43FD: 43FD DD 2B dec ix ; (no param2) 43FF DD 2B dec ix 4401 E9 jp (hl) ; thunk over
So this would run arbitrary external code.
Another is a conditional 'goto' of sorts, where the block processing is directed to another place (hopefully within the list!) if --*((uint8_t*)param2) != 0:
43D3 ; XXX 3c: goto block @ param1 43D3 fxn3c_43D3: 43D3 EB ex de, hl 43D4 35 dec (hl) 43D5 28 03 jr z, leave_43DA ; leave if --*((uint8_t*)param2) == 0 43D7 D5 push de 43D8 DD E1 pop ix 43DA leave_43DA: 43DA C9 ret
So this seems to be implementing a form of 'next' using an implicit down counter. Similarly, there is an 'if':
43B2 ; XXX 3a: if ( *((uint8_t*)param2) ), goto param1 43B2 fxn3a_43B2: 43B2 1A ld a, (de) 43B3 B7 or a 43B4 28 03 jr z, leave_43B9 43B6 E5 push hl 43B7 DD E1 pop ix 43B9 leave_43B9: 43B9 C9 ret
So these 'blocks' seem to suggest being a 'byte code' of sorts for a virtual machine.
It's worth noting also that most values referenced within this machine are treated big-endian; e.g.:
434D ; XXX 2d(4): *(uint16be_t*)param1 <<= param2.l 434D fxn2d_434D: 434D C5 push bc ; save 434E 46 ld b, (hl) ; high byte first! 434F 23 inc hl 4350 4E ld c, (hl) ; then low byte! ...
However, param1 and param2 are host cpu native, i.e. little-endian. I add the notation 'param2.l' to make it clearer which byte in a neutral way.
Along the way I also found and, or, xor. But then I needed to call it a night, but lot's more to do in this area. Since the Cefucom seems to embed scripts for this thing, it may be useful for me to make a disassembler for it. This will be a little challenge since the 'compiled' scripts have absolute memory references, but I can probably manage. It will take some effort, but it might be worth it for more rapidly understanding what's going on.
It was a thing back then to use 'p-code' as a means of achieving code density. This is not p-code per se, because it's not (p)ortable at all. It has absolute address references and is mixed-endian. But it seems to be in that spirit.
A non-important observation is that all this code is located from 3f00-4dff. (there is a couple dead bytes at the beginning and many at the end). So it feels like a distinct 'component' of sorts. 3840 bytes for this 'virtual machine'.
-
20241111 - Catching Up and Publishing Work
11/13/2024 at 11:16 • 0 commentsLast week's PCU push put me further behind in posting, so I caught up those log entries.
Also, I don't know why I didn't already, but I put the listing in a github so others can check it out if they are curious. There's a link in the project's 'link' section, and also here if you happen to be reading this entry:
https://github.com/ziggurat29/cefucom-21.git
It's a pity I didn't start this at the beginning so that I could see the history of this past 3 week's work, but oh well. I was moving fast then. Still am.
Now that all the puzzle pieces are on the table, it's time to see what picture emerges when solved. There's no depiction on the packaging. ;)
-
20241110 -- PPI (8255) Catalogue
11/12/2024 at 22:55 • 0 commentsToday I went though all the references to the 3 Programmable Peripheral Interface (PPI; 8255) devices. To my relief they are configured once and stay that way. Also, they are all set to be garden-variety gpio -- no special modes.
8255 Summary:
8255a:
Port A: in
Port B: out
Port C: in8255b:
Port A: out
Port B: out
Port C (upper): out
Port C (lower): in8255c:
Port A: in
Port B: in
Port C: outMostly I don't know what these do, though, because I have no schematic. It's worth noting that my nomenclature of "8255a" and "CTCa" is purely made up. I don't know what actual device on the board these are associated with. Otherwise I'd use the parts marking on the board. If I ever find out, then I'll update the info. This did send me on a side activity of going through the screen shot and collecting all the parts. (Well, just the IC's.) So I made a BOM for future reference. The BOM occasionally gives me some ideas; e.g. there are two 7474's on board. Maybe clock dividers? And if someone has a physical board and is willing, I can be specific about requests to buzz out specific pins. (Especially the address decoders to the various chip selects would be handy!)
I can say that 8255b-a is unused, and that the unusual 8255b-c is used for the bit-bang serial, and possibly RTS/CTS.
Watchdog?
In the course of this, I found a bit which is polled periodically, and will reset a down counter. If the counter reaches zero, a request for reboot is flagged. This check is registered in the CTCb2 task list (entry 1).
0D7D isrCTCb2task01_watchdog_D7D: D7D DB 69 in a, (69h) ; 8255c-b; checking b6 0D7F 47 ld b, a 0D80 DB 69 in a, (69h) ; 8255c-b; checking b6 0D82 B8 cp b 0D83 20 14 jr nz, resetWatchdog_D99 ; transitioned during subsequent reads; lets try again 0D85 CB 77 bit 6, a 0D87 20 10 jr nz, resetWatchdog_D99 ; b6 = 1 == system ready? 0D89 3A 69 C0 ld a, (byte_C069) ; XXX a watchdog timer; initted to 5 on 69h b6 set (system ready) 0D8C 3D dec a 0D8D 32 69 C0 ld (byte_C069), a ; XXX a watchdog timer; initted to 5 on 69h b6 set (system ready) 0D90 20 0C jr nz, nullsub_10 ; leave 0D92 3E FF ld a, 0FFh 0D94 32 68 C0 ld (byte_C068), a ; XXX flag causing warm boot (or maybe sleep) in main task 00 0D97 18 05 jr nullsub_10 0D99 resetWatchdog_D99: 0D99 3E 05 ld a, 5 ; give it 5 chances to come back 0D9B 32 69 C0 ld (byte_C069), a ; XXX a watchdog timer; initted to 5 on 69h b6 set (system ready) 0D9E nullsub_10: 0D9E C9 ret
I'm calling this a 'watchdog' for now, but it might actually be something else that isn't supposed to stay low for too long. Maybe a sensor of some sort.
Ring Counter?
Maybe the most interesting find was three bits that are configured in what appears to be a ring counter:
5D44 sub_5D44: 5D44 3A 02 81 ld a, (byte_8102) 5D47 32 03 81 ld (byte_8103), a 5D4A 21 B0 80 ld hl, byte_80B0 5D4D CB 96 res 2, (hl) ; clear 5D4F CB 8E res 1, (hl) 5D51 CB 86 res 0, (hl) 5D53 3D dec a 5D54 28 07 jr z, was0_5D5D 5D56 3D dec a 5D57 28 07 jr z, was1_5D60 5D59 3D dec a 5D5A 28 07 jr z, was2_5D63 5D5C C9 ret 5D5D was0_5D5D: 5D5D CB CE set 1, (hl) 5D5F C9 ret 5D60 was1_5D60: 5D60 CB D6 set 2, (hl) 5D62 C9 ret 5D63 was2_5D63: 5D63 CB C6 set 0, (hl) 5D65 C9 ret
and later:
047D F3 di ; critical section 047E 3A B0 80 ld a, (byte_80B0) ; XXX a bitfield; also goes out 8255b-b 0481 E6 07 and 7 ; just the lower 3 bits 0483 32 B0 80 ld (byte_80B0), a ; XXX a bitfield; also goes out 8255b-b 0486 FB ei ; end critical section 0487 D3 65 out (65h), a ; 8255b-b
A ring counter is a bit peculiar and in this product maybe drives a 4-wire 3-phase stepper motor. Such a motor might be useful for advancing the paper roll display.
-
20241109 -- Tables, and Tables of Tables
11/12/2024 at 18:04 • 0 commentsThere's still a fair amount of unexplored data and code. Can't say much for data, but code you can figure out where functions demarcate in many cases just by looking for the C9 that is the RET that often punctuates a function end. I found several 'orphan' functions this way.
As a new tack, I searched the binary for the orphaned function's address. Ostensibly to find a call site, but many times I found it in a list of addresses that mapped to other orphaned functions. So I had found some dispatch tables. Oftentimes is was easy to work backwards from such to find the start of the function table because it would abut some code ending. Then I could do a similar address search to find the code that dispatches through the table.
It was a bit tedious, but there are presently 42 such dispatch tables currently found! And just for fun, it turns out that there are dispatch tables of dispatch tables in some cases! There is a huge table of 128 entries at 4002h, a double-dispatch table at 2DA6 (with associated paramters table at 2DE0) and another double-dispatch table at 2200.
With that many dispatch tables, this surely is some state machine design. To think I was daunted by the one in ROM 4! This is proportionately larger. But who knows, this might be a gift as it might make the intent clearer.
Bloopers
Some amusing treats were nullsubs that have a subsequent jump to the nullsub. (The code in the subsequent jump is not referenced anywhere.)
3E9C nullsub_4: 3E9C C9 ret 3E9D C3 9C 3E jp nullsub_4
or
3EA8 nullsub_5: 3EA8 C9 ret 3EA9 C3 A8 3E jp nullsub_5
And a dispatch table of jump addresses that ends with a ret; lol.
... 40FC 9E 41 dw sub_419E ; XXX IX -= 4 40FE 9E 41 dw sub_419E ; XXX IX -= 4 4100 9E 41 dw sub_419E ; XXX IX -= 4 4102 C9 ret
My personal favorite:
... 0475 18 00 jr loc_477 ; well let's jump right on that! 0477 loc_477: ...
I think more symptoms of machine generated code.
It took a day, but it got me to my desired 100% code coverage milestone. Now that I have the puzzle pieces, it's time to see what picture emerges.
-
20241108 -- PCU; RSTs and Rendering and Tasks and Serial
11/12/2024 at 15:03 • 0 commentsDesultorily 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.
-
20241107 -- Back to PCU Grindstone
11/11/2024 at 23:28 • 0 commentsReflecting on yesterday's experience with ROM 4, I'm a little daunted by what lays ahead. That was a 2 KiB ROM. This is a 32 KiB ROM, so 16 x as much to cover. At the same time, the understandings I got from ROM 4 might provide context that speeds things along.
The PIO A is configured much like on the MCU2, with differences in bit 7 and 6. On the MCU2 b7 is the interrupt in, and b6 is unused. On the PCU they are both outputs. B7 does something. B6 seems unused so far.
PIO A does not provide an interrupt source as it does on MCU2, but there are plenty of timers, and in particular CTCb0 is configured free running. It prescales by 256 and has a time constant of 3fh, so that would imply a total division of 16384. I don't know its clock source. If it was the 4 MHz system clock, then that would be 244 per second, and if that were externally prescaled by 4, then that would be 61 per second. Again, I don't know if any of that is the case.
What I did negatively confirm is that there seems to be no systick as with the other board. There is a counter that is incremented, but it is 8 bits and it saturates rather than rolling over.
I was eager to find such a tick, because my assumption was that PIO Port B would be serviced similarly here -- and it is, except for the timeout. Had it been the case I might have been able to work out what the clock rate likely is. Oh well.
The PIO B ISR is freaky. It starts off sane:
3D48 isrPIOb_3D48: 3D48 FB ei ; allow nesting 3D49 F5 push af 3D4A C5 push bc 3D4B D5 push de 3D4C E5 push hl 3D4D CD 56 3D call impl_isrPIOb_3D56 ; XXX PIO B isr implementation; invoked after saving all regs 3D50 E1 pop hl 3D51 D1 pop de 3D52 C1 pop bc 3D53 F1 pop af 3D54 ED 4D reti
and then things get weird:
3D56 impl_isrPIOb_3D56: 3D56 4F ld c, a ; XXX freaky; where was A set? non-isr code? 'expect'? 3D57 DB 10 in a, (10h) ; XXX PIO A data; ostensibly 'state' (though we aren't masking off high bits for some reason) 3D59 B9 cp c 3D5A 20 FA jr nz, impl_isrPIOb_3D56 ; loop, there it is! 3D5C E6 07 and 7 ; mask only b2,b1,b0 ('state') 3D5E C2 68 3E jp nz, sub_3E68
So, two things:
- a spin-wait in an ISR for anything tweaks my spidey-sense
- we are checking for a value that is specified in A, but we never explicitly set A ourselves. A will be whatever it was in the pre-interrupt environment. All the more curious because Port B I/O is not synchronous to this system.
And I guess a minor thing is that we didn't mask off bits 7 and 6.
This is a head-scratcher. I'll have to come back to it later. (maybe some weird coordination with non-isr code: "wait for this and let me know". I'll keep an eye out for that pattern.)
Another treat:
7FA9 3E 4F ld a, 4Fh 7FAB D3 E3 out (0E3h), a ; XXX set PIO B mode 1 (input) 7FAD 3E 87 ld a, 87h 7FAF D3 E2 out (0E2h), a ; XXX wut? it's now an input port, so what is write doing?
Since [Nigel] was concerned about the port 30/38/39 stuff I took a little look at that. On cold boot, port 38h is read, and then jumps into the warm boot routine which immediately writes it out to port 39h. Warm boot ("boot_100") may be entered in other ways, and those ways a value left over from a previous write to both 30h and 38h is the one that is written to 39h.
And that is the end of the story for those ports. They don't affect code flow in direct way (conceivably they might indirectly by causing some unknown hardware to do something different). So looking into what the 'leftover value' is that comes in on the warm boot path led me to some RTC stuff.
02C3 sub_2C3: 02C3 3A 6D C0 ld a, (byte_C06D) ; a flag set in NMI 02C6 B7 or a 02C7 20 0D jr nz, loc_2D6 ; horror 02C9 3A 68 C0 ld a, (byte_C068) ; another flag that could cause us to reboot 02CC B7 or a 02CD C8 ret z 02CE F3 di 02CF CD A2 0E call sub_EA2 ; XXX RTC alarm stuff; leaves something in A 02D2 D3 38 out (38h), a ; hmm! 02D4 D3 30 out (30h), a ; hmm! 02D6 loc_2D6: ; horror; warm boot 02D6 CD AC 02 call sub_2AC ; a very long delay 02D9 C3 00 01 jp boot_100 ; bootilicious
The sub_EA2 sets the RTC alarm. The oddity is that it is setting down to the seconds, but the RTC data sheet explicitly says that the seconds are ignored. So why does it bother? Also, we only are writing out the 10's digit, not the 1s digit. Anyway, that is the last value that is in A before exiting the routine, and so is the value that is written to 38h and 30h. I'm suspecting that this is a bug of sorts, and that the value in the ostensible 'alarm seconds' is either something that is not really alarm seconds, or that it doesn't matter. Have to dig in more...
Anyway, it makes me suspect that the port 30h,38h,39h are somehow related to sleeping or power or something. But they do not directly guide the flow of execution.