EEVblog® Electronics Community Forum

Electronics => Microcontrollers => Topic started by: Rjevski on April 12, 2020, 01:35:19 am

Title: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on April 12, 2020, 01:35:19 am
Hello,

I'm trying to reverse-engineer a very obscure piece of equipment - an AES Prodata bus ticketing machine - it can read/write and print (thermal) paper tickets with a low-coercivity magstripe. The one I got is actually an Australian one as it had a sticker about "not for Opal cards" on the face of it. It's one of old green ones as in this picture (https://railgallery.wongm.com/sydney-ticketing/E115_9101.jpg.html).

(I've updated the post with my latest findings/understanding of the device - original post will be below for reference)



Update: I managed to make some progress with LLMs, will update the thread when I try the findings on the real device (which is currently in storage). I've pushed the LLM-assisted notes on Github: https://github.com/Rjevski/prodata-ticket-machine (https://github.com/Rjevski/prodata-ticket-machine)



The main CPU is a HD63B03RP, and there's an IO multiplexer chip HD63B21P. There's also a timer chip HD63B40. Board photos are available here (https://rjevski-my.sharepoint.com/:f:/g/personal/hi_rjevski_io/EtvU2rrlwRNOk5D7cHymoesB4Y3EBhf90xPPfwzkDdG_Yw?e=l0IfKJ) (the forum doesn't allow me to upload the full resolution photos unfortunately).

The board has two ROMs (one EEPROM "P28F010-120" marked "BOOT DRIV" dumped as "ROM2.hex" and one EPROM "AM27C128-200DC" dumped as "ROM.hex") as well as an SRAM chip "HY62256ALP-10" powered by a backup battery which I've temporarily shorted to try and clear it thinking it would make it go into a factory/debug mode (it didn't) so let's assume the contents of that one are now lost/unreliable. There's also an FPGA "XC2064-50 PC68C" which I assume would be used for the magnetic card reading/writing, printing or motor control for the card transport mechanism.

At the moment the machine itself powers on and displays a fixed value on the LCD - something like "SBZ001" and stays stuck there, presumably it's waiting for the right sequence of bytes on the serial port to begin operating. I'd like to figure out what it wants so I can send it to it and make it boot properly. There's an RS485 transceiver on the board. The device spews out regular garbage on the serial port - I'm not sure what baud rate it is (is there even a concept of baud rate in RS485? My USB converter presents itself to the system as a standard serial port to the OS and I can set a baud rate) but I've tried them all and I get garbage in every case so I assume it's just outputting binary for now. I do not yet have a scope to probe further.

There are also 16 DIP switches on the board however no combination of them appears to have any visible results even after rebooting.

Power-wise it seems to be able to run on both 12 and 24 volts - there seems to be a huge block on the board around the power area and several connectors coming back to the board which can be configured in different ways - presumably you can either feed the right voltage directly to the board or change the connector to make the power go through the extra block which would step it down to a proper level. I've got the board powered up properly (I've fed in 19V from a laptop PSU both with and without the extra block and the board powers on and appears to be fine - going through the extra block makes it take longer to power on - I assume that one expects a higher voltage normally and the delay is just its caps charging up on a lower voltage than it expects).

What would be the next steps that you would suggest?

I will eventually get around to tracing the full board which would allow me to figure out the address space (my el-cheapo multimeter has a good 500ms delay on the continuity mode making this process extremely tedious, but I'm planning to get a better one), in the meantime I'd like to make as much progress as possible with what I've got at hand. At the moment the memory map is assumed to be like the following thanks to abyrvalg (https://www.eevblog.com/forum/microcontrollers/extracting-firmware-from-a-hd63b03rp/msg3021788/#msg3021788):

Quote
boot:
ROM2:0:4000 -> C000 - this is an FPGA loader (FPGA bitstream is at +0x40), it jumps to "main" ROM C022 after load. Looks like a standalone banked ROM.

main:
ROM1:0:4000 -> C000 - entry point at C022, vector table at end
ROM1:6000:8000 -> A000
ROM1:8000:C000 -> 6000 - entry point at 6022

0:6000 must be HY62256 RAM (overlaid by CPU’s SFRs 0:20 and IRAM 80:100)

Regarding disassembling, IDA is out of the question - I don't have a license and the Home one wouldn't be suitable as it doesn't support this architecture. I've reached out to them to see if they can sell me a "special" non-commercial license for that architecture at a more affordable price but no luck. Ghidra was recommended here, however I'm not sure which CPU architecture to pick. Here's a list of architectures it supports (https://github.com/NationalSecurityAgency/ghidra/wiki/Frequently-asked-questions#what-processors-are-currently-supported) - which one should I pick? Alternatively, do you know any other GUI-based disassemblers (free or paid but reasonably priced) that would support it?

I've been thinking of getting a logic analyzer - I'd need 32 channels at a minimum for this and they are reasonably pricey - do you guys know if there's a way to combine multiple cheap 16-channel analyzers instead? There are cheap Chinese USB ones on Amazon and it would come out cheaper to buy 2 or 3 of these and somehow combine them than buy the "proper" one.

Also I'd like to figure out what I can use to substitute the "AM27C128-200DC" EPROM. I've searched around and most drop-in flash alternatives are no longer in production (though eBay is full of fakes) - would a modern microcontroller with the right pin count be fast enough to emulate one? Alternatively, are there commercially-available emulators that would take a ROM file from a computer and emulate it? I've seen such a product on an automotive tuning website but the price was in the 300 bucks range - does anyone know if others exist? I could see this being useful for cartridge-based consoles too so surely something like that must exist?

Thanks.



Hello,

I'm trying to reverse-engineer a very obscure piece of equipment - an AES Prodata bus ticketing machine. The one I got is actually an Australian one as it had a sticker about "not for Opal cards" on the face of it. It's one of old green ones as in this picture: https://railgallery.wongm.com/sydney-ticketing/E115_9101.jpg.html (https://railgallery.wongm.com/sydney-ticketing/E115_9101.jpg.html)

The main CPU seems to be a HD63B03RP, which according to my understanding has 64K of internal ROM. The board does have an EEPROM which I have dumped and while it does contain some strings (that would be displayed on the LCD) my disassembly efforts have failed (all the free disassemblers out there only produce nonsensical assembly, although I haven't tried IDA yet as I can't justify the price of a license) so I'm assuming it must be just configuration data and the actual firmware is in the microcontroller itself. I've attached the ROM file as well in case someone is curious.

Assuming I am correct about the chip having internal firmware, does anyone have ideas about dumping it? I would like to do so to understand what it expects in the config ROM and what's the protocol on the serial port - so far all combinations of baud rates I have tried produce unintelligible text, so I'm assuming it's just speaking binary which isn't easy to make sense of.

At the moment the machine itself powers on and displays a fixed value on the LCD - something like "SBZ001" on the LCD and stays stuck there, presumably it's waiting for the right sequence of bytes on the serial port to begin operating. I'd like to figure out what it wants so I can send it to it and make it boot properly.

Regards.
Title: Re: Extracting firmware from a HD63B03RP
Post by: oPossum on April 12, 2020, 02:24:22 am
The HD63B03 has a 16 bit address bus so it can use up to 64 kB of memory. The only internal memory is 128 bytes of RAM. There is no internal ROM. It is the functional equivalent of a Motorola MC6803. Instruction set is the same as 6800/6801/6802/6803/6808.
Title: Re: Extracting firmware from a HD63B03RP
Post by: up8051 on April 12, 2020, 09:18:20 am
You can try to use :
http://www.atastro.com/software/prog/dasmx.html (http://www.atastro.com/software/prog/dasmx.html)
Title: Re: Extracting firmware from a HD63B03RP
Post by: PA0PBZ on April 12, 2020, 10:38:47 am
Can you check your EPROM dump again? It should at max be 64K, the top half of your file is empty and the first part 0000-3FFF should be at C000-FFFF. Maybe they tried to obfuscate stuff by swapping some high address lines or you used the wrong EPROM type to read it?
Title: Re: Extracting firmware from a HD63B03RP
Post by: Rjevski on April 12, 2020, 03:32:35 pm
Thanks for getting back to me. There is another EEPROM on the board which at first I discarded because it had references to Xilinx in it (there is an FPGA on the board as well), but now reading it more closely it might actually contain the actual code (the sticker on the EEPROM says "BOOT DRIV"). I've attached it to this post.
Title: Re: Extracting firmware from a HD63B03RP
Post by: PA0PBZ on April 12, 2020, 03:55:46 pm
Your first dump is the boot ROM, I can see the RESET, NMI and IRQ vectors in there, but the 'problem is that they are supposed to sit at FFF8-FFFF but in your dump file they are at 3FF8-3FFF. If I relocate the 0000-3FFF part to C000-FFFF it makes sense:

Code: [Select]
ROM:FFF8 IRQ:            fdb $E8E2
ROM:FFFA SOFTI:          fdb $D30B
ROM:FFFC NMI:            fdb $D89C
ROM:FFFE RESET:          fdb $C475
ROM:FFFE ; end of 'ROM'

Code: [Select]
ROM:C475 ; ---------------------------------------------------------------------------
ROM:C475                 sei                     ; RESET
ROM:C476                 ldaa    #6
ROM:C478                 bsr     sub_C480
ROM:C47A                 bra     loc_C4B9
ROM:C47A ; ---------------------------------------------------------------------------

So the question is what type of EPROM is it and what type did you use to make the dump?
Title: Re: Extracting firmware from a HD63B03RP
Post by: Rjevski on April 12, 2020, 04:04:12 pm
The second dump in the post above is an EPROM and seems to be an "AM27C128-200DC", it's a ceramic package with the window for the UV lamp to erase it.

I've used a TL866II Plus with the "minipro" software to dump it, with the following command line:

Code: [Select]
minipro -p 'AM27C128@DIP28' -r ROM2.hex
Could it be that there's some circuitry in hardware that would relocate this ROM at a different address space?

The first dump in the initial post was from a Intel P28F010-120 dumped with "minipro -p 'P28F010@DIP32' -r ROM.hex".
Title: Re: Extracting firmware from a HD63B03RP
Post by: oPossum on April 12, 2020, 05:08:37 pm
There is probably some sort of banking scheme that allows access to all off the Flash (128 kB) and EPROM (32 kB). That would also do mapping necessary to put the vectors at the right address.
Title: Re: Extracting firmware from a HD63B03RP
Post by: Rjevski on April 12, 2020, 06:49:26 pm
Just wondering, how would I go about disassembling the first ROM with the understanding that the data should be relocated at C000? I've tried the Online Disassembler at https://onlinedisassembler.com and despite selecting m68k and setting the base address at 0xc000 I'm still getting what seems to be garbage.
Title: Re: Extracting firmware from a HD63B03RP
Post by: PA0PBZ on April 12, 2020, 07:09:36 pm
Just wondering, how would I go about disassembling the first ROM with the understanding that the data should be relocated at C000? I've tried the Online Disassembler at https://onlinedisassembler.com and despite selecting m68k and setting the base address at 0xc000 I'm still getting what seems to be garbage.

M68K is a 68000 32-bit processor so that is why you get garbage. What I did is take the 0000-3FFF part of ROM.hex and saved it as a seperate file. Upload that file and pick 68HC11 for Arch and load address 0xC000. Press No when asked to start disassembly at the beginning of the file. Note that the last 2 bytes in the file are C475, that is the reset vector. Navigate to C475, press C and enjoy.

[attach=1]

Your ROM2.hex is also a bootable 6800 file, maybe used to reprogram the xilinx? What is IC21 and what is the size of the IC16 RAM?
Title: Re: Extracting firmware from a HD63B03RP
Post by: Rjevski on April 12, 2020, 07:56:00 pm
Thanks so much. I'm going to try the disassembly in a second.

IC16 is a HY62256ALP-10 SRAM chip powered by a lithium battery backup (there's a battery on the board and I measure 2.55V on the power pin of the chip even if the board is unpowered). Seems to be 32K in size.

IC21 is a XC2064-50 PC68C FPGA.

Here are the board photos: https://rjevski-my.sharepoint.com/:f:/g/personal/hi_rjevski_io/EtvU2rrlwRNOk5D7cHymoesB4Y3EBhf90xPPfwzkDdG_Yw?e=l0IfKJ
Title: Re: Extracting firmware from a HD63B03RP
Post by: abyrvalg on April 18, 2020, 01:49:11 pm
The complete memory mapping looks like this:

boot:
ROM2:0:4000 -> C000 - this is an FPGA loader (FPGA bitstream is at +0x40), it jumps to "main" ROM C022 after load. Looks like a standalone banked ROM.

main:
ROM1:0:4000 -> C000 - entry point at C022, vector table at end
ROM1:6000:8000 -> A000
ROM1:8000:C000 -> 6000 - entry point at 6022

0:6000 must be HY62256 RAM (overlaid by CPU’s SFRs 0:20 and IRAM 80:100)
Title: Re: Extracting firmware from a HD63B03RP
Post by: Rjevski on April 18, 2020, 05:30:04 pm
Thanks for the information.

I just got a 16-channel logic analyser (Kingst LA1010). It doesn't have enough channels to cover both the entire address bus and the data bus (in fact even for the address bus alone I'm missing an extra channel for the clock pin). I'm thinking to return this and get a bigger one (or get 2, have both share a pin and then sync the two captures in software based on that clock pin) but in the meantime does anyone know any tricks I could do with this logic analyser to get further?

The A0-A7 address lines from the MCU go into a latch (PC74HC573P) and the latch enable pin goes to the MCU's AS (address strobe) pin. Presumably this is to multiplex the address/data bus (the first A0-A7 pins are also marked D0-D7) of the MCU. At the moment I'm probing the outputs of the latch for A0-A7 and then the A8-A14 directly and using the last channel for the clock. My idea is to at least get a look at which addresses the MCU is looking at during powerup.

Quote
it jumps to "main" ROM C022 after load

In your post you say both ROMs are mapped to C000 - is this correct or is this a typo? And if it is correct, there must be a mechanism to switch the ROMs right before jumping to C022, correct?
Title: Re: Extracting firmware from a HD63B03RP
Post by: abyrvalg on April 19, 2020, 04:16:45 pm
Yes, the two regions mapped to the same C000 address are switched somehow (and there are some PORT1 bits manipulations before jump). But the boot rom looks like a standalone component, there are no function calls between it and the main rom. No need to load them both into the same disassembly.
I can be wrong about jump to C022, it’s a calculated jump to something+22 and both C000 and 6000 parts have entry points at +22, so it can be 6022 actually. But the C022 entry leads to a jump to 6022 pretty quickly, so it is not much difference which one you’ll choose for the start.
I’m sure about rom1 mappings described above - all those 3 parts have absolute calls from one to other and the call targets look sane with that mapping (a call from one part leads to a code sequence looking like a function start in the other part).
Title: Re: Extracting firmware from a HD63B03RP
Post by: Rjevski on April 19, 2020, 08:25:03 pm
Thanks for the info. May I ask which disassembler you're using and how you're loading the ROMs into it (at the proper addresses)? Do you manipulate the files beforehand to arrange the regions at the proper address or is your disassembler able to let you remap regions of the ROM?

Also there is a IO multiplexer chip HD63B21P which I'm assuming is connected to the main CPU and probably manages the ROM mapping.
Title: Re: Extracting firmware from a HD63B03RP
Post by: abyrvalg on April 20, 2020, 09:17:57 pm
I use IDA, it allows to load the binary piece-by-piece at any desired addresses. But it is a bit too pricy for a one time project, I recommend to try Ghidra, it even has a decompiler (outputting C code) for 68xx.
HD63B21 is a memory-mapped parallel I/O port (so a few yet unknown addresses are directed to it) adding 16 gpio lines to the system.
Edit: HD63B40 (from your photo) is another memory-mapped I/O device (timer).
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on May 03, 2021, 01:32:44 pm
Bump (edited my first post with more details and questions).
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 06, 2022, 06:26:05 pm
Update:

So I have some time on my hands now and more money to throw at the problem. I got my hands on IDA Pro and am in the process of sourcing a scope (maybe not strictly needed right now, but can't hurt to have one anyway).

Could someone help me load the ROMs I dumped earlier into IDA? I've made some attempts yesterday and it seems OK-ish but I wanted to double-check:

Which architecture to use? I see many options:

- Hitachi HD63701/03
- Hitachi HD63703/01
- Motorola MC6301
- Motorola MC6303
- Motorola MC6800
- Motorola MC6801
- Motorola MC6803
- Motorola MC6805
- Motorola MC6808
- Motorola MC6809
- Motorola MC6811

When it comes to loading the first ROM (ROM.hex), how should I proceed given that I need to load parts of it at various addresses? Currently when I open the file I only load part of it (by varying the load address, file offset & length parameters) and then go "File -> Load -> Additional binary file" and repeat the process with the same file several times until I get all the parts in the right place. Am I doing this right or is there a better/easier way?

Is there a way to search for references to an address range? There are some strings in the ROM that would be displayed on the LCD - I can't get any hits on an exact address but was just wondering if I could do a search for references to the entire range containing all the strings?

Thanks.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: bingo600 on March 06, 2022, 07:06:41 pm
We mentioned a lot of free 68xx tools in this thread

https://www.eevblog.com/forum/fpga/replicating-a-custom-6800-in-fpga/?all (https://www.eevblog.com/forum/fpga/replicating-a-custom-6800-in-fpga/?all)

I chose this one
https://www.eevblog.com/forum/fpga/replicating-a-custom-6800-in-fpga/msg3714880/#msg3714880 (https://www.eevblog.com/forum/fpga/replicating-a-custom-6800-in-fpga/msg3714880/#msg3714880)

https://github.com/Arakula/dasmfw (https://github.com/Arakula/dasmfw)

Ohh i missed you got the IDA Pro

Well for others that might need a free tool




/Bingo
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: bingo600 on March 06, 2022, 07:31:02 pm
Just ran a simple :

$ strings -tx ROM.hex >ROM-strings.txt
$ strings -tx ROM2.hex >ROM2-strings.txt

Attached the string txt files  (the offset is in hex)

/Bingo
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: nali on March 06, 2022, 08:06:33 pm
Just noticed this thread... FWIW it looks like what you have is just the card reader / printer, not the ticketing machine itself which is the beige box above & next to the steering wheel.

Not familiar with that machine but have seen a few others of the same era. RS485 will probably be binary, try looking for 0x02 STX characters at the start of the packets (or 0xFD if you've got the RS485 lines swapped), suggest 9600 or 19200 baud for starters.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 06, 2022, 09:06:21 pm
Yes - I have ran that back in the day and indeed there are strings. My question was more about how would I search for references to the address ranges the strings are located at in IDA? An exact match doesn't yield any results but I was wondering if there's a way to do a broader search for anything that references the general area of where the strings are located? No worries if not.

Just noticed this thread... FWIW it looks like what you have is just the card reader / printer, not the ticketing machine itself which is the beige box above & next to the steering wheel.

Not familiar with that machine but have seen a few others of the same era. RS485 will probably be binary, try looking for 0x02 STX characters at the start of the packets (or 0xFD if you've got the RS485 lines swapped), suggest 9600 or 19200 baud for starters.

I will have a look at the serial traffic again when I get the machine powered up and the entire development environment set up (everything has been packed and sitting in storage since I moved last year). I'll have a scope so the baud rate issue will be settled anyway.

Regarding the machine, indeed I don't have the driver's console. Just FYI I remember that these machines were also used in France with an ERG DF4000 driver's console (pictured here: https://www.flickr.com/photos/85000178@N05/20522269625 (https://www.flickr.com/photos/85000178@N05/20522269625)).
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 07, 2022, 12:24:38 am
I managed to get some action out of it.

I'm using a TTL USB to serial converter (an Arduino Uno with the micro itself held in reset, just using its serial converter part) and hooking up the RX line to the DS75176BTN (a RS422/485 transciever chip on the board)'s DI pin (4) (and obviously having grounds connected), just to rule out any RS485-related issues (I had no idea whether my converter worked, don't have anything else that can speak that protocol to test).

On 9600/8/N/1 I managed to get the board into a weird state where it would not boot, however on the serial port I saw this:

Code: [Select]
XILINX - IC
XILINX -ˇ
XIL
XILIN. - IC 21 - PR
XILINX - IC 21 -
XILINX - IC 21 ˇ
XILH
.ˇ.
XILI
XILINX - IC 21 - PRO
XILINX
XILIˇ
XILINX - IC 21 - P
XILINX - IC 21 - P
.I¸
XILINX ˝
XILINX % IC 21 - PROBLEM : PROGØ
XILINX - IC 21 - PROBLEM :
XIL.NX - IC ˙
XILINX - IC 20 - PROBL

ï1%9aÅ- IC 21 - PROBLE
XILINX - IC¯

XILINXˇ
˛
ÿ
XILINX )
 
XILILX - IC 21 -
XILINX - IC 21 - PROˇ
XILINX - I. 2
XILINX - KC 21 -ÇI=.1.5Å: PROG/
XILINX - IC 21 - ¸
XILINX - IC 21 - PROBLEM :ÇI=.ΩDOˇ
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3/PI˛
XILINX ˇ
XILIN
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3/PIN15)
XILINX - IC 21 - PROBLEM : PROG/DON≈
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3.PIN15) STAYS†
ä
XILINX - IC 21 - PROBLEM :
˛
XILINX - IC 21 - PROBLEˇ

ÿ
XIL.NX - …
XILINÿ
XILINX
XILIN
XILINX - IC 21 -
X
XILINX -˛
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3/
XILINX - IC 21
XILINÿ
XILILX
XILINX - IC 21 - PROBLEM : PRGG/DONE LINE (IC
XILINX - IC 21 - PROBL.M * PR
XILI
XILINX - ˇ

XÈ
XILINX - IC 21 - PROBLEM : PROG/DON≈
XILˇ
XILIN

XILIN˛
XIÃ
XILˇ
X
XILI
XILINX -
XILINX - IC 21 - PRO
XILINX % IC 21 - PROBLEM : PROG/DONE LIŒ
ÿ
XILINX - …
X.LINP - IC 21 - PROBLEM : PROG/DONE LINE (IC3/PIN15) S¸
XILIN.j.Jˇ
˙.
XILI
XILINX - IC 21 - PRO
XILINX - IC 21 - –
XILINX`- IC 21 - PROBLEM : P
XILINXj.J
Å21
.˘ˇ
XIÃ
·
XILINX ˝
XI
XILINX - IC 21 - PROBLEM
XILINX - IC 21 -ê%ı.1.5Å:
XILINX - IC 21 - PROBLEM :ˇ
XILINX - IC 21
XILINX - IC 21 - PROBLEM : PROG/DONE LI


XILINX - IC 21 KÇI=.1.5Å: –
XILINX - IC 2±Õ
XI
XILINX - IC 21 - PROBLEM : .R
!ï1%9aÅ-
XILI
ˇ
XILINX - IC˛
XILINX -
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (…Õ
XILINX -êR(í
XIHINX - IC 21 -
XILINX -†
XILINX - IC 21 - P.
XILINX - ˇ
XILINX ˝
Xˇ
˛
XIL.NX - IC 21 - P™J1ˇ
XILINX - IC 21 - PROBLEM : PRO
XILINX - IC 21 - PRJ1.5Å
XIDINX - IC 21 ˇ
XILINX - IC 21 - PROBLEL : PROG/DONQb%9.Å
XILINX - IC 21 - PROBLEM
XILINX - IC

!ï1%9aŎ
XIL…
XILINX - IC
XILINX ˝
XILINX - IC 21†
XILINX
XILˇ
XILIŒX - ˘
X.LINX - IC 21 - PROBLEM : PROG/DONE
XILINX - IC 21 % ¯
XIL˘
XILINX - IC 21 - PROBLEM : PROG/DONE LINˇ
XILINX - IC 21 - PROBLEM ˛
ï1%9aÅ- IC 01 - PROBLEM : PROG/DONQb%9.Å(IC3/PIN15) STAYS
XILINX
XILINX -
XILINX - IC 21 - PROBLEM :†
XILINX - IC 21 - P.OBLEM : PROCÔç
ÿ
XILINX -

XILIN
XILINX - I(íä.j.ÇI=˘
X˝
XILINX - IC 21 - PROBL≈
XILIFX
XILI
XILINX - IC 21 - PROBLEM : PRKG/DNNE
XILINX
XI¸

XILINX - IC 21 -

XILINX - IC 21 -
XILINX - IC 21 - @RO¬
XILINX -
˛
¯
XIÃ
XILINX - IC 21 - PROBLEM : P
XI
XIˇ
XILINX`-¯H
XILINX - IC 21 - PROBLEM : P˛

XILINX - IC 21 KÇI=.1.5Å: PROG/DONE LILE (IC3/PI
XILINX - IC 21 -

XILINX - IC 21 - PRKBLEM.: PROG/DO˛
XILINX - IC 21 - PROB
XILINX - IC 21
XI*
.j.J
Å

XILINX - IC 21
XILANX
XILINX - IC 21 - PROBLEM : PROG/DONE LINE
HILINX - IC 21 - PROBLEM.: PROG/DONE LINE (IC3/PIN1µ
XILINX - IC 21 - PROB™5Å: PROG/D
XILINX - ˇ
X.LINX - IC 21 - PRJ1.5 : P“
XILINX - IC 21 - PROBLEM : PROG/DONE L
XILINX - IC ˙

XILINX
XILINX - IC 21 - PROBLEM : PROG/DONE LINˇ
XILINX - IC 21 - PROBLE
 : PROG/DONE LINˇ
XILINX - IC 21 - PROBLEM : PROG/DONE LILE (IC3/PIN.5) STA
XILINX - I√
XILINX
 IC 21 - PROBLEM : PROG/DONE LINE (AC3/PIN15) STAYS AT ZER
ä
XILINX
XILIN˛
XILINX - I(íä.j.ÇI=.1.˘
¯
˝
XILINX -ˇ
XILINX - .C 21 - PROBˇ
XILINX - IC 2Lj.ÇI=.1.5Å: PROG/D
XILINX
XILIÓ
XILI
.j.J
Å21 - PROBLEM : PROG/DON
X
XILINX - IC 21 - PROBLEM : PRO./DONE LINE (IC3/PIN15) ˇ

X…
XILINX - IC 21 - PRˇ
 
XILINX - IC 21 -.
X
XILINX - I(íä.j.ÇI=.1.M
XILINX - IC 21 - .ROBLEM :‡
XILINX - IC 21¯
XILINX -
XILINX - IC 21¯
X
XILINX - IC 21 - PROBLQS“.ÇI=.ΩDONE LIŒ
XILINX - IC 21 - PROBLEM : –
XILINX KJ
Å
XI
XILINX - IC 21
XILINX - IC 21 , PROBLEM¯
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3/PIN15) STAYS AT Z
˛
XILINX¯
XI˛

XILINX - IC 21 - PRO
XILINX - IC 21
XILINÿ
XILINX -
XILINX - I˚
XI˛
XILINX - IC 21 - PROBLEM
XILINX - IC 21 -
XILINX - IC 21 -
X.LÈ
X
XILI.X -ˇ
XILINX - IC 21 - PROBLQS“.ÇI˝
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3/Pˇ
XILINX % ICˇ
XILINX - IC 21 ˝
XILINX - IC 21 KÇI=.1.5Å: PROE/DONE ˛

XR*
.j.J
Å21 - PROBLEM :¯
X…
XILINX - IC 21 - PRNBLEM :.PROG/DONE LINE (R®¶.ï9Ö5JöQ.eMÅAT
XI
X
HALINX - IC 21 Ì
X

XILINX - IC 21 - PROBLEM : PRˇ
XILINX - IC 2±
XILINX -ˇ
XILINX - IC 2
XILINX - .C 21 - PROBLEM : PROG/DONE LINE (IC3/PI˛ˇ
X
XILINX - IC 21
X…
XILINX - I
XILINX - IC 21 - PROBLE
 : PROG/DONE LINE (IC3
XILINX - IC 21 ) PROBLEM : PROG/D ™ÅLINE (KC3/PIN15) STAYS AT ZERG AFTER L
XI.INX - IC 21 - PRO(™5ˇ
XILIFX - IC 21 - PRO
XILINX - IC 21 - PRO@LEM : PR
XILINX - IC 21 - PROBLEM¯
XILINX - IC 21 - PROBLEM : PROG/DOFE LINE (IC3/PIŒ
XILINX - ˇ
XILINX - IC 21 - PROBLEM : –
XILINX - IC
XILINX - IC 21 - PROBLEM : PROG/DONE LINE (IC3/P
XILINX - IC 21 - PRO
XILINX - Iˇ
XILINX - IC¸
XILINX - IC 21 - PROBLEM : PRGG/DOND
XI.INX - IC 21 - PROBÃ
XILINX - IC 2!j.ÇI9.1.5Å
XILINX - IC 21 - P
XILINX - IC‡
.ILINX - I(Çä.j.Ç.=.1.5Å2 PROG/DONE LINE (IC3/PIN15) STA.S AT ˙
XILI˛.
X
XILINX - IC 21 - P˙
XILINX - IC 21
XI¸
XILINX - IC¸
XILINX -
XILINX - IC 21 - PROBLEM : P“
XILINX - IC 21 - PROBLEM : PROG/DON
XILINX - IC 2˝
XILINX - IC 21 - PRO
XILINX‡
.XILINX¸
XILINX - IC
XILINX - IC 21 -
.XILINX¸
XILINX - IC 21 - PRO...ˇ
XILINX - IC Õ
XILINX - IC 2˝ˇ
XILINX - IC
XKLI.X - IC 21 - PROBLQ”. ï1%NX - IC .1 - PROBLÂ
XILINX - IC 21 - PROBLEM : PROG/ˇ
XILINX ) IC 21 - PROBLÕ
XILINX¯
XIL…

XILINX - IC 21 - PROBLEˇ
XILINX -
XILINX - IC 21 -¸
X.LINX - IC 21 - PROBL.M : PRNG/DONE LINˇ
˛

XILINX - IC "1 - PROBLEM
X
XILINX - IC Ú
XILINX - ICˇ
XILIN
XILINX - IC 21 - P
XILINX - ˇ
˙
XILINX - IC 61 - PROBLEM†
X
XILˇ
XILINX - IC 21 - PROBLEM : PROG/DONE L
XILINX ˝
XI
X
XILINX - IA 2
XILI
XILˇ
X…
XIˇ
XILÈ
˛
X
ï1%9X¯
XILINX - IC 21 -
X.L
XILINX - IC 21 - PRO˙

XILINX - IC 21 - PROBLEM : PROG.DONE LINE
XILINX - IC 21 - PRœ
XILIJX - .C 21 - PROBLDM : PROG/DONE LINE (IC3/PIN15) STAYS AT
X…
XILINX - IC 21 - ˛
ˇ
XI
XILINX - IC 21 - .
XILINX - IC
XI*
.j.J
Å21 - P
˙
XILINP - IC 21 ) PROBL.I : PRO./DONE
XILINX % IC 21 .
XILINX - IC ˛
Xˇ
XILINX - IB 21 Kġ
XILINX‡ˇ
XI
XILINX - IC 21 - PRœ
XILINX - IC 21 -ÇI˝
XILIŒ
Í˝
XILINX`- IC 21 KÇI=.1.5Å: PR
XALINX - IC 21 - ˛
X˘
XILINH - IC 21 -
XILINXjÇ
Xˇ
XILINX - HC
HILINX - IC 21‡

XILINX - IC 21
X
 
XILINX
XILINX - IC 21 - P
XIL.NX - IC 21 - PRO˛
XI*
.j.J
Å2
ˇ
XILINXÕ˝.
PILIN¯˝

XILINX


XILINX - IC 21†Õ
XILINX -
¯
XILI˛
XILIN¯
XILINX ≠
XILH
XI¸
XILINX - IC 21 - Pˇˇˇ
.XILINX
X…
˙
XILINX - I√
ï1%9aÅ-‡

XILIN
XILI
XILINX - IC
XILINX - IC 21 - PROBLEM : PROGˇ
XILINX - IC 21 - PROBLEM 'ÇI=./DO.E¯
XILI˛
XILINX - IC 21 - PROBLı
XILINX - IC 21 - PROBLEÕ
XIˇ
Xˇ
XILINX - IC!ï1%9aÅ- IC 21

XILINX - IC 21 - PRO
XILI
˝ˇˇˇ.


Note the interruption in the middle (with dots) - on the hex view it was just zeroes. I believe the board might have a watchdog that resets everything in case of lockup and this happened there (I've noticed the watchdog-like behavior when I accidentally touched some pins on the board and upset it - the serial output would stop which I suspect means the board has frozen, then the entire thing reboots and starts over).

So at least it seems like my terrible attempt at getting serial output out of it is working.

Edit: so the board may have a bad contact which would get it into that error state where it can't load the FPGA bitstream. I've flexed the board a bit and pushed down on all the socketed chips and managed to make it boot like it was before (card mechanism moves briefly at startup, "S1.0Z181" displayed on LCD and repeating binary spam on the serial port).

Now that I got it booted and am confident that the serial setup is correct (at least hardware-wise), here's what I get on 9600/8N1:

Code: [Select]
$ xxd Documents/CoolTerm\ Capture\ 2022-03-07\ 00-37-07.txt
00000000: ff04 c1c1 05ff 04c2 c205 ff04 c3c3 05ff  ................
00000010: 04c4 c405 ff04 c5c5 05ff 04c6 c605 ff04  ................
00000020: c7c7 05ff 04c8 c805 ff04 c9c9 05ff 04ca  ................
00000030: ca05 ff04 cbcb 05ff 04cc cc05 ff04 cdcd  ................
00000040: 05ff 04ce ce05 ff04 cfcf 05ff 04c1 c105  ................
00000050: ff04 c2c2 05ff 04c3 c305 ff04 c4c4 05ff  ................
00000060: 04c5 c505 ff04 c6c6 05ff 04c7 c705 ff04  ................
00000070: c8c8 05ff 04c9 c905 ff04 caca 05ff 04cb  ................
00000080: cb05 ff04 cccc 05ff 04cd cd05 ff04 cece  ................
00000090: 05ff 04cf cf05 ff04 c1c1 05ff 04c2 c205  ................
000000a0: ff04 c3c3 05ff 04c4 c405 ff04 c5c5 05ff  ................
000000b0: 04c6 c605 ff04 c7c7 05ff 04c8 c805 ff04  ................
000000c0: c9c9 05ff 04ca ca05 ff04 cbcb 05ff 04cc  ................
000000d0: cc05 ff04 cdcd 05ff 04ce ce05 ff04 cfcf  ................
000000e0: 05ff 04c1 c105 ff04 c2c2 05ff 04c3 c305  ................
000000f0: ff04 c4c4 05ff 04c5 c505 ff04 c6c6 05ff  ................
00000100: 04c7 c705 ff04 c8c8 05ff 04c9 c905 ff04  ................
00000110: caca 05ff 04cb cb05 ff04 cccc 05ff 04cd  ................
00000120: cd05 ff04 cece 05ff 04cf cf05 ff04 c1c1  ................
00000130: 05ff 04c2 c205 ff04 c3c3 05ff 04c4 c405  ................
00000140: ff04 c5c5 05ff 04c6 c605 ff04 c7c7 05ff  ................
00000150: 04c8 c805 ff04 c9c9 05ff 04ca ca05 ff04  ................
00000160: cbcb 05ff 04cc cc05 ff04 cdcd 05ff 04ce  ................
00000170: ce05 ff04 cfcf 05ff 04c1 c105 ff04 c2c2  ................
00000180: 05ff 04c3 c305 ff04 c4c4 05ff 04c5 c505  ................
00000190: ff04 c6c6 05ff 04c7 c705 ff04 c8c8 05ff  ................
000001a0: 04c9 c905 ff04 caca 05ff 04cb cb05 ff04  ................
000001b0: cccc 05ff 04cd cd05 ff04 cece 05ff 04cf  ................
000001c0: cf05 ff04 c1c1 05ff 04c2 c205 ff04 c3c3  ................
000001d0: 05ff 04c4 c405 ff04 c5c5 05ff 04c6 c605  ................
000001e0: ff04 c7c7 05ff 04c8 c805 ff04 c9c9 05ff  ................
000001f0: 04ca ca05 ff04 cbcb 05ff 04cc cc05 ff04  ................
00000200: cdcd 05ff 04ce ce05 ff04 cfcf 05ff 04c1  ................
00000210: c105 ff04 c2c2 05ff 04c3 c305 ff04 c4c4  ................
00000220: 05ff 04c5 c505 ff04 c6c6 05ff 04c7 c705  ................
00000230: ff04 c8c8 05ff 04c9 c905 ff04 caca 05ff  ................
00000240: 04cb cb05 ff04 cccc 05ff 04cd cd05 ff04  ................
00000250: cece 05ff 04cf cf05 ff04 c1c1 05ff 04c2  ................
00000260: c205 ff04 c3c3 05ff 04c4 c405 ff04 c5c5  ................
00000270: 05ff 04c6 c605 ff04 c7c7 05ff 04c8 c805  ................
00000280: ff04 c9c9 05ff 04ca ca05 ff04 cbcb 05ff  ................
00000290: 04cc cc05 ff04 cdcd 05ff 04ce ce05 ff04  ................
000002a0: cfcf 05ff 04c1 c105 ff04 c2c2 05ff 04c3  ................
000002b0: c305 ff04 c4c4 05ff 04c5 c505 ff04 c6c6  ................
000002c0: 05ff 04c7 c705 ff04 c8c8 05ff 04c9 c905  ................
000002d0: ff04 caca 05ff 04cb cb05 ff04 cccc 05ff  ................
000002e0: 04cd cd05 ff04 cece 05ff 04cf cf05 ff04  ................
000002f0: c1c1 05ff 04c2 c205 ff04 c3c3 05ff 04c4  ................
00000300: c405 ff04 c5c5 05ff 04c6 c605 ff04 c7c7  ................
00000310: 05ff 04c8 c805 ff04 c9c9 05ff 04ca ca05  ................
00000320: ff04 cbcb 05ff 04cc cc05 ff04 cdcd 05ff  ................
00000330: 04ce ce05 ff04 cfcf 05ff 04c1 c105 ff04  ................
00000340: c2c2 05ff 04c3 c305 ff04 c4c4 05ff 04c5  ................
00000350: c505 ff04 c6c6 05ff 04c7 c705 ff04 c8c8  ................
00000360: 05ff 04c9 c905 ff04 caca 05ff 04cb cb05  ................
00000370: ff04 cccc 05ff 04cd cd05 ff04 cece 05ff  ................
00000380: 04cf cf05 ff04 c1c1 05ff 04c2 c205 ff04  ................
00000390: c3c3 05ff 04c4 c405 ff04 c5c5 05ff 04c6  ................
000003a0: c605 ff04 c7c7 05ff 04c8 c805 ff04 c9c9  ................
000003b0: 05ff 04ca ca05 ff04 cbcb 05ff 04cc cc05  ................
000003c0: ff04 cdcd 05ff 04ce ce05 ff04 cfcf 05ff  ................
000003d0: 04c1 c105 ff04 c2c2 05ff 04c3 c305 ff04  ................
000003e0: c4c4 05ff 04c5 c505 ff04 c6c6 05ff 04c7  ................
000003f0: c705 ff04 c8c8 05ff 04c9 c905 ff04 caca  ................
00000400: 05ff 04cb cb05 ff04 cccc 05ff 04cd cd05  ................
00000410: ff04 cece 05ff 04cf cf05 ff04 c1c1 05ff  ................
00000420: 04c2 c205 ff04 c3c3 05ff 04c4 c405 ff04  ................
00000430: c5c5 05ff 04c6 c605 ff04 c7c7 05ff 04c8  ................
00000440: c805 ff04 c9c9 05ff 04ca ca05 ff04 cbcb  ................
00000450: 05ff 04cc cc05 ff04 cdcd 05ff 04ce ce05  ................
00000460: ff04 cfcf 05ff 04c1 c105 ff04 c2c2 05ff  ................
00000470: 04c3 c305 ff04 c4c4 05ff 04c5 c505 ff04  ................
00000480: c6c6 05ff                                ....


Note that there's a jumper on the board near the RS485 transceiver IC that's been set by default. If I remove it, the serial output is much slower and as follows:

Code: [Select]
$ xxd Documents/CoolTerm\ Capture\ 2022-03-07\ 00-39-39.txt
00000000: ff04 c1c1 05ff 04c2 c205 ff04 c3c3 05ff  ................
00000010: 04c4 c405 ff04 c5c5 05ff 04c6 c605 ff04  ................
00000020: c7c7 05ff 04c8 c805 ff04 c9c9 05ff 04ca  ................
00000030: ca05 ff04 cbcb 05ff 04cc cc05 ff04 cdcd  ................
00000040: 05ff 04ce ce05 ff04 cfcf 05ff 04c1 c105  ................
00000050: ff04 c2c2 05ff 04c3 c305 ff04 c4c4 05ff  ................
00000060: 04c5 c505 ff04 c6c6 05ff 04c7 c705 ff04  ................
00000070: c8c8 05ff 04c9 c905 ff04 caca 05ff 04cb  ................
00000080: cb05 ff04 cccc 05ff 04cd cd05 ff04 cece  ................
00000090: 05ff 04cf cf05 ff04 c1c1 05ff 04c2 c205  ................
000000a0: ff04 c3c3 05ff 04c4 c405 ff04 c5c5 05ff  ................
000000b0: 04c6 c605 ff04 c7c7 05ff 04c8 c805 ff04  ................
000000c0: c9c9 05ff 04ca ca05 ff04 cbcb 05ff 04cc  ................
000000d0: cc05 ff04 cdcd 05ff 04ce ce05 ff04 cfcf  ................
000000e0: 05ff 04c1 c105 ff04 c2c2 05ff 04c3 c305  ................
000000f0: ff04 c4c4 05ff 04c5 c505 ff04 c6c6 05ff  ................
00000100: 04c7 c705 ff04 c8c8 05ff 04c9 c905 ff04  ................
00000110: caca 05ff 04cb cb05 ff04 cccc 05ff 04cd  ................
00000120: cd05 ff04 cece 05ff 04cf cf05 ff04 c1c1  ................
00000130: 05ff 04c2 c205 ff04 c3c3 05ff 04c4 c405  ................
00000140: ff04 c5c5 05ff 04c6 c605 ff04 c7c7 05ff  ................
00000150: 04c8 c805 ff04 c9c9 05ff 04ca ca05 ff04  ................
00000160: cbcb 05ff 04cc cc05 ff04 cdcd 05ff 04ce  ................
00000170: ce05 ff04 cfcf 05ff 04c1 c105 ff04 c2c2  ................
00000180: 05ff 04c3 c305 ff04 c4c4 05ff 04c5 c505  ................
00000190: ff04 c6c6 05ff 04c7 c705 ff04 c8c8 05ff  ................
000001a0: 04c9 c905 ff04 caca 05ff 04cb cb05 ff04  ................
000001b0: cccc 05ff 04cd cd05 ff04 cece 05ff 04cf  ................
000001c0: cf05 ff04 c1c1 05ff 04c2 c205 ff04 c3c3  ................
000001d0: 05ff 04c4 c405 ff04 c5c5 05ff 04c6 c605  ................
000001e0: ff04 c7c7 05ff 04c8 c805 ff04 c9c9 05ff  ................
000001f0: 04ca ca05 ff04 cbcb 05ff 04cc cc05 ff04  ................
00000200: cdcd 05ff 04ce ce05 ff04 cfcf 05ff 04c1  ................
00000210: c105 ff04 c2c2 05ff 04c3 c305 ff04 c4c4  ................
00000220: 05ff 04c5 c505 ff04 c6c6 05ff 04c7 c705  ................
00000230: ff04 c8c8 05ff 04c9 c905 ff04 caca 05ff  ................
00000240: 04cb cb05 ff04 cccc 05ff 04cd cd05 ff04  ................
00000250: cece 05ff 04cf cf05 ff04 c1c1 05ff 04c2  ................
00000260: c205 ff04 c3c3 05ff 04c4 c405 ff04 c5c5  ................
00000270: 05ff 04c6 c605 ff04 c7c7 05ff 04c8 c805  ................
00000280: ff04 c9c9 05ff 04ca ca05 ff04 cbcb 05ff  ................
00000290: 04cc cc05 ff04 cdcd 05ff 04ce ce05 ff04  ................
000002a0: cfcf 05ff 04c1 c105 ff04 c2c2 05ff 04c3  ................
000002b0: c305 ff04 c4c4 05ff 04c5 c505 ff04 c6c6  ................
000002c0: 05ff 04c7 c705 ff04 c8c8 05ff 04c9 c905  ................
000002d0: ff04 caca 05ff 04cb cb05 ff04 cccc 05ff  ................
000002e0: 04cd cd05 ff04 cece 05ff 04cf cf05 ff04  ................
000002f0: c1c1 05ff 04c2 c205 ff04 c3c3 05ff 04c4  ................
00000300: c405 ff04 c5c5 05ff 04c6 c605 ff04 c7c7  ................
00000310: 05ff 04c8 c805 ff04 c9c9 05ff 04ca ca05  ................
00000320: ff04 cbcb 05ff 04cc cc05 ff04 cdcd 05ff  ................
00000330: 04ce ce05 ff04 cfcf 05ff 04c1 c105 ff04  ................
00000340: c2c2 05ff 04c3 c305 ff04 c4c4 05ff 04c5  ................
00000350: c505 ff04 c6c6 05ff 04c7 c705 ff04 c8c8  ................
00000360: 05ff 04c9 c905 ff04 caca 05ff 04cb cb05  ................
00000370: ff04 cccc 05ff 04cd cd05 ff04 cece 05ff  ................
00000380: 04cf cf05 ff04 c1c1 05ff 04c2 c205 ff04  ................
00000390: c3c3 05ff 04c4 c405 ff04 c5c5 05ff 04c6  ................
000003a0: c605 ff04 c7c7 05ff 04c8 c805 ff04 c9c9  ................
000003b0: 05ff 04ca ca05 ff04 cbcb 05ff 04cc cc05  ................
000003c0: ff04 cdcd 05ff 04ce ce05 ff04 cfcf 05ff  ................
000003d0: 04c1 c105 ff04 c2c2 05ff 04c3 c305 ff04  ................
000003e0: c4c4 05ff 04c5 c505 ff04 c6c6 05ff 04c7  ................
000003f0: c705 ff04 c8c8 05ff 04c9 c905 ff04 caca  ................
00000400: 05ff 04cb cb05 ff04 cccc 05ff 04cd cd05  ................
00000410: ff04 cece 05ff 04cf cf05 ff04 c1c1 05ff  ................
00000420: 04c2 c205 ff04 c3c3 05ff 04c4 c405 ff04  ................
00000430: c5c5 05ff 04c6 c605 ff04 c7c7 05ff 04c8  ................
00000440: c805 ff04 c9c9 05ff 04ca ca05 ff04 cbcb  ................
00000450: 05ff 04cc cc05 ff04 cdcd 05ff 04ce ce05  ................
00000460: ff04 cfcf 05ff 04c1 c105 ff04 c2c2 05ff  ................
00000470: 04c3 c305 ff04 c4c4 05ff 04c5 c505 ff04  ................
00000480: c6c6 05ff 04c7 c705 ff04 c8c8 05ff 04c9  ................
00000490: c905 ff04 caca 05ff 04cb cb05 ff04 cccc  ................
000004a0: 05ff 04cd cd05 ff04 cece 05ff 04cf cf05  ................
000004b0: ff04 c1c1 05ff 04c2 c205 ff04 c3c3 05ff  ................
000004c0: 04c4 c405 ff04 c5c5 05ff 04c6 c605 ff04  ................
000004d0: c7c7 05ff 04c8 c805 ff04 c9c9 05ff 04ca  ................
000004e0: ca05 ff04 cbcb 05ff 04cc cc05 ff04 cdcd  ................
000004f0: 05ff 04ce ce05 ff04 cfcf 05ff 04c1 c105  ................
00000500: ff04 c2c2 05ff 04c3 c305 ff04 c4c4 05ff  ................
00000510: 04c5 c505 ff04 c6c6 05                   .........


(now that I look at it the outputs seem completely identical from a data point of view, just that removing the jumper seems to make it slower)
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: ozcar on March 07, 2022, 03:46:29 am
I think ideally you would have to use a disassembler that understands 6303. I have an ancient 6803 disassembler, and that produces output like this (from a chunk of ROM.hex relocated as suggested by PA0PBZ):

Code: [Select]
D69A CC   6000             LDD    #$6000
D69D C3   0006             ADDD   #$0006
D6A0 18                    FCB    $18
D6A1 EC   00               LDD    0,X
D6A3 38                    PULX
D6A4 ED   04               STD    4,X
D6A6 3C                    PSHX

That is a step up from using a 6800 disassembler, because it knows of instructions LDD,  STD, ASLD, LSRD,  ADDD, SUBD, PSHX, PULX, ABX and MUL, which did not exist for 6800. However, note the FCB $18 in there, apparently that is an instruction that does not exist on 6803 either (XGDX). There seem to be other instructions that exist for 6303 but not 6803, including BCLR, BSET, BTGL, BTST, OIM, AIM, EIM and TIM and maybe more (that is just from a quick RTFM - I’m not really familiar with 6303).

As for finding references to the ASCII strings, you would probably have to relocate them for a start (maybe as abyrvalg suggested, I did not check if that makes sense).
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: bingo600 on March 07, 2022, 05:22:47 pm
This one (handbook) could be handy
https://usermanual.wiki/Document/HD6301HD6303SeriesHandbook1989.865407655.pdf (https://usermanual.wiki/Document/HD6301HD6303SeriesHandbook1989.865407655.pdf)


Build 6801 Disassembler - My choice was dasmfw (Seems to be a newer version of f9dasm)
https://github.com/Arakula/dasmfw (https://github.com/Arakula/dasmfw)

Seems to handle 6803 instructions

Code: [Select]
$ ./dasmfw -dasm 6303    -cchar '*' -conv off -cref on  -out ROM.asm -offset C000 ROM.hex
Code: [Select]
        LDD     #M6000                  * D69A: CC 60 00       '.`.'
        ADDD    #M0006                  * D69D: C3 00 06       '...'
        XGDX                            * D6A0: 18             '.'
        LDD     ,X                      * D6A1: EC 00          '..'
        PULX                            * D6A3: 38             '8'
        STD     $04,X                   * D6A4: ED 04          '..'
        PSHX                            * D6A6: 3C             '<'

Mnemonic brief
https://www.jaapsch.net/psion/mcmnemal.htm (https://www.jaapsch.net/psion/mcmnemal.htm)

Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: bingo600 on March 07, 2022, 06:53:49 pm
IMHO

The MCU must POR on ROM2

This PROM sets up the stack (to 0xff , end of internal ram) , before the first BSR / JSR  - In ROM we have a BSR wo. a SP setup, that would RTS to random land".


ROM2 Vectors

Code: [Select]
        FDB     vec_IRQ_SCI             * FFF0: F7 75          '.u'
        FDB     vec_IRQ_T0F             * FFF2: F7 9F          '..'
        FDB     vec_IRQ_ICF             * FFF4: FD 87          '..'
        FDB     vec_IRQ_ICF             * FFF6: FD 87          '..'
        FDB     vec_IRQ_EXT             * FFF8: FA 13          '..'
        FDB     vec_SWI                 * FFFA: F7 2E          '.."R
* Xref: $C0C3
ZFFFC   FDB     vec_NMI                 * FFFC: FD 33          '.3'
* Xref: $C0BA, $C15F, $C19B, $C230, $C3A9, $E723
MFFFE   FDB     vec_IRQ_ICF             * FFFE: FD 87          '..'


ROM2 RESET Routine

Code: [Select]
* Xref: $FFF4, $FFF6, MFFFE
vec_IRQ_ICF
        LDAA    #$01                    * FD87: 86 01          '..'
        BRA     ZFDA1                   * FD89: 20 16          ' .'
* Xref: $FD57
ZFD8B   LDAA    #$02                    * FD8B: 86 02          '..'
        BRA     ZFDA1                   * FD8D: 20 12          ' .'
        LDAA    #$03                    * FD8F: 86 03          '..'
        BRA     ZFDA1                   * FD91: 20 0E          ' .'
        LDAA    #$04                    * FD93: 86 04          '..'
        BRA     ZFDA1                   * FD95: 20 0A          ' .'
        LDAA    #$05                    * FD97: 86 05          '..'
        BRA     ZFDA1                   * FD99: 20 06          ' .'
* Xref: $E305
ZFD9B   LDAA    #$06                    * FD9B: 86 06          '..'
        BRA     ZFDA1                   * FD9D: 20 02          ' .'
* Xref: $F7DA
ZFD9F   LDAA    #$07                    * FD9F: 86 07          '..'
* Xref: $FD89, $FD8D, $FD91, $FD95, $FD99, $FD9D
ZFDA1   SEI                             * FDA1: 0F             '.'
        STAA    M0080                   * FDA2: 97 80          '..'
* Xref: $FD84, $FDC4, $FDD0
ZFDA4   LDS     #M00FF                  * FDA4: 8E 00 FF       '...'
        JSR     ZF063                   * FDA7: BD F0 63       '..c'
        JSR     ZEF7B                   * FDAA: BD EF 7B       '..{'
        TSTB                            * FDAD: 5D             ']'
        BEQ     ZFDC6                   * FDAE: 27 16          ''.'
        DECB                            * FDB0: 5A             'Z'
        LDX     #MFE4E                  * FDB1: CE FE 4E       '..N'
        ASLB                            * FDB4: 58             'X'
        ABX                             * FDB5: 3A             ':'
        LDX     ,X                      * FDB6: EE 00          '..'
        JSR     ZFFAF                   * FDB8: BD FF AF       '...'
        AIM     #$DF,M0000              * FDBB: 71 DF 00       'q..'
        LDX     #MFFFF                  * FDBE: CE FF FF       '...'
* Xref: $FDC2
ZFDC1   DEX                             * FDC1: 09             '.'
        BNE     ZFDC1                   * FDC2: 26 FD          '&.'
        BRA     ZFDA4                   * FDC4: 20 DE          ' .'
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 07, 2022, 08:30:10 pm
As I've told earlier, the smaller 16K ROM is a standalone FPGA loader. Don't waste time on it, there are no communications, it just loads an embedded FPGA image, test the RAM, does some hw init and switches to the main 128K ROM that contains "main" code.
With 128K ROM mapped per my description above finding string refs is trivial. An example:
- "BAD CARD" message is at 0x8A44
- Press Alt-B (search for binary data)
- Enter 8a44
- ref is found at 88AB: ldd #8A44 - that's it, press O there to convert it to offset
- the message pointer is passed to a function at 0x794A (by 88B3: jsr 794A) - that's some kind of OutTextAt(row, msg), check other refs to it to find more message output.

FF 04 xx xx 05 packets seen in your txt dumps are sent by a func at 0xD417. D310 is TxByte(val), check other refs to it to find more packet types. D340 is RxByte(), several comms processing funcs call it. E862 is UpdateCRC(val), updating wCrc at 0xC7 (in IRAM), used by comms.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: nali on March 07, 2022, 09:12:52 pm
Now that I got it booted and am confident that the serial setup is correct (at least hardware-wise), here's what I get on 9600/8N1:

It's a bit of a long shot but as it's European it may be using VDV300 - in which case try 9600/E/7/1 or maybe even 1200/E/7/1. You'll see ASCII strings if it is.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 07, 2022, 10:18:00 pm
Not sure when that standard was released - this is a pretty old machine & system and while it's been inspected relatively recently (2015) according to a sticker inside I would expect that the actual software & protocols were designed in the late 90s/very early 00s. But I've tried anyway, nothing conclusive - see the two kinds of output (the change in the middle is me swapping between the first setting you suggested and the second one).

Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 07, 2022, 11:53:03 pm
It seems like some parts of the main firmware has some sort of checksum on it. Attempting to do a benign modification (in this case, swapping a character in the string "CARD") makes the machine not boot and not produce any serial output. However, changing a byte in a different region worked fine (as in it prevented it from displaying anything on the screen, but I was still getting serial output at least).
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 08, 2022, 12:45:46 pm
Each flash bank has CRC16. Bank header:
Code: [Select]
+0 dw crc16 //lsb first
+2 dw magic == 0x87CD
+4 dw start
+6 dw end

Example: 16K ROM header: { .crc16=0xDE33, .magic=0x87CD, .start=0xC002 - skip crc16 itself, .end=0xFFFF }. CRC16 calculation in my 010Editor (initial value=0, polynomial=0xA001) produces 0x33DE.

Python crcmod has a builtin implementation with these params, example:
Code: [Select]
#!/usr/bin/python3
import crcmod
crc16func = crcmod.predefined.mkCrcFun('crc-16')
data = open('boot.bin', 'rb').read()
print(hex(crc16func(data[2:])))

Another finding: 16K bootloader ROM can reflash the 128K part.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 08, 2022, 06:03:50 pm
Thanks for looking into this, I'll keep this in mind in my next attempt and will write a script to regenerate the CRCs.

I guess the bootloader reflashing the flash might be for some remote firmware update mechanism? Either it waits for a magic packet at boot over serial or alternatively, the 128k part can be sent a command to pass control back to the bootloader (by simply rebooting?) once it receives a firmware update command. This would allow firmware updates without having to physically open the machine and swap chips.

Also, another bit of trivia from yesterday - I managed to make the machine reboot by sending it garbage over serial at 9600/8N1 by mashing the keyboard. The machine rebooted and I got 1.0Z183 on the screen. Sadly, I couldn't reproduce it. I tried just sending /dev/urandom to it for a few minutes and nothing happened so I don't think it was a crash - rather I must've accidentally sent a valid command manually.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: bingo600 on March 08, 2022, 07:06:04 pm
Quote
Another finding: 16K bootloader ROM can reflash the 128K part.

Strange they did it that way around ...
The Boot EEPROM can reprogram the EPROM , that requires UV light  to erase (AM27C128-200DC)

Well ... There are some blocks of 0xFFFF they can patch some stuff into.

/Bingo
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 08, 2022, 08:02:40 pm
Bingo, no, the opposite - the bootloader in 16K 27C128 writes the main app to 128K 28F010.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 08, 2022, 09:37:32 pm
04 Cx Cx 05 packets are some pings - the machine, acting as a master, is looking for some slave device, Cx values are slave addresses. Depending on some unclear condition (DIP switch?) the machine can also act as a slave and wait for 04 Cx Cx 05 packet (x is set somehow, another DIP sw?). Expected replies are single bytes 04, 05, 10 and some complex packet starting with 02, ending with 03, having control bytes (like 02, 03) escaped as 10 xx and containing a crc16.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 08, 2022, 10:53:41 pm
There are indeed 2x8 DIP switches - pretty sure all were off when I got the machine. I've cycled through them all one by one and didn't notice any change in behavior - bruteforcing all the potential combinations is going to be very time-consuming (given it takes ~8 seconds to boot). Haven't had time to play with it today but will try throwing some serial traffic at it tomorrow.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 12, 2022, 03:56:14 am
Got some progress.

I managed to put the device into slave mode - the 7th switch of the topmost DIP switch bank seems to control the master/slave mode. At least some of the remaining switches also control the address of the device.

Sending a 04 xx xx 05 (xx varies depending on the address set on the DIP switches) - I've had C7, C3, etc appears to make the machine boot - it displays "CLOSED" on the screen and returns some data on the serial port:

Code: [Select]
00000000: 1002 4c00 0001 0ffe de33 9b0e bdce 5632  ..L......3....V2
00000010: 2e30 2f30 2030 312e 3037 2e39 3156 372e  .0/0 01.07.91V7.
00000020: 3020 2020 3033 2f30 332f 3938 5331 2e30  0   03/03/98S1.0
00000030: 5a20 2020 2020 2020 2020 2000 0546 5210  Z          ..FR.
00000040: 038d 5a05 0505 0505                      ..Z.....

5A seems to be the terminator of the frame - the "05" bytes after arrive at about 1/second, suggesting some kind of keep alive or something. Sending more data during the "05" period returns FF. The byte before the terminator must be the CRC.

This response seems to be an example of the complex format Abyrvalg mentioned - I'm going to write some Python scripts to send them and do escaping/CRC properly.

Also, besides its "primary" address, it seems like the machine also responds on other addresses. I've made myself a quick bruteforcer to iterate through all the 255 possible addresses, seems like address 131 (0x83) also responds (as usual, I am sending the same command type, so here it would be 04 83 83 05) - I get "10 30" back immediately with 4 further "15" responses every ~10 seconds - seems like it's waiting for more data. If sent first after powerup, this will also set the machine in the "CLOSED" state.
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 12, 2022, 07:37:40 am
Yes, in slave mode the incoming address is masked with 0xBF before comparison and bit 6 is used later for some other purpose - two completely separate code paths depending on it, something like control/data bit??
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 12, 2022, 03:14:34 pm
Slave response to 04 xx xx 05 with addr.6 bit set:

10 02 - DLE STX
variable-length payload with every 10 symbol (DLE) replaced by 10 10 - from some buf filled by other funcs not related to comms
10 03 - DLE ETX
crc16l crc16h - crc of payload (before 10 -> 10 10 stuffing !) and final 03 byte (w/o 10 !)

after that the machine waits for 10 31 acknowledge, sending a 05 (ENQ) after each rx timeout, trying 5 times (those 05 05 05 05 05 in your dump)

finally it sends a 04


Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 12, 2022, 03:42:42 pm
Just wondering, is this all from disassembly or did you somehow manage to run parts of the code in an emulator?

I've tried again - seems like the machine's response to 04xxxx05 is inconsistent.

After initial powerup, the first response will be a single 04 (and it changing to its "CLOSED" state). Further commands like it will produce the responses described previously with the stream of 05 05 05...

If I send a 10 31 in response to those 05 05 05 (gotta be quick - seems like it's buffered, by the time I see the second "05" it's too late and the microcontroller has already timed out the conversation) results with a 04 response - after which the 04xxxx05 command only ever elicits that response and nothing more. Seems like it establishes some kind of "conversation" context where it expects different commands from now on, and presumably there's a way to exit this context as well?
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 12, 2022, 06:06:52 pm
Yes, disasm.
I see now, doing that full addr.6=1 exchange clears some flag and disables further 10 02 ... 10 03 crc packet sending until that flag is set again (by some code not related to comms - card scan?).

But there is another path available:
send 04 xx xx 05 with addr.6=0
the machine either responds with NAK (15) and continues the comms loop
or does this:
send 10 30 (or 10 31, the LSB toggles from iteration to iteration)
receive 10 02 ... 10 03 crcl crch packet
send 10 3x
continue the comms loop
this also sets a flag signaling to non-comms code to process the rx buf content

Rx buf content (10 02 ... 10 03 packet payload):
buf[0]==4D -> do nothing (ping?)
buf[0]==E3 -> show "CMD MODE" on LCD and jump to some other ROM bank (back to 16K boot ROM ?)
other values - ??




Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on March 12, 2022, 10:31:07 pm
> doing that full addr.6=1 exchange clears some flag and disables further 10 02 ... 10 03 crc packet sending until that flag is set again (by some code not related to comms - card scan?).

> card scan?

Does it first do something to put the machine in a state to accept cards? There's a solenoid on the card slot that is closed by default (no power = card slot closed), so the machine has to explicitly "want" a card for a card to end up in the card slot. Simply doing the first handshake as described in my previous post (which puts it in a "CLOSED" state) isn't enough to unlock the card slot.

> show "CMD MODE" on LCD and jump to some other ROM bank (back to 16K boot ROM ?)

This could be interesting. Looking at the strings in the ROM I don't see anything for "day to day" operations such as validating tickets and confirming to the user that their ticket has been validated (or denied), so I suspect the machine is just a very dumb peripheral that relies on the serial bus for every operation, including the card validation and display of messages (it'll presumably send a packet when it reads a card, expect in the reply what to write on the magstripe, print on the ticket and display on the screen). In this case, the next question would be whether it's supposed to receive commands or raw code to execute.

Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: abyrvalg on March 13, 2022, 11:55:19 am
It’s quite hard to move further from this point, I’ve consumed all easily visible "entry points" (refs to text messages, UART registers, flash IDs array, CRC poly). A code controlling the solenoid would be indistinguishable from any other GPIO access. Perhaps you could trace some important hw control paths to CPU/PPI pins to get an idea what reg.bit corresponds to them in code?
Title: Re: Reverse-engineering a HD63B03RP-based AES Prodata bus ticket machine
Post by: Rjevski on June 07, 2026, 05:49:53 pm
Update: out of curiosity I tried to get LLMs on the case and threw the binaries and the existing notes into Claude Code (alongside a throwaway virtual machine for it to play around with), and it does look like it managed to figure out parts of the serial protocol - at least what it's saying appears plausible.

I don't have physical access to the machine at the moment, but if I find time I will try it out and update this thread. I pushed the whole working directory to Github (https://github.com/Rjevski/prodata-ticket-machine), but quoting here its notes below (again AI generated, so take it with a grain of salt... but it does sound plausible at least):

Code: [Select]
Reverse-engineered from two ROM dumps (`ROM.hex` = 128 K main EPROM, `ROM2.hex`
= 16 K boot/FPGA-loader). CPU is a **Hitachi HD63B03RP** (CMOS 6803-compatible);
support chips: **HD63B21** PIA (I/O multiplexer), **HD63B40** PTM (timer),
**XC2064** FPGA (magstripe/print/motor datapath), **HY62256** 32 K battery-backed
SRAM, **DS75176** RS-485 transceiver.

The unit is a **logic-less peripheral**: it reads/writes/prints under command from
an RS-485 bus master; all fare/validation logic lives on the host. Its entire
"personality" (layout, IDs, tables) lives in the battery-backed SRAM.

Legend: **confirmed** = read directly from the disassembly / verified against a
captured frame; *inferred* = strong hypothesis, flagged where it matters.
Addresses are CPU addresses unless noted "file".

Tooling that accompanies this doc: `prodata_slave.py`, `prodata_master.py`,
`test_emulators.py` (see §18).

---

## 1. ROM memory map & banking

The HD6303 has a 64 K address space; ROM/RAM/IO are banked into it. The 128 K ROM
is only ~⅓ programmed.

| File pages (16 K) | Contents |
|---|---|
| page 0 `$00000-$03FFF` | main: reset, vectors, comms stack, ISRs |
| page 1 `$04000-$07FFF` | `$4000-$5FFF` **blank**; `$6000-$704A` used (bankA code/strings) |
| page 2 `$08000-$0BFFF` | main runtime code + strings (entry) |
| pages 3-7 `$0C000-$1FFFF` | **blank (0xFF)** |

**Runtime CPU map** (main ROM active):

| CPU range | Source | Note |
|---|---|---|
| `$0000-$001F` | 6303 on-chip regs | ports/timer/SCI |
| `$0020-$007F` | external I/O (FPGA/PIA) | see §3 |
| `$0080-$00FF` | 6303 on-chip RAM | hot vars, CRC, stacks |
| `$0100-$5FFF` | SRAM (direct + paged) | see §2 |
| `$6000-$9FFF` | ROM file `$8000-$BFFF` | runtime code; **CPU = file − 0x2000**; entry `$6022` |
| `$A000-$BFFF` | ROM file `$6000-$7FFF` | more code |
| `$C000-$FFFF` | ROM file `$0000-$3FFF` | reset/vectors; entry `$C022` |

Vector table (file `$3FF0` → `$FFF0`): SCI=`$D35B`, TOF=`$D843`, OCF=`$F29C`,
ICF=`$F1FD`, **IRQ1=`$E8E2`**, **SWI=`$D30B`**, **NMI=`$D89C`**, **RESET=`$C475`**.

**Bank registers** (read-modify-write latches with RAM shadows):
- `$0030` (shadow `$82`), set via `C2A2`. bits 0-4 = ROM/SRAM bank; **bit 6** =
  map FPGA/buffer into window; **bit 7** = magstripe bit-bang enable.
- `$0028` (shadow `$81`), set via `C2AB`.
- Set-bank routines: `C207` (uses count `$0C65`), `C1D3` (uses count `$0C64`).
- Window trampolines: `FEF6`→`C283` (set `$0030` bit6), `FEF3`→`C28A` (clear it).

`ROM2.hex` is a **standalone boot ROM** (reset `$FD87`); see §12.

---

## 2. Battery-backed SRAM (HY62256, 32 K)

Lives in the low/data space, partly direct and partly paged. **Battery-backed → it
holds all downloaded config + persistent counters.** Shorting the backup battery
**wipes it** — which is why a unit boots fine from ROM but sits in a degraded state.

| Range | Use |
|---|---|
| `$0100-$0FFF` | boot scratch / low vars (boot tests `$09D4-$0BD3`, calls it "IC 16"; boot stack `$0BD3`) |
| `$1000-$1FFF` | main working RAM, FIFOs, state, **relocated magstripe code `$1DAC`** |
| `$2000-$3FFF` | downloaded config buffers (print bitmap `$2002`, layout `$2A86`, G params `$30C8`, H text `$3110`) |
| `$4000-$5FFF`+ | more data, reached via paged window |

**Auto-sizing at boot:** `C375`/`C382` walk bank numbers, write the complement to
`$5FFF`, read it back and compare a table (`C353`: `LDAA $5FFF / COM $5FFF / EORA
$5FFF`) — a presence/aliasing probe. Bank counts → `$0C64` / `$0C65`.

The config "consumers" that have no fixed-address reads (e.g. the `G` scalars) read
through the **paged window via set-bank**, which is why they don't show as simple
absolute references.

---

## 3. I/O registers (FPGA / PIA latches & ports)

External I/O occupies the `$20-$7F` gap (above on-chip regs, below on-chip RAM),
decoded by the FPGA / HD63B21.

| Addr | Dir | Function |
|---|---|---|
| `$00-$07` | — | 6303 ports; reset sets `DDR1=$AB`, `DDR2=$12`, etc. |
| `$08-$0F` | — | 6303 / HD63B40 timer |
| `$10-$13` | — | 6303 SCI (RS-485): `$10` rate/mode, `$11` TRCSR, `$12` RDR, `$13` TDR |
| `$0020` | R/W | **sensor inputs (low 3 bits) + control-out** (shadow `$83`, set via `C2B4`). bits 4/5/7 of the *output* are the **DIP/sensor mux scan-select**; low 3 bits of *input* = selected group |
| `$0028` | W | control latch (shadow `$81`, set via `C2AB`) |
| `$0030` | W | **system control** (shadow `$82`): bits0-4 bank, bit6 window-map, bit7 magstripe mode |
| `$0041,$0044,$004A-4C,$0048,$0049` | R/W | **FPGA/PTM capture & timer** — motor tacho + magstripe flux timing; serviced by IRQ `E8E2`, `CEA4`, `C622` |
| `$0060` (+`$0068-6F`) | W | **stepper coil drive + head/solenoid control lines** |
| `$6000` (windowed) | W | FPGA **control strobe** during magstripe bit-bang (`$40`/`$C0`/`$00`) |
| `$9F00` (windowed) | R/W | FPGA **data port** — shared serializer for thermal head *and* magstripe head |

Per-bit `$0020` control helpers: `C245`/`C24C` set/clear bit4, `C253`/`C25A` bit5,
`C261`/`C268` bit7. *(Which exact bit drives the gate solenoid vs motor-enable vs
head-strobe is unconfirmed — needs a scope; see §19.)*

---

## 4. DIP switches (16, two banks of 8)

**Not a parallel port** — multiplexed onto `$0020`: output bits 4/5/7 form a scan
select, input bits 0-2 are the selected group (read primitive `D7F1`/`D774`, with a
settle delay `E2BD`). Likely the HD63B21 PIA does the muxing.

Only ~4 lines are actually consumed:
- **Bus address** — a 3-bit field `N` (0-7). Device answers at `0xC0|N` (control
  channel) and `0x80|N` (data channel); slave-match masks the incoming address with
  `0xBF` so both collapse to `N`. (`D360`: `addr = 0xC0 + [M0399]+1`.) Matches the
  forum's observed `C1`/`C3`/`C7` and `0x83` (= same unit N=3, data channel). An
  8-entry channel table lives at `$0397`.
- **Master/slave mode** — flag `$00A6` tested at `D288` (forum: switch 7, top bank).

The remaining ~12 switches are scan-capable but **never referenced** in this
firmware (consistent with the forum finding most had no effect) — reserved/unused.

---

## 5. RS-485 link protocol

**Roles:** the *master* polls; the *slave* is polled. (See §17 — role is wire
arbitration only.) Address `N` and mode come from DIP (§4).

**Poll:** `04 A A 05` (`EOT addr addr ENQ`). `A`'s bit 6 selects the channel:
- **report** (`0xC0|N`): slave sends a data packet, master ACKs `10 31`.
- **command** (`0x80|N`): slave sends `10 30` ready, master sends a frame, slave
  ACKs `10 3x` (LSB toggles).

**Frame:** `10 02 <DLE-stuffed payload> 10 03 crcL crcH`
- `STX=02 ETX=03 DLE=10 ENQ=05 EOT=04 NAK=15`.
- DLE stuffing: each `0x10` in the payload is sent as `0x10 0x10`.
- **CRC = CRC-16/ARC** (poly `0x8005` reflected = `0xA001`, init `0x0000`; nibble
  table @ `$EA19`, update routine `E862`) computed over **payload + `0x03`**
  (un-stuffed, *excludes* the leading `02`), transmitted **low byte first**.
  Verified against a real frame: payload+`03` → `0x5A8D` → bytes `8D 5A`.

**Handshake / retries:** on the report channel, no-ACK → `05` (ENQ) up to 5 times,
then `04`. On the command channel, slave replies `15` (NAK) if not ready.

**Key routines:** `D310` TxByte (writes TDR, `SWI` yields), `D340` RxByte
(timeout via `$00AE` seeded from `$00A8`, `SWI` yields, checks TRCSR `$C0`),
`D417` build poll, `E7EB` receive-frame, `E862` CRC, `D511` master↔slave exchange.

**Master poll loop (`D360`)** — round-robin `C1..max` (max = `0xC0+N`), presence
table `$0812` (bit7 present / bit6 handled), short Rx timeout (4 ticks in master
mode), bounded retries (3× discovery, up to 33× persistent). Never blocks (runs as
the comms coroutine, §14). Master timeout/mode setup at `D288`/`D292`/`D29D`.

---

## 6. Command set (A–Q + specials)

`payload[0]` = opcode. Pre-dispatch at `6E3A`:
- `0x4D` = ping/no-op; `0xE3` = show "CMD MODE" + bank-switch toward boot ROM.
- everything else is queued (`6715` into FIFO `$171D`) and interpreted by `6ADA`,
  which requires opcode `0x41-0x51` ('A'-'Q') and jumps through the table at
  **`$6B35`**, index `(opcode-0x41)*2`.

| Op | Hex | Handler | Function |
|---|---|---|---|
| A | 41 | 6B57 | bulk load → print bitmap buf `$2002` (2560 B), `payload[1]`=segment# (0 resets) |
| B | 42 | 6BA1 | bulk load → param buf `$2A04` (128 B) |
| C | 43 | 6BEB | bulk load → ticket-layout buf `$2A86` (1602 B) |
| D | 44 | 6C35 | bulk load → big paged buffer (~16 K, via `AB29`/`ABA6`/`AB79`) |
| E | 45 | 6C89 | set string `$119A`, parse (`7375`), display (`792D`) |
| F | 46 | 6CB1 | **arm transaction** — set event flag `$1035`, load 4 param words (`$1209/$141C/$1418/$141A`), set state (`AA8B`); gate opens, waits for card |
| G | 47 | 6CF2 | set params + lookup table (≤72 B → `$30C8`); see §7 |
| H | 48 | 6D42 | load text/print block (≤300 B → `$11CA`/`$3110`) |
| I | 49 | 6D8E | set display / operating mode (`AA8B`), copy message → `$105D` |
| J | 4A | 6DF8 | nop / unimplemented |
| K | 4B | 6DC3→8138 | post transport-motor job (internal code `0x4B`) |
| L | 4C | 6DC8→8177 | post transport-motor job (internal code `0x4C`) |
| M | 4D | 6DF8 | nop (also the `0x4D` ping at `6E3A`) |
| N | 4E | 6DCD | set flag `$11AA` |
| O | 4F | 6DF8 | nop / unimplemented |
| P | 50 | 6DD4→8371 | **self-test** (sub-code 5/6/8); see §11 |
| Q | 51 | 6DE6→8A80 | **magstripe encode + verify**; see §9 |

---

## 7. Config buffer formats

All downloaded into SRAM and battery-persisted. *Structure confirmed; the actual
field values lived in the wiped SRAM and aren't in the dump.*

**G (`$30C8`, ≤72 B)** — handler `6CF2` unpacks a header then a table:
| Offset | Size | → | Role |
|---|---|---|---|
| 0-1 | word | `$11FC` | scalar param |
| 2-5 | 4 B | `$11FF-$1202` | 4-byte value (`$11FE`=4 is its length tag) |
| 6 | byte | `$1203` | scalar |
| 7 | byte | (`$30CF`) | **table entry count** |
| 8…71 | N B | `$30D0…` | **lookup table**, searched for key `$1208` (`A52C`) |

**C (`$2A86`)** — ticket layout: `[word L = record-array length][L bytes of 4-byte
records][data pool]`. Each record = `{field-id:2, data-offset:2}`. Composer `A38D`
scans by field-id, computes `data_ptr = $2A88 + L + record[2:3]`, copies the
length-prefixed field content into `$11BD` (len → `$11C5`); not-found → status 5.

**H (`$3110`/`$11CA`)** — variable field text, pulled char-by-char by `A42E`
(`$3113` length, `$3114` data).

**A / B / D** — print bitmap / small param block / big paged work-or-data buffer.

Render pipeline: **C (where) + H (text) + A (bitmap) → composer → print lines.**

---

## 8. Card transport (stepper motor)

- Coil phase patterns written to **`$0060`** (+`$0068-6F`); 3 aux control lines kept
  in shadow `$A4` and preserved each step (`AIM #$F8,$A4 / ORAA $A4 / STAA $A4 /
  STAA $0060`). Phase sequence via nibble-remap `CF04`, advanced by `CF5A`. De-energize
  = write `$00`. Driver entry points `$CExx-$D0xx`.
- **Closed-loop position:** IRQ `E8E2` + `F5B8` step until current position `$8A`
  reaches target `$EF`. **Non-continuous** — runs in bursts to the next target.
- **Sensors:** read at `$0020`; state machine `FB3A` maps sensor-bit patterns (table
  `$FB32`) to card-position states (0 empty, 1 at-entry, 3 transit, 4 at-head,
  5/6 exit) in `$FA`, firing event `E93E` on change.
- **Gate solenoid** = one of the `$0020`/`$0028` control bits, asserted only while a
  transport job is active (why the slot stays locked until armed). Jobs posted by
  `K`/`L` and the idle auto-handler (`8177`).

---

## 9. Magstripe reader / writer

The datapath is in the FPGA mapped into the `$6000-$9FFF` window (which overlaps
ROM), so the driver (`8AA7`) is **copied to RAM `$1DAC` and executed there**.

1. `8B09` sets `$0030` bit 7 → enter bit-bang mode.
2. Per byte (interrupts off): ctrl `$40`→`$6000`, data byte→`$9F00`, strobe
   `$C0`→`$6000`, read `$9F00` back, ctrl `$00`, **compare** (write-and-verify, up to
   25 = `$19` retries). Loop over `$1EB5` bytes (`$1EB1` src ptr, `$1EB3` dest port).
3. `8B17` clears bit 7 → restore ROM bank.

Reading uses the FPGA flux-capture regs (`$0041`/`$0044`) under IRQ as the card
moves; bit decoder `F94C`. Invoked by command `Q` (`8A80`, params → `$1EB1/3/5`).

---

## 10. Thermal printer

Buffers: `A`→bitmap `$2002`, `C`→layout `$2A86`, `H`→text `$3110`. The composer
(`A38D`/`A39E`) merges layout records with text into print lines, and dot data is
shifted to the head via the **same `$9F00` FPGA serializer** (head vs magstripe
selected by `AF97`/`AE89`) while the transport stepper advances paper.
Primitives: `ADE8` open datapath (with `$9F00`), `AF97`/`AE89` load head shift
register, `AFCA` strobe, `A850` finish/feed.

---

## 11. Self-test suite

Invoked over the bus via **`P` (0x50)** + a sub-code (`8371`):

| Sub | Routine | Test |
|---|---|---|
| 5 | 83DD | **PRINTING TEST** — prints 20 rows of an all-elements `L`/`H` dot pattern + `SERIAL : n` (via `$9F00`) |
| 6 | 8458/84AF | **ENCODING TEST** — write a known pattern to the stripe and read it back/verify |
| 8 | 83A7 | **CLEAN** — head-cleaning-card cycle + counter |

Counters (SRAM): `$1D8C`/`$1D8E` print tests/errors, `$1D90`/`$1D92` encode
tests/errors. Report routine `84D9-855C` prints `TOTAL TESTS` / `TOTAL ERRORS`
(format engine `FF74`). Card-read test (`8886-88C9`): `FF95`→`EE69` reads a card to
`$1CD8`; unreadable → `BAD CARD`. Message/format table near `$88CA`.

---

## 12. Boot ROM (ROM2) / FPGA loader

16 K standalone ROM, banner `BOOT V2.0/0 01.07.91`, reset `$FD87`. Sequence:
1. Test external RAM (`$09D4-$0BD3`; failure → serial `ERRORS IN EXTERNAL RAM MEMORY
   - IC 16`).
2. Find the Xilinx config header at `$C040` (else `NO XILINX CONFIGURATION DATA
   HEADER FOUND IN BOOTPROM AT ADDRESS 0xC040`) and stream the bitstream (file
   `+$40`) into the XC2064, watching PROG/DONE (failures → serial `XILINX - IC 21 -
   PROBLEM : …` — the messages seen when a socket contact was flaky).
3. Bank-switch and jump into the main ROM (`$6022`).

Has a `command_mode/c2001` path. **No comms code** — irrelevant to protocol work.

---

## 13. LCD display paths & the startup "S1.0Z" line

Three ways the screen is written:
1. **`OutTextAt` (`$794A`)** — pop-up/overlay strings via the `%`-formatter `FF74`.
   Catalog: `CMD MODE` (`$6F33`), `BAD CARD` (`$8A44`), `PRINTING` (`$88D4`),
   `ENCODING` (`$8926`), a `…LF/SELF TEST` string (`$A307`), and dynamic formatted
   buffers `$1C95`/`$1C6D` (self-test counters, `E`/`P` output).
2. **Field/widget renderer (`6025`→`ABE9`)** — the main operational screen, built
   from a screen table at `$32A0`, fed by `E`/`I` commands and the operating state.
3. **Boot ROM** power-up status.

**The stuck "S1.0Z181" / "SBZ001":** there is **no such string in either ROM** —
it is built at runtime as the idle/status line shown while waiting for the bus.
Structurally it is the **3rd version field** (the identity packet concatenates
`BOOT ver` + `app ver` + this field — captured as `…03/03/98S1.0Z          `). The
trailing number is **dynamic and SRAM-resident** — it changed `181`→`183` across
your reboots, consistent with a **reset/power-cycle counter** (matches the watchdog
resets you triggered). Because the SRAM was wiped, this field is now corrupted,
which is why your readings vary. It is **not an error code**, and the exact original
value isn't recoverable (it lived only in the erased SRAM).

---

## 14. Firmware architecture

- **Cooperative coroutines via SWI / stack-swap.** The "SCI" vector `D35B` is a
  context switch: `STS $A9 / LDS $AB / RTI` — it flips between the main task and the
  comms task, each with its own stack. TX/RX "block" by executing `SWI` to yield,
  with a timeout countdown in `$00AE`.
- **ISRs:** IRQ1 `E8E2` = timer/transport (clock `$CB += $FE`, motor stepping,
  capture regs, calls `CEA4`/`F5B8`); SWI `D30B` = serial primitive driver.
- **Boot:** `C475` reset → `C022` → `6022` → `AAB9` main loop.
- **Main loop (`AAB9`):** poll inputs (`6E71`: comms + keypad + sensors, abstracted
  as "input source #4" = the bus, via `FEE4`/`E707`/`E784`); when event flag `$1035`
  is set, run the state handlers (dispatch table at `A98B` via `A968`) and refresh the
  LCD (`6025`). Operating state via `AA8B`/`$1038` and the screen table `$32A0`.

---

## 15. Key routine catalog

| Addr | Name / function |
|---|---|
| C475 | RESET |
| C022 / 6022 | bank entry points (→ `AAB9`) |
| AAB9 | main loop |
| 6E71 | input poll (comms+keypad+sensors) |
| 6E3A | command pre-dispatch (4D/E3/queue) |
| 6ADA | command interpreter (jump table `$6B35`) |
| 6715 / 64F2 | FIFO put / get (`$171D`) |
| 794A | OutTextAt(pos, msg) |
| FF74 | `%`-format print |
| 6025 / ABE9 | LCD field renderer / flush |
| AA8B | set operating state (`$1038`) |
| A968 | main-loop event dispatch (table `A98B`) |
| D310 / D340 | TxByte / RxByte (SWI-yield, timeout) |
| D417 | build `04 A A 05` poll |
| D360 / D511 | master poll loop / master exchange |
| E7EB | receive framed packet |
| E862 | CRC-16/ARC update (table `$EA19`) |
| FECC | memcpy (→ `EB28`) |
| FEF6 / FEF3 | window bank in / out (`$0030` bit6) |
| C207 / C1D3 | set bank ($0030/$0028) |
| CF04 / CF5A | stepper phase remap / advance |
| F5B8 | motor position control |
| FB3A | sensor → card-position state |
| EE69 | full card read (used by self-test) |
| 8A80 / 8AA7 | magstripe encode (sets up / RAM bit-bang) |
| 8B09 / 8B17 | magstripe mode enter / exit ($0030 bit7) |
| ADE8/AF97/AE89/AFCA/A850 | print/encode datapath primitives |
| 8138 / 8177 | post transport jobs (K / L) |
| 8371 | self-test dispatch (P) |

`$FExx`/`$FFxx` is a trampoline table of `JMP`s into the C000 bank (far-call ABI for
banked code) — e.g. `FECC→EB28`, `FEF3→C28A`, `FEF6→C283`, `FF95→EE69`,
`FFCE→D8D9`. Document the full table when expanding this section.

---

## 16. RAM variable map

**On-chip (`$80-$FF`):**
| Addr | Use |
|---|---|
| $82 / $83 | shadows of `$0030` / `$0020` latches |
| $A0-$A4 | stepper state (phase, dir, aux lines) |
| $A6 | master/slave flag |
| $A7 / $A8 | comms timeout params |
| $A9-$AB / $AB | coroutine stack pointers |
| $AE | live Rx-timeout countdown |
| $B0 | device bus address (`0xC0+N`) |
| $C7-$C8 | CRC-16 accumulator |
| $CB | clock tick |
| $D0 | banked-access counter (watchdog pat) |
| $EF / $8A | motor target / current position |
| $F3 / $FA | card-read retry / sensor-position state |

**SRAM (`$1000+`):**
| Addr | Use |
|---|---|
| $1035 | main-loop event flag |
| $1036/$1037/$1038 | operating state |
| $105D | display message |
| $119A / $11AA | E-string / N-flag |
| $11BD / $11C5 | composed field text / length |
| $11CA / $3110 | H text buffer |
| $11FC-$1203 / $1421 | G scalar params |
| $1208 | G lookup-table search key |
| $1209/$141C/$1418/$141A | F transaction params |
| $171D / $1A24,$1A26,$1A2A | message FIFO / ring pointers |
| $1B30 | received command buffer |
| $1C31 | D-buffer paged pointer |
| $1C95 / $1C6D | dynamic LCD text buffers |
| $1CD8 | card-read buffer |
| $1D8C-$1D92 | self-test counters |
| $1DAC | relocated magstripe bit-bang code |
| $1EB0-$1EB7 | magstripe op state |
| $2002 / $2A04 / $2A86 / $30C8 / $3110 | A/B/C/G/H config buffers |
| $32A0 | screen-widget table |
| $0390-$039B / $0397 | comms/address config struct / 8-channel table |
| $0812 | per-address slave presence table |
| $0904 | master-collected rx buffer |
| $0C64 / $0C65 | auto-detected SRAM bank counts |

---

## 17. Master/slave roles & production lifecycle

The serial role (DIP flag `$00A6`) is **wire-arbitration only** — command/data
semantics are identical either way, because the bus is just "input source #4"
behind `FEE4`/`E784`/`E7D2`.
- **Slave:** waits to be polled, executes `A`–`Q` commands, reports card/status.
  *To drive the unit, set it to slave and act as master (poll + send commands).*
- **Master:** round-robin polls `C1..max`, collects/dispatches packets to/from
  subordinate units. "Master" ≠ "brain" — data can flow either way (channel bit 6),
  so a master unit can take commands from a slave it polls. *To interact with a
  master unit you must impersonate a slave and answer its polls.*

**Production lifecycle (master → unit):**
1. power-up → boot/FPGA → idle (`S1.0Z…`).
2. master polls `04 N N 05`; unit replies identity packet → `CLOSED`.
3. configure while CLOSED: `G` params, `A`/`C`/`D` images/layout/tables, `B`.
4. open service: `F` (arms → gate opens, waits for card) + `I`/`E` display.
5. *(autonomous)* card inserted → entry sensor → stepper feeds card past head →
   magstripe decoded → event → card-data report queued.
6. master polls report channel → gets card data.
7. master validates (host-side) → `Q` encode new value → `H`/`A` print → `E`/`I`
   display "OK".
8. unit ejects/returns card (`A850`); loop. On shutdown: `I` "CLOSED".

---

## 18. Emulators (tooling)

- **`prodata_slave.py`** — emulates the unit as a slave (develop a master against
  it). Faithful CRC/framing/addressing/handshake; non-blocking `idle`/`await_cmd`
  state machine; decodes `A`–`Q`; `--pty` (virtual port) or `--port` (real); console
  `card <hex> | status | buffers | addr <n> | quit`.
- **`prodata_master.py`** — emulates the bus controller: `configure()` (G/C/H) →
  `open_service()` (F/I) → `service_loop()` (poll report; on card: Q→H→E→F) →
  `close_service()` (I CLOSED) on exit. Reuses the slave's CRC/framing; no pyserial
  dependency required (termios fallback).
- **`test_emulators.py`** — single-threaded deterministic integration test; passes:
  `configure → open → card(encode/print/ok) → close, all ACKed`.

**Faithful:** framing, CRC-16/ARC, DLE-stuffing, addressing/channels, handshake,
opcodes. **Illustrative placeholders:** card-report payload tags and the G/C scalar
*contents* (no real host capture exists). Raw terminal mode is required on a pty
(binary control bytes `03`/`04` are terminal specials).

---

## 19. Open questions / unverified items & next steps

Confirmed vs. needs hardware/capture:

1. **Gate solenoid bit** — which exact `$0020`/`$0028`/`$0060` bit drives the gate vs
   motor-enable vs head-strobe. → scope/LA on the latch outputs.
2. **In-service command** — confirm which state-handler (F vs I) asserts the gate +
   arms the sensor. → trace the `AA8B` state table.
3. **Card-report packet layout** — the byte format the unit sends to the host on a
   card read. → needs a real bus capture (none available).
4. **G semantics** — meaning of each scalar, and the purpose of the G lookup table
   (key `$1208`). → trace consumers / capture.
5. **C record attributes** — any bytes beyond `{field-id, data-offset}` and how print
   position/font are encoded.
6. **Identity-packet builder / "S1.0Z" number** — which SRAM address feeds the 3rd
   version field (reset counter vs config-version vs sequence). → trace the builder.
7. **Full DIP map** — per-switch function for the ~12 inert lines (likely truly unused).
8. **Magstripe bit format** — decode `F94C` (F2F/Aiken cell timing, track layout).
9. **Config requirement** — confirm the unit reaches in-service without SRAM config
   (forum evidence: bus comms work without it).

Probe points for a logic analyzer: `$0060`/`$0068` (motor phases), `$0020`
(sensors/gate), `$6000`/`$9F00` strobes (head/magstripe during `P 05`/`P 06`).