Page 1 of 1

PCJr Cartridge with battery-backed S-RAM

Posted: Sun Jan 23, 2022 9:47 pm
by ImperatorBanana
Howdy folks!

As I've been reading up on the IBM PCJr (both the criticism of the day and modern looks at the system), it seems that the primary re-occurring theme of both then and now is a focus on the IBM PC: what it lacks in comparison to a PC, how to modify it to make it a somewhat useable PC, etc. I can't really argue with that (and in hindsight I suspect that was for the best), but it did seem pretty interesting to me that the actual base PCJr "platform" itself seems to have never really been exercised with a practical example. Almost everything run on it is PC DOS software after the system has been modified to 640K, and I think other than running the cartridge games from launch, BASIC-ally( :lol: ) nothing runs on a base PCJr.

I wanted to take a step back and start a thought experiment of what choices IBM and Microsoft would have had if they had decided to try and save the platform, with a focus on the base PCJr as a driver for some hardware design projects to do in my spare time. Looking at other cartridge-based systems, I think there were three major "Force-Multipliers" when it comes to the complexity of the games that ran on them, and wanted to look at each of them:

1.) Save games (and extra RAM)
2.) Banking to support larger amounts of memory than the cartridge address window supports
3.) Co-processors

All three of these run into a one-way wall on the PCJr: the PCJr does not support writing data to the cartridge port, only reading (system sends an address and a chipselect, the cartridge reads that address/chip select and writes data, and system reads that data). As cartridge based systems evolved, most of them added a read/write signal that the system also sends to the cartridge which the PCJr lacks.

Which would be the end of it, if it wasn't for a technicality: the PCJr can't signal a write or send data over the data lines but most definitely does send some information (the address it wants to read). A slightly more complex cartridge design could use part of a read address or a sequence of reads to special addresses as a write. Raphnet's modern SD-Cart JR proves (via existence) that it's possible (and the period Integrity Technology Clock Cartridge likely does something similar). The SD-Cart JR technically completely solves 1 & 2 with essentially infinite free space, but also does have tradeoffs for speed and system RAM usage (and of course SD cards came to the market a little late for the PCJr).

Goal #1:
Design and test a prototype cartridge that has a program ROM, a battery-backed S-RAM to save data, and simple LS style logic that, while still relatively expensive, are close-ish to having been a feasible option as the platform games hypothetically got more advanced. Sorta. Squint a little if you need to :roll: .

Components (everything except the PCB I pulled from parts I had lying around):
1 PCB
1 ROM chip
1 S-RAM chip
1 LS373 Octal latch
1 LS00 NAND gate
2 Diodes
6 pull-up/pull-down resistors
4 capacitors (bypass/bulk)
1 CR2025 (game-boy save battery)

API:
1.) Read ROM
Having the CPU read from the D0000h-D7FFFh address space (CS2) simply has the CPU retrieve the expected value from the ROM (in my prototype I just have an 8K ROM so it is mirrored multiple times)

2.) Read RAM
Having the CPU read from the E0000h-E7FFFh address space (CS4) has two affects:
a.) Actually performs the intended read: CPU retrieves the expected value from the RAM (in my prototype I have a 128K RAM, only the upper-most 32K is used)
b.) Stores (caches) the lower 8-bits of the address in the LS373 Octal Latch

3.) Writing RAM
Having the CPU read from the E8000h-EFFFFh address space (CS5) has the LS373 output the last cached data onto the data bus which:
a.) CPU ends up retrieving the cached data (which is a dummy read)
b.) More importantly, CS5 is wired to the RAM's Write pin so the LS373 output on the databus is also written to RAM

Behavior:
Cartridge ROM Read: should behave like any ROM: set the data segment and read into registers with no wait-states. I believe this is more or less the fastest possible speed that the base PCJr is capable of retrieving data at. You can read bytes or words. Copying data from ROM to System RAM likely needs DS/ES addressing (extra bytes for encoding) and the base PCJr System RAM is shared with the video card (extra wait-states, though I would guess if you timed a transfer during V-Blank you could get the wait-states to a minimum?)

Cartridge RAM Read: same as the ROM: set the data segment and read into registers with no wait-states, copying to System RAM. You can read bytes or words.

