Reverse Engineering a 6800 system
I am not sure this post is in the right section but any assistance would be appreciated.
I am trying to restore an old ZIP30 teleprinter but I do not have any technical information or schematics and I have got it working except for a final problem that I am trying to resolve. The CPU is an MC6800 and controls everything via 4 6821 PIA's.
Because I have no schematics I have decided to reverse engineer the Firmware to see where the fault lies but I have run into a problem that hopefully someone here can help with.
I have removed the ROM's (2708's) and read them and decompiled them into .asm and .lst files.
I have identified where in the address space the ROM's are and the address range of each ROM.
The problem is that while I have been writing in machine code for decades I am not very familiar with the 6800 and something odd has come up.
At the start of one of the ROM's there is a series of jmp instructions to direct the code to various handlers.
They are instruction 7E which I understand should simply load an absolute 16 bit address into the instruction pointer.
eg 7E 33 C0 should jump to 33C0
But this is not happening
I have a logic analyser attached and I am triggering on the jmp instruction address which I can see is correct but the processor jumps to a different address than that given by the 2nd and 3rd byte of the instruction.
Can anyone shed any light on this?
I did not think that 7E was an indexed address so I assume it does not use X as part of the jump calculation.
I do not think it is stuck address bits because the difference in the expected and actual addresses has too many bits different and the system is working for most functions which it would not if so many bits were stuck. Also other addresses are as expected such as the address of the actual jmp instruction.
i can't see anything wrong with any of your assumptions. Can you provide any additional details regarding the address space that the rom is mapped into and where it actually jumps to?
Is it possible that an interrupt might be kicking in and redirecting the execution to one of the interrupt addresses FFF8/9 (IRQ), FFFA/B (SWI) OR FFFC/D (NMI)?
Thanks for the reply
I do not have any schematics so I have had to figure out all the mapping but it appears to be as follows (hex)
4x PIA'a 8000 - 8023
1x AYSYNC Chip 8040 - 8043
4x RAM (6810) 0000 - 0200
4x ROM (2708) 3000 / 3400 / 3800 / 3C00
The ROM in question is mapped to 3000.
The first few lines of the disassembly are
org $3400 (I set this based on the chip mapping - Line 3003 looks suspect)
3400 : 7E 33 C0 "~3 " jmp L33C0
3403 : FF FF FF " " stx XFFFF
3406 : 7E 31 4B "~1K" jmp L314B (This actually jumps to 3CA5)
3409 : 7E 32 06 "~2 " jmp L3206
340C : 7E 30 13 "~0 " jmp L3013
340F : 7E 32 DF "~2 " jmp L32DF
At 314B there is the following but this looks a little odd for a function start with a load followed by a branch.
314B : D6 33 " 3" ldab X0033
314D : 26 63 "&c" bne L31B2
314F : D6 4E " N" ldab X004E
3151 : 27 40 "'@" beq L3193
At 3CA5 (on a different ROM) the code is (It seems to check for a minus flag and jump back to 3006 if not. Surely this would loop indefinitely if the minus flag was not set)
3CA5 : 2B 03 "+ " bmi L00AA
3CA7 : 7E 30 06 "~0 " jmp L3006
I would expect the line at 3006 to jump to 314B but when I capture this on a logic analyser it actually jumps to 3CA5
I have the analyser capturing the 6800 interrupts and they are not active during this time so I do not think this is the cause.
The actual byte values following the jump as shown on the analyser are the same as those in the ROMs at that address (3CA5) but I cannot see why it is jumping there.
I am starting to think that the values at 3400 to 3411 are actually data that the disassembler has shown as jump instructions.
The analyser only captures address access so it could just be showing a load or something like that.
I will do some more investigation to see why those memory addresses are accessed and post a follow up.
Are you familiar with DASMX which is the disassembler I am using?
The ROM code is split across a number of chips and I would like to combine them into a single file so that I have a clearer disassembly.
I was intending to combine the bin files into a single file and then run the disassembler on it to get a single listing.
On a slightly different note. I believe that the 6800 reset vector is at FFFE and FFFF and the reset code entry address is stored at 3FE and 3FF of the ROM mapped to 3C00 (so 3CFE and 3CFF) but I cannot figure out how the CPU manages to load the two bytes at startup that appear to be outside the ROM map.
It is possible that the ROM's are mirrored at several locations by only doing partial address decoding, so for example the ROM could appear at 3000, 7000,B000 and F000 which would allow it to get the necessary values on reset.
Thanks, I think I finally figured it out.
The ROM space is mapped to 2000 - 4000 so address bits 10 - 13 are used to select the required ROM (although only 4 non consecutive slots are used).
Bits 0-9 are of course used to select the byte within the (1k) ROM
Bits 10 - 12 are connected from the address bus via 4050 buffers.
Bit 13 is connected via a 4050 buffer but then has a 74125 (tri state buffer) connected between A13 and the ROM's
Address bits 14 and 15 are also connected to the 74125 and this can be used to remap the ROM's to E000 - FC000
Now I have to try to reverse engineer the code. There is a fault with the unit and without a schematic the best way to track it down is to see what the code is trying to do.
Cool. You really have been busy working out how it all goes together. I'd be interested to see the ROM dump you have. I have a reasonable understanding of how the kind of hardware used in olds systems like this but my skills really lie in the low level software side but I've not had much dealings with 6800. I've mainly worked in 6502 and 68000, some arm and sh2 and a bit of z80 for good measure.
If you were to continue posting your progress on here I for one would be very interested
I will keep you posted, Any comments on my posts would be appreciated.
The device is an old teleprinter terminal with a tape punch and reader attached.
When I got it the entire thing was dead and pretty much scrap but I like to restore old electronics and this seemed like it would be a challenge.
It took me a few weeks to get it working, Nothing worked, Mechanics seized, CPU system dead, Power supply and motor driver board had loads of faults but I finally got it running (mostly).
I am sure you know that these early units were a weird mix of digital CPU and discrete logic with some analog thrown in and some functions are split between firmware and hardware so can be difficult to fault find even with service info and schematic but when flying blind they are much harder.
The fault I am left with is a bit odd.
Everything works except for the tape reader.
It has 3 buttons which I have determined to be Stop, Single character Print and Run (Read and print the entire tape).
The button lights are driven from the CPU and they all illuminate as they should so I know that the CPU can see and respond to the buttons.
However it will only work when the single character key is pressed. It reads and prints the right character but just the one (as it should).
The Run key simply drives the tape to the next sprocket hole and then stops without printing the character. The reader is a little odd if you are familiar with tape readers because it does not have a sprocket to drive the tape. Instead it has a friction drive and two sensors instead of one for the sprocket hole reading. All optical sensors are working.
I think an important point is a fairly subtle one.
If the run key is pressed the tape drives to the next sprocket hole and then stops.
But if the button is pressed again nothing happens, even if stop is pressed first. BUT if the single character key is pressed and then the run key the tape will advance to the next sprocket hole and then stop.
Also if the power is cycled then each time the run key will drive the tape to the next sprocket hole and then stop so it seems that the reader is triggering the firmware as it should but something is failing to complete.
That is why I decided to approach it from the code side as I have no idea how the reader is supposed to run continuously.
It could be a ROM with corrupt code but it would have to be just a few bytes (Cant find binaries anywhere so I cannot check this) or it could be a hardware issue but it would have to be timing or something as all signals from the reader seem to be getting to the PIA and the PIA mode is changing when in run mode.
I am getting into the code and while I have been programming for decades in machine code as well as high level languages it is of course much harder to deconstruct a hybrid device operation and I have also never coded for the 6800.
I have attached a file taken from the logic analyser and I have added some comments on what I think is going on in the CPU from reading the 6800 datasheet. Please let me know if anything is incorrect.
There are two lines for each cycle as I have to capture on both clock edges in state mode or some signals are not properly captured.
I have found the PIA interrupt output for the tape sprocket but unfortunately every INT in the system drives the CPU IRQ so the code will most likely be difficult to decipher as they all jump to the same handler.
The reader can send to a PC via RS232 so it could be something relating to bit timing in the ASYNC chip (Had to replace this) not working or even some odd option somewhere.
I have joined all the ROM dumps into a single file and padded each the missing 'ROM' blocks with 1k of nop's
I then ran this through the disassembler using the correct base address and it now gives me a single list file with all the correct address's
Unfortunately the disassembler was not able to properly process the file using threading so I still need to do most of the work from here.
I have replaced all known addresses with Symbols so that the file is becoming more readable and I am adding comments and figuring out what each code block does. I am using the logic analyser to capture specific address ranges so that I can kind of debug the code (observing only of course).
This is not as easy as I had hoped because it looks like it was either complied with a compiler that optimised the code for size or the writer did this manually. It has resulted in a lot of short jumps to avoid far jumps and jumps to 'local' interrupt returns, presumable to avoid multiple pushes and pops where possible as these generate a lot of code.
I am still trying to fully understand the 6821 PIA's as the code performs read, modify, writes and they seem to be able to read a port that is set as and output. I think this must be some kind of internal buffer arrangement in the 6821, I will need to read the spec sheet more carefully.
From the above I have now got as far as realising that the code seems to be arranged as some sort of state machine where a single event is handled in multiple passes so this adds another level of complexity and creates a lot of RAM reads and writes.
I have at least found the part of code where the two modes I mentioned in my last post part ways so I can now follow each to see why one works and the other does not.
I assume by the analyser output that the 6800 loads the next instruction before it is executed as the next instruction in memory is always hit even if a branch occurs.
Does anyone have any idea why there would be code that has two jumps one after the other to different addresses . The second can never be hit.
Also code such as
This seems to load an address into the accumulator and then have the option to branch there. It does seem to be targeting the start of ROM's that are not fitted so could this be the code checking if option ROM's are present?
The value returned when a ROM is not there is 0xFF so the bmi instruction always branches.
I am hopefully getting to grips with this unit but it is slow because of how it is constructed and the system design.
I am however starting to identify specific subroutines and functions such as the following.
; PRINT HEAD MOVE SUBROUTINE
; RAM 0036 = Head move stepper step count
; RAM 0037 = Head column position
; RAM 0032 = Stepper bit drive value
39E6 L39E6: ;Not called when paper drives only head move
39E6 : D6 36 " 6" ldab X0036 ;Load Head Move stepper step count
39E8 : 96 37 " 7" ldaa X0037 ;Load Column position value
39EA : 81 50 " P" cmpa #$50 ;Are we at column 80?
39EC : 27 2D "'-" beq L3A1B ;Yes so do not drive any further
39EE : 5C "" incb ;Next Stepper step count value
39EF : D7 36 " 6" stab X0036 ;Save Stepper step count value to RAM 0036
39F1 : C5 02 " " bitb #$02
39F3 : 27 02 "' " beq L39F7
39F5 : C8 01 " " eorb #$01
39F7 : 96 32 " 2" ldaa X0032 ;Load current Stepper bit drive value
39F9 : 84 FC " " anda #$FC ;Next stepper phase value
39FB : C4 03 " " andb #$03 ;Strip off unwanted bits
39FD : 1B " " aba
39FE : 97 32 " 2" staa X0032 ;Save current stepper bit drive value
3A00 : B7 80 0A " " staa PRINTER_STEPPER_PORT ;Update Motor drive
3A03 : 96 36 " 6" ldaa X0036 ;Load Step count
3A05 : 85 10 " " bita #$10 ;Is bit 4 set (at next column)?
3A07 : 27 12 "' " beq L3A1B ;Exit if not, next pass will step again
3A09 : 84 0F " " anda #$0F ;Yes so update column value and stop driving
3A0B : 27 09 "' " beq L3A16 ;Check that the lower 4 bits are 0
3A0D : D6 37 " 7" ldab X0037 ;Not 0 so Load column value
3A0F : 27 01 "' " beq L3A12 ;Not complete yet so set column back
3A11 : 5A "Z" decb
3A12 : D7 37 " 7" stab X0037
3A14 : 20 03 " " bra L3A19
3A16 : 7C 00 37 "| 7" inc X0037 ;Increment Column Position
3A19 : 97 36 " 6" staa X0036 ;Store current step count
3A1B : 39 "9" rts ;RETURN FROM HEAD MOVE SUBROUTINE
Adding function headers and comments as above and Replacing addresses and naming RAM locations with labels also helps.
Once I have the bulk of the code traced I will hopefully be able to start looking for the fault.
The entire system runs as multiple state machines so untangling it will be fun.
I also had a closer look at the 6801 PIA datasheet and can now see how a port can be an output and an input at the same time (Port A)
That had me puzzled for a long time as I was looking for code that switched the port from input to output but it did not seem to exists and is because It is always an output that can be simultaneously used as an input.
Well, Progress of sorts.
I have completed approx. 30% of reverse engineering the code but unfortunately I do not know the history of this machine.
There are positions on the ROM board for up to 8 ROM's with another on the Keyboard pcb although that one is not mapped into the CPU memory map.
Having found the PIA port that drives and reads data from the reader and the code that sets the PIA configurations I have been able to determine that on a particular port an INT is generated in response to the reader sprocket sensor input and the CPU Interrupt handler checks the two interrupt flags in the PIA that are associated with the reader.
With both flags set it is a single read and with only one flag set it is trying to read the tape continuously.
After locating the point in code where the flags are checked it appears that the code is looking for a ROM that is not fitted.
The code tries to read the first byte of the ROM and if it is not there the value read is FF. The CPU then jumps one way if the number is positive or another way of the number is negative (bit 7 set).
The first byte in each ROM is a NOP (01) so this test seems to be checking if a ROM is fitted and if not it just ignores the task.
The ROM it is looking for should be at address 3400 and this ROM is designated as slot 6 on the board but that ROM is not installed and it does not appear as if it ever was. There is no socket and no sign that a chip has been removed so I am wondering if that option was never fitted even though the reader is in place. Maybe they built the unit with all hardware in place and only fitted the ROM if the customer paid for it.
I will keep investigating but it certainly looks as if that is the case.
Not a good result but at least it gives me some idea of what is going on.
So my options are (assuming I cannot find any other means by which the reader is supposed to work)
1) Find someone with a ZIP30 that can send me the ROM image or lend me the ROM so I can read it (probably labelled '305') [anyone ??]2) keep reverse engineering the code until I know enough about it to write a new handler for the reader and put it into a new ROM.
NOTE If anyone does have a ZIP30 and wants to try to help please do NOT try to read the ROM in a cheap ROM programmer as they are 2708 and are triple supply ROM's. They are nothing like 2732 devices which can be easily read and programmed.
I have a special (and expensive) adaptor for these devices so I can safely read and program them if anyone is willing to lend me a ROM.
Further to my last post
If anyone has a ZIP30 with a working tape reader but does not want to let me borrow the ROM or cannot read it then just confirming what ROMs are fitted would still be very helpful.
looks like you are progressing nicely.
Does anyone have any idea why there would be code that has two jumps one after the other to different addresses . The second can never be hit.
The only reason I can imagine you would see this would be if its a jump table accessed using indirect addressing so the disassembler didn't assign a label. I can't imagine why you could do this for only two values though as it would make more sense just to branch to the second one.
I can imagine you might end up with something like this if you were coding the equivalent of
;we enter here with value in A
TAB ;copy A to B
ABA ;A = A + B
ABA ;A = A + B
STA A modify+1 ;self modifying code to update the JMP below
Yes I think that is what is happening. I spent some time looking at this section of code. As I said I am not familiar with the 6800 (although much more so now). There are a lot of branches to jumps and I assume that this is because the branch address is encoded into the instruction so can only branch over a limited range whereas the JMP can jump to anywhere so branches to 'local' JMP instructions seem to have been used. The instances where one jmp follows another appear to be in blocks of code where a value is being used as part of branch using the nn,X instruction where nn may be one of a fixed number of values. Possibly the people writing the code did not know where the jump was going to go (there seems to be option ROM's missing) and so wanted to test the code for a single ROM. I cannot think of any other reason as the nn,X can of course branch to any address.
Or possibly the lookup tables for the ASCII character set is over 400 bytes so possibly they are using the nn byte value as an index and the X pointer as a selector. I will check the destination addresses at some point to see if this makes any sense.
I would add that this code is very complex and is a series of nested state machines which complete different tasks at the same time in multiple passes.
It incorporates tricks such as using the X register and stack pointer as source destination pointers to move data around in an efficient way.
It is certainly an interesting project but it is starting to look like there is no actual fault and that the ROM to drive the reader was never fitted as the code seems to be looking for a ROM that was never installed when the reader is asked to 'run' rather than read a single character. When it reads a single character it acts as if a keyboard key was pressed or a serial byte was received and this is unrelated to a constant read.
Back then hardware was cheaper than IC's so possibly they always fitted all hardware and only fitted option ROM's when purchased.
Unfortunately this means that my only option may be to try writing code for the missing ROM.
Interesting read, well done.
If it helps, I am in the process of creating an emulator for a car engine ECU based on the 6803. You can load code, disassemble it, write new code and save it etc. Plus, single step and run at varying speeds, set breakpoints, watch specific locations etc. Feel free to use it if its any help.
There's plenty of documentation on there, but its a work in progress.
That's brilliant, thanks
I have made a lot of progress in reverse engineering the code. Once I got the ROM code into a single file and identified how it controlled parts of the hardware I was able to start replacing Address and data values with labels to make the code readable such as the following block that I have fully reversed.
; READER STEPPER DRIVE ****************************************************************************************
;This function drives the Reader stepper motor
;It uses 16 passes to cycle the stepper through a full cycle 10, 00, 01, 11
;Bits 0, 1 of the Reader PORT A (READER CONTROL) are used to drive the stepper
;0X0055 is used to store READER_STEP_PHASE It is incremented every 4 cycles
325D : 0E " " cli ;Clear int flag to enable interrupts
325E : 96 55 " U" ldaa READER_STEP_PHASE ;Load value at RAM address 0055 into Acc A
3260 : 4C "L" inca ;Increment Acc A
3261 : 81 14 " " cmpa #$14 ;Too many steps without an INT?
3263 : 2B 13 "+ " bmi L3278 ;Yes so branch to L3278
3265 : 4F "O" clra
3266 : 97 4F " O" staa SEARCHING_FOR_SPROCKET_FLAG ;
3268 : 97 05 " " staa X0005
326A : B6 80 22 " "" ldaa READER_DATA_PORT ;Clear int flag
326D : B6 80 20 " " ldaa READER_CONTROL_PORT
3270 : 8A 0C " " oraa #$0C
3272 : 84 F7 " " anda #$F7
3274 : B7 80 20 " " staa READER_CONTROL_PORT ;Step the reader motor
3277 : 4F "O" clra
3278 : 97 55 " U" staa READER_STEP_PHASE ;Store Acc A in RAM at 0055
327A : 96 56 " V" ldaa READER_LAST_PORT_VALUE ;Load the value in RAM at 0056
327C : 84 03 " " anda #$03
327E : 85 02 " " bita #$02
3280 : 27 02 "' " beq L3284
3282 : 88 01 " " eora #$01
3284 : F6 80 20 " " ldab READER_CONTROL_PORT
3287 : C4 FC " " andb #$FC ;Clear lower 2 bits of PIA-11 PA port (Reader Stepper bits)
3289 : 1B " " aba ;Add this value to Acc A
328A : B7 80 20 " " staa READER_CONTROL_PORT ;Write value back to PIA-11 PA port
328D : 7C 00 56 "| V" inc READER_LAST_PORT_VALUE ;Increment the last port value
It is formatted better in my editor but you can get the idea. The capital labels are names I have assigned to the identified RAM addresses.
I have also worked out what are data lookup tables and what is code.
However as in my previous post it seems that the ROM that drives the Reader was never fitted so the code just spins round the main loop when the reader is put into run mode without doing much.
It looks like I will need to write code for the missing ROM but I need to fully understand how the entire code works first.
I was actually thinking that an emulator would be good for doing this so I will certainly give it a go.
The main difficulty is that the code was compiled with some form of optimisation and space saving tricks so it is a real maze to unravel.
An emulator would allow me to try to unravel blocks of code. I have been using a logic analyser to capture blocks of code under different trigger settings but this is slow.
I do have a Fluke 9010a with a pod for the 6800 but the main difficulty with using it on an old piece of equipment like this is that you have to be really careful what values are written to the various ports because it would be very easy to do a lot of damage if the wrong values are combined.
If I can work out what all the RAM locations are used to control the state machines then an emulator would be useful to write a block of code to drive the reader without having to mess with ROMS until I was close to a solution.
What further adds to the complexity and what had me confused for a long time is that it uses the 6821 PIA's in very odd ways. For example it uses ports set as outputs to also read as inputs without changing the configuration registers to redefine the pins as inputs. It took a while studying the 6821 datasheet for me to figure that one out.
I will post updates as I make progress. I am really hoping that someone with a ZIP30 reads this and confirms what ROM's they have fitted and even possibly send me images of them. It is even possible that the manufacturer never wrote the code to control the reader but that seems unlikely so just confirmation that the reader ever worked on these machines would be useful.