07/17/2022 at 01:21 •
Now that we were able to get some basic ROM routines working, setting up the 68K in an initial state and testing RAM, verifying that all of the hardware is operational, we can move onto writing yet another protocol for handshaking between the data card and the CPU/Graphics card.
Some thought had to be paid attention here. It is not as simple as just writing a little protocol to read out the FIFO and write those values into RAM and execute. It is a bit more involved than that.
To understand why, we need to take a look at the system architecture again:
So if we take a look at this, we basically have two system busses here. the main system bus which is the VME bus, is also part of the main CPU card and contains the RAM for the whole system, and its ROM and EEPROM is part of the main address space and can be accessed by any bus mastering cards at any point.
But, the Graphics Card also has its own system bus. it has its own ROM, its own RAM, and VRAM.
However, the system bus for the GPU isnt the same system bus as the main bus. it has its own. Therefore, the Graphics Card can only access the main bus by a 128K assigned address block and a paging register.
This poses a problem because the operation is one-way. the main CPU cannot access the graphics card in any way. only the graphics card can reach out and do anything.
However, we have bi-directional interrupts between the graphics card and the main CPU. the main CPU can send an auto-vectored interrupt to the Graphics Card, but also the Graphics Card has an IVEC register and can send manually-vectored interrupts back to the CPU card. This allows the graphics card to target a specific vector over on the CPU and run that code block, and the CPU can send an interrupt back at the graphics card at any time.
Great for handshaking between the CPU and the GPU. Buuuuut there's a problem with this.
The data card.... the data card only has specific registers exposed to the bus as we talked about previously. the data card itself cannot master the bus or read/write to it on its own. it can only load up registers, and its up to another master on the system bus to read/write the card. it is peripheral only whereas the GPU can be a bus-master.
However... the issue is the data card interrupt only goes back to the CPU. the graphics card is unaware what is going on with the data card at any point, no way for the graphics card to know what the data card is doing unless it polls it.
So circling back around, this is the issue we need to keep in mind when designing a bootstrapping protocol for the ROM on the CPU and graphics cards.
Now, there are a couple different approaches we can take here. One, we could download the graphics card binary to the CPU's shared RAM space, and then tell the graphics card via interrupt to download its binary from main system RAM. In fact, this is how they did it originally.
Or, we can simply have the Graphics Card reach out directly on the system bus to the data card and it can handle the data card on its own which removes the burden of data handling from the CPU to get to the graphics card.
Problem with the second approach is once again the graphics card cannot easily see whats going on with the data card unless its constantly polling. Polling is a bad idea because it has to take over the bus to do it. this will impede main CPU operations and slow things down.
So we work around it by routing interrupt traffic. So we design a specific command in the CPU ROM protocol that's specifically designed to bootstrap the graphics card.
Instead, we load the data card FIFO with a chunk of binary data meant for the graphics card, then shoot an interrupt to the main CPU. the main CPU then sees the vector and the CPU simply passes the interrupt down to the graphics card. so the graphics card's bootloader knows the next chunk of data is ready. Clever.... So this is the method we will go with.
using this approach, we need to download the binary into the graphics card first before downloading the CPU binary. Reason being, once the main program on the CPU executes and takes over the ROM bootloader, it will sever the path between getting signals between the data card and graphics card unless i add code overhead. Lets avoid that.
Bootstrap the graphics card and check a flag to know if its running. Once it is, we move on to the CPU and download its program into RAM and execute.
So now, we have a plan.... Lets execute that plan.
this system thus far has been architecting one protocol after another. and this is no exception. I made up a preliminary document of how i think the protocol should work:
Now, the data card does have a manually vectored interrupt register, IVEC. So i could have setup specific vectors for each of this. But instead of using up all of my vectors and making this more complicated than it needed to be, I kept data card operations on one specific vector and thats it.
However, what i did do was use the FIFO for all data blocks. and then I used the CPU register for commands. I would then fire over an interrupt to the CPU, and the CPU would read this register to know what to do, and then read the FIFO if necessary. That was the plan.
A million hours of thinking, debugging, and time later we end up with something like this:
And then as I discussed before, my clever solution of just handing off a command/interrupt over to the GPU for processing:
Then, the Vector 64 interrupt handler for the data card:
So really, its that simple. it reads the 8-bit latch from the data card when it receives an interrupt to vector 64 from the data card, sets a flag for mainloop to catch and then handle what it just received.
The way the bootloader works is very simple. I made a CMD01 which sets up the transfer. that routine determines which card is being targeted. if it is targeted for the graphics card, it simply just hands off the interrupt generated by CMD01 over to the graphics card, and the ROM bootloader for that card takes over!
But, once we determine we are targeted for the CPU program, I then simply send a pointer to RAM to begin the transfer:
The pointer signifies the start of the binary block that the transfer will commence at.
PAY ATTENTION! the 68K is a Big Endian system, and everything else including the PC and raspberry pi are little endian. so you cant just generate an int to bytes and send that over unless you want things to explode, so you need an endian-aware conversion.
Once we are initialized with a CMD01, we start sending subsequent CMD02s. the CMD02 is a block transfer command. it has a block number, and number of bytes to read as well as the bytes. Each CMD02 will advance the pointer internally in the bootloader by X bytes. If you need to start over, you must send a new CMD01 and then start sending CMD02s again. the block number insures they are sent in order, and if there is an out-of-order condition it throws it out. or if the same block was received twice, it'll ignore it.
And like before, if it's not targeted for the CPU, it just hands off the interrupt.
Once all of the CMD02 blocks are sent and the entire binary has been uploaded into RAM at the pointer you specified, we need to execute this code.
So we created a CMD03. the CMD03 will verify the integrity of the program and jump to the address pointer specified.
Pretty straight forward, takes the entry point (reset vector) address you want to divert execution to. as well as the expected binary length, and expected checksum of the binary. It will verify the calculated checksum against the expected checksum and execute the program!
the bootloader will send a success ACK back to the data card just before it jumps to the reset vector.
There you have it. we now have a method of bootstrapping both 68Ks from the data card!
Now, we need to test this.... If you can tell by the additional commands in the mainloop, it didn't go well at first with lots of trial and error.
07/16/2022 at 14:52 •
So in order to make this system useful in any way and now that we are capable of assembling and running code, we need to come up with some method of booting the system into an initial state and running it.
Because the ROMs are only so large, and the running program would be very dynamic especially during the debugging stage, the current line of thinking would be keeping the ROM very simple in nature with basic self tests/checks, and downloading binary into RAM and executing it.
One method would be using a pre-made 68K ROM monitor that would have been made back in the 80s at least, and using the RS232 port on the back of the unit. But, i would have to modify the codebase for the architecture we are dealing with, and writing a driver for putchar, getchar, etc for this particular I/O card and protocol. That is certainly an option!
But... In typical me fashion, I decided to roll my own. The original ROM is very specific to the original data card and how it worked, plus there are other goodies in the original ROMs that we will get to later like VRTX-32.
The first part of this though is to do some basic stuff like testing RAM, spitting out characters from the RS232 port on the I/O card, things like this. So I wrote all the pre-startup and initialization code first.
This required a lot of debugging, and re-burning ROMs back and forth.
Because I needed to see each stage of the ROM startup process with some visual indicator, Remember the bargraph LED display I put onto the Data card? Yep! that came in handy...
For each routine I wrote, I bumped the LED indicator by one. this way, if something were to crash/die I knew where approximately, just not what...
It was time to start writing the framework of the ROM/Bootstrapper
The beginning of the startup code for the CPU's ROM looks about like this:
That is basically it. Now that we have the startup code written and lots of debugging over and over later, we get this:
Presto! we have a very primitive bootloader that is able to output to the RS232 port as well as indicate a status of where things are on the LED bargraph display.
Eventually I added RAM test routines which were a bit slow, but they worked:
And that's it for the first part of the ROM bootloader. Again, we have not working on the GPU side yet but that will likely be much of the same.
Next we need to write the handling protocol between the bootloader and the data card.
07/16/2022 at 13:37 •
So up until this point, I had no knowledge or prior experience writing any 68K program C, ASM or otherwise.
My background in programming up to this point was Visual Basic 6/VBScript and microcontroller ASM, as that's what I grew up with, VB being taught in school and ASM being self-taught. I didn't even know C yet, but i knew PHP and PHP is a C-like syntax.
Now that the hardware engineering and reverse engineering is out of the way, we need to get some sort of program running on the 68K hardware to some degree.
In the beginning, I chose ASM so I could get familiar with the 68K, and we needed a bootstrap anyways. We need some method of getting code running on the hardware and eventually downloaded into the machine to run in RAM.
So, first and foremost there were two websites (one appears defunct now with broken SSL and blocked by malware services) that I learned a lot of programming aspects of the 68K and learned about VASM which is an assembler that is compatible with the m68K arch:
I decided to start with the CPU card, I will worry about the graphics card later. I wanted to start writing a hello world example, at least to keep the watchdog happy on the CPU card. If the CPU card stayed alive, I knew it was running my program.
Since I had the I/O card protocol and firmware written, That was one of the first things I tested when I assembled my hello world example. I used the LEDs on the I/O card as a visual indicator that code was running, and I can write the status of the LEDs.
Not only is the watchdog happy, but I was able to write a byte value to the I/O card using the protocol I created and turn on an LED!
I know this isn't a whole lot of fanfare, but this was a MAJOR milestone. I now have code running on the 68K CPU's ROM that was assembled by VASM and can communicate with other hardware! Now we have the proper building blocks for writing bootstrapping code and ultimately getting this machine running again
So, Hello World!
06/24/2022 at 18:01 •
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.
06/24/2022 at 15:03 •
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.