# # Copyright 2024 Thomas Shanks (thomas.shanks@gmail.com) # # Licensed under the MIT License, which is included in the 'LICENSE' file. # # Based on ideas and advice from Henry Strickland (strick@yak.net) # import time import machine import rp2 # Data Output Enable changes the direction of the 3-state Bus Transciever; when # asserted, this will drive the output of RP2040 GPIO 1-7 onto the CoCo data bus DOE_N_PIN = 12 doe_n = machine.Pin(DOE_N_PIN, machine.Pin.OUT, value=1) # Connected to CoCo data bus via 3-state Bus Transciever; see doe_n above DATA_BUS_FIRST_PIN = 0 DATA_BUS_WIDTH = 8 data_bus = [machine.Pin(DATA_BUS_FIRST_PIN + i, machine.Pin.OUT, value=0) for i in range(DATA_BUS_WIDTH)] DATA_INIT_STATE_TUPLE = (rp2.PIO.OUT_HIGH, ) * DATA_BUS_WIDTH HALT_N_PIN = 8 SLENB_N_PIN = 9 halt_n = machine.Pin(HALT_N_PIN, machine.Pin.OUT, value=1) slenb_n = machine.Pin(SLENB_N_PIN, machine.Pin.OUT, value=1) RESET_N_PIN = 10 E_CLOCK_PIN = 11 reset_n = machine.Pin(RESET_N_PIN, machine.Pin.IN) e_clock = machine.Pin(E_CLOCK_PIN, machine.Pin.IN) # Number of cycles after reset to send the reset vector #CYCLES_BEFORE_RESET_VECTOR = ?? # Moved into PIO program # ID of PIO to run each of the two PIO programs on RUN_WAIT_FOR_RESET_PROG_ON_PIO_ID = 0 IRQ_THAT_SHOULD_LAUNCH_SEND_RESET_VECTOR_PROG = rp2.PIO.IRQ_SM0 RUN_SEND_RESET_VECTOR_PROG_ON_PIO_ID = 1 # Reset vector to provide when the MC6809 tries to load it from RAM RESET_VECTOR = 0xA027 ## 0x8C1B # We will place the reset vector on the requisite PIO's TX FIFO before we start the send_reset_vector PIO # program so that it will already be there when the time comes to send it to the MC6809. # # Since the TX FIFO operates one 32-bit word at a time, we put the 16-bit reset vector into the FIFO twice to # avoid the need to consume 16 bits of useless zeros out of the FIFO each time. RESET_VECTOR_TWICE = (RESET_VECTOR << 16) & RESET_VECTOR @rp2.asm_pio(set_init=rp2.PIO.OUT_HIGH) def wait_for_reset(): # Constants don't carry across into PIO code # TODO: Find a way to avoid duplicating these constants! RESET_N_PIN = 10 wait(0, gpio, RESET_N_PIN) # Wait until we see RESET being asserted # type: ignore set(pins, 0) # Assert HALT # type: ignore wait(1, gpio, RESET_N_PIN) # Wait until we see RESET no longer being asserted # type: ignore irq(0) # Send interrupt to tell CPU to handle the RESET # type: ignore wait(1, irq, 4) # Wait for the reset vector to be sent before continuing # type: ignore @rp2.asm_pio(set_init=(rp2.PIO.OUT_HIGH, rp2.PIO.OUT_HIGH), sideset_init=rp2.PIO.OUT_HIGH, out_init=DATA_INIT_STATE_TUPLE, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=16) def send_reset_vector(): # Constants don't carry across into PIO code # TODO: Find a way to avoid duplicating these constants! CYCLES_BEFORE_RESET_VECTOR = 3 E_CLOCK_PIN = 11 DATA_BUS_WIDTH = 8 set(pins, 0b01) # Assert SLENB and stop asserting HALT # type: ignore # set counter (reg 'x') to # of cycles to wait set(x, CYCLES_BEFORE_RESET_VECTOR) # type: ignore label('keep_waiting') # do { # type: ignore wait(1, gpio, E_CLOCK_PIN) # wait for E clock to go high # type: ignore wait(0, gpio, E_CLOCK_PIN) # then wait for E clock to go low # type: ignore jmp(x_dec, 'keep_waiting') # } while( x-- > 0 ); # type: ignore wait(1, gpio, E_CLOCK_PIN) # wait for E clock to go high # type: ignore # Put high byte of reset vector on bus and assert DOE out(pins, DATA_BUS_WIDTH) .side(0) # type: ignore wait(0, gpio, E_CLOCK_PIN) # wait for E clock to go low # type: ignore wait(1, gpio, E_CLOCK_PIN) # then wait for E clock to go high # type: ignore out(pins, DATA_BUS_WIDTH) # Put low byte of reset vector on bus # type: ignore wait(0, gpio, E_CLOCK_PIN) # wait for E clock to go low # type: ignore set(pins, 0b11) .side(1) # Stop asserting DOE and SLENB # type: ignore irq(4) # Tell other PIO to resume watching for RESET # type: ignore # Terminate (actually, wait forever) label('wait_forever') # type: ignore jmp('wait_forever') # while True: pass # type: ignore wait_for_reset_sm = rp2.StateMachine( RUN_WAIT_FOR_RESET_PROG_ON_PIO_ID, wait_for_reset, freq=125_000_000, set_base=halt_n, ) send_reset_vector_sm = rp2.StateMachine( RUN_SEND_RESET_VECTOR_PROG_ON_PIO_ID, send_reset_vector, freq=125_000_000, set_base=halt_n, sideset_base=doe_n, out_base=data_bus[0], ) def reset_irq_handler(pio): print('Handling RESET IRQ') #print(pio.irq().flags()) # stop the PIO program that sends the reset vector here if it's already running if send_reset_vector_sm.active(): send_reset_vector_sm.active(False) send_reset_vector_sm.restart() time.sleep(2) # Place the reset vector onto the TX FIFO if the TX FIFO is empty # # (Since the TX FIFO operates one 32-bit word at a time, we put the 16-bit reset vector into the FIFO # twice to avoid the need to consume 16 bits of useless zeros out of the FIFO each time.) # # TODO: See if setting "shift=16" here (instead of sending the vector twice) will avoid the extra zeros # getting into the OSR! if send_reset_vector_sm.tx_fifo() == 0: send_reset_vector_sm.put(RESET_VECTOR_TWICE) ## RESET_VECTOR, shift=16) send_reset_vector_sm.active(True) print('Done handling RESET IRQ') # Set up the handler for the IRQ that indicates a RESET is in progress # # TODO: What does "hard=True" do? rp2.PIO(RUN_WAIT_FOR_RESET_PROG_ON_PIO_ID).irq( reset_irq_handler, trigger=IRQ_THAT_SHOULD_LAUNCH_SEND_RESET_VECTOR_PROG, ##hard=True ) wait_for_reset_sm.active(True) last_vals = {} def print_if_changed(var_name, val): try: last_val = last_vals[var_name] except KeyError: last_val = None if val != last_val: last_vals[var_name] = val print(f'{var_name}: {val}') while True: print_if_changed('reset', reset_n.value()) print_if_changed('halt', halt_n.value()) time.sleep(1)