From 55c1cb180d99a6328f06a9184566051a633def4f Mon Sep 17 00:00:00 2001
From: Oliver Schmidt
Date: Fri, 19 Aug 2022 15:10:26 +0200
Subject: [PATCH] Added implementation of the alternative hardware type.
The implemenation is based on a Raspberry Pi Pico - see https://github.com/a2retrosystems/A2retroNET
The new hardware type has a simple UART-type interface. Beside the two data registers, there are only the two bits to indicate readability and writability of the data registers.
---
RaspberryPiPico/CMakeLists.txt | 27 ++++
RaspberryPiPico/LICENSE | 21 +++
RaspberryPiPico/board.c | 73 +++++++++
RaspberryPiPico/bus.pio | 206 ++++++++++++++++++++++++++
RaspberryPiPico/incbin.S | 6 +
RaspberryPiPico/main.c | 122 +++++++++++++++
RaspberryPiPico/pico_sdk_import.cmake | 73 +++++++++
7 files changed, 528 insertions(+)
create mode 100644 RaspberryPiPico/CMakeLists.txt
create mode 100644 RaspberryPiPico/LICENSE
create mode 100644 RaspberryPiPico/board.c
create mode 100644 RaspberryPiPico/bus.pio
create mode 100644 RaspberryPiPico/incbin.S
create mode 100644 RaspberryPiPico/main.c
create mode 100644 RaspberryPiPico/pico_sdk_import.cmake
diff --git a/RaspberryPiPico/CMakeLists.txt b/RaspberryPiPico/CMakeLists.txt
new file mode 100644
index 0000000..1364538
--- /dev/null
+++ b/RaspberryPiPico/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(PROJECT_NAME Apple2-IO-RPi)
+
+cmake_minimum_required(VERSION 3.12)
+
+include(pico_sdk_import.cmake)
+pico_sdk_init()
+
+project(${PROJECT_NAME} C CXX ASM)
+add_executable(${PROJECT_NAME})
+pico_add_extra_outputs(${PROJECT_NAME})
+
+pico_enable_stdio_uart(${PROJECT_NAME} 0)
+pico_enable_stdio_usb(${PROJECT_NAME} 1)
+
+pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/bus.pio)
+
+target_sources(${PROJECT_NAME} PRIVATE
+ main.c
+ board.c
+ incbin.S
+ )
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ pico_stdlib
+ pico_multicore
+ hardware_pio
+ )
diff --git a/RaspberryPiPico/LICENSE b/RaspberryPiPico/LICENSE
new file mode 100644
index 0000000..8617998
--- /dev/null
+++ b/RaspberryPiPico/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/RaspberryPiPico/board.c b/RaspberryPiPico/board.c
new file mode 100644
index 0000000..e8e6dc0
--- /dev/null
+++ b/RaspberryPiPico/board.c
@@ -0,0 +1,73 @@
+/*
+
+MIT License
+
+Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+#include "pico/multicore.h"
+
+#include "bus.pio.h"
+
+extern const __attribute__((aligned(4))) uint8_t firmware[];
+
+static uint32_t __not_in_flash("page") page;
+
+void __not_in_flash_func(board)() {
+ while (true) {
+ uint32_t enbl = pio_sm_get_blocking(pio0, sm_enbl);
+ uint32_t addr = enbl & 0x0FFF;
+ uint32_t io = enbl & 0x0F00; // IOSTRB or IOSEL
+ uint32_t strb = enbl & 0x0800; // IOSTRB
+ uint32_t read = enbl & 0x1000; // R/W
+
+ if (read) {
+ if (!io) {
+ switch (addr & 0x7) {
+ case 0x3:
+ pio_sm_put(pio0, sm_read, !multicore_fifo_rvalid() << 7 |
+ !multicore_fifo_wready() << 6);
+ break;
+ case 0x6:
+ pio_sm_put(pio0, sm_read, sio_hw->fifo_rd);
+ break;
+ }
+ } else {
+ if (!strb) {
+ pio_sm_put(pio0, sm_read, firmware[page | addr]);
+ }
+ }
+ } else {
+ uint32_t data = pio_sm_get_blocking(pio0, sm_write);
+ if (!io) {
+ switch (addr & 0x7) {
+ case 0x5:
+ sio_hw->fifo_wr = data;
+ break;
+ case 0x7:
+ page = (data & 0x30) << 7;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/RaspberryPiPico/bus.pio b/RaspberryPiPico/bus.pio
new file mode 100644
index 0000000..496bb65
--- /dev/null
+++ b/RaspberryPiPico/bus.pio
@@ -0,0 +1,206 @@
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+
+MIT License
+
+Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+
+.define public gpio_addr 2 // 12 pins
+.define public gpio_rw 14
+.define public gpio_data 15 // 8 pins
+.define public gpio_led 25
+.define public gpio_enbl 26 // DEVSEL | IOSEL | IOSTRB
+.define public gpio_irq 27
+.define public gpio_rdy 28
+
+.define public size_addr 13 // incl. R/W
+.define public size_data 8
+
+.define public sm_enbl 0
+.define public sm_read 1 // from Apple II perspective
+.define public sm_write 2 // from Apple II perspective
+
+.define irq_write 4
+
+/*
+
+Implementation of the Apple II peripheral bus protocol:
+
+- /DEVSEL, /IOSEL and /IOSTRB are supposed to combined to ENBL via an AND gate.
+
+- On the falling edge of ENBL, the lines A0-A11 and R/W are sampled and pushed
+ into the 'enable' state machine RX FIFO.
+
+- In case of an Apple II write cycle, the lines D0-D7 are sampled ~300ns later
+ and pushed into the 'write' state machine RX FIFO.
+
+- If a byte is pushed into the 'read' state machine TX FIFO, it is driven out
+ to the lines D0-D7 until the rising edge of ENBL.
+
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+
+.program enbl
+
+.wrap_target
+
+idle:
+ wait 1 gpio gpio_enbl // wait for ENBL to rise
+ wait 0 gpio gpio_enbl // wait for ENBL to fall
+
+ in pins, size_addr // shift A0-A11 + R/W into ISR
+
+ jmp pin idle // jump to idle if R/W is high
+
+ irq irq_write // Set 'write' IRQ
+
+.wrap
+
+% c-sdk {
+static inline void enbl_program_init(uint offset) {
+ pio_sm_config c = enbl_program_get_default_config(offset);
+
+ // in_base: gpio_addr
+ sm_config_set_in_pins(&c, gpio_addr);
+
+ // shift_right: false
+ // autopush: true
+ // push_threshold: size_addr
+ sm_config_set_in_shift(&c, false, true, size_addr);
+
+ // pin: gpio_rw
+ sm_config_set_jmp_pin(&c, gpio_rw);
+
+ // state_machine: sm_enbl
+ // initial_pc: offset
+ pio_sm_init(pio0, sm_enbl, offset, &c);
+
+ // state_machine: sm_enbl
+ pio_sm_set_enabled(pio0, sm_enbl, true);
+}
+%}
+
+///////////////////////////////////////////////////////////////////////////////
+
+.program write
+
+.wrap_target
+
+ wait 1 irq irq_write [31] // wait for 'write' IRQ to be set and clear it
+ // [31 cycles to allow 6502 to set up D0-D7]
+
+ in pins, size_data // shift D0-D7 into ISR
+
+.wrap
+
+% c-sdk {
+static inline void write_program_init(uint offset) {
+ pio_sm_config c = write_program_get_default_config(offset);
+
+ // in_base: gpio_data
+ sm_config_set_in_pins(&c, gpio_data);
+
+ // shift_right: false
+ // autopush: true
+ // push_threshold: size_data
+ sm_config_set_in_shift(&c, false, true, size_data);
+
+ // state_machine: sm_write
+ // initial_pc: offset
+ pio_sm_init(pio0, sm_write, offset, &c);
+
+ // state_machine: sm_write
+ pio_sm_set_enabled(pio0, sm_write, true);
+}
+%}
+
+///////////////////////////////////////////////////////////////////////////////
+
+.program read
+
+/*
+
+Both set and set-side are limited to 5 pins each. So both set and side-set are
+configured to set (the direction of) 4 pins. This approach allows to set the
+direction of D0-D7 in one operation.
+
+Note: The naive approach would have been
+ mov osr, ~x // move 0xFFFFFFFF to OSR
+ out pindirs, size_data // enable output of D0-D7
+ [...]
+ mov osr, x // move 0x00000000 to OSR
+ out pindirs, size_data // disable output of D0-D7
+but this would have required two operations and destroyed OSR.
+
+*/
+
+.side_set (size_data / 2) opt pindirs
+
+.wrap_target
+
+ pull block // pull data into OSR, block on empty FIFO
+
+ out pins, size_data // shift OSR out to D0-D7
+
+ set pindirs, 15 side 15 // enable output of D0-D7
+
+ wait 1 gpio gpio_enbl // wait for ENBL to rise
+
+ set pindirs, 0 side 0 // disable output of D0-D7
+
+.wrap
+
+% c-sdk {
+static inline void read_program_init(uint offset) {
+ pio_sm_config c = read_program_get_default_config(offset);
+
+ // out_base: gpio_data
+ // out_count: size_data
+ sm_config_set_out_pins(&c, gpio_data, size_data);
+
+ // shift_right: true
+ // autopull: false
+ // pull_threshold: size_data
+ sm_config_set_out_shift(&c, true, false, size_data);
+
+ // set_base: gpio_data
+ // set_count: size_data / 2
+ sm_config_set_set_pins(&c, gpio_data, size_data / 2);
+
+ // sideset_base: gpio_data + size_data / 2
+ sm_config_set_sideset_pins(&c, gpio_data + size_data / 2);
+
+ // state_machine: sm_read
+ // initial_pc: offset
+ pio_sm_init(pio0, sm_read, offset, &c);
+
+ // state_machine: sm_read
+ pio_sm_set_enabled(pio0, sm_read, true);
+}
+%}
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/RaspberryPiPico/incbin.S b/RaspberryPiPico/incbin.S
new file mode 100644
index 0000000..c43f6fb
--- /dev/null
+++ b/RaspberryPiPico/incbin.S
@@ -0,0 +1,6 @@
+.section .time_critical.firmware
+.global firmware
+.type firmware, %object
+.balign 4
+firmware:
+.incbin "../../Apple2/AT28C64B.bin"
diff --git a/RaspberryPiPico/main.c b/RaspberryPiPico/main.c
new file mode 100644
index 0000000..6184a85
--- /dev/null
+++ b/RaspberryPiPico/main.c
@@ -0,0 +1,122 @@
+/*
+
+MIT License
+
+Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+#include
+#include "pico/printf.h"
+#include "pico/stdlib.h"
+#include "pico/multicore.h"
+
+#include "bus.pio.h"
+
+void board();
+
+#ifdef TRACE
+void uart_printf(uart_inst_t *uart, const char *format, ...) {
+ static char buffer[0x100];
+
+ va_list va;
+ va_start(va, format);
+ vsnprintf(buffer, sizeof(buffer), format, va);
+ va_end(va);
+
+ buffer[0xFF] = '\0';
+ uart_puts(uart, buffer);
+}
+#endif
+
+int main() {
+ stdio_init_all();
+ stdio_set_translate_crlf(&stdio_usb, false);
+
+#ifdef TRACE
+ uart_init(uart0, 115200);
+ uart_set_translate_crlf(uart0, true);
+ gpio_set_function(0, GPIO_FUNC_UART);
+ gpio_set_function(1, GPIO_FUNC_UART);
+#endif
+
+ for (uint gpio = gpio_addr; gpio < gpio_addr + size_addr; gpio++) {
+ gpio_init(gpio);
+ gpio_set_pulls(gpio, false, false); // floating
+ }
+
+ for (uint gpio = gpio_data; gpio < gpio_data + size_data; gpio++) {
+ pio_gpio_init(pio0, gpio);
+ gpio_set_pulls(gpio, false, false); // floating
+ }
+
+ gpio_init(gpio_enbl);
+ gpio_set_pulls(gpio_enbl, false, false); // floating
+
+ gpio_init(gpio_irq);
+ gpio_pull_up(gpio_irq);
+
+ gpio_init(gpio_rdy);
+ gpio_pull_up(gpio_rdy);
+
+ gpio_init(gpio_led);
+ gpio_set_dir(gpio_led, GPIO_OUT);
+
+ uint offset;
+
+ offset = pio_add_program(pio0, &enbl_program);
+ enbl_program_init(offset);
+
+ offset = pio_add_program(pio0, &write_program);
+ write_program_init(offset);
+
+ offset = pio_add_program(pio0, &read_program);
+ read_program_init(offset);
+
+ multicore_launch_core1(board);
+
+ while (true) {
+ bool conn = stdio_usb_connected();
+ if (conn) {
+ if (multicore_fifo_rvalid()) {
+ uint32_t data = multicore_fifo_pop_blocking();
+ putchar(data);
+#ifdef TRACE
+ uart_printf(uart0, "> %02X\n", data);
+#endif
+ }
+ }
+
+ if (multicore_fifo_wready()) {
+ int data = getchar_timeout_us(0);
+ if (data != PICO_ERROR_TIMEOUT) {
+ multicore_fifo_push_blocking(data);
+#ifdef TRACE
+ uart_printf(uart0, "< %02X\n", data);
+#endif
+ }
+ }
+
+ gpio_put(gpio_led, conn);
+ }
+
+ return 0;
+}
diff --git a/RaspberryPiPico/pico_sdk_import.cmake b/RaspberryPiPico/pico_sdk_import.cmake
new file mode 100644
index 0000000..65f8a6f
--- /dev/null
+++ b/RaspberryPiPico/pico_sdk_import.cmake
@@ -0,0 +1,73 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG master
+ GIT_SUBMODULES_RECURSE FALSE
+ )
+ else ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG master
+ )
+ endif ()
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ FetchContent_Populate(pico_sdk)
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})