This commit is contained in:
2026-08-30 19:27:26 +02:00
parent 073a538794
commit 52d4d286c6
8 changed files with 391 additions and 130 deletions

255
core1.c
View File

@@ -1,12 +1,17 @@
#include "core1.h"
#include <stdint.h>
#include <stdio.h>
#include <stdatomic.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/irq.h"
#include "hardware/pio.h"
#include "core_comm.h"
#include "z80_bus.pio.h"
#include "text_mode.h"
#include "cvideo.h"
#define Z80_PIO pio1
#define Z80_MONITOR_SM 0
@@ -20,25 +25,102 @@
#define Z80_CS_PIN 12
#define Z80_A0_PIN 13
#define Z80_A1_PIN 14
#define Z80_CLK_PIN 15
#define Z80_WAIT_PIN 16
#define Z80_RD_MASK (1u << Z80_RD_PIN)
#define Z80_CS_MASK (1u << Z80_CS_PIN)
#define Z80_ADDR_MASK 0x3u
#define Z80_DATA_MASK 0xffu
#define Z80_DIRTY_SHIFT 32u
#define Z80_DIRTY_MASK (UINT64_C(0x0f) << Z80_DIRTY_SHIFT)
// Bits 0..31 contain registers 0..3. Bits 32..35 are their dirty flags.
static _Atomic uint64_t z80_registers;
uint16_t cursor_x = 0;
uint16_t cursor_y = 0;
uint16_t pixel_x = 0;
uint16_t pixel_y = 0;
#define VID_CMD_SET_TEXT_MODE 0
#define VID_CMD_SET_PIXEL_MODE 1
#define VID_CMD_CLR 2
#define VID_CMD_TEXT_POS_X 3
#define VID_CMD_PIXEL_POS_X 4
#define VID_CMD_TEXT_POS_Y 5
#define VID_CMD_PIXEL_POS_Y 6
typedef enum VID_MODE{
VID_MODE_TEXT,
VID_MODE_PIXEL,
VID_MODE_TEXT_POS_X,
VID_MODE_TEXT_POS_Y,
VID_MODE_PIXEL_POS_X_1,
VID_MODE_PIXEL_POS_X_2,
VID_MODE_PIXEL_POS_Y_1,
VID_MODE_PIXEL_POS_Y_2,
} vid_mode_t;
vid_mode_t vid_mode = VID_MODE_TEXT;
// --------------------------------
// CORE 1
// --------------------------------
static void core1_puts(const char *s) {
static void core1_putc(char c){
video_command_t cmd;
switch((uint8_t)c){
case '\r':
cursor_x = 0;
break;
case '\n':
cursor_y = (cursor_y<CHAR_LINES-1) ? cursor_y+1 : cursor_y;
break;
default:
cmd.cmd = VIDEO_WRITE_TEXT,
cmd.data[0] = (uint16_t)c,
cmd.data[1] = cursor_x,
cmd.data[2] = cursor_y,
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
cursor_x = (cursor_x<CHARS_PER_LINE-1) ? cursor_x+1 : cursor_x;
}
}
static void core1_puts(char * s){
while(*s){
core1_to_core0_write_blocking((uint32_t)*s);
core1_putc(*s);
s++;
}
}
static void z80_write_monitor_init(void) {
uint offset = pio_add_program(Z80_PIO, &z80_write_monitor_program);
static void z80_bus_irq_handler(void) {
while (!pio_sm_is_rx_fifo_empty(Z80_PIO, Z80_MONITOR_SM)) {
uint32_t bus_sample = pio_sm_get(Z80_PIO, Z80_MONITOR_SM);
uint8_t addr = (bus_sample >> Z80_A0_PIN) & Z80_ADDR_MASK;
uint32_t shift = (uint32_t)addr * 8u;
uint64_t registers = atomic_load_explicit(&z80_registers,
memory_order_relaxed);
if ((bus_sample & Z80_RD_MASK) != 0) {
uint8_t data = (bus_sample >> Z80_DATA_PIN) & Z80_DATA_MASK;
registers &= ~(UINT64_C(0xff) << shift);
registers |= (uint64_t)data << shift;
registers |= UINT64_C(1) << (Z80_DIRTY_SHIFT + addr);
atomic_store_explicit(&z80_registers, registers,
memory_order_release);
} else {
uint8_t data = (registers >> shift) & Z80_DATA_MASK;
pio_sm_put(Z80_PIO, Z80_MONITOR_SM, data);
}
}
}
static void z80_bus_init(void) {
uint offset = pio_add_program(Z80_PIO, &z80_bus_program);
// Keep the bus isolated while selecting B (Z80) -> A (Pico).
gpio_init(Z80_OE_PIN);
@@ -47,60 +129,149 @@ static void z80_write_monitor_init(void) {
gpio_init(Z80_DIR_PIN);
gpio_put(Z80_DIR_PIN, 0);
gpio_set_dir(Z80_DIR_PIN, GPIO_OUT);
gpio_init(Z80_WAIT_PIN);
gpio_put(Z80_WAIT_PIN, 1);
gpio_set_dir(Z80_WAIT_PIN, GPIO_OUT);
for (uint pin = Z80_DATA_PIN; pin <= Z80_A1_PIN; pin++) {
for (uint pin = Z80_OE_PIN; pin <= Z80_CLK_PIN; pin++) {
pio_gpio_init(Z80_PIO, pin);
}
pio_sm_set_consecutive_pindirs(Z80_PIO, Z80_MONITOR_SM,
Z80_DATA_PIN,
Z80_A1_PIN - Z80_DATA_PIN + 1,
false);
pio_gpio_init(Z80_PIO, Z80_WAIT_PIN);
pio_sm_config config = z80_write_monitor_program_get_default_config(offset);
pio_sm_config config = z80_bus_program_get_default_config(offset);
sm_config_set_in_pins(&config, Z80_OE_PIN);
sm_config_set_in_shift(&config, false, false, 32);
sm_config_set_out_pins(&config, Z80_DATA_PIN, Z80_DATA_WIDTH);
sm_config_set_out_shift(&config, true, false, 32);
sm_config_set_set_pins(&config, Z80_WAIT_PIN, 1);
sm_config_set_sideset_pins(&config, Z80_OE_PIN);
pio_sm_init(Z80_PIO, Z80_MONITOR_SM, offset, &config);
pio_sm_set_enabled(Z80_PIO, Z80_MONITOR_SM, true);
// Enable only after every Pico-side data pin has become an input.
gpio_put(Z80_OE_PIN, 0);
uint32_t control_mask = (1u << Z80_OE_PIN) |
(1u << Z80_DIR_PIN) |
(1u << Z80_WAIT_PIN);
uint32_t initial_levels = (1u << Z80_WAIT_PIN);
pio_sm_set_pins_with_mask(Z80_PIO, Z80_MONITOR_SM,
initial_levels, control_mask);
pio_sm_set_pindirs_with_mask(Z80_PIO, Z80_MONITOR_SM,
control_mask, control_mask);
pio_sm_set_consecutive_pindirs(Z80_PIO, Z80_MONITOR_SM,
Z80_DATA_PIN,
Z80_CLK_PIN - Z80_DATA_PIN + 1,
false);
// IRQ configuration is per-core. This function runs on core 1, so bus
// events preempt diagnostics on core 1 and never involve core 0.
pio_set_irq0_source_enabled(Z80_PIO,
pis_sm0_rx_fifo_not_empty,
true);
irq_set_exclusive_handler(PIO1_IRQ_0, z80_bus_irq_handler);
irq_set_priority(PIO1_IRQ_0, PICO_HIGHEST_IRQ_PRIORITY);
irq_set_enabled(PIO1_IRQ_0, true);
pio_sm_set_enabled(Z80_PIO, Z80_MONITOR_SM, true);
}
void core1_entry() {
z80_write_monitor_init();
core1_puts("core 1: Z80 write monitor started\r\n");
uint64_t write_count = 0;
uint8_t last_addr = 0;
uint8_t last_data = 0;
absolute_time_t next_report = make_timeout_time_ms(100);
z80_bus_init();
core1_puts("core 1: Z80 read/write bus started > ");
while (true) {
while (!pio_sm_is_rx_fifo_empty(Z80_PIO, Z80_MONITOR_SM)) {
uint32_t bus_sample = pio_sm_get(Z80_PIO, Z80_MONITOR_SM);
// Check if it is for us -> CS=1
if ((bus_sample & Z80_CS_MASK) != 0) {
uint8_t addr = (bus_sample >> Z80_A0_PIN) & Z80_ADDR_MASK;
if ((bus_sample & Z80_RD_MASK) != 0) {
// WR
last_addr = addr;
last_data = (bus_sample >> Z80_DATA_PIN) & Z80_DATA_MASK;
write_count += 1;
} else {
// RD
}
// Take a coherent snapshot and consume all four dirty flags. A
// concurrent later write sets its register's flag again.
uint64_t registers_i = atomic_fetch_and_explicit(&z80_registers, ~Z80_DIRTY_MASK, memory_order_acq_rel);
registers_t regs = *((registers_t*)&registers_i);
if((regs.flags & 0x01) == 0x01){
// Write to vid_cmd happened
switch(regs.vid_cmd){
case VID_CMD_SET_TEXT_MODE: {
video_command_t cmd = {
.cmd = VIDEO_COMMAND_MODE_SET,
.data[0] = VIDEO_MODE_TEXT,
};
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
vid_mode = VID_MODE_TEXT;
} break;
case VID_CMD_SET_PIXEL_MODE: {
video_command_t cmd = {
.cmd = VIDEO_COMMAND_MODE_SET,
.data[0] = VIDEO_MODE_PIXEL,
};
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
vid_mode = VID_MODE_PIXEL;
} break;
case VID_CMD_CLR: {
if(vid_mode == VIDEO_MODE_TEXT){
video_command_t cmd = {
.cmd = VIDEO_CLR_TEXT
};
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
}else if(vid_mode == VIDEO_MODE_PIXEL){
video_command_t cmd = {
.cmd = VIDEO_CLR_PIXEL
};
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
}
} break;
case VID_CMD_TEXT_POS_X: {
vid_mode = VID_MODE_TEXT_POS_X;
} break;
case VID_CMD_PIXEL_POS_X: {
vid_mode = VID_MODE_PIXEL_POS_X_1;
} break;
case VID_CMD_TEXT_POS_Y: {
vid_mode = VID_MODE_TEXT_POS_Y;
} break;
case VID_CMD_PIXEL_POS_Y: {
vid_mode = VID_MODE_PIXEL_POS_Y_1;
} break;
default:
}
}
if((regs.flags & 0x02) == 0x02){
// Write to vid_dat happened
switch(vid_mode){
case VID_MODE_TEXT_POS_X:
cursor_x = (regs.vid_data<CHARS_PER_LINE) ? regs.vid_data : cursor_x;
break;
case VID_MODE_TEXT_POS_Y:
cursor_y = (regs.vid_data<CHAR_LINES) ? regs.vid_data : cursor_y;
break;
if (time_reached(next_report)) {
char message[48];
snprintf(message, sizeof(message), "Z80 writes: %llu\r\n", (unsigned long long)write_count);
core1_puts(message);
snprintf(message, sizeof(message), "Last address: %02x\r\n", last_addr);
core1_puts(message);
snprintf(message, sizeof(message), "Last data: %02x\r\n", last_data);
core1_puts(message);
next_report = make_timeout_time_ms(100);
case VID_MODE_PIXEL_POS_X_1:
pixel_x = pixel_x&0xff00 | regs.vid_data;
vid_mode = VID_MODE_PIXEL_POS_X_2;
break;
case VID_MODE_PIXEL_POS_X_2:
pixel_x = pixel_x&0x00ff | regs.vid_data<<8;
vid_mode = VID_MODE_PIXEL_POS_X_1;
pixel_x = (pixel_x<VIDEO_WIDTH) ? pixel_x : 0;
break;
case VID_MODE_PIXEL_POS_Y_1:
pixel_y = pixel_y&0xff00 | regs.vid_data;
vid_mode = VID_MODE_PIXEL_POS_Y_2;
break;
case VID_MODE_PIXEL_POS_Y_2:
pixel_y = pixel_y&0x00ff | regs.vid_data<<8;
vid_mode = VID_MODE_PIXEL_POS_Y_1;
pixel_y = (pixel_y<VIDEO_HEIGHT) ? pixel_y : 0;
break;
case VID_MODE_PIXEL:{
video_command_t cmd = {
.cmd = VIDEO_WRITE_PIXEL,
.data[0] = regs.vid_data,
.data[1] = pixel_x,
.data[2] = pixel_y,
};
pixel_x = (pixel_x<VIDEO_WIDTH-8) ? pixel_x+8 : pixel_x;
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
} break;
case VID_MODE_TEXT:
default:
core1_putc(regs.vid_data);
}
}
tight_loop_contents();