Let's make a Pi Pico 2 powered video card.
-
I own a lot of ISA cards and I took measurements from several video cards, and there's no real standard - things vary a lot. You'll notice on this particular layout, the board edge dips down to give the most usable real estate after the end of the 8-bit ISA edge connector.
Unfortunately this means you can't plug an an original IBM CGA into your AT. We'll probably want to avoid that limitation. With a Pico replacing most of the logic on the board I don't really think we're going to need a full-length card in the first place.
On GlyphBlaster currently, I fight a lot with bus contention between the two ARM cores. Embassy, the USB-CDC connection and the network stack live on Core 0, whereas GlyphBlaster's video routines run on Core 1, so you might assume they could run independently.
-
On GlyphBlaster currently, I fight a lot with bus contention between the two ARM cores. Embassy, the USB-CDC connection and the network stack live on Core 0, whereas GlyphBlaster's video routines run on Core 1, so you might assume they could run independently.
But they still contend for the same flash - if I add some intensive video effect, I can starve Core 0 and it will stop responding to network requests or my USB debugging session.
The solution is to tag routines on Core 1 with
#[unsafe(link_section = ".data.ram_func")]to force it to run out of RAM instead of flash, but this compounds my already dire RAM situation. -
But they still contend for the same flash - if I add some intensive video effect, I can starve Core 0 and it will stop responding to network requests or my USB debugging session.
The solution is to tag routines on Core 1 with
#[unsafe(link_section = ".data.ram_func")]to force it to run out of RAM instead of flash, but this compounds my already dire RAM situation.With a full board though, we could have a separate microcontroller that just handles the Wi-Fi. Maybe another RP2350? Maybe an ESP32? STM32 lol? I don't know. Worry about that later.
-
With a full board though, we could have a separate microcontroller that just handles the Wi-Fi. Maybe another RP2350? Maybe an ESP32? STM32 lol? I don't know. Worry about that later.
We'll definitely be using an RP2350B directly instead of soldering on a Pico 2 board. But I guess it's okay to still call this a Pico.
We basically double the number of available GPIO pins, meaning I no longer have to make compromises. Light pen? Sure. Capture every address line? You betcha. QSPI PSRAM? All day long.
Raspberry Pi is nice enough to provide a reference KiCad project, so you can more or less copy and paste a RP2350 into your project.

-
To simulate the OSC pin, I will program another Pico to just generate a 14.3181818 clock.
"What's my purpose?"
"You generate a clock."
@gloriouscow glad we're not at the state of vibe hardware yet where we need to convince it to actually do that
-
We'll definitely be using an RP2350B directly instead of soldering on a Pico 2 board. But I guess it's okay to still call this a Pico.
We basically double the number of available GPIO pins, meaning I no longer have to make compromises. Light pen? Sure. Capture every address line? You betcha. QSPI PSRAM? All day long.
Raspberry Pi is nice enough to provide a reference KiCad project, so you can more or less copy and paste a RP2350 into your project.

@gloriouscow If you're making a whole ISA card, then you're overlapping with #PicoMEM, #PicoGUS, and #PicoIDE which could be a good place to steal from / ask about!
-
@gloriouscow If you're making a whole ISA card, then you're overlapping with #PicoMEM, #PicoGUS, and #PicoIDE which could be a good place to steal from / ask about!
-
We'll definitely be using an RP2350B directly instead of soldering on a Pico 2 board. But I guess it's okay to still call this a Pico.
We basically double the number of available GPIO pins, meaning I no longer have to make compromises. Light pen? Sure. Capture every address line? You betcha. QSPI PSRAM? All day long.
Raspberry Pi is nice enough to provide a reference KiCad project, so you can more or less copy and paste a RP2350 into your project.

Okay, first priority - let's make our Pico OSC pin simulator.
Raspberry Pi has a very nice Pico development plugin for Visual Studio Code. We just choose "New Rust Project," name it, and click create.

