Building a 0 chip cartridge
Posted: Wed Feb 26, 2020 9:25 am
I have an evil and completely useless idea (just a proof of concept that this can be done) of making a cartridge that has 0 chips, only discrete elements (like resistors, diodes, probably transistors will be needed to). Let's assume that the "project" will be run on Famicom (or oter 100% compatible famiclone).
By wiring CIRAM-/CE to GND and CIRAM-A10 to PPU-A13 we can use the console's internal video RAM can be used as both nametable and pattern table.
Next step is to use internal CPU RAM as the place where code would be executed from. That gives only 2048 bytes of space (minus the amount of cells used for variables), but I believe this should be enough for a simple game or demo. Of course the code first need to be copied to the RAM from something, but I still want to have no single chip in the cartridge. So you ask how?
Let's assume that there is some passive logic in the cartridge that
* for read from $FFFC will set CPU-D7..D0 to $16
* for read from $FFFD will set CPU-D7..D0 to $40
That would force the CPU to try to fetch next opcode from $4016. If this is 1-byte opcode, next one will be fetch from $4017 (which will be be one-byte) If this is 2-byte opcode, its argument will be fetch from $4017. Let's assume we are not using 3-byte opcodes.
Next, CPU will try to fetch one from $4018 but then some passive logic in the cartridge will
* for read from $4018 will set CPU-D7..D0 to $4C (JMP $4016)
* for read from $4019 will set CPU-D7..D0 to $16
* for read from $4020 will set CPU-D7..D0 to $40
Now CPU is going to start executing code again from $4016.
But what will drive the data bus for reads from $4016-$4017? There will be some "magic-black-box", plugged into 15-pin expansion port that when read from $4016/$4017 will occur, it will transmit the true byte that should be executed using the $4016.D1/$4017.D1 (as data) and /IRQ line (as clock), bit per bit, on falling edge of /IRQ line:
^=HIGH, _=LOW, -=OPEN BUS, x=no matter
The "passive logic" on the cartridge has access to the /IRQ line (and $4016.D1 because it is just inverted CPU-D1 on true famicom). After receiving those 8 bits, this logic will drive the D7..D0 with received value until end of cycle and CPU will interpret this as proper opcode and execute it.
Of course it cannot drive all bits, because some of them are connected to the 74368 inside famicom and are already driven, so bus conflict would appear. But those bits can be driven directly by the "magic black-box", while other bits can be driven using the passive logic inside cartridge.
Because after power-up, I-bit in status register is set so we can toggle /IRQ and nothing will happen. And because this is real famicom, so driving data bus from edge connector while reading from $4016/$4017 is still possible.
We are limited only to 1-byte and 2-byte opcodes, but that's not problem cause we can use the:
* LDA #$xx to load value
* STA ($xx),Y to store value at desired RAM cell
* PHA to push return address (-1)
* RTS to jump to RAM after execution is done
The "back-box" plugged inside 15-pin expansion port can be just simple CPLD + ROM chip for store opcodes.
Now I am wondering how to implement the described above "passive logic" in cartridge not using single chip.
* Buffers can be done using transistors in open collector manner.
* Decoders can be done using diodes and resistor (AND/OR logic or transistor as inverter).
* Shift register - using flip-flops and flip-flops using two transistor Eccles-Jordan
The question is.. if the 74368 inside FAMICOM is fast enough to drive data bus 8 times per CPU cycle.
By wiring CIRAM-/CE to GND and CIRAM-A10 to PPU-A13 we can use the console's internal video RAM can be used as both nametable and pattern table.
Next step is to use internal CPU RAM as the place where code would be executed from. That gives only 2048 bytes of space (minus the amount of cells used for variables), but I believe this should be enough for a simple game or demo. Of course the code first need to be copied to the RAM from something, but I still want to have no single chip in the cartridge. So you ask how?
Let's assume that there is some passive logic in the cartridge that
* for read from $FFFC will set CPU-D7..D0 to $16
* for read from $FFFD will set CPU-D7..D0 to $40
That would force the CPU to try to fetch next opcode from $4016. If this is 1-byte opcode, next one will be fetch from $4017 (which will be be one-byte) If this is 2-byte opcode, its argument will be fetch from $4017. Let's assume we are not using 3-byte opcodes.
Next, CPU will try to fetch one from $4018 but then some passive logic in the cartridge will
* for read from $4018 will set CPU-D7..D0 to $4C (JMP $4016)
* for read from $4019 will set CPU-D7..D0 to $16
* for read from $4020 will set CPU-D7..D0 to $40
Now CPU is going to start executing code again from $4016.
But what will drive the data bus for reads from $4016-$4017? There will be some "magic-black-box", plugged into 15-pin expansion port that when read from $4016/$4017 will occur, it will transmit the true byte that should be executed using the $4016.D1/$4017.D1 (as data) and /IRQ line (as clock), bit per bit, on falling edge of /IRQ line:
^=HIGH, _=LOW, -=OPEN BUS, x=no matter
Code: Select all
M2 ___^^^^^^^^^^^^^^^^^^^___
$4016.CLK ^^^___________________^^^
$4016.D1 ---__^^__^^__^^__^^^^^---
/IRQ ^^^^_^__^__^__^__^_^^^^^^
CPU-D7 xxxxxxxxxxxxxxxxxxx^^^----
CPU-D6 xxxxxxxxxxxxxxxxxxx___----
CPU-D5 xxxxxxxxxxxxxxxxxxx^^^----
CPU-D4 xxxxxxxxxxxxxxxxxxx___----
CPU-D3 xxxxxxxxxxxxxxxxxxx^^^----
CPU-D2 xxxxxxxxxxxxxxxxxxx___----
CPU-D1 xxxxxxxxxxxxxxxxxxx^^^----
CPU-D0 xxxxxxxxxxxxxxxxxxx___----
Of course it cannot drive all bits, because some of them are connected to the 74368 inside famicom and are already driven, so bus conflict would appear. But those bits can be driven directly by the "magic black-box", while other bits can be driven using the passive logic inside cartridge.
Because after power-up, I-bit in status register is set so we can toggle /IRQ and nothing will happen. And because this is real famicom, so driving data bus from edge connector while reading from $4016/$4017 is still possible.
We are limited only to 1-byte and 2-byte opcodes, but that's not problem cause we can use the:
* LDA #$xx to load value
* STA ($xx),Y to store value at desired RAM cell
* PHA to push return address (-1)
* RTS to jump to RAM after execution is done
The "back-box" plugged inside 15-pin expansion port can be just simple CPLD + ROM chip for store opcodes.
Now I am wondering how to implement the described above "passive logic" in cartridge not using single chip.
* Buffers can be done using transistors in open collector manner.
* Decoders can be done using diodes and resistor (AND/OR logic or transistor as inverter).
* Shift register - using flip-flops and flip-flops using two transistor Eccles-Jordan
The question is.. if the 74368 inside FAMICOM is fast enough to drive data bus 8 times per CPU cycle.