ASPEN32 wrote:
So...I got another beginner's project (maybe a cheater's...) idea I'd like to implement using at most (or as little) as 2 or 3 chips - a 6502 mated with a Raspberry Pi Pico. Here's what I'd like to design...
* A 6502 SBC based around a 6502 CPU and a Pico.
* I would like the Pico to emulate RAM/ROM and VIA/UART through USB serial terminal software like PuTTY. I can supply the logic chips if necessary (74xx series, etc.)
* If necessary, I'd like to have removeable storage via SD card
For starters, I'd like to just see if there is a way I can test communications between the Pico and the 6502 and work up from there. I'm using a Rockwell R65C02 if it makes a difference. Does anyone know where a good place to start with this would be and how I can work up from that?
I see you have plenty of answers of the form "Google is your friend" but as I started on basically the exact same path as you I thought I could share my experience.
Inspired by Ben Eater's videos (and later disappointed to discover that Garth wasn't credited for the core design ideas) I thought I'd start with a Pico W on a breadboard wired to be a semi-permanent in circuit debugger for the 6502 bus. The Pico W has GP0 - GP15 in order and this makes for very easy setting or inspecting the address bus; like you, I'm most comfortable in C but the micro python environment makes getting in and poking at the device so easy that it was worth learning micropython on the fly to be able to program the computer.
So to get started, I made a file named `bus.py` to make it possible to inspect the bus, and the address code looks like this:
Code:
from machine import mem32, Pin
import time
SIO_BASE = 0xD0000000
GPIO_IN = SIO_BASE + 0x0004
GPIO_OUT = SIO_BASE + 0x010
GPIO_OUT_SET = SIO_BASE + 0x014
GPIO_OUT_CLR = SIO_BASE + 0x018
GPIO_OUT_XOR = SIO_BASE + 0x01C
GPIO_OE = SIO_BASE + 0x020
GPIO_OE_SET = SIO_BASE + 0x024
GPIO_OE_CLR = SIO_BASE + 0x028
GPIO_OE_XOR = SIO_BASE + 0x02C
for n in range(16 + 7):
p = Pin(n, Pin.IN)
p = Pin(26, Pin.IN)
# address bus as outputs
MASK = 0x7FFFFF | (1 << 26)
DATA_MASK = 0x7F0000 | (1 << 26)
ADDR_MASK = 0x00FFFF
def address(a):
"Set address lines"
mem32[GPIO_OUT_CLR] = ADDR_MASK
mem32[GPIO_OUT_SET] = a
mem32[GPIO_OE_SET] = ADDR_MASK
def readaddress():
"Get address lines"
mem32[GPIO_OE_CLR] = ADDR_MASK
return mem32[GPIO_IN] & 0xFFFF
So this lets you read the 16 GPIOs that make up the address bus in one shot. The next 8 GPIOs are not sequential, so you have to shift the high bit into place for the data lines (that's why the data mask is defined as
0x7F0000 | (1 << 26)):
Code:
def dataraw(v):
"Set data with bits positioned with the high bit offset"
mem32[GPIO_OE_SET] = DATA_MASK
mem32[GPIO_OUT_CLR] = DATA_MASK
mem32[GPIO_OUT_SET] = v
def databyte(b):
"Set data after moving high bit over"
raw = ((b & 0x7F) << 16) | ((b & 0x80) << (26 - 7))
dataraw(raw)
def datarawread():
"Read the raw GPIOs masked for data pins only"
mem32[GPIO_OE_CLR] = DATA_MASK
return mem32[GPIO_IN]
def dataread():
"Read a byte of data from the data bus."
raw = datarawread()
value = ((raw >> 16) | (raw & (1 << 26)) >> (26 - 7)) & 0xFF
return value
Armed with just these two functions you can snoop on the data bus.
I don't know if the Rockwell chip you are using will work properly with the clock halted, check your datasheet. The chip I have will patiently await the clock edges and I start with the clock low and wired to GPIO 28.
For better or worse, that accounts for
all pins available on the Pico W design. For me, this is an asset that will stop me building too much functionality into the pico itself; it can respond to the bus and deliver "RAM" to the computer, but I'm not even doing that with it anymore. However back at the start, I had just the 6502 on the bus plus the pico, and had the pico serve up small programs on $FF00 by simply embedding and array into the micropython code and returning those values to the bus when the CPU read out of that address range. This enabled wiring up the 6522 VIA and a two line display to see that everything was working, before adding any ROM or RAM to the system.
Code:
cycle = 0
ram = bytearray([0xEA] * 256)
ram[0xFC] = 0x00
ram[0xFD] = 0xFF
def clockmon(_):
"Put up the monitor output on each rising clock edge"
global cycle
rw = Pin(27, Pin.IN).value()
a = readaddress()
if a == 0xFFFC:
cycle = -1
d = 0
if rw and (a & 0xFF00) == 0xFF00:
offset = a & 0xFF
d = ram[offset]
databyte(d)
else:
d = dataread()
print(f"{cycle:06d} {a:04x}: {rw:01b} {d:02x}")
cycle += 1
pin = Pin(28, Pin.IN, Pin.PULL_DOWN)
pin.irq(trigger=Pin.IRQ_RISING, handler=clockmon)
from hello import code
for i in range(len(code)):
ram[i] = code[i]
This produces a cycle counter, address, read write flag, and data bus monitor line for each tick of the clock. To change the clock to Pico control (I found early on I preferred to have a debounced button wired to pin 28 rather than software driven clock), you need something like this:
Code:
def step():
clock = Pin(28, Pin.OUT)
clock.value(1)
time.sleep(0.01)
clock.value(0)
It ought to be possible to use the code as given with the pico wired to the address and data bus, the R/W signal wired to GPIO 27, and the clock wired to the CPU's clock. Hope that's helpful.
PhotoEdit: I forgot to say that the command line I use to get into the REPL is
Code:
mpremote connect /dev/tty.usbmodem1101 mount . run bus.py repl
and this drops you into a python REPL where you can then call the functions above at the >>> prompt.