6 days ago •
In order to bring this project to the 21st century and make it as easy as possible to interface to modern data standards, we need to design a new data card.
When i made the schematic for the original data card, i yeeted the audio circuitry because honestly I did not care as I didn't need as sophisticated of a circuit for the audio stages.
At this point in time, the audio and warning circuits weren't even in my mind. The most important thing is how do I interface with the bus, and how/what do I use to get data into the system?
The venerable Raspberry Pi has entered the chat...
Yeah, that's right. yet another raspberry pi project. BUT... it is the most convenient tool to interface with the data card, and act as a host. downloading the system binaries, graphics assets, and data on the fly in one swoop during startup. It makes the most sense to me logically.
So with our control host chosen, we need to design the data card around this concept.
First thing I need to do is pick the design. Physically, it will be just like the original one as far as shape/size. But the electronics for interfacing to the bus are going to be interesting.
However, i figured why not use the original stuff they already had? Thing is... unlike the I/O card dumpster fire, the data card must have been designed by a different engineer. it actually uses gated logic to interface to the 68K bus and its cycles, reading and writing out of latches and a FIFO. so CPU intervention isn't necessary.
So, to start off the new design, lets just "copy and paste" the original bus logic.
I kept the bus logic so original, that i even kept the original PALs! So you can just move the PALs over from the old data card onto the new prototype card.
As you can see, I simply kept the same logic design as they had, I even pay this as a tribute to the original data card kind of as a nice send-off.
We do need to make some modifications. First, I replaced the old FIFO with a much bigger one. I can load up to 4K chunks and then have the 68K read out that entire FIFO. This allows me to send data across the bus faster when i break large binary files up into 4K chunks versus 512byte chunks. alot less bus activity and back/forth negotiation.
Also, the key here was to figure out how to interface this logic to the Pi. My thought process here was to eliminate the old 8044 MCS51 CPU and replace it with the Pi instead.
However, anyone that has worked with a Pi, knows the GPIO is 3.3V. this is a 5V card so we need to design in some logic shifting circuitry:
Which we have done here. Then, we just wire up the Pi's GPIO header into the logic level shifters which then wires up to the same spots the old 8051 used to be.
Presto! we have now successfully wired up a raspberry pi GPIO into the old bus interface logic borrowed from the original card. In theory, this should work...
This offers many benefits as in I dont have to design new logic circuitry from the ground up, but also it can allow a pi in direct control with the main CPU card via interrupts. This logic cannot access the bus directly, it can only load up the registers or FIFO and then issue a manually vectored interrupt back to the main 68K CPU. so the 68K itself has to handle whats going on with the data card, and its logic.
So even with this, we still need some minimal bootstrap code on the 68K ROM itself to handle whats going on with the data card.
With the data/control side out of the way, that leaves just the audio part. For the prototype data card, I did not design any of the audio circuitry on the card since I did not quite know yet exactly how to approach it as I wanted to simplify the original audio circuit into a single audio path plus warning alert path. All the extras the system did originally, did not matter here.
I did wire up the original latch logic they had, but I left them unconnected here.
So for now, I will forgo the audio circuitry for now.
Still, the original logic had one design flaw. There is no return FIFO. so the 68K CPU from the main CPU card can only read block transfers from the data card, and not the other way around. the CPU can only write one byte at a time to a register on the data card, which raises an interrupt flip-flop back to the 8051 which is now the Raspberry Pi. Trouble is if the Pi doesn't catch the byte and read it quickly enough, the 68K CPU can overwrite the byte it just sent with a new one, Say during block transfers.
This makes debugging the system difficult because I cannot send stack dumps from crashes back to me for debugging purposes, so I literally have to fly blind. I will have to use the UART on the I/O card as best as I can which comes with its own problems.
Anyways... As a troubleshooting aid knowing the above being a problem, I wired up an LED bargraph to one of the two audio latches and used that as an 8-bit debug port. I can set an LED on or off during the execution of subroutines on the 68K to know if something is good, or bad. Literally the only feedback out of the system is turning an LED on or off.
So, with the preliminary design solidified, it is time to start laying out and designing the card!
Going swimmingly, but routine can be a nightmare. I have EAGLE set to the smallest built-in via size and trace widths on some of this. Im sure there is a way to set it smaller but I dont feel like making the board house angery.
Once I had the layout done, I wanted to print it to scale and to a test fit:
Remember when I decided to forgo the audio circuitry? Well I didn't want to entirely shoot myself in the foot, so i decided to apply a prototyping area to the board. Which turned out to be beneficial when I added the LEDs to one of the audio latches for debugging purposes.
There... that should help with throwing something together for testing once i move past the hurdle of actually making the machine run code again.
With that, we have our final prototype design:
And, a PCBWay order later, we have our final product:
the logic circuit area is identical in design of the original card with the exception of the 8051 and the change on the FIFO size.
Time to build up the card, and then I added the LED readout for debugging purposes. I could have gone 7-segment but I used what I had which was an old radioshack bargraph display. Doing this will prove crucial later.
And there we have it! a method of interfacing a 68K machine to the modern world.
6 days ago •
That leaves the last piece to this puzzle. Now that we have the I/O card, CPU card, and graphics card figured out, that leaves behind the data card.
The data card serves a few purposes in the machine, it also contains the audio circuitry. it contains the FM demods, the audio switching network, output amplifier and the warning tone generator.
previously in the project we had already netlisted this board and created a schematic.
This board is originally designed to demodulate the subcarriers from the satellite transponder.
Once again, I decide to take a look at the ROM to figure out potentially what is going on with this card, and there is a patent available for the STAR 4000 which explains the data packet format, what all the different types are, and etc. the ROM pretty much follows the patent for the most part.
Other interesting thing is, there are some stuff in the bottom of the ROM which im not sure what does, but its interesting:
Some text values there used for something. the Data card has no serial in/out so it must be something that gets sent back to the 68K at some point over the FIFO.
The architecture of the data card's design, basically has a single byte latch register for the 68K to be able to read, as well as one to be able to write. there is also a FIFO that is read-only by the 68K.
so therefore, the 68K has two data registers it can read from the data card, and one to write back. There is also two audio registers the 68K can also write to, which can control the audio matrix.
The other problem is, some of the audio stuff is also controlled by the data CPU. so the two have to work in tandem to control the audio stage. This is painful. THIS... is also why when a 4000 would go into alert mode, the warning tone appeared to have some "breakup" in it. It is due to the fact the data card's CPU has to go off and do some other stuff handling incoming data which would cause the audio latch to de-assert in short bursts. Even though the 68K is keeping the alert tone enable line asserted during the entire duration.
The data format at which it uses, they used the old legacy STAR 3000/Jr for text-based products at which the scripts would use and format to display on screen in a graphical manner, you can see this here in ROM with the data parser I had commented:
So this machine did, in-fact, use the old 37-byte Teletext frame format for controlling its products and parsing the OMCW to determine what is being displayed.
This coincides with the release of the 4000 being all text based, mimicking the 3000's operation. It was easy for them to do.
Anyways, a lot of effort would need to be put into conforming to, and formatting the data in a particular way if i were to use this data card.
Also, we need to build a satellite subcarrier encoder, and an SDLC framer. None of which I felt like doing.
The satellite no longer exists anymore either.
I could do what I did with the I/O card, and replace the ROM with my own since it too is also MCS51 based. Trouble is, I would still be stuck with SDLC framing with NRZI-FSK encoding.
At the end of the day, we really needed to bring the data delivery system into the 21st century to work with our modern standards if we really wanted to use a 1980s machine with 2019 data standards.
So? I voted for the nuclear option. forgo the entire data card and engineer a new one!
06/24/2022 at 01:26 •
Since we had a good success with writing a program and running it on the I/O card, we need to start writing the initialization routines for the various hardware bits on that card, as well as writing the protocol for communicating back and forth between it, and the driver on the 68K side.
The UART took a little bit of trickery. If you look back at the schematic, the baud clock for the UART IC is driven from one of the PIO chip's internal timer.
We decided to setup the UART to work with 1200 baud. Slow? yes... but it provides a method for getting characters in and out of the 68K system so we will stick with this.
Initializing the UART:
And, setting up the initial PIO states and setup to produce the proper baudclock:
Presto! Now, we need to write some protocol code so I can send commands across the bus back and forth to the UART, an ex:
Seems pretty simple in concept, doesn't it? It does, however it took a lot of time and iterations/modifications and rewriting the ROM over and over to get this to work correctly. And when I say a lot.. I mean A LOT of trial and error.
Anyways... that's basically it in a nutshell.
The mainloop handles all of the commands coming across the bus, it also has a Watchdog reset you must keep happy or the I/O card will keep resetting on you.
So with that mostly complete, we need to test each part of the firmware as we go along so we dont get caught up in bug hell...
Testing the UART <-> Bus communication:
Success!!! We can communicate across the bus from the arduino, to my protocol, and out of the UART.
So we are well on our way to making functional I/O card firmware
Next part is to make sure the protocol is working with the 68K's System bus by testing it in an actual system.
The trouble is, we have not written any 68K code at all yet. So quickly, I grabbed a copy of VASM and made a simple little ROM that keeps the watchdog happy on the CPU card, and then i sent a random LED set byte across the bus over to the I/O card and then loop forever. a very simple "hello world" program on the 68K side:
perfect! We knocked two birds out with one stone here. one, we confirmed the 68K can communicate with my firmware on the I/O card, and then the 68K CPU itself can run a "Hello World" ASM program assembled with VASM and send a byte out to the I/O card.
I know, I jumped way ahead here but i needed to demonstrate the functionality of the IO card.
Next up... the data card.
06/24/2022 at 00:50 •
Given the original ROM is a dumpster fire to me, trying to write a driver to run that from the 68K side is going to be a nightmare since we do not have the original codebase that used this card, that was downloaded from the satellite and embedded into the binary.
So to save what little sanity I had left, I need to look at custom ROM development for the 8051 with this particular card.
Prior to this point, I have never written MCS51 assembly before! We are going to need to find a toolchain, and learn the assembly language so we can do something with it. Luckily the assembly language is fairly similar to any other 8 bit microcontroller of the era, so it wasnt hard to pick up.
But.... before we can do anything, we need to prep the hardware so we can shove some bytes back and forth into the card to test operations.
I did try the brute-force method with the original ROMs with poor results.
Yep. This one was just as crazy as the graphics card.
I am going things this way to eliminate every potential variable I can, up to this point I have not touched the 68K architecture at all, nor written a hello world program to download and run on the 68K system itself.
Using an arduino as the "CPU" in place of the 68K makes this task much easier when I need to hack on and work with a specific piece of hardware, as I did with the graphics card.
The next piece of the puzzle, I need a toolchain for the MCS51. I did some searching around and the ONLY thing I could find was Keil. Keil is a paid product sadly so I had to deal with this situation. I dont know of any open-source assemblers for the MCS51 either, So I stuck with Keil.
We need to start the skeleton of the ROM code, such as defining port pins and where they are going to go:
Since the INT0 and INT1 routines from the original ROM are fairly simple and timing critical, we shall borrow those for the new ROM project as I think that would be the easiest:
Now, when writing a new firmware for a completely foreign card that you wish to do your own thing with, you have to start thinking about the protocol you are going to use in which your driver will be written to communicate with the card.
With that in mind, you can see my commented code/lines in the original INT handlers for bus communications where I tried to do some special things my own way for my own protocol adaptation.
Unfortunately, no matter how hard I tried, anything i added/took away here would break the bus timing and prevent the 68K from reading/writing legible bytes from the I/O card.
so this interrupt absolutely COULD NOT be messed with in any way, it was timing critical! So I left these routines bland for the most part, all they do is read/write into the external SRAM in each PIO chip. C0XXX writes the first chip, C4XXXX writes the 2nd chip. From this point, it is up to me to detect byte changes in that chip in the mainloop and handle whats going on, change those bytes, and raise an interrupt back to the 68K CPU to service what I just did.
But, before we get so carried away with making a protocol, writing the control logic for the RS232 UART, GPIO, LEDs, etc... how about a simple hello world ROM?
why, of course! the best way to test this card standalone imho to make sure the ROM I am writing is working properly is to attempt to initialize the PIO hardware, and do some blinkenLEDS:
Success!!!! We able to write code, and assemble it. Burn it onto a ROM, and run the board. :-)
At this point? we need to start looking at initializing all the peripherals on the card, and then writing a protocol to communicate with all of this from the 68K side.
Now, time to move onto developing the ROM for this card!
06/23/2022 at 23:50 •
After completing the schematic and architecture of the I/O card, It is time to take a look at the ROM.
With how unique the bus interface logic is back to the 68K, essentially using a PIO and a few other things to mesh with the 68K read/write cycles from the MCS51, I just had to take a look at the routines and document how they pulled this off.
A quick study of the bus interface logic, reveals a PAL which acts as an address decoder. This address decoder is tied to INT0 and INT1 of the CPU:
This helps aid in finding where those routines are in ROM. Digging through ROM, I was able to locate the routines:
The above subroutine is INT1, which is decoded at the address space I had commented in the above code. It is a fairly clever subroutine!
I commented all of the ASM code to better understand what is going on with the above subroutine. It may not be 100% accurate, but its as close as I can get it.
The INT0 routine is a bit longer, as its doing some other stuff.
As we can see, the C0XXXX code is much larger, and it has additional checks in it. I am going to assume at this point, this is where the commands to actually control the card are sent. whereas the INT1 handler might just be for data only. But your guess would be about as bad as mine.
At this point, we have somewhere to start at least. Since INT0/INT1 are the only way the 68K can speak to this card, we can assume the command processor has to be triggered from this as well. you can see where it reads 4 bytes into 0x14-0x17 in the CPU's RAM space. So we must find the routine that handles all of this outside of the interrupt in order to learn how to control this card in the method they proposed.
So to help try and find this, Lets take a look at the RESET vector:
As you can see with the above, we have basic initialization code for the registers and RAM, a call to a code block at 2047, and then a branch always loop at code_52. so it just sits there indefinitely.
That clues us in that this device is entirely interrupt driven. Now, bear in mind this device also has to run a modem, AT keyboard, and a host of other things.
But the problem begins with the code block at 2047. its literally a nightmare scenario, and its super hard to follow as it goes through.
If we take a look at the timer interrupt, we have this:
And that's it, all it does. Appears to be settings/clearing flags and changing some things in RAM, however I do not know precisely what its actually doing. It might be related to the modem or AT keyboard. since they are using the same UART on the CPU for both the modem, and the AT keyboard it could be related to both. Who knows...
However, with all of that said I fail to see how it catches and processes data from the bus. this code is super hard to follow. For example, that code block called from RESET just goes on and on and on forever:
So instead of spending days and days and days trying to chase this code since theres no examples on how to use the API of this ROM, I had to make a decision to abandon the ROM entirely and just write my own thing to save grace here, I could also avoid writing the routines for the modem, and AT keyboard since I did not need either of them. I only wanted the LEDs, the RS232 port, and general purpose I/O.
The main code block are literally ljmp ops all over the place, back and forth. they arnt using any rts or jsrs, its the epitome of spaghetti code...
Hence, trouble in paradise...
06/23/2022 at 01:36 •
Up to this point, we have only focused on the basic architecture of the system and went into detail on the graphics card, and CPU card, only briefly mentioning the data and I/O cards.
But before we can begin to get a hello world program on the system, we must take a closer look at the I/O card.
In a previous blog post, we mentioned the schematic of the I/O card, however to keep things simple, we will post the schematic again here:
With that, here is the system architecture of said card:
Peripheral - I/O Card 8051 Memory Map: $0000 - $01FF = RAM $0200 = UART Data $0201 = UART Command $8000 = PIO1 Command/Status Register $8001 = PIO1 PORTA (VME Address/Data AD1-AD8) $8002 = PIO1 PORTB (Rear External Switches 1-8) $8003 = PIO1 PORTC (Various) $8004 = PIO1 TCNT0 Low $8005 = PIO1 TCNT0 High $8100 = PIO2 Command/Status Register $8101 = PIO2 PORTA (Indicator LEDs) $8102 = PIO2 PORTB (DTMF Dialer Codes, 1 Indicator LED) $8103 = PIO2 PORTC (Bus Clear, Charging Indicator) $8104 = PIO2 TCNT0 Low $8105 = PIO2 TCNT0 High I/O Card VME Addressing: $C00000 to $C001FF = 8051 INT0 $C04000 to $C041FF = 8051 INT1 PIO-1: PA0: VME Address/Data 0 PA1: VME Address/Data 1 PA2: VME Address/Data 2 PA3: VME Address/Data 3 PA4: VME Address/Data 4 PA5: VME Address/Data 5 PA6: VME Address/Data 6 PA7: VME Address/Data 7 PB0: Switch 1 PB1: Switch 2 PB2: Switch 3 PB3: Switch 4 PB4: Switch 5 PB5: Switch 6 PB6: Switch 7 PB7: Switch 8 PC0: (LED 7) Modem In Use LED PC1: Modem ALB PC2: Modem SQT PC3: Modem/AT Serial Select PC4: Cube Relay Control PC5: VME IRQ2 PIO-2: PA0: (LED 10) System Error LED PA1: (LED 8) Spare LED PA2: (LED 2) Sat Data LED PA3: (LED 3) Local Video LED PA4: (LED 6) On Air LED PA5: (LED 4) Spare LED PA6: (LED 1) Satellite Video LED PA7: (LED 5) Pre-Roll LED PB0: DTMF Keypad Column 1 PB1: DTMF Keypad Column 2 PB2: DTMF Keypad Column 3 PB3: DTMF Keypad Row 1 PB4: DTMF Keypad Row 2 PB5: DTMF Keypad Row 3 PB6: DTMF Keypad Row 4 PB7: (LED 9) Spare LED PC0: VME IACK2 PC1: Battery Charging Indicator PC2 - PC5 unused.
This is basically where everything is hooked up as far as the address map. Essentially, it is an 8051 based card, with two PIO/RAM ICs, and a UART IC.
The card handles the LEDs, a modem, AT keyboard, serial port, and some general purpose I/O.
The funny thing about this card however, is the 68K bus interface logic and timing. It is all software-based and its controlled via interrupt on the 8051, so the interrupt execution time has to be in-step with the 68K Bus Cycles. absolutely insane, and there is NO room for timing variances or error on the bus read/write interrupt handlers or errors will ensue. I tried it.
One thing i noticed right away on the design of this particular card, is how there is an inverter off of the Q output on the flipflop, instead of just using the /Q output. As seen here:
This part made no sense to me. You would save a gate if you just wired the /Q output instead of using a makeshift inverter. Maybe there is a timing reason? Honestly, I do not know.
In the next part, we need to disassemble the ROM and take a look at what is going on, since we have no driver or any details on how to drive this card.
06/23/2022 at 00:44 •
Now up to this point we have the graphics card pretty much figured out. We have the framebuffer control on its fundamental levels where we can set palettes and viewports and their resolutions.
The next part of this puzzle though is how do we achieve animation? the weather icons used on the WS4000 had an animation to them where you can see the rain falling, such as:
How do we achieve this effect on the WS4000, and how did they do this originally?
Well, they use palette swapping trickery. Thing is, you can upload a palette to the RAMDAC as we explained in an earlier post, and you can rotate colors around on the index to do this.
BUT. the WS4000 actually offers a method to do this on its own, which is identified by this function from ROM that I had documented here:
code:00000776 CHECK_FOR_ANIMATE: ; CODE XREF: code:0000111Cp code:00000776 ; code:0000113Fp ... code:00000776 mov A, RAM_39 code:00000778 jz code_782 code:0000077A mov DPTR, #0x601 code:0000077D clr A code:0000077E movx @DPTR, A code:0000077F lcall Animate_Palette code:00000782 code:00000782 code_782: ; CODE XREF: CHECK_FOR_ANIMATE+2j code:00000782 lcall code_734 code:00000785 ret code:00000785 ; End of function CHECK_FOR_ANIMATE code:00000785 code:00000786 code:00000786 ; =============== S U B R O U T I N E ======================================= code:00000786 code:00000786 code:00000786 Animate_Palette: ; CODE XREF: CHECK_FOR_ANIMATE+9p code:00000786 ; Animate_Palettej ... code:00000786 jb RAM_20.3, Animate_Palette ; Wait for previous operations to clear code:00000789 mov R7, RAM_39 ; How many color blocks to copy code:0000078B mov R0, #0x35 ; '5' ; Beginning location for RAMDAC Palette Addresses code:0000078D code:0000078D code_78D: ; CODE XREF: Animate_Palette+4Ej code:0000078D mov DPTR, #0x601 ; Location Pointer to Palette Table code:00000790 movx A, @DPTR code:00000791 mov R1, A ; Store Value at RAM 0x0601 into R1 code:00000792 mov A, @R0 code:00000793 mov P2, #0x80 ; 'Ç' ; Set Palette Write Address code:00000796 movx @R0, A ; Store Value in I-RAM 0x35 as RAMDAC Palette Address code:00000797 mov P2, #0x88 ; 'ê' ; Set RAMDAC to Palette Memory code:0000079A mov A, #7 code:0000079C clr C code:0000079D subb A, R1 ; Subtract 7 from the contents in 0x0601 code:0000079E mov R2, A ; Store Result into R2 code:0000079F mov A, @R0 ; Read Palette Address from I-RAM 0x35 code:000007A0 add A, R1 ; Add Current 0x0601 Value to RAMDAC Palette Address code:000007A1 mov B, #3 ; B Register code:000007A4 mul AB ; Multiply Added Palette Address with 3. A contains Low byte, B contains High Byte Result code:000007A5 mov DPL, A ; Data Pointer, Low Byte code:000007A7 mov A, B ; B Register code:000007A9 add A, #0 ; Store 16-bit Multiply Result into Data Pointer. code:000007AB mov DPH, A ; Data Pointer, High Byte code:000007AD code:000007AD code_7AD: ; CODE XREF: Animate_Palette+30j code:000007AD movx A, @DPTR ; Read Red Value from RAM @DPTR code:000007AE inc DPTR ; Advance to Next Value (Green) code:000007AF movx @R0, A ; Write Red Value to RAMDAC Palette code:000007B0 movx A, @DPTR ; Read Green Value from RAM @DPTR code:000007B1 inc DPTR ; Advance to Next Value (Blue) code:000007B2 movx @R0, A ; Write Green into RAMDAC Palette code:000007B3 movx A, @DPTR ; Read Blue Value from RAM @DPTR code:000007B4 inc DPTR ; Advance to Next Value (Red) code:000007B5 movx @R0, A ; Write Blue into RAMDAC Palette code:000007B6 djnz R2, code_7AD ; Decrement R2, Repeat Palette Write until 0 code:000007B8 mov A, R1 code:000007B9 jz code_7D3 code:000007BB mov A, @R0 code:000007BC mov B, #3 ; B Register code:000007BF mul AB code:000007C0 mov DPL, A ; Data Pointer, Low Byte code:000007C2 mov A, B ; B Register code:000007C4 add A, #0 code:000007C6 mov DPH, A ; Data Pointer, High Byte code:000007C8 code:000007C8 code_7C8: ; CODE XREF: Animate_Palette+4Bj code:000007C8 movx A, @DPTR code:000007C9 inc DPTR code:000007CA movx @R0, A code:000007CB movx A, @DPTR code:000007CC inc DPTR code:000007CD movx @R0, A code:000007CE movx A, @DPTR code:000007CF inc DPTR code:000007D0 movx @R0, A code:000007D1 djnz R1, code_7C8 code:000007D3 code:000007D3 code_7D3: ; CODE XREF: Animate_Palette+33j code:000007D3 inc R0 code:000007D4 djnz R7, code_78D code:000007D6 mov DPTR, #0x601 code:000007D9 movx A, @DPTR code:000007DA inc A code:000007DB cjne A, #7, code_7DF code:000007DE clr A code:000007DF code:000007DF code_7DF: ; CODE XREF: Animate_Palette+55j code:000007DF movx @DPTR, A code:000007E0 ret code:000007E0 ; End of function Animate_Palette
As we can see, this function is built into ROM. and within the CMD01, it looks for the number of blocks of 7 colors in the palette to rotate. So you would send the speed at which the palette swapping occurs, and then you send each index number which includes the 7 colors that will swap around.
//Set up Animating Palette. GPUFIFO = 3; //Number of Color Blocks to Animate GPUFIFO = 0xC0; //Target Slot on Palette (In groups of 7 Colors) GPUFIFO = 0xC7; //Target Slot on Palette (In groups of 7 Colors) GPUFIFO = 0xCE; //Target Slot on Palette (In groups of 7 Colors) GPUFIFO = 8; //Number of NTSC Frames per Step.
This breaks down in detail how this works with the WS4000. you send the number of color blocks to rotate, the index of the beginning of the first of 7 colors in the palette, for each block. And then the speed in NTSC fields.
So the color palette would look something like this:
This would be your rain colors! the pointer to this block would be on the left of the group of colors here, and it will rotate one at a time, that entire block of 7 colors, but not touch the 8th color on the right side.
this one was palette swapping too :-)
Now, we have animation down, we need to take a look at how to run the hardware overall as a whole. The graphics card has been entirely figured out for the critical stuff...
Time to look at the I/O card.
05/01/2021 at 00:59 •
As we discussed in the previous installment, we can switch resolutions and send color palettes via the CMD01 being sent to framebuffer control.
Now, as we discussed there must be a way to be able to change where the on-screen viewport points to in memory.
When discovering the command, I found this in ROM:
To most people, this doesnt mean much. But... This is called on startup very early in the process as that 8051 ROM boots. the location in RAM 0x710 is the same areas where CMD01 configures for its viewports.
With that known, the above basically gives us an initial state the framebuffer is during startup. So with my analysis of CMD01 in the previous installment, lets take a peek at ROM to figure out the code flow and see how the viewports are configured.
Looking at the startup code, its looped 3 times. So thats something to keep in mind for going forward.
One thing that clued me in however, was from a conversation we had with Michael Searce. He had mentioned the WS4000 had at least 3 framebuffers that can be shown on screen at any point in time. Aha. So.... lets follow the code-flow to confirm our suspicions.
If we thumb through the CMD 01 handler, we can see towards the bottom here our familiar memory location 0x710 that I have labeled. So, lets take a look at that subroutine.
So if we follow this code, First off it reads a byte from the FIFO and stores it into Register 2. Then it sets the DPTR to point to 0x710 in RAM. It then reads 4 bytes from the FIFO, then performs a calculation whether it sets the 5th byte 0x30 or 0x60, but it is not read from the FIFO so it does not matter here.
Then.... you encounter the djnz instruction. This Decrements R2, and if its not Zero, it will jump off back to $0x13A5 which reads another block of 4 bytes.
So given this, it certainly confirms the information Mike has supplied about having multiple framebuffers. Trouble is, there are no boundary checks so technically you could copy 4 byte blocks 255 times. That.... would be bad because you will corrupt the rest of SRAM, and then run out of it! So.... don't do that.
So.... if we reference what we see in the above code, versus the example byte values in the Startup code, we can make a reasonable assumption of what to send it.
We can now formulate an example command:
01 02 03 00 00 01 00 00 00 F0
So lets break this down a bit:
01 = Command 01
02 = Enable Graphics Output
03 = Resolution (768x480)
00 = Send Palette
00 = Send Animation
01 = Number of 4 byte viewport blocks
00 = Viewport Byte 1
00 = Viewport Byte 2
00 = Viewport Byte 3
F0 = Viewport Byte 4
So this is what we know so far. Not much at this point, I know. But at least we have something to start with. And matter of fact, This is the command I used initially to get graphics to show up on the output at all as this was critical.
So at this point its a process of elimination. Try changing byte values and see what the hell happens. But before we do that, lets take a look at the value F0 at the end
This was the value that was in in ROM to begin with as an initial setting, so there must be some significance to this. So lets take an educated guess on what that means. So the first thing we should to is convert the 0xF0 to Decimal, which is: 240. Huh interesting. My mind is trained to see patterns so knowing the framebuffer height is 480, its awful convenient that this value is 240.
So, I must assume 0xF0 is the Viewport height Divided by 2. so if we multiply the decimal equivalent of 0xF0, we get 480. Magic....
So that just leaves 3 values that are unknown at this point. Sending this command as it is will use the default SMPTE Palette that is built into ROM, and show a 768x480 image of whatever is sitting framebuffer.
So the next step in this process is lets get something drawn into the framebuffer. We start with something like this:
So what we did here was simply draw some text on the screen. This will give me a reference where I am at in the framebuffer before I get lost.
To understand this: Page 1 means the first 480 lines of framebuffer memory. Line 481 would be "Page 2". this will help me identify where I am at in RAM. and of course the Line numbers are self explanatory. Each line is 36 pixels tall so i can easily to the math.
So.... Lets change the first byte value and see what happens:
Woah! it appears modifying the byte jumps the position of the text over, and it kinda "Wraps around" Ok.... what does that mean? I gotta sit and think about it for a bit.
I don't have any footage of my experimentation to know what value jumps it by how much. What I have figured out, is the first TWO byte are little-endian formatted "offset" at which pixel will be the start pixel that the 1st line begins on, for the output display.
The other thing is per increment of 1 of the value, the screen shifts over by exactly 8 pixels. So this means that the 16-bit word for the offset pointer, is also X8.
now that clears up what the first 2 bytes do in that 4 byte command. we know what the 4th byte is.
That just leaves the 3rd byte. What i quickly learned is the first 4 bits (nibble) of that 3rd byte change how the framebuffer is being displayed as far as chroma key. Turning a bit on would enable the video passthrough. turning that bit off would disable the video passthrough on graphics. Basically, disables the chromakey/alpha. not particularly important, but good to know.
So we are left with the upper nibble. We have 4 bits on the upper nibble which gives us 16 potential states to chose from. However, what ive noticed is each bit acts independently of one another.
So therefore, 0, 4, 8, C are the only values that do anything. Any value in-between wont register. So its looking at the bits individually.
So lets pass something random in there and see what happens:
Throws me way down in memory! So i am not quite sure at this point what to make of it. we know the offset value can only be a maximum of 0xFFFF so this might be what bumps it up to the next block. However for now, lets not think about that for a second, and go back to the 2 first values that we do know. See how far we can push it.
So lets push those 2 bytes up to the offset for line 481 and see what we get: (We will get to the math later)
Well.... we have a problem here. We can see that we now start to show what is at line 481. and it continues. But then you see somewhere in the middle there it just completely ends. And when it does end, it seems to show whats back at the very beginning of the memory block its currently looking at. and its totally out of alignment.
Almost looks like an overflow problem, doesn't it? Well lets take a look:
This is a picture clipping of the upper left corner of the graphics card. To the left, you can see a set of 74LS193 ICs. these are 4 bit counters. There are 4 of them... Stands to reason we can make a pretty good assumption that these counters are responsible for what pixels are displayed on the screen, and where... given they are coupled to the PALs, like so:
Notice these chips can count up, down, can be loaded with a value, etc...
With this information, lets do some calculations. We know that each 1 tick of the count value will represent 8 pixels on screen. So. a count value of 65535 or the maximum value of a 16-bit word, represents 8191.875 blocks of 8 pixels. so if we multiply that to get per pixel, thats 524,280 total reachable pixels with that 16-bit value. There are 768 pixels per line.
So... That gives us a window of 682.65625 lines before the counter overflows! And on that screenshot above, That's about where we end up before it "wraps around".
Knowing this, That suddenly triggers the memory of that 3rd byte. That must be the "paging" register for each group of 682 lines.
However there is a caveat. Notice its 682.65625 lines. Well... that means at line 682, somewhere at .65XXX we lose pixel information.
Therefore line 682 is basically lost. And since we need to switch pages using the paging register to see line 683 and higher, We absolutelly cannot draw anything that crosses these "overflow boundaries". So effectively, line 682 is useless. you have to skip over from line 681 to 683 and use 2 viewports on screen at the same time with two different window heights as we discussed above.
This is a pain in the ass, and one of the limitations of the framebuffer. So personally, I skip over this. Anything past line 480 is basically wasted except radar. radar takes 120 full lines, so you can fit it within a 682 line "page"
So with the paging register, we have 4 682 line pages essentially. and you cant display between pages that cross the overflow boundary without two viewports.
With the above information, we can now clarify the 4 byte viewport values:
00 = Pixel Offset Low
00 = Pixel Offset High
00 = Page and Chroma key
F0 = Viewport Window Height / 2
Hey we are getting somewhere.... On to the next one.
04/30/2021 at 01:40 •
Now that we are able to draw images into the framebuffer, we basically have the graphics card under our belt now. Except for one thing....
Framebuffer control. We briefly touched on this in a previous blog entry, but now its time to go into some more detail.
Thing is, we can draw images into the framebuffer that is being displayed, but unfortunately drawing is fairly slow. so you would see the drawing process on screen.
So, to prevent this from happening and to provide a better experience to the viewer, we need to double buffer the drawing. The way to do this is simply drawing graphics/text into other areas of RAM that are not currently visible on the screen.
This is going to require more experimentation and studying of one of the most important commands. CMD01.
This command not only contains the Palette data thats sent, it also contains up to 3 framebuffer window settings. So, you can have up to 3 different set viewport partitions on screen at the same time. Plus there is CMD06 which is an overlay window that comes up from the bottom on a specified height.
from Studying the ROM, this was my initial analysis on the CMD01:
=============================================================================================================================================== Command $01: (Ex: 01 00 FF 00 00 01 00 00 00 00 ) Byte 1 gets copied into a couple other registers before manipulated and written into the FPGA. Byte 1.0 = Enable/Disable Local Video Byte 1.1 = Does a multiply by 2 on the counters. (Maybe switch active pages) Byte 1.2 = Disable/Enable Graphics Output. When disabled, Ends CMD01 processing entirely. (Set to Disable, MUST be last byte when Set!) Byte 1.3 = Does the same thing as Byte 1.1 (MAybe at different times though) -- Byte 2 (Switches Output Resolution) 0 = 384x120 1 = 384x240 2 = 768x240 3 = 768x480 Byte 2.0 = Sets/Clears $22.1 Byte 2.1 = Sets/Clears $20.6, and $28.3 ------------------------------------------------------------------------------------- Byte 3 Condition Flag whether to do a full Palette Copy and Counters signaling copy. If 0 We skip Palette load, and Read Byte 4. Byte 4 if Byte 3 = 0 Condition flag whether we copy animate data or not. If 0, we skip animation info and go into copy data into 0x710. Return from cMD01. ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- Byte 3 If 1 then we load the palette1 with data. A full 0x0300 byte load is EXPECTED/REQUIRED! Byte 4 if Byte 3 was 1. Still serves the same purpose, with an addition. IF 0, We skip the animation info and go into the copy data into 0x710. BUT, Now, after that, the recently loaded Palette gets copied into RAMDAC. End CMD08 ------------------------------------------------------------------------------------- If Byte 4 > 0 in either case, it loads the appropriate info for Palette animation. Byte 4 = Number of Blocks of color palette to animate. Each Block is 7 Colors. Each byte after this is each block location. Last byte is the speed at which the animation occurs. ================================================================================================================================================
Sorry if the above is confusing, but it was my quick analysis at the time. But it will get a bit more clear the further we go on.
Now, if we put 768x480 images in framebuffer, we only have enough RAM for maybe 4 of those images. We already know from watching in the past that the radar is at least 6 frames of animation. These frames are obviously not drawn in real-time because it would be too slow. So its definitely pre-rendered into RAM, and the pointers move around to point where each image has been drawn in VRAM.
So in order to do this, the machine has to switch resolutions to a lower resolution so we can "fit more" into memory.
Thus enters Byte 2. This byte seems to change the resolution of the output.
The way the lower resolutions work, is it takes the full 768 pixel line and splits it into two halves. so the second group of 384 pixels (on the right side) show up 1 line below the first set. So basically like Even/Odd lines.
As pictured here:
I sent the radar image and switched the Palette to the Radar color palette.
You quickly notice that the image appears to be split in two. This is the odd/even line that I was talking about. the 2nd line of the image is drawn to the immediate right of the 1st line. This essentially allows you to double your framebuffer space or even 4x your framebuffer space for lower resolutions.
This is how radar was accomplished.
So. Modifying byte 2 to set the framebuffer to 384x240, yields this:
Perfect. that image looks normal. And its occupying only 384x240 in framebuffer, but.... its technically 120 lines of 768 pixels as i explained earlier.
Confused yet? Good! :-)
In the next part, we will talk about the framebuffer pointers and how we achieve which section of memory is show on screen.
04/28/2021 at 23:25 •
So as we saw in the previous blog, we have an image being drawn in the framebuffer, and the colors set.
The problem though, I noticed that some of the colors either not being set, or kept flipping to different colors after they were set. I started doubting my framebuffer command that I was sending, or my setup and started to get frustrated.
But then it suddenly dawned on me. Maybe this was the degradation that started happening to alot of these machines later in their lives. This was acting very similarly.
Here are some well-known cases of it going on in the field:
Here is a picture of one in my files that also had problems:
Here is another one a friend of mine owns which is running my software:
So my mind quickly shifted from being a problem on my end with my experiments, to the potential of hardware failure.
Now giving my troubleshooting skills are very sharp, and I have repaired electronics for years and years prior to this point, I had a pretty good idea right where to go to.
the RAMDAC. So, on an indexed-color system, most graphics cards from this era have whats called a RAMDAC. the RAMDAC means Random Access Memory Digital to Analog Converter.
Without forcing you all to go to wikipedia, the nutshell is basically this. the RAM of RAMDAC means there is a small section of SRAM inside the chip which holds the R, G, B values for each color index starting from 0, up to 255. DAC is the analog converter. it takes the digital R,G,B values that are stored in the SRAM lookup table, and then applies those as a voltage or current. the higher the number for each R,G,B value, the higher the voltage/current level is on the output. and 0 being no output. Each value from 0 to 255 is applied from the bus through an 8-bit port. This is known as the index. this index will pick which slot to apply to the DAC output with that slot's programmed color values. There are also mask registers, etc but i don't want to get into that, and the 4000 doesn't use them anyway.
Pretty straight forward.
With the colors suddenly and spontaneously changing from their set values, or unable to be changed, points me to believe the SRAM inside the RAMDAC has failed in some rather unique way. So it is unable to retain its set RGB values. so bits will flip. when bits flip states, its going to change the color for that index value.
So the next step at this point is to just basically replace the BT471.
However there is a catch. the BT471 is hard to find since it wasnt commonly used. Everyone used the BT478, as it supports full 8bit RGB.
So, i bought a few of the BT478s on ebay. I figured why not, plus it can upgrade the possible number of colors to its mask! or so i thought.
I put the new chip in, but the picture was very dark. I even sent full RGB888 values, and the picture was still very dark.
So i took a peek at the ROM, and sure enough this little bit here stands in the way:
If you look at that, you can see the anl 0x3F opcode. That basically clips off the byte value and keep it as 6 bits. So damn.... So much for full 8bit RGB. haha. Those operations are peppered everywhere in ROm so its not as simple as just patching that bit out.
So i left it be. I figured i was going to need to replace the BT471 with another 471.
Turns out, studying the datasheet there is a pin which you can connect to ground, and it forces the chip to run in backwards-compatible RGB666 mode!
So, lets do that!
Here is the new chip installed.
Turns out, the pin I needed to ground was right next to a ground. So i didnt even need a bodge wire. the pin was unconnected on the original design as well, so all I did was lay down a solder blob to ground the pin.
Time to see if it worked:
BOOM fixed! that's exactly what it was. Although there is still some color noise, but that was due to the graphics card itself rather than the RAMDAC.
So that was exactly the cause. the SRAM cells start failing in the BT471 chips over time. So any degraded unit you saw back in the day, this was the cause.
Perfect. Now we can move on....