Hi all,
You've probably been wondering where my Dexter progress has gone since I haven't posted anything here in a long time.
Unfortunately, having a newborn baby is taking its toll on me and I haven't spent ANY time working on Dexter for several weeks. At CAX, I alluded to this during my presentation when I said that my wife was going to have a baby and it would impact my time to work on Dexter. Our baby just turned 3 months old and is still waking up 2-3 times in the middle of the night to eat and until that stops, I will need to use my spare time to catch up on sleep. I just wanted to let everyone know what is going on. The good news is that this 'wake up in the middle of the night' condition is ALWAYS temporary and goes away. :)
Tuesday, October 28, 2014
Tuesday, October 14, 2014
Dragon's Lair ticks
I haven't posted a blog entry in a while and this is something pretty interesting that I'd like to have saved somewhere so here goes.
Dragon's Lair's scene data has a concept of 'ticks' which roughly map to clock ticks generated by the hardware. They are used to determine how long scene segments should last.
One issue with Dragon's Lair hardware is that the clock tick frequency differs slightly across different revisions of the hardware. Dragon's Lair's ROM program does a speed test upon boot-up to determine whether the hardware clock is faster, normal, or slower. The result of this speed test is displayed in the scoreboard as a 1 (faster), 2 (normal), or 3 (slower).
Here is the disassembled code that documents this speed test:
ROM:11BA SpeedTestLoop: ; CODE XREF: START+7B j
ROM:11BA ; START+88 j ...
ROM:11BA ld hl, SpeedTest_A001 ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11BD bit 1, (hl)
ROM:11BF jr nz, SpeedTestGotIrq ; if this is non-zero, it means IRQ has been triggered
ROM:11C1 inc bc
ROM:11C2 jr SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11C4 ; ---------------------------------------------------------------------------
ROM:11C4
ROM:11C4 SpeedTestGotIrq: ; CODE XREF: START+78 j
ROM:11C4 res 1, (hl) ; clear IRQ flag
ROM:11C6 inc d
ROM:11C7 ld a, 48 ; don't start tracking IRQs until we've got 48 of them
ROM:11C9 cp d
ROM:11CA jr c, loc_11D1
ROM:11CC ld bc, 0
ROM:11CF jr SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11D1 ; ---------------------------------------------------------------------------
ROM:11D1
ROM:11D1 loc_11D1: ; CODE XREF: START+83 j
ROM:11D1 ld a, 50
ROM:11D3 cp d
ROM:11D4 jr nz, SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11D6 ld a, c
ROM:11D7 ld (unk_E035), a ; least significant digit from BC
ROM:11DA srl a
ROM:11DC srl a
ROM:11DE srl a
ROM:11E0 srl a
ROM:11E2 ld (unk_E034), a
ROM:11E5 ld a, b
ROM:11E6 ld (unk_E033), a
ROM:11E9 srl a
ROM:11EB srl a
ROM:11ED srl a
ROM:11EF srl a
ROM:11F1 ld (unk_E032), a ; most significant digit from BC
ROM:11F4 ld a, 0Ah
ROM:11F6 cp b
ROM:11F7 jr c, BcGreaterThanOrEq0A00 ; if BC >= 0x0A00 then branch
ROM:11F9 set 3, (hl) ; set bit 3 of A001 to indicate that speed test's result is 'faster'
ROM:11FB ld a, 1 ; speed test got a '1' result
ROM:11FD jr SpeedTest1Or2Result ; set bit 0 of A001 to indicate the speed test's result is a 1 or 3
ROM:11FF ; ---------------------------------------------------------------------------
ROM:11FF
ROM:11FF BcGreaterThanOrEq0A00: ; CODE XREF: START+B0 j
ROM:11FF ld a, 15h ; check to see if BC >= 0x1500
ROM:1201 cp b
ROM:1202 jr c, SpeedTest3Result ; branch if BC >= 0x1500
ROM:1204 ld a, 2 ; speed test got a '2' result
ROM:1206
ROM:1206 SpeedTest1Or2Result: ; CODE XREF: START+B6 j
ROM:1206 set 0, (hl) ; set bit 0 of A001 to indicate the speed test's result is a 1 or 3
ROM:1208
ROM:1208 ExitSpeedTest: ; CODE XREF: START+CC j
ROM:1208 ld (unk_E038), a ; set speed test result to scoreboard
ROM:120B set 2, (hl) ; set bit 2 of A001 to indicate that the speed test is finished
ROM:120D jr BootPhase2 ; play 'move' beep
ROM:120F ; ---------------------------------------------------------------------------
ROM:120F
ROM:120F SpeedTest3Result: ; CODE XREF: START+BB j
ROM:120F res 0, (hl) ; clear bit 0 to indicate that speed test result is slower
ROM:1211 ld a, 3 ; display a 3 on scoreboard
ROM:1213 jr ExitSpeedTest ; set speed test result to scoreboard
ROM:1215 ; ---------------------------------------------------------------------------
So what does the result of the speed test mean for the ticks?
If the speed test got a 1 (faster) or a 2 (normal), then every 31st tick is dropped. If the speed test got a 3 (slower) then every 255th tick is dropped. Here is the code which handles this:
ROM:1922 ld hl, SpeedTest_A001 ; bit 0: 1=drop every 31st tick (speedtest result was 1 of 2), 0=drop every 255th tick (speedtest result was 3)
ROM:1922 ; bit 1: (during speed test) 1=IRQ has been triggered, 0=IRQ has not been triggered yet
ROM:1922 ; bit 2: 1=normal operation, 0=speedtest active
ROM:1922 ; bit 3: 1=speedtest got a '1' (faster), 0=speedtest got a '2' or '3' (just right or slower respectively), see 11F9
ROM:1925 bit 0, (hl)
ROM:1927 jr z, DropEvery255thTick ; drop every 255th tick
ROM:1929 ld b, 31 ; drop every 31st tick
ROM:192B jr UpdateGlobalTickCounter
ROM:192D ; ---------------------------------------------------------------------------
ROM:192D
ROM:192D DropEvery255thTick: ; CODE XREF: IRQ+107 j
ROM:192D ld b, 255 ; drop every 255th tick
ROM:192F
ROM:192F UpdateGlobalTickCounter: ; CODE XREF: IRQ+10B j
ROM:192F ld hl, GlobalTickCounter_A000 ; keeps a count of how many ticks we've processed so that we can drop ticks from time to time
ROM:1932 ld a, (hl)
ROM:1933 inc a ; increment tick counter
ROM:1934 ld (hl), a
ROM:1935 cp b ; do we need to drop this tick?
ROM:1936 jr nz, UpdateAllTickCounters
ROM:1938 xor a
ROM:1939 ld (hl), a
ROM:193A jp PostCountersUpdate ; we go here after the tick counters have been updated
ROM:193D ; ---------------------------------------------------------------------------
ROM:193D
ROM:193D UpdateAllTickCounters: ; CODE XREF: IRQ+116 j
Additionally, the IRQ service routine will periodically skip certain work if the speed test was a '1' in order to 'slow down' the timer in software.
Therefore, to reliably understand how many seconds a certain number of 'ticks' mean in the Dragon's Lair move data, one can apply this algorithm:
Assume that the hardware clock 'ticks' every 32.768 milliseconds and that the CPU is 4 MHz. This will yield a speed test result of 2 (normal).
Dragon's Lair's scene data has a concept of 'ticks' which roughly map to clock ticks generated by the hardware. They are used to determine how long scene segments should last.
One issue with Dragon's Lair hardware is that the clock tick frequency differs slightly across different revisions of the hardware. Dragon's Lair's ROM program does a speed test upon boot-up to determine whether the hardware clock is faster, normal, or slower. The result of this speed test is displayed in the scoreboard as a 1 (faster), 2 (normal), or 3 (slower).
Here is the disassembled code that documents this speed test:
ROM:11BA SpeedTestLoop: ; CODE XREF: START+7B j
ROM:11BA ; START+88 j ...
ROM:11BA ld hl, SpeedTest_A001 ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11BD bit 1, (hl)
ROM:11BF jr nz, SpeedTestGotIrq ; if this is non-zero, it means IRQ has been triggered
ROM:11C1 inc bc
ROM:11C2 jr SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11C4 ; ---------------------------------------------------------------------------
ROM:11C4
ROM:11C4 SpeedTestGotIrq: ; CODE XREF: START+78 j
ROM:11C4 res 1, (hl) ; clear IRQ flag
ROM:11C6 inc d
ROM:11C7 ld a, 48 ; don't start tracking IRQs until we've got 48 of them
ROM:11C9 cp d
ROM:11CA jr c, loc_11D1
ROM:11CC ld bc, 0
ROM:11CF jr SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11D1 ; ---------------------------------------------------------------------------
ROM:11D1
ROM:11D1 loc_11D1: ; CODE XREF: START+83 j
ROM:11D1 ld a, 50
ROM:11D3 cp d
ROM:11D4 jr nz, SpeedTestLoop ; when this loop starts for the first time, HL is FFFF, DE is FFFF, and BC is 0000
ROM:11D6 ld a, c
ROM:11D7 ld (unk_E035), a ; least significant digit from BC
ROM:11DA srl a
ROM:11DC srl a
ROM:11DE srl a
ROM:11E0 srl a
ROM:11E2 ld (unk_E034), a
ROM:11E5 ld a, b
ROM:11E6 ld (unk_E033), a
ROM:11E9 srl a
ROM:11EB srl a
ROM:11ED srl a
ROM:11EF srl a
ROM:11F1 ld (unk_E032), a ; most significant digit from BC
ROM:11F4 ld a, 0Ah
ROM:11F6 cp b
ROM:11F7 jr c, BcGreaterThanOrEq0A00 ; if BC >= 0x0A00 then branch
ROM:11F9 set 3, (hl) ; set bit 3 of A001 to indicate that speed test's result is 'faster'
ROM:11FB ld a, 1 ; speed test got a '1' result
ROM:11FD jr SpeedTest1Or2Result ; set bit 0 of A001 to indicate the speed test's result is a 1 or 3
ROM:11FF ; ---------------------------------------------------------------------------
ROM:11FF
ROM:11FF BcGreaterThanOrEq0A00: ; CODE XREF: START+B0 j
ROM:11FF ld a, 15h ; check to see if BC >= 0x1500
ROM:1201 cp b
ROM:1202 jr c, SpeedTest3Result ; branch if BC >= 0x1500
ROM:1204 ld a, 2 ; speed test got a '2' result
ROM:1206
ROM:1206 SpeedTest1Or2Result: ; CODE XREF: START+B6 j
ROM:1206 set 0, (hl) ; set bit 0 of A001 to indicate the speed test's result is a 1 or 3
ROM:1208
ROM:1208 ExitSpeedTest: ; CODE XREF: START+CC j
ROM:1208 ld (unk_E038), a ; set speed test result to scoreboard
ROM:120B set 2, (hl) ; set bit 2 of A001 to indicate that the speed test is finished
ROM:120D jr BootPhase2 ; play 'move' beep
ROM:120F ; ---------------------------------------------------------------------------
ROM:120F
ROM:120F SpeedTest3Result: ; CODE XREF: START+BB j
ROM:120F res 0, (hl) ; clear bit 0 to indicate that speed test result is slower
ROM:1211 ld a, 3 ; display a 3 on scoreboard
ROM:1213 jr ExitSpeedTest ; set speed test result to scoreboard
ROM:1215 ; ---------------------------------------------------------------------------
So what does the result of the speed test mean for the ticks?
If the speed test got a 1 (faster) or a 2 (normal), then every 31st tick is dropped. If the speed test got a 3 (slower) then every 255th tick is dropped. Here is the code which handles this:
ROM:1922 ld hl, SpeedTest_A001 ; bit 0: 1=drop every 31st tick (speedtest result was 1 of 2), 0=drop every 255th tick (speedtest result was 3)
ROM:1922 ; bit 1: (during speed test) 1=IRQ has been triggered, 0=IRQ has not been triggered yet
ROM:1922 ; bit 2: 1=normal operation, 0=speedtest active
ROM:1922 ; bit 3: 1=speedtest got a '1' (faster), 0=speedtest got a '2' or '3' (just right or slower respectively), see 11F9
ROM:1925 bit 0, (hl)
ROM:1927 jr z, DropEvery255thTick ; drop every 255th tick
ROM:1929 ld b, 31 ; drop every 31st tick
ROM:192B jr UpdateGlobalTickCounter
ROM:192D ; ---------------------------------------------------------------------------
ROM:192D
ROM:192D DropEvery255thTick: ; CODE XREF: IRQ+107 j
ROM:192D ld b, 255 ; drop every 255th tick
ROM:192F
ROM:192F UpdateGlobalTickCounter: ; CODE XREF: IRQ+10B j
ROM:192F ld hl, GlobalTickCounter_A000 ; keeps a count of how many ticks we've processed so that we can drop ticks from time to time
ROM:1932 ld a, (hl)
ROM:1933 inc a ; increment tick counter
ROM:1934 ld (hl), a
ROM:1935 cp b ; do we need to drop this tick?
ROM:1936 jr nz, UpdateAllTickCounters
ROM:1938 xor a
ROM:1939 ld (hl), a
ROM:193A jp PostCountersUpdate ; we go here after the tick counters have been updated
ROM:193D ; ---------------------------------------------------------------------------
ROM:193D
ROM:193D UpdateAllTickCounters: ; CODE XREF: IRQ+116 j
Additionally, the IRQ service routine will periodically skip certain work if the speed test was a '1' in order to 'slow down' the timer in software.
Therefore, to reliably understand how many seconds a certain number of 'ticks' mean in the Dragon's Lair move data, one can apply this algorithm:
Assume that the hardware clock 'ticks' every 32.768 milliseconds and that the CPU is 4 MHz. This will yield a speed test result of 2 (normal).
extraTicks = ticks / 30; /* (no remainder, for example, 31/30 would be 1, not 1.0333) */
adjustedTicks = ticks + extraTicks;
seconds = (adjustedTicks) * 0.032768;
Wednesday, September 17, 2014
Soldering Dexter rev 3b
Here's part of my soldering process for these prototype boards.
Monday, September 1, 2014
Java annoyances
So I've wanted to learn java for years and am kinda forcing myself to do the desktop portion of the Williams IC Tester in java.
Quick take: C# is a better language in pretty much every way (which makes sense since MSFT shamelessly copied java and improved upon it). And in some ways, C++ is better than java, which is why I am writing this blog post.
I discovered (re-discovered) that java does not have unsigned data types. Everything is signed. If you google for information about this, you will find java apologists demanding people to justify why unsigned data types are needed. I found this attitude somewhat absurd.
Being an emulation author and a machine language enthusiast, unsigned data types are a no-brainer. The benefit of unsigned types is that the maximum size of the type in question is doubled from the signed version (in the positive direction). This benefit is something I take advantage of so often that I take it for granted. Here is a quick example.
An unsigned byte has a range of 0-255, while a signed byte is -128 to 127. I work with bytes a lot, by default, I expect to have a max upper range of 255. I've expected this for years (many many years). In fact, I can't remember the last time where I willingly chose to use a signed byte for any meaningful work. If I am going to use a signed number for any purpose, I will almost always use whatever the native CPU is optimized for which means on a 32-bit CPU, I would use a 32-bit signed integer, on a 64-bit CPU, I would use a 64-bit signed integer, etc. I can't think of any practical use for a signed byte when it's so common/easy to use an 'int'. So the fact that java only supports signed bytes is a major blunder in the design, IMO.
Now, you may be saying that I can just ignore the sign for many operations and I will still get the same result. This is somewhat true, or rather, would be somewhat true, if java didn't get in my way.
In C++, I am used to doing something like this:
uint8_t u8 = 0xFF; // C++ assigns this to have a value of 255
int i = u8;
^ - I would _always_ expect i to have a value of 0xFF after performing this operation. It is a no-brainer.
However, to my dismay, I discovered that java does the wrong thing here:
byte u8 = 0xFF; // java assigns this to have a -1 value, even though it has a datatype called 'byte' hur hur
int i = u8; // now i is set to -1 also (0xFFFFFFFF) instead of 255 (0xFF).
To workaround this poor language design, one has to do this:
byte u8 = 0xFF;
int i = u8 & 0xFF; // hurrr hurrrrrr
Now it works correctly because I manually hacked the value to have the proper sign. Nevermind that performing an extra AND for no practical reason wastes CPU cycles (admittedly not that many) that wouldn't have to be wasted if I could simply tell the language that u8 was supposed to be unsigned and to treat it accordingly.
What I've got from reading about java is that if you want to deal with unsigned types, you need to use a signed type that is bigger to spoof the unsigned type. So if you want to represent an unsigned byte, you'd use a short, if you wanted to represent an unsigned short, you'd use an int, if you wanted to represent an unsigned int, then you'd have to go crazy and use a long. It's really pathetic for any "serious" language to have to require this kind of hackery.
This doesn't mean that I've given up on java. After all, C# is proprietary/MSFT so it's not really available on other platforms (don't get me started on mono). So java still has its place, but I am not going to be fleeing C++ any time soon since java hasn't given me a great reason to do so.
Quick take: C# is a better language in pretty much every way (which makes sense since MSFT shamelessly copied java and improved upon it). And in some ways, C++ is better than java, which is why I am writing this blog post.
I discovered (re-discovered) that java does not have unsigned data types. Everything is signed. If you google for information about this, you will find java apologists demanding people to justify why unsigned data types are needed. I found this attitude somewhat absurd.
Being an emulation author and a machine language enthusiast, unsigned data types are a no-brainer. The benefit of unsigned types is that the maximum size of the type in question is doubled from the signed version (in the positive direction). This benefit is something I take advantage of so often that I take it for granted. Here is a quick example.
An unsigned byte has a range of 0-255, while a signed byte is -128 to 127. I work with bytes a lot, by default, I expect to have a max upper range of 255. I've expected this for years (many many years). In fact, I can't remember the last time where I willingly chose to use a signed byte for any meaningful work. If I am going to use a signed number for any purpose, I will almost always use whatever the native CPU is optimized for which means on a 32-bit CPU, I would use a 32-bit signed integer, on a 64-bit CPU, I would use a 64-bit signed integer, etc. I can't think of any practical use for a signed byte when it's so common/easy to use an 'int'. So the fact that java only supports signed bytes is a major blunder in the design, IMO.
Now, you may be saying that I can just ignore the sign for many operations and I will still get the same result. This is somewhat true, or rather, would be somewhat true, if java didn't get in my way.
In C++, I am used to doing something like this:
uint8_t u8 = 0xFF; // C++ assigns this to have a value of 255
int i = u8;
^ - I would _always_ expect i to have a value of 0xFF after performing this operation. It is a no-brainer.
However, to my dismay, I discovered that java does the wrong thing here:
byte u8 = 0xFF; // java assigns this to have a -1 value, even though it has a datatype called 'byte' hur hur
int i = u8; // now i is set to -1 also (0xFFFFFFFF) instead of 255 (0xFF).
To workaround this poor language design, one has to do this:
byte u8 = 0xFF;
int i = u8 & 0xFF; // hurrr hurrrrrr
Now it works correctly because I manually hacked the value to have the proper sign. Nevermind that performing an extra AND for no practical reason wastes CPU cycles (admittedly not that many) that wouldn't have to be wasted if I could simply tell the language that u8 was supposed to be unsigned and to treat it accordingly.
What I've got from reading about java is that if you want to deal with unsigned types, you need to use a signed type that is bigger to spoof the unsigned type. So if you want to represent an unsigned byte, you'd use a short, if you wanted to represent an unsigned short, you'd use an int, if you wanted to represent an unsigned int, then you'd have to go crazy and use a long. It's really pathetic for any "serious" language to have to require this kind of hackery.
This doesn't mean that I've given up on java. After all, C# is proprietary/MSFT so it's not really available on other platforms (don't get me started on mono). So java still has its place, but I am not going to be fleeing C++ any time soon since java hasn't given me a great reason to do so.
Friday, August 29, 2014
Thursday, August 28, 2014
First Dexter rev 3b prototype finished
I've finished soldering the first rev 3b prototype and so far, I can't find any problems with it. I have 3 more of these boards to assemble before ordering more from the fabrication place.
Friday, August 22, 2014
Soldering the next round of Dexter prototypes
The new board without any soldering done.
The new board with all of the surface mount ICs soldered in. A few solder bridges, but no big deal. All in all, the soldering took me about 2 hours. I can't wait to have a machine assemble these :)
Subscribe to:
Comments (Atom)


