Global/Allied Signal GNS-X CDU display
This page documents my efforts to reverse-engineer a Global/Allied Signal GNS-X CDU display and make it do something useful…
Disassembly
Remove front panel
Loosen four Phillips screws (two on the left front, two on the right front) - these are captive
Remove keyboard and display filter assembly: this is attached by a cable.
Unplug cable
PCBs
The PCBs install left to right, in order:
PCBs pull out via cable tie, which is screwed onto the PCB and formed into a loop.
Outer chassis
CRT high voltage section
Caution - there may be a large blob of silicone dielectric grease on the back of the module. This is extremely sticky and gets everywhere…
Left side: two black screws in a line along the left side
Bottom: two black screws in a line in the centre
Push the block out of the back
Reassembly is the reverse of disassembly. Remember to get the boards in the right place!
Firmware
I've dumped the firmware ROM (CPU board U14): cpucard-rom-mbm27c64--816288-02_u-14_c038_9-19-91.bin
I'm currently using Ghidra to reverse-engineer it. The ROM loads at address 0x0000
, for 8 kilobytes.
The code makes extensive use of the HL
and DE
registers for pointers, and inlined text strings after calls. Ghidra scripts are required to add references and fix up code (set fallthrough addresses and define text strings) to make the disassembly more readable. I've written a Ghidra script called the Inline String Processor which fixes up these function calls.
(TBD.)
Hardware overview
Memory map
Start addr | End addr | Device |
0000 | 1FFF | CPU board - Program EPROM |
2000 | 3FFF | - Possibly space for larger program EPROM. |
4000 | 5FFF | CPU board - 8K RAM |
6000 | 67FF | |
6800 | Unknown hardware device. Possibly keyboard.
Addresses 6800 and 6808 read-only.
Address 6810 and 6818 write-only. |
6900 | Unknown hardware device.
Address 6900 write-only. |
6A00 | 6A01 | CRT board - HD6845 CRTC.
Address 6A00 is CRTC register address.
Address 6A01 is CRTC data. |
6B00 | Unknown hardware device. Possibly CRTC related (attributes?)
Address 6B00 write-only. Rest of space unused. |
6C00 | Unknown. Not accessed. |
6D00 | 6D07 | Unknown hardware device. Linked to the /RSTA interrupt. |
6E00 | 6E01 | U13 ACIA (HD68A50). Initialised but not used. |
6F00 | 6F01 | U16 ACIA (HD68A50). |
7000 | 97FF | |
9800 | 9FFF | Filled with FF on startup. |
A000 | B7FF | |
B800 | BBFF | Possibly display memory |
BC00 | BFFF | Possibly display memory |
C000 | DFFF | |
E000 | FFFF | Expansion ROM . Checked for a jump instruction on boot. Ignored if this is not present. |
Interrupt map
Interrupt | Source device | Notes |
/RSTA | Periodic? | Inverse of U6 (CD4520) Q3b (B counter, value 4)
→ From U4/HC04 pin 4 (inverter B out)
→ U4 pin 3 (inverter B in) → U6/CD4520 pin13 (Q3b) |
/RSTB | U16 UART | |
/RSTC | U13 UART | Interrupt returns immediately. |
Connector
Pin | Function | Wire colour |
A | n.c. | none |
B | n.c. | none |
C | 28V ground | Black |
D | | Purple |
E | | Brown |
F | n.c. | none |
G | | White, blue stripe |
H | n.c. | none |
J | | White, yellow stripe |
K | 5V - Lighting - Backlight (A) | Red (thick) to front panel connector |
L | n.c. | none |
M | | Yellow (thick) to front panel connector |
N | Earth | White, clear. Connects to back panel. |
P | 5V - Lighting - Backlight (B) | Green (thick), to front panel power connector |
R | | White, orange stripe |
S | U16 UART transmit “Y” | White |
T | U16 UART receive “+” | Purple |
U | U16 UART receive “-” | White |
V | | White, red stripe |
W | +28V DC | White (thick). Diode anode (cathode to e ). |
X | U16 UART transmit “Z” | Green |
Y | U13 UART transmit “Y” | White |
Z | U13 UART transmit “Z” | Blue |
a | | Orange |
b | | Black |
c | n.c. | none |
d | | Diode cathode |
e | | Blue, plus loop to f |
f | | Blue, plus loop to e |
g | U13 UART receive + | White |
h | U13 UART receive - | Yellow |
j | n.c. | none |
n.c. indicates no connection inside the CDU (unused pins). A blank column indicates that the function of the pin is unknown.
Connector: MIL-DTL-26482 series I style connector with MIL-STD-1560 "22-32" contact arrangement.
The connector on the CDU is the panel-mounting receptacle version, with male pins, ITT part number KPT02E22-32P.
The mating cable-mount connector should be the “plug” version, with socket contacts. A suitable plug would be e.g. the Amphenol PT06A-22-32S.
CPU board interconnects
U13 UART
Rx Data (2) → U17 SNK75182 “2OUT” (8)
Tx Data (6) → U18 SNJ75183 “1D” (4)
1A,1B,1C tied high
1Y (5) → Back panel Y
1Z (6) → Back panel Z
IRQ (7) → NSC800 “RSTC” (24)
U16 UART
Rx Data (2) → U17 SNK75182 “1OUT” (6)
Tx Data (6) → U18 SNJ75183 “2A” (10)
2B,2C,2D tied high
2Y (9) → Back panel S
2Z (8) → Back panel X
IRQ (7) → NSC800 “RSTB” (23)
References
Appendix: parts list by board
Deflection board
-
-
-
-
Harris HI3-201-5 analog switch
-
Five adjustment pots: top to bottom, R1, R2, R56, R3, R4.
CRT controller board
The CRTC is similar to that on the
Ferguson Big Board II, except the Big Board has two 2K-byte SRAMs while the CDU has a single 8K-byte SRAM.
CRTC section
PLL section
31.5kHz TTL oscillator (clocks CD4060 pin 11, CD4520 pin 1)
Exar XR210CN PLL
CD4060 binary counter with built-in oscillator
CD4520 dual binary up-counter
Misc
Keyboard interface board
-
54LS377 x2
54HC541
DAC0808
54HC139
MC14584B
MC14027B
MC14538B
CD4011B
LM140LAH-12 LDO regulator, mil-
spec
CPU board
-
36-pin (2×18) turned-pin connector
-
-
-
-
-
4MHz TTL oscillator (likely CPU clock)
2.88MHz TTL oscillator (likely baud rate clock)
MC54HC74
MC54HC32
MC54HC04
MC54HC08
CD4520B
MC54HC4040
MC54HC138 (x2)
54HC373
54HC245
54HC541
8464 RAM
Appendix: Ghidra scripts
These can be added to Ghidra with the Script Manager: in the Code Browser, Window → Script Manager
.
Inline String Processor
This searches for inline strings placed after write-to-screen calls, and converts them to offset-string-terminator blocks. Fallthroughs are added to the CALL
instruction to tell the Ghidra decompiler to ignore the string data when looking for the next instruction.
Click to view code
- GNSX_InlineStringProcessor.py
#Global / AlliedSignal GNS-X CDU - Inline string processor
#@author Phil Pemberton
#@category 00-Projects.GNS-X
#@keybinding
#@menupath
#@toolbar
from ghidra.program.model.listing import CodeUnit
from ghidra.program.model.symbol import FlowType
###############
# helper function to get a Ghidra Address type
def getAddress(offset):
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset)
###############
# setup
fm = currentProgram.getFunctionManager()
mem = currentProgram.getMemory()
# Find methods which call inline print functions
xrefs = []
funcs = fm.getFunctions(True)
for func in funcs:
if func.getName().startswith('DisplayScreenAfterCall'):
print("Found inline print function {} @ 0x{}".format(func.getName(), func.getEntryPoint()))
entry_point = func.getEntryPoint()
references = getReferencesTo(entry_point)
for xref in references:
xrefs.append(xref)
# Work on each xref...
fixlist = []
for xref in xrefs:
if xref.getReferenceType() != FlowType.UNCONDITIONAL_CALL:
print("*** Warning, requires manual fixup: " + str(xref))
createBookmark(xref.getFromAddress(), "InlineStringError", "Manual fixup required, xref source is not a CALL")
continue
# Start from the xref address. Seek past the CALL instruction.
# Convert byte to data. (charactor position byte)
# Scan bytes until 0xFF (terminator)
# Convert text to string
# Get the xref's from address (the call)
addr = xref.getFromAddress()
# Get an Instruction to the call instruction
callIns = getInstructionAt(addr) # the call address
# DEBUG
print("xref - {} => {}".format(str(xref), callIns.getMnemonicString()))
# DEBUG
# First byte after the call is the Offset Address. Mark it as data.
offsetByteAddr = addr.add(callIns.getLength())
clearListing(offsetByteAddr)
offsetByteData = createByte(offsetByteAddr)
offsetByteData.setComment(CodeUnit.EOL_COMMENT, "Byte offset from start of VRAM")
# Advance past the offset byte
stringStartAddr = offsetByteAddr.next()
# Scan the input string
stringEndAddr = stringStartAddr
sbyte = getByte(stringEndAddr) & 0xFF # AND 0xFF is to force the byte to be unsigned
text = ""
while sbyte != 0xFF:
text += chr(sbyte)
stringEndAddr = stringEndAddr.next()
sbyte = getByte(stringEndAddr) & 0xFF # AND 0xFF is to force the byte to be unsigned
# calculate string length
stringLen = stringEndAddr.getOffset() - stringStartAddr.getOffset()
print(" String starts at: {}, ends at {}, text '{}' ({} characters)".format(stringStartAddr, stringEndAddr, text, stringLen))
# String start and end address got - end address points at the terminator.
# Wipe out anything already there and replace it with a string
clearListing(stringStartAddr, stringEndAddr)
createAsciiString(stringStartAddr, stringLen)
# Turn the terminator into a byte
strTermData = createByte(stringEndAddr)
strTermData.setComment(CodeUnit.EOL_COMMENT, "Terminator")
# Set a fallthrough on the call instruction
callIns.setFallThrough(stringEndAddr.next())
# Set a bookmark on the end of the string (in case manual cleanup is needed)
createBookmark(stringEndAddr.next(), "InlineString", "End of inline string, disassembly cleanup may be required")
# Start disassembly at end of string
disassemble(stringEndAddr.next())