diff --git a/pipico/CMakeLists.txt b/pipico/CMakeLists.txt new file mode 100644 index 0000000..243db6a --- /dev/null +++ b/pipico/CMakeLists.txt @@ -0,0 +1,27 @@ +set(PROJECT_NAME Apple-II-Pi) + +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/pipico/LICENSE b/pipico/LICENSE new file mode 100644 index 0000000..8617998 --- /dev/null +++ b/pipico/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/pipico/board.c b/pipico/board.c new file mode 100644 index 0000000..04c3e2d --- /dev/null +++ b/pipico/board.c @@ -0,0 +1,93 @@ +/* + +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 "bus.pio.h" + +extern const __attribute__((aligned(4))) uint8_t firmware[]; + +static bool __not_in_flash( "active") active; +static uint32_t __not_in_flash("command") command; +static uint32_t __not_in_flash("control") control; + +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 (addr == 0x0FFF) { + active = false; + } + + if (read) { + if (!io) { // DEVSEL + switch (addr & 0xF) { + case 0x8: + pio_sm_put(pio0, sm_read, sio_hw->fifo_rd); + break; + case 0x9: + // SIO_FIFO_ST_VLD_BITS _u(0x00000001) + // SIO_FIFO_ST_RDY_BITS _u(0x00000002) + pio_sm_put(pio0, sm_read, (sio_hw->fifo_st & 3) << 3); + break; + case 0xA: + pio_sm_put(pio0, sm_read, command); + break; + case 0xB: + pio_sm_put(pio0, sm_read, control); + break; + } + } else { + if (!strb || active) { + pio_sm_put(pio0, sm_read, firmware[addr]); + } + } + } else { + uint32_t data = pio_sm_get_blocking(pio0, sm_write); + if (!io) { // DEVSEL + switch (addr & 0xF) { + case 0x8: + sio_hw->fifo_wr = data; + break; + case 0xA: + command = data; + break; + case 0xB: + control = data; + break; + } + } + } + + if (io && !strb) { + active = true; + } + + gpio_put(gpio_led, active); + } +} diff --git a/pipico/bus.pio b/pipico/bus.pio new file mode 100644 index 0000000..496bb65 --- /dev/null +++ b/pipico/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/pipico/incbin.S b/pipico/incbin.S new file mode 100644 index 0000000..50ef96a --- /dev/null +++ b/pipico/incbin.S @@ -0,0 +1,6 @@ +.section .time_critical.firmware +.global firmware +.type firmware, %object +.balign 4 +firmware: +.incbin "../../pidrive/PIROM#062000" diff --git a/pipico/main.c b/pipico/main.c new file mode 100644 index 0000000..9e67110 --- /dev/null +++ b/pipico/main.c @@ -0,0 +1,117 @@ +/* + +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) { + 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 + } + } + } + + return 0; +} diff --git a/pipico/pico_sdk_import.cmake b/pipico/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/pipico/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})