Cartridge RAM Write: very slow (I haven't done any timing / transfer rate calculations yet). Essentially at a minimum you're doing reads to two different addresses. My proof of concept is meant more fore readability / obvious behavior than it is for efficiency, but you're definitely going to be shuffling segments or adding 08000h. There is no writing a "Word" in little-endian style natively either, it's byte-by-byte so if you want to read data as words you need to write the data accordingly.

Code: Select all

	;; al = data byte to write
	;; bx = "Lower 32K" version of the RAM address, returns bx + 1 for slightly easier sequential writes
	push ds
	push bx
	
	;; Cache Data
	mov bx,ram_seg_read ; ram_set_read = 0E000h
	mov ds,bx
	mov bh,00h ;; bh can technically be anything less than 80h
	mov bl,al
	mov al,[bx] ; dummy read to cache the data
	
	;; Write Data
	mov bx,ram_seg_write ; ram_set_read = 0E800h
	mov ds,bx
	pop bx
	mov al,[bx]
	inc bx	
	pop ds
	iret
Boot:
Because the PCJr BIOS reads every 2K in cartridge space looking for cartridges to boot, and reads in the E8000h-EFFFFh trigger "write" circuitry, action must be taken to protect the Cartridge RAM. If you intend to let the BIOS finish booting (using the int18h entry), the init code must also modify the PCJr BIOS ROM counter so that it thinks it already read the cartridge space. My init function looks like:

Code: Select all

INIT PROC FAR
	; The BIOS pushes DX (the current ROM block being checked for signatures) to the stack
	; and then does a call (far) (which pushes the segment and offset before jumping)
	; Assumes your init didn't push additional data to the stack
	
	mov 	bp,sp ; Set the base pointer
	mov 	ax,0F000h ; 
	mov 	[bp+4],ax ; Set the "current ROM block" to be after the end of the cartridge
	
	; Set up the interrupt vector to boot into the cartridge
	mov 	ax,0000h
	mov 	es,ax ; Set ES to 0000h (the interrupt vector segment)
	mov 	ax,cs
	mov 	es:int18seg_loc,ax ; Move the current Code Segment to the Interrupt Vector 2nd 2 bytes
	mov 	ax,offset MAIN
	mov 	es:int18off_loc,ax ; Move the Main Offset to the Interrupt Vector 1st 2 bytes
ret
INIT ENDP

With the primary goal being just "Save-game" data, the write-speed being pretty slow/inefficient wouldn't necessarily be an issue. You can also use the save RAM area as graphics decompression area: decompression would be slow but once the area's graphics are decompressed you'd have full speed read-access to them. In the end, you'd have the flexibility to use 32K of System RAM to double buffer the screen, and have 32K of Cartridge RAM (very slow-write, fast-read) and ~30ish K of System RAM (slow/medium write, slow/medium read) to juggle graphics and game data.

Proof of concept seems to work, though with some of the components sub-optimal I do need to exercise it a bit more (example, the random diodes I grabbed have a voltage drop that is a bit too high for this application, so it may be surviving based on stored energy in the capacitors in the short term, need to see if it retains the data over a few days. I've also only exercised the first few bytes). It's also a bit longer than a normal cartridge but a swap to surface mount (or smarter routing) would fix that. As most of the cartridge games I've seen on the system tend to look like older Atari type games, I included a pair of photos of mockups I did when first experimenting to show what GameBoy graphics might look like [running on my full PCJr]. My guess is even with page flipping and copying from cartridge to video ram during V-Blank the lack of hardware scrolling support would make something like that pretty unlikely, but game-boy graphics in a CGA palette at "high" resolution does look pretty cool.

Next steps for me:
SW - write a more robust proof of concept usage that is a little more game-like and more completely verify the cartridge is working as designed
HW - Next proof of concept is to try and make a bank-switching circuit to allow for larger cartridges

PS. Is there a summary list out there of software that actually uses the PCJr specific audio and graphics features without modification?

Re: PCJr Cartridge with battery-backed S-RAM

Posted: Fri Jan 28, 2022 12:56 am
by DarkStar2032
Imho, I believe both you and raphnet hit it on the head as to the PCjr's #1 party trick being the cartridge port. Had big blue not given up so quickly on the idea we might have seen the kind of variety we see in the Commodore 64. Poetic, since that was what the peanut was designed to compete against.

Hmm, could this setup be coupled with a bit of PCjr specific software on the same cart? Not necessarily a game, but something that exploits the sound and graphics?

Another thought that occurred was a clock cart. While better solutions exist, imagine this could be a useful addon for those users who bought the ram add-on instead of the jrIDE.

Re: PCJr Cartridge with battery-backed S-RAM

Posted: Sat Jan 29, 2022 3:40 pm
by ImperatorBanana
It looks like there actually was a period clock chip (https://www.brutman.com/PCjr/pcjr_optio ... _Cartridge:) and it's (eventually) on my list to add as some more advanced Gameboy games (Pokemon Gold/Silver for example) has RTCs.

Any software running on cartridge would have the same access that PCJr-aware DOS software would have with respect to Graphics and Sound, though having the S-RAM on the cart does not allow you to use the PCJr 32K graphics modes (16-color 320x200, 4-color 640x200) as that can only (to my knowledge) be gated in via the PCJr internal RAM expander, so in that respect my cartridge isn't really bringing anything new to the table. My primary motivation is to try and see what a base PCJr is capable of since it seems that it was never really exercised in its stock form (which, to be fair, is pretty limiting). It remains to be seen if I can get the PCJr to render something game-boy "ish" as the lack of DMA is particularly brutal, but in theory copying data off of a cartridge is about as fast as the PCJr can get and the S-RAM can in theory also be used as a graphics/sound/data decompression area to allow for more data than a 32 or 64K cartridge would've otherwise allowed on its own without eating into the meager base ~30K of system RAM you'd have left if you were using a 16K graphics mode with 2 pages.

Re: PCJr Cartridge with battery-backed S-RAM

Posted: Sat Jan 29, 2022 4:03 pm
by Brutman
The Dallas "ROM Clock" chip used an old trick for writing to a device that only had read wiring. It's quite clever when you think about it; the device has a state machine that watches the address lines. You disable interrupts, then wiggle the lines in a particular way to control the behavior of the device. To anything else on the system it looks like a pattern of reads. If the sequence is long enough then the probability of triggering the sequence is infinitesimally small. If you do something obvious like backward reads that a processor or code would never do you make the odds even smaller.

To me the cartridge port is a failure. It's a little more user friendly that trying to put ROM chips into a card without bending the pins, but the 64K space limit is very confining. The lack of I/O ports or being able to write to it also hurts. The only good thing about it is that they are kind of durable and they are ROM, so the access to them is faster than the RAM on the machine. (I think 200ns instead of 250?)

You can get around the limitations. Bank switching can be done, and to choose the bank you can use the state machine watching the address lines trick. If you swap in 16 or 32K at a time that is not so bad. You can also compress whatever you put in the cart and decompress it into RAM before running. If you were really silly you can hide an entire CPU behind the cartridge interface; it's just not going to be fast to read data from it. (A plug in cryptography processor so I can add SSL support to mTCP would be hilarious.)

(I need to dig out Alan's supercart project and play with it ...)

Re: PCJr Cartridge with battery-backed S-RAM

Posted: Sun Jan 30, 2022 6:04 pm
by ImperatorBanana
Haha yea I think if they had put a read/write toggle line in there it would've been a lot more flexible, but gotta play the cards we've been dealt :lol: . I suspect that for a co-processor that requires generally requires the same number or greater number of writes vs. reads, it probably makes more sense to do it as a sidecar. For applications that require significantly more reads than writes (like a graphics decompression RAM area), I suspect it'll still make sense to do on a cartridge since reads occur at the same speed that ROM reads do (aka the maximum possible speed). In theory a cartridge co-processor that might make sense is something like a sprite rotator or transparency calculator: you preload the background and sprite data once when entering the zone, then every frame you just have to send it an angle if you want the sprite rotated or tile IDs if you want calculate an overlapped transparency for you, then you can read back at full speed multiple times. At the end of the day though, at that point you're back into pretty high cost territory and the PCJr was already suffering from the get-go of that.

I think IBM always said the cartridge was limited to 64K, but I'm not sure why (maybe power restrictions?): the same address and 6 CS lines go to both cartridge slots so you could have native 128K (D0000-EFFFF) or 196K (D0000-FFFFF) minus whatever space a micro-BIOS replacement you'd have to write would take up. The Gameboy got away with having cartridge 16K ROM fixed and 16K ROM banked in (along with supporting bankable 8K RAM), so in theory having a PCJr cartridge with a 32K (D0000-D7FFF) Fixed ROM, 32K (D8000-DFFFF) Banked ROM, and 32K (read E0000-E7FFF, write E8000-EFFFF) Banked RAM is a little bit more freeing and would've been more relatively affordable as time went on.

But yea, it just looked like a challenge that there wasn't much out there that could run on a stock, base-model PCJr. Your only real entry points on a base JR are Cassette, Cartridge, or Side-Car*. Side-car install/uninstall, while pretty awesome, is still basically taking a screw driver to the computer. Cassette dumps you into RAM and is limited to one-direction reads (it can command the cassette drive forward, but not backwards unless you design a controller to read a sequence of commands and have a specially modified cassette drive to run it forwards and backwards 8-)), and so that left Cartridges!

*I don't think the PCJr can boot off of the serial port natively. Coupled with a cartridge though you could basically have a crude predecessor to PXE I suppose.