stuff
This commit is contained in:
@@ -48,6 +48,7 @@ target_link_libraries(picopal
|
|||||||
hardware_pio
|
hardware_pio
|
||||||
hardware_timer
|
hardware_timer
|
||||||
hardware_clocks
|
hardware_clocks
|
||||||
|
pico_atomic
|
||||||
pico_multicore
|
pico_multicore
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
255
core1.c
255
core1.c
@@ -1,12 +1,17 @@
|
|||||||
|
#include "core1.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/multicore.h"
|
#include "pico/multicore.h"
|
||||||
|
#include "hardware/irq.h"
|
||||||
#include "hardware/pio.h"
|
#include "hardware/pio.h"
|
||||||
|
|
||||||
#include "core_comm.h"
|
#include "core_comm.h"
|
||||||
#include "z80_bus.pio.h"
|
#include "z80_bus.pio.h"
|
||||||
|
#include "text_mode.h"
|
||||||
|
#include "cvideo.h"
|
||||||
|
|
||||||
#define Z80_PIO pio1
|
#define Z80_PIO pio1
|
||||||
#define Z80_MONITOR_SM 0
|
#define Z80_MONITOR_SM 0
|
||||||
@@ -20,25 +25,102 @@
|
|||||||
#define Z80_CS_PIN 12
|
#define Z80_CS_PIN 12
|
||||||
#define Z80_A0_PIN 13
|
#define Z80_A0_PIN 13
|
||||||
#define Z80_A1_PIN 14
|
#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_RD_MASK (1u << Z80_RD_PIN)
|
||||||
#define Z80_CS_MASK (1u << Z80_CS_PIN)
|
#define Z80_CS_MASK (1u << Z80_CS_PIN)
|
||||||
#define Z80_ADDR_MASK 0x3u
|
#define Z80_ADDR_MASK 0x3u
|
||||||
#define Z80_DATA_MASK 0xffu
|
#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
|
// 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){
|
while(*s){
|
||||||
core1_to_core0_write_blocking((uint32_t)*s);
|
core1_putc(*s);
|
||||||
s++;
|
s++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void z80_write_monitor_init(void) {
|
static void z80_bus_irq_handler(void) {
|
||||||
uint offset = pio_add_program(Z80_PIO, &z80_write_monitor_program);
|
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).
|
// Keep the bus isolated while selecting B (Z80) -> A (Pico).
|
||||||
gpio_init(Z80_OE_PIN);
|
gpio_init(Z80_OE_PIN);
|
||||||
@@ -47,60 +129,149 @@ static void z80_write_monitor_init(void) {
|
|||||||
gpio_init(Z80_DIR_PIN);
|
gpio_init(Z80_DIR_PIN);
|
||||||
gpio_put(Z80_DIR_PIN, 0);
|
gpio_put(Z80_DIR_PIN, 0);
|
||||||
gpio_set_dir(Z80_DIR_PIN, GPIO_OUT);
|
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_gpio_init(Z80_PIO, pin);
|
||||||
}
|
}
|
||||||
pio_sm_set_consecutive_pindirs(Z80_PIO, Z80_MONITOR_SM,
|
pio_gpio_init(Z80_PIO, Z80_WAIT_PIN);
|
||||||
Z80_DATA_PIN,
|
|
||||||
Z80_A1_PIN - Z80_DATA_PIN + 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
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_pins(&config, Z80_OE_PIN);
|
||||||
sm_config_set_in_shift(&config, false, false, 32);
|
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_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.
|
uint32_t control_mask = (1u << Z80_OE_PIN) |
|
||||||
gpio_put(Z80_OE_PIN, 0);
|
(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() {
|
void core1_entry() {
|
||||||
z80_write_monitor_init();
|
z80_bus_init();
|
||||||
core1_puts("core 1: Z80 write monitor started\r\n");
|
core1_puts("core 1: Z80 read/write bus started > ");
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
while (!pio_sm_is_rx_fifo_empty(Z80_PIO, Z80_MONITOR_SM)) {
|
// Take a coherent snapshot and consume all four dirty flags. A
|
||||||
uint32_t bus_sample = pio_sm_get(Z80_PIO, Z80_MONITOR_SM);
|
// concurrent later write sets its register's flag again.
|
||||||
// Check if it is for us -> CS=1
|
uint64_t registers_i = atomic_fetch_and_explicit(&z80_registers, ~Z80_DIRTY_MASK, memory_order_acq_rel);
|
||||||
if ((bus_sample & Z80_CS_MASK) != 0) {
|
registers_t regs = *((registers_t*)®isters_i);
|
||||||
uint8_t addr = (bus_sample >> Z80_A0_PIN) & Z80_ADDR_MASK;
|
if((regs.flags & 0x01) == 0x01){
|
||||||
if ((bus_sample & Z80_RD_MASK) != 0) {
|
// Write to vid_cmd happened
|
||||||
// WR
|
switch(regs.vid_cmd){
|
||||||
last_addr = addr;
|
case VID_CMD_SET_TEXT_MODE: {
|
||||||
last_data = (bus_sample >> Z80_DATA_PIN) & Z80_DATA_MASK;
|
video_command_t cmd = {
|
||||||
write_count += 1;
|
.cmd = VIDEO_COMMAND_MODE_SET,
|
||||||
} else {
|
.data[0] = VIDEO_MODE_TEXT,
|
||||||
// RD
|
};
|
||||||
}
|
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)) {
|
case VID_MODE_PIXEL_POS_X_1:
|
||||||
char message[48];
|
pixel_x = pixel_x&0xff00 | regs.vid_data;
|
||||||
snprintf(message, sizeof(message), "Z80 writes: %llu\r\n", (unsigned long long)write_count);
|
vid_mode = VID_MODE_PIXEL_POS_X_2;
|
||||||
core1_puts(message);
|
break;
|
||||||
snprintf(message, sizeof(message), "Last address: %02x\r\n", last_addr);
|
case VID_MODE_PIXEL_POS_X_2:
|
||||||
core1_puts(message);
|
pixel_x = pixel_x&0x00ff | regs.vid_data<<8;
|
||||||
snprintf(message, sizeof(message), "Last data: %02x\r\n", last_data);
|
vid_mode = VID_MODE_PIXEL_POS_X_1;
|
||||||
core1_puts(message);
|
pixel_x = (pixel_x<VIDEO_WIDTH) ? pixel_x : 0;
|
||||||
next_report = make_timeout_time_ms(100);
|
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();
|
tight_loop_contents();
|
||||||
|
|||||||
28
core1.h
28
core1.h
@@ -1,3 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
|
||||||
|
enum VIDEO_COMMAND{
|
||||||
|
VIDEO_COMMAND_MODE_SET, // data[0] VIDEO_MODE
|
||||||
|
VIDEO_WRITE_TEXT, // write data[0] to (data[1],data[2])
|
||||||
|
VIDEO_WRITE_PIXEL, // write 8 pixels (low byte of data[0]) to (data[1],daa[2])
|
||||||
|
VIDEO_SCROLL_TEXT, // Sroll text up
|
||||||
|
VIDEO_CLR_TEXT, // Clear screen
|
||||||
|
VIDEO_CLR_PIXEL, // Clear screen
|
||||||
|
};
|
||||||
|
|
||||||
|
enum VIDEO_MODE{
|
||||||
|
VIDEO_MODE_TEXT, // Text mode
|
||||||
|
VIDEO_MODE_PIXEL, // Pixel mode
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct __packed{
|
||||||
|
uint16_t cmd;
|
||||||
|
uint16_t data[3];
|
||||||
|
} video_command_t; // fits inside uint64_t
|
||||||
|
|
||||||
|
typedef struct __packed{
|
||||||
|
uint8_t vid_cmd;
|
||||||
|
uint8_t vid_data;
|
||||||
|
uint8_t unused[2];
|
||||||
|
uint32_t flags;
|
||||||
|
} registers_t; // fits inside uint64_t
|
||||||
|
|
||||||
void core1_entry();
|
void core1_entry();
|
||||||
37
core_comm.c
37
core_comm.c
@@ -3,18 +3,43 @@
|
|||||||
#include "pico/multicore.h"
|
#include "pico/multicore.h"
|
||||||
#include "pico/platform.h"
|
#include "pico/platform.h"
|
||||||
|
|
||||||
bool core1_to_core0_try_write(uint32_t data) {
|
bool core1_to_core0_try_write(uint64_t data) {
|
||||||
hard_assert(get_core_num() == 1);
|
hard_assert(get_core_num() == 1);
|
||||||
return multicore_fifo_push_timeout_us(data, 0);
|
if (!multicore_fifo_push_timeout_us((uint32_t)data, 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the low word is committed, complete the pair to preserve framing.
|
||||||
|
multicore_fifo_push_blocking((uint32_t)(data >> 32));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void core1_to_core0_write_blocking(uint32_t data) {
|
void core1_to_core0_write_blocking(uint64_t data) {
|
||||||
hard_assert(get_core_num() == 1);
|
hard_assert(get_core_num() == 1);
|
||||||
multicore_fifo_push_blocking(data);
|
multicore_fifo_push_blocking((uint32_t)data);
|
||||||
|
multicore_fifo_push_blocking((uint32_t)(data >> 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool core0_try_read_from_core1(uint32_t *data) {
|
bool core0_try_read_from_core1(uint64_t *data) {
|
||||||
|
static bool have_low_word;
|
||||||
|
static uint32_t low_word;
|
||||||
|
|
||||||
hard_assert(get_core_num() == 0);
|
hard_assert(get_core_num() == 0);
|
||||||
hard_assert(data != NULL);
|
hard_assert(data != NULL);
|
||||||
return multicore_fifo_pop_timeout_us(0, data);
|
|
||||||
|
if (!have_low_word) {
|
||||||
|
if (!multicore_fifo_pop_timeout_us(0, &low_word)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
have_low_word = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t high_word;
|
||||||
|
if (!multicore_fifo_pop_timeout_us(0, &high_word)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*data = (uint64_t)low_word | ((uint64_t)high_word << 32);
|
||||||
|
have_low_word = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,16 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// The RP2350 multicore FIFO holds eight 32-bit messages in each direction.
|
// The RP2350 multicore FIFO is physically 32 bits wide. Each logical 64-bit
|
||||||
|
// message is transferred low word first, followed by its high word.
|
||||||
// These functions intentionally enforce the sending/receiving core roles.
|
// These functions intentionally enforce the sending/receiving core roles.
|
||||||
|
|
||||||
// Try to send one word from core 1. Returns false instead of waiting when the
|
// Try to send one word from core 1. Returns false instead of waiting when the
|
||||||
// FIFO is full.
|
// FIFO is full.
|
||||||
bool core1_to_core0_try_write(uint32_t data);
|
bool core1_to_core0_try_write(uint64_t data);
|
||||||
|
|
||||||
// Send one word from core 1, waiting until core 0 has made FIFO space.
|
// Send one word from core 1, waiting until core 0 has made FIFO space.
|
||||||
void core1_to_core0_write_blocking(uint32_t data);
|
void core1_to_core0_write_blocking(uint64_t data);
|
||||||
|
|
||||||
// Try to read one word on core 0. Returns false when no message is available.
|
// Try to read one word on core 0. Returns false when no message is available.
|
||||||
bool core0_try_read_from_core1(uint32_t *data);
|
bool core0_try_read_from_core1(uint64_t *data);
|
||||||
|
|||||||
130
main.c
130
main.c
@@ -15,87 +15,49 @@
|
|||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
static video_framebuffer_t fb0, fb1;
|
static video_framebuffer_t fb0, fb1;
|
||||||
|
static video_framebuffer_t pixel_mode_buffer;
|
||||||
static text_mode_buffer_t text_mode_buffer;
|
static text_mode_buffer_t text_mode_buffer;
|
||||||
|
|
||||||
static volatile bool framebuffer_switched = true;
|
static volatile bool framebuffer_switched = true;
|
||||||
static bool text_mode = true;
|
static bool text_mode = true;
|
||||||
static bool text_mode_dirty = true;
|
static bool text_mode_dirty = true;
|
||||||
static uint console_row = 0;
|
|
||||||
static uint console_col = 0;
|
|
||||||
|
|
||||||
void framebuffer_switched_cb(){
|
void framebuffer_switched_cb(){
|
||||||
framebuffer_switched = true;
|
framebuffer_switched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void text_mode_clear_buffer(void) {
|
static void text_mode_clear_buffer() {
|
||||||
memset(text_mode_buffer, 0, sizeof(text_mode_buffer));
|
memset(text_mode_buffer, 0, sizeof(text_mode_buffer));
|
||||||
console_row = 0;
|
|
||||||
console_col = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void text_mode_scroll_up(void) {
|
static void text_mode_scroll_up() {
|
||||||
memmove(&text_mode_buffer[0][0],
|
memmove(&text_mode_buffer[0][0],
|
||||||
&text_mode_buffer[1][0],
|
&text_mode_buffer[1][0],
|
||||||
(CHAR_LINES - 1) * CHARS_PER_LINE * sizeof(text_mode_buffer[0][0]));
|
(CHAR_LINES - 1) * CHARS_PER_LINE * sizeof(text_mode_buffer[0][0]));
|
||||||
memset(text_mode_buffer[CHAR_LINES - 1], 0, CHARS_PER_LINE * sizeof(text_mode_buffer[0][0]));
|
memset(text_mode_buffer[CHAR_LINES - 1], 0, CHARS_PER_LINE * sizeof(text_mode_buffer[0][0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void text_mode_newline(void) {
|
static void pixel_mode_write_byte(uint16_t x, uint16_t y, uint8_t pixels) {
|
||||||
console_col = 0;
|
if (x >= VIDEO_WIDTH || y >= VIDEO_HEIGHT) {
|
||||||
if (console_row + 1 >= CHAR_LINES) {
|
return;
|
||||||
text_mode_scroll_up();
|
}
|
||||||
|
|
||||||
|
for (uint bit_index = 0; bit_index < 8 && x + bit_index < VIDEO_WIDTH;
|
||||||
|
bit_index++) {
|
||||||
|
uint pixel_x = x + bit_index;
|
||||||
|
uint word = pixel_x >> 5;
|
||||||
|
uint bit = 31u - (pixel_x & 31u);
|
||||||
|
uint32_t mask = 1u << bit;
|
||||||
|
|
||||||
|
if ((pixels & (1u << (7u - bit_index))) != 0) {
|
||||||
|
pixel_mode_buffer[y][word] |= mask;
|
||||||
} else {
|
} else {
|
||||||
console_row++;
|
pixel_mode_buffer[y][word] &= ~mask;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void text_mode_put_char(char c) {
|
|
||||||
if (c == '\r') {
|
|
||||||
console_col = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '\n') {
|
|
||||||
text_mode_newline();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '\b' || c == 0x7f) {
|
|
||||||
if (console_col > 0) {
|
|
||||||
console_col--;
|
|
||||||
text_mode_buffer[console_row][console_col] = 0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((unsigned char)c < 32 || (unsigned char)c > 126) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
text_mode_buffer[console_row][console_col] = c;
|
|
||||||
|
|
||||||
console_col++;
|
|
||||||
if (console_col >= CHARS_PER_LINE) {
|
|
||||||
text_mode_newline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void text_mode_puts(const char *s) {
|
|
||||||
while (*s) {
|
|
||||||
text_mode_put_char(*s++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void poll_usb_console(void) {
|
|
||||||
int ch;
|
|
||||||
while ((ch = getchar_timeout_us(0)) != PICO_ERROR_TIMEOUT) {
|
|
||||||
text_mode_put_char((char)ch);
|
|
||||||
putchar(ch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
stdio_init_all();
|
|
||||||
multicore_launch_core1(core1_entry);
|
multicore_launch_core1(core1_entry);
|
||||||
|
|
||||||
init_font_cache();
|
init_font_cache();
|
||||||
@@ -104,33 +66,57 @@ void main() {
|
|||||||
video_init(fb0);
|
video_init(fb0);
|
||||||
|
|
||||||
text_mode_clear_buffer();
|
text_mode_clear_buffer();
|
||||||
text_mode_puts("+------------------------------+\r\n");
|
memset(pixel_mode_buffer, 0, sizeof(pixel_mode_buffer));
|
||||||
text_mode_puts("| Pico-PAL Text Mode |\r\n");
|
|
||||||
text_mode_puts("+------------------------------+\r\n");
|
|
||||||
|
|
||||||
gpio_init(25);
|
|
||||||
gpio_set_dir(25, GPIO_OUT);
|
|
||||||
|
|
||||||
int fbnum = 1;
|
int fbnum = 1;
|
||||||
video_framebuffer_ptr_t fbs[] = {fb0, fb1};
|
video_framebuffer_ptr_t fbs[] = {fb0, fb1};
|
||||||
while (true) {
|
while (true) {
|
||||||
poll_usb_console();
|
video_command_t cmd;
|
||||||
|
while (core0_try_read_from_core1((uint64_t*)&cmd)) {
|
||||||
uint32_t value;
|
switch(cmd.cmd){
|
||||||
while (core0_try_read_from_core1(&value)) {
|
case VIDEO_COMMAND_MODE_SET:{
|
||||||
char ch = (char)value;
|
switch(cmd.data[0]){
|
||||||
text_mode_put_char((char)ch);
|
case VIDEO_MODE_TEXT:
|
||||||
putchar(ch);
|
text_mode = true;
|
||||||
|
break;
|
||||||
|
case VIDEO_MODE_PIXEL:
|
||||||
|
text_mode = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case VIDEO_WRITE_TEXT:{
|
||||||
|
char c = (char)cmd.data[0];
|
||||||
|
int posx = ((int)cmd.data[1] >= CHARS_PER_LINE) ? CHARS_PER_LINE-1 : (int)cmd.data[1];
|
||||||
|
int posy = ((int)cmd.data[2] >= CHAR_LINES) ? CHAR_LINES-1 : (int)cmd.data[2];
|
||||||
|
text_mode_buffer[posy][posx] = c;
|
||||||
|
} break;
|
||||||
|
case VIDEO_WRITE_PIXEL:{
|
||||||
|
pixel_mode_write_byte(cmd.data[1], cmd.data[2],
|
||||||
|
(uint8_t)cmd.data[0]);
|
||||||
|
} break;
|
||||||
|
case VIDEO_SCROLL_TEXT:{
|
||||||
|
text_mode_scroll_up();
|
||||||
|
} break;
|
||||||
|
case VIDEO_CLR_TEXT:{
|
||||||
|
text_mode_clear_buffer();
|
||||||
|
} break;
|
||||||
|
case VIDEO_CLR_PIXEL:{
|
||||||
|
memset(pixel_mode_buffer, 0, sizeof(pixel_mode_buffer));
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if need to draw new frame
|
// Check if need to draw new frame
|
||||||
if(framebuffer_switched){
|
if(framebuffer_switched){
|
||||||
gpio_put(25, fbnum);
|
|
||||||
|
|
||||||
if(text_mode){
|
if(text_mode){
|
||||||
draw_text_mode(fbs[fbnum], text_mode_buffer);
|
draw_text_mode(fbs[fbnum], text_mode_buffer);
|
||||||
|
} else {
|
||||||
|
memcpy(fbs[fbnum], pixel_mode_buffer,
|
||||||
|
sizeof(pixel_mode_buffer));
|
||||||
|
video_set_framebuffer(fbs[fbnum]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fbnum = (fbnum+1)%2;
|
fbnum = (fbnum+1)%2;
|
||||||
framebuffer_switched = false;
|
framebuffer_switched = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
|
|
||||||
// Text mode config
|
// Text mode config
|
||||||
#define CHARS_PER_LINE 60
|
#define CHARS_PER_LINE 60
|
||||||
#define CHAR_LINES 24
|
#define CHAR_LINES 18
|
||||||
#define CHAR_SCALE 2
|
#define CHAR_SCALE 2
|
||||||
|
|
||||||
// Text origin in framebuffer pixels. Set both to 0 for the top-left corner.
|
// Text origin in framebuffer pixels. Set both to 0 for the top-left corner.
|
||||||
#define TEXT_MARGIN_LEFT (30)
|
#define TEXT_MARGIN_LEFT (30)
|
||||||
#define TEXT_MARGIN_TOP (42)
|
#define TEXT_MARGIN_TOP (40)
|
||||||
|
|
||||||
// Crop glyph pixels before scaling. Padding is split across both sides; for
|
// Crop glyph pixels before scaling. Padding is split across both sides; for
|
||||||
// odd values, the extra pixel is removed from the right or bottom.
|
// odd values, the extra pixel is removed from the right or bottom.
|
||||||
#define GLYPH_PADDING_X 1
|
#define GLYPH_PADDING_X 1
|
||||||
#define GLYPH_PADDING_Y 4
|
#define GLYPH_PADDING_Y 0
|
||||||
|
|
||||||
// Set to 1 for a white background with black text, or 0 for the default
|
// Set to 1 for a white background with black text, or 0 for the default
|
||||||
// black background with white text.
|
// black background with white text.
|
||||||
|
|||||||
61
z80_bus.pio
61
z80_bus.pio
@@ -1,10 +1,59 @@
|
|||||||
.program z80_write_monitor
|
; Z80 register bus on PIO1.
|
||||||
|
;
|
||||||
|
; GP0 /OE, GP1 DIR, GP2..9 D0..D7, GP10 /IOREQ, GP11 /RD,
|
||||||
|
; GP12 CS (A7), GP13..14 A0..A1, GP15 CLK, GP16 /WAIT.
|
||||||
|
;
|
||||||
|
; side-set values for GP1:GP0:
|
||||||
|
; 0 = B->A enabled, 1 = B->A disabled
|
||||||
|
; 2 = A->B enabled, 3 = A->B disabled
|
||||||
|
|
||||||
|
.program z80_bus
|
||||||
|
.side_set 2 opt
|
||||||
|
|
||||||
.wrap_target
|
.wrap_target
|
||||||
wait 0 gpio 10 ; /IOREQ asserted
|
wait 0 gpio 10
|
||||||
|
nop [1] ; allow synchronized bus signals to settle
|
||||||
mov isr, null
|
mov isr, null
|
||||||
; IN base is GP0. Capture /OE, DIR, D0-D7, /IOREQ, /RD, CS,
|
in pins, 15 ; snapshot GP0..GP14
|
||||||
; A0 and A1 in their corresponding GPIO bit positions.
|
|
||||||
in pins, 15
|
; Decode /RD (bit 11) and CS (bit 12), preserving ISR for core 1.
|
||||||
|
mov osr, isr
|
||||||
|
out null, 11
|
||||||
|
out x, 1 ; X = /RD
|
||||||
|
out y, 1 ; Y = CS
|
||||||
|
jmp !y cycle_done
|
||||||
|
jmp !x read_cycle
|
||||||
|
|
||||||
|
write_cycle:
|
||||||
push block
|
push block
|
||||||
wait 1 gpio 10 ; do not count this bus cycle again
|
jmp cycle_done
|
||||||
|
|
||||||
|
read_cycle:
|
||||||
|
set pins, 0 side 1 ; assert /WAIT, disable transceiver
|
||||||
|
push block ; send address request to core 1
|
||||||
|
pull block side 1 ; wait for response byte
|
||||||
|
|
||||||
|
; Prepare data and direction while the transceiver remains disabled.
|
||||||
|
mov x, osr side 1
|
||||||
|
mov osr, ~null side 3 ; select A->B
|
||||||
|
out pindirs, 8 side 3 ; GP2..GP9 outputs
|
||||||
|
mov osr, x side 3
|
||||||
|
out pins, 8 side 3 ; put response on A-side pins
|
||||||
|
|
||||||
|
; Keep /WAIT asserted through a real falling Z80 clock edge. PIO observes
|
||||||
|
; the edge through its synchronizer and releases /WAIT just afterwards,
|
||||||
|
; guaranteeing that the Z80 sampled it low and inserted a wait state.
|
||||||
|
wait 1 gpio 15 side 3
|
||||||
|
wait 0 gpio 15 side 3
|
||||||
|
set pins, 1 side 2 ; enable A->B and release /WAIT
|
||||||
|
|
||||||
|
wait 1 gpio 11 side 2 ; wait for /RD to deassert
|
||||||
|
nop side 3 ; disable before changing direction
|
||||||
|
mov osr, null side 3
|
||||||
|
out pindirs, 8 side 3 ; GP2..GP9 inputs
|
||||||
|
nop side 1 ; select B->A while disabled
|
||||||
|
nop side 0 ; enable B->A for write reception
|
||||||
|
|
||||||
|
cycle_done:
|
||||||
|
wait 1 gpio 10
|
||||||
.wrap
|
.wrap
|
||||||
|
|||||||
Reference in New Issue
Block a user