-
Okay, first priority - let's make our Pico OSC pin simulator.
Raspberry Pi has a very nice Pico development plugin for Visual Studio Code. We just choose "New Rust Project," name it, and click create.

This gives you an rp-hal project, and GlyphBlaster is currently written against Embassy, and I don't really feel like rewriting all the overclocking code, so I'm just going to switch this to Embassy too and copy-paste that stuff.
I would like to thank FreddyV of PicoMEM for giving me the tips on how to stably overclock a Pico. It requires tweaking the on-board flash timings in a way I never would have figured out for myself.
The key do doing this in Embassy is this bit:
const PICO_SYS_CLOCK_HZ: u32 = 300_000_000;
const FLASH_QMI_TIMING_UPDATE_THRESHOLD_HZ: u32 = 280_000_000;
const FLASH_QMI_TIMING_HIGH_SPEED_HZ: u32 = 380_000_000;
const OVERCLOCK_FLASH_QMI_CLKDIV: u8 = if PICO_SYS_CLOCK_HZ > FLASH_QMI_TIMING_HIGH_SPEED_HZ {
4
} else {
3
};
const OVERCLOCK_FLASH_QMI_RXDELAY: u8 = if PICO_SYS_CLOCK_HZ > FLASH_QMI_TIMING_HIGH_SPEED_HZ {
4
} else {
3
};
//...
let timing = embassy_rp::pac::QMI.mem(0).timing();
timing.modify(|w| {
w.set_clkdiv(OVERCLOCK_FLASH_QMI_CLKDIV);
w.set_rxdelay(OVERCLOCK_FLASH_QMI_RXDELAY);
}); -
@gloriouscow With your graphics card, all that's needed is an Pico 8086 emulator and you've got an "Oops, all Pico!" IBM PC

-
@gloriouscow With your graphics card, all that's needed is an Pico 8086 emulator and you've got an "Oops, all Pico!" IBM PC

@Kroc Don't give me more ideas
-
@Kroc Don't give me more ideas
@Kroc There's already a Teensy 8088 emulator so i'm sure its possible
-
This gives you an rp-hal project, and GlyphBlaster is currently written against Embassy, and I don't really feel like rewriting all the overclocking code, so I'm just going to switch this to Embassy too and copy-paste that stuff.
I would like to thank FreddyV of PicoMEM for giving me the tips on how to stably overclock a Pico. It requires tweaking the on-board flash timings in a way I never would have figured out for myself.
The key do doing this in Embassy is this bit:
const PICO_SYS_CLOCK_HZ: u32 = 300_000_000;
const FLASH_QMI_TIMING_UPDATE_THRESHOLD_HZ: u32 = 280_000_000;
const FLASH_QMI_TIMING_HIGH_SPEED_HZ: u32 = 380_000_000;
const OVERCLOCK_FLASH_QMI_CLKDIV: u8 = if PICO_SYS_CLOCK_HZ > FLASH_QMI_TIMING_HIGH_SPEED_HZ {
4
} else {
3
};
const OVERCLOCK_FLASH_QMI_RXDELAY: u8 = if PICO_SYS_CLOCK_HZ > FLASH_QMI_TIMING_HIGH_SPEED_HZ {
4
} else {
3
};
//...
let timing = embassy_rp::pac::QMI.mem(0).timing();
timing.modify(|w| {
w.set_clkdiv(OVERCLOCK_FLASH_QMI_CLKDIV);
w.set_rxdelay(OVERCLOCK_FLASH_QMI_RXDELAY);
});I'll use GPIO16 for this which conveniently puts it on the top right corner of the Pico 2 board.
Our PIO program is stupid simple:
let clock_program = pio_asm!(
".wrap_target",
"set pins, 1 [10]",
"set pins, 0 [10]",
".wrap"
);.wrap_targetis just a standard label for the PIO loop, which will be restarted at the end with.wrap. The value in brackets is how many cycles to spin - the set itself takes one cycle, then we spin for 10 after. This should give us the 11 cycles on, 11 cycles off behavior we want.'pins' here just targets GPIO16, via this:
let clock_pin = pio1.common.make_pio_pin(p.PIN_16);
clock_sm_config.set_set_pins(&[&clock_pin]); -
I'll use GPIO16 for this which conveniently puts it on the top right corner of the Pico 2 board.
Our PIO program is stupid simple:
let clock_program = pio_asm!(
".wrap_target",
"set pins, 1 [10]",
"set pins, 0 [10]",
".wrap"
);.wrap_targetis just a standard label for the PIO loop, which will be restarted at the end with.wrap. The value in brackets is how many cycles to spin - the set itself takes one cycle, then we spin for 10 after. This should give us the 11 cycles on, 11 cycles off behavior we want.'pins' here just targets GPIO16, via this:
let clock_pin = pio1.common.make_pio_pin(p.PIN_16);
clock_sm_config.set_set_pins(&[&clock_pin]);PIO is the secret sauce that makes Picos so good at interfacing with retro hardware. They allow you to react to pin changes instantly, and read and write busses, doing the sort of high speed bus interactions that were normally the exclusive purview of FPGAs.
-
PIO is the secret sauce that makes Picos so good at interfacing with retro hardware. They allow you to react to pin changes instantly, and read and write busses, doing the sort of high speed bus interactions that were normally the exclusive purview of FPGAs.
All in all, not too shabby for a $5 board. Let's see how our clock looks, right after I unbag this and hook it up.

-
All in all, not too shabby for a $5 board. Let's see how our clock looks, right after I unbag this and hook it up.

14.318MHz, baby.
Believe it or not, a sawtooth clock isn't that hideous. This is pretty much what the OSC pin looks like for realsies.

-
14.318MHz, baby.
Believe it or not, a sawtooth clock isn't that hideous. This is pretty much what the OSC pin looks like for realsies.

@gloriouscow Could that be a probe issue? Not all probes go up that high in frequency.
-
@gloriouscow Could that be a probe issue? Not all probes go up that high in frequency.
@casandro Nah, I've gotten a clean read from a 66MHz crystal on this probe from a 386 motherboard. This is probably a limitation of the Pico's GPIO drive strength. I'm not sure if that's something you can configure on a Pico, you can on an STM32, although I usually don't as it can produce overshoots which ends up being worse to deal with.
-
@casandro Nah, I've gotten a clean read from a 66MHz crystal on this probe from a 386 motherboard. This is probably a limitation of the Pico's GPIO drive strength. I'm not sure if that's something you can configure on a Pico, you can on an STM32, although I usually don't as it can produce overshoots which ends up being worse to deal with.
@casandro The real CGA also cleans up the OSC pin by immediately passing it through an 74LS04 which we will probably also do.
-
I'll use GPIO16 for this which conveniently puts it on the top right corner of the Pico 2 board.
Our PIO program is stupid simple:
let clock_program = pio_asm!(
".wrap_target",
"set pins, 1 [10]",
"set pins, 0 [10]",
".wrap"
);.wrap_targetis just a standard label for the PIO loop, which will be restarted at the end with.wrap. The value in brackets is how many cycles to spin - the set itself takes one cycle, then we spin for 10 after. This should give us the 11 cycles on, 11 cycles off behavior we want.'pins' here just targets GPIO16, via this:
let clock_pin = pio1.common.make_pio_pin(p.PIN_16);
clock_sm_config.set_set_pins(&[&clock_pin]);@gloriouscow the 2350 also has an extremely flexible clock tree for micro; each off the gpouts has an individual int/frac divider with duty cycle correction and coarse phase adjustment
The PLL can also run up to 1600MHz (don't think any of the IO buffers can manage that though) and you can input an external clock to it (I think this is what you're doing)