Skip to the content
PCBKitNew board
2 layers99 × 96.5 mm6502-pactvm v01, the printed circuit board in 3D

Resources for an 8 bit computer based on the 6502 micro processor

Size
99 × 96.5 mm
Copper layers
2
Thickness
1.6 mm
Parts
15
Nets
88
Pads
224
Tracks, vias
829, 10
Routed
Yes
KiCad
KiCad 8.0 (file 20240108)
Created
6 Jun 2020
Updated
25 Apr 2025

Overview

6502-pactvm v01 is an open-source printed circuit board by napicella, from the napicella/8-bit-computer repository on GitHub. It is a 2-layer board of 99 × 96.5 mm with 15 parts on 88 nets, routed with 829 tracks and 10 vias.

It is built around the UM245R, 74HC00D and 74HC4075D, with the AT28C256-15PU and MXO45T-2C-1M000000 among its other parts. Its parts include 8 ICs, 4 resistors, 1 connector, 1 switch and 1 crystal. It belongs with the retro computing and games boards of this library.

Main components

5

README

README.md

Resources for an 8 bit computer based on the 6502 microprocessor.

Components

Check the datasheets folder for the list of components.

Memory mapping

Address (16 bits)Address (hex)DeviceLength
0000 0000 0000 00000x0000RAM
0111 1111 1111 11110x7FFFRAM0x8000
1000 0000 0000 00000x8000VIA
1000 1111 1111 11110x8FFFVIA0xFFF
1001 0000 0000 00000x9000UM245
1001 1111 1111 11110x9FFFUM2450xFFF
1010 0000 0000 0000Unused
1011 1111 1111 1111Unused
1110 0000 0000 00000xE000ROM
1111 0000 0000 00000xFFFFROM0x2000 = 0xFFFF-0xE000+1

The bits highlighted in the address are part of the address decoding which enable the chip. For example, if the most significant bit of the address is low, the RAM is enabled, if the three most significant bits are (100) the VIA is enabled. Address decoding is described later.

CPU stack (in RAM addresses)

The 65c02 needs a stack for the jsr and rst instructions (assembly subroutines).
The stack boundaries are set in the chip and expected to be between 0x0100 and 0x01FF. The stack grows from 0x01FF (top) to 0x0100 (bottom), for a total of 265 bytes.

Address (16 bits)Address (hex)DeviceLength
0000 0000 0000 00000x0000RAM
0000 0001 0000 00000x0100RAM Stack
0000 0001 1111 11110x01FFRAM Stack0xFF
0111 1111 1111 11110x7FFFRAM0x8000
VIA device

The whole address space between 0x8000 and 0x9FFF is mapped to the VIA to simplify the address decoding, but the VIA only needs the first 16 bytes in that address space.

Address (hex)Description
0x8000I/O Register B
0x8001I/O Register A
0x8002Data Direction Register B
0x8003Data Direction Register A
0x8004T1 Low Order Latches/Counter
0x8005T1 High Order Counter
0x8006T1 Low Order Latches
0x8007T1 High Order Latches
0x8008T2 Low Order Latches/Counter
0x8009T2 High Order Counter
0x800aShift Register
0x800bAuxiliary Control Register
0x800cPeripheral Control Register
0x800dInterrupt Flag Register
0x800eInterrupt Enable Register
0x800fI/O Register A sans Handshake
0x8010 - 0x8fffMirrors of the sixteen VIA registers
UM245 device

The whole address space between 0x9000 and 0x9FFF is mapped to the UM245 to simplify the address decoding, but the UM245 only needs the 3 bytes in that address space.

Address (hex)Description
0x9000Unused
0x9001Write
0x9002Read
0x9003Unused
0x9004Status
0x9005 - 0x900fUnused
0x9010 - 0x9fffMirrors

The UM245 contains an internal bidirectional FIFO buffer, decoupling the communication.
The Status address allows to check if the device is ready to accept data (MCU can write) and if the device has data to be read (MCU can read). If the device is busy for a write, the Status contains a 1 in the 1st bit (LSB). If the device does not have data to be read, the Status contains a 1 in the 2nd bit (LSB).

Example assembly routine to read one byte of data:

asm
UART_WRITE  = $9001
UART_READ   = $9002
UART_STATUS = $9004

UART_WRITE_BUSY_MASK   = %00000001
UART_READ_NO_DATA_MASK = %00000010

read_uart_byte:
read_uart_byte_loop:
  lda #UART_READ_NO_DATA_MASK
  and UART_STATUS
  bne read_uart_byte_loop        ; can't read, no data

  ; set return value 
  ; low word  : (A low byte, X high byte)
  lda UART_READ
  ldx #$00
  rts

Tools

Minipro

Download minipro 0.5. Follow the installation instructions.

After installing, check it works by attaching the programmer to the laptop, then run the minipro --version. You should see minipro recognizing the device:

bash
> minipro --version
Supported programmers: TL866A/CS, TL866II+,T48 (experimental)
Found TL866II+ 04.2.132 (0x284)
Device code: 02092661
Serial code: 5BJVT6MR20ZZJBB0JPAE
minipro version 0.6     A free and open TL866 series programmer
Commit date:    2024-02-11 20:34:27 -0800
Git commit:     77c26fd2e5d8674d65382a3eff4628013483c4d2
Git branch:     master
TL866A/CS:      14184 devices, 44 custom
TL866II+:       16281 devices, 45 custom
Logic:            283 devices, 4 custom

Note: For Ubuntu 20, version 0.6 (latest as of writing fails during upload):

bash
minipro -p AT28C64B -w kernel.bin
Found TL866II+ 04.2.132 (0x284)
Device code: 02092661
Serial code: 5BJVT6MR20ZZJBB0JPAE
Erasing... 0.02Sec OK
Writing Code...  1.67Sec  OK
Reading Code...  0.11Sec  OK
Verification failed at address 0x0000: File=0x00, Device=0xFF
make: *** [Makefile:20: install] Error 1
Install cc65
bash
git clone https://github.com/cc65/cc65.git
cd cc65
make

Move the generated binaries somewhere under $PATH.

Hello world

The 8 bit computer running a simple program which turns on eight leds, first the ones in a even position and then the ones in a odd position, looping forever.
You do not need cc65 for the hello world.

Run:

bash
python blink-leds.py

which generates the image for the ROM in the file program.bin.
Then connect the eeprom to the programmer and run:

bash
minipro -p AT28C64B -w program.bin

WIP

Build assembly file with the cc65 compiler suite

You need cc65.

Build blink.s
text
cl65 -C load.cfg --cpu 65C02 --no-target-lib blink.s

which builds the blink binary.

Note
This binary should be exactly the same as the one generated by the python program

text
python blink-leds.py
cl65 -C load.cfg --cpu 65C02 --no-target-lib blink.s

compare with:

text
diff <(xxd ./blink) <(xxd ./program.bin)

which should return nothing (meaning the binary are the same).

Upload to mini pro
bash
minipro -p AT28C64B -w blink
Resources

calling assembly routines from C (calling sequence and fast calls) https://www.cc65.org/doc/customizing-7.html

How do hardware timer/time sleep works https://www.youtube.com/watch?v=g_koa00MBLg&ab_channel=BenEater

Reserving memory location in assembly

text
.zeropage
_led_data:    .res 1, $00

.segment	"CODE"
; you can use _led_data in the code, eg.
lda  #$FF
sta  _led_data

Links

How to have ca65 output as raw binary

You can't.

From: https://www.reddit.com/r/beneater/comments/mm3m9s/how_to_have_ca65_output_as_raw_binary/

Simple assemblers convert assembler source code file(s) directly into a final binary output file in one step.

More complex development systems convert each source code file into an intermediary object code file, then a linker/locater pulls together and converts all object files into one target binary file. All linkers require some type of configuration file that tells it where your RAM and ROM (aka Flash) memory is located, unless it has some known assumptions about the final target. Though more complicated, this approach is a lot more flexible, because this concept supports any combination of assemblers and compilers that creates the same object code.

Currently this development system only supports assembler and C, but theoretically new compilers could be created for other high-level computer languages too. As long as future compilers create compatible object files then LD65 should be able to support them too.

From a generic point of view of these concepts, there are plenty of books about this subject matter, and likely lots of stuff written about it on the internet. I'll leave it up to you to further investigate and educate yourself. Good luck.

text
C Source Code Files(s) ---> CC65 [compiler] ---> Object Code File(s)
Assembler Source Code File(s) ---> CA65 [assembler] ---> Object Code File(s)
Object Code File(s) and optional Libraries ---> LD65 [linker] and Config File ---> Binary or Hex File
Binary or Hex File ---> Chip Programmer Tool ---> ROM/Flash/EEPROM chip
ORG directive and VASM

Note that ca65 do not handle the org directive as other assemblers for 6502.

From: https://wiki.cc65.org/doku.php?id=cc65:the_.org_directive

Most other assemblers for the 6502 handle .ORG different than ca65. The reason is, that these tools usually do not use a linker. Professional development systems separate the job of the assembler and the linker to gain more flexibility and to implement features not possible with just one tool.

If you don't have a separate linker, the assembler is responsible for code translation and code placement. With such an assembler, you use the .ORG directive to place your code at a specific address.

For example:

text
  .org $0000
  NOP
  .org $FFFF
  NOP

When compiled:

text
ca65 --cpu 65sc02 -o sample.o sample.s
ld65 -t none -o sample sample.o

Produces a 2 bytes ROM image, instead of 64K:

text
> ls -lha ./sample
-rw-r--r-- 1 user domain^users 2 Apr 20 23:19 ./sample

vasm on the other hand for the same program produces 64K:

text
> /tmp/vasm/vasm6502_oldstyle -Fbin -dotdir -o binary.out sample.s
...

> -rw-r--r-- 1 napicell domain^users 64K Apr 20 23:25 ./binary.out

> hexdump -C binary.out 
00000000  ea 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
0000fff0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 ea  |................|
00010000

Disassemble

text
da65 --cpu 65C02 -g --comments 4 blink_subroutines | less

Incremental levels of abstractions

Before we start. 6502 reads from 0x1ffc (low byte) and 0x1ffd (high byte) the address where to read the first instruction. We need to write at that address where that is. In this project, the first instruction is at location 0 in the ROM, which is 0xE000 (1110000000000000) as seen by the CPU (see memory mapping)

  • blink-leds.py creates the binary by specifing the 6502 instructions in binary. No assembler. The program does not RAM. It blinks without a wait, so run it with a slow clock. The python script creates a binary that exactly fits the rom and sets the address 0x1ffc and 0x1ffd to 0xE000
  • blink.s it's the assembly version of the previous program.
  • ram_test.s is the first program that needs a ram to be installed. It verfies the ram works by storing something in ram and reading it back
  • blink_subroutines.s runs the blink program with subroutines. This requires a ram since the RTS function (return sub routine) needs to know where to return to. This info is stored in the stack by the JSR (jump sub routine). The assemply also use PHX and PLX (push/pull x register to stack) to showcase how a subroutine can avoid interfere with registers being set outside of the subroutine.

Parts

15
QtyValueFootprintReferences
41kResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_HorizontalR1, R2, R3, R4
274HC00Package_DIP:DIP-14_W7.62mm_Socket_LongPadsU2, U4
174HC4075Package_DIP:DIP-14_W7.62mm_Socket_LongPadsU3
1AS6C62256Package_DIP:DIP-28_W15.24mm_Socket_LongPadsU5
1AT28C256-15PUSocket:DIP_Socket-28_W11.9_W12.7_W15.24_W17.78_W18.5_3M_228-1277-00-0602JIC1
1Conn_01x06_PinConnector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_VerticalJ1
1MXO45T-2C-1M0000MXO45T_OSCILLATOR:MXO45T2C1M0000Y1
1SW_SPSTButton_Switch_THT:SW_PUSH_6mmSW2
1UM245RUM245_LIB:UM245R-DIP-24_W15.24mm_Socket_LongPadsU7
1W65C02SxPPackage_DIP:DIP-40_W15.24mm_SocketU1
1W65C22SxPPackage_DIP:DIP-40_W15.24mm_SocketU6

Questions

How big is the 6502-pactvm v01 PCB?

It measures 99 × 96.5 mm and has 2 copper layers, 1.6 mm thick.

What parts does the 6502-pactvm v01 use?

15 parts, built around the UM245R, 74HC00D, 74HC4075D and AT28C256-15PU. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the 6502-pactvm v01 KiCad files?

From the napicella/8-bit-computer repository on GitHub, at the commit this page was made from: the board is kicad-projects/6502-pactvm-v01/6502-pactvm-v01.kicad_pcb and its schematic kicad-projects/6502-pactvm-v01/6502-pactvm-v01.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the 6502-pactvm v01 design in my own project?

Its repository states no licence, so its author keeps all rights: ask napicella before reusing the design.

Similar boards

  • 2 layers299.7 × 198.1 mmCisc-8, the printed circuit board in 3D

    Cisc-8

    nailed-barnacle/Cisc-8-ttl-processor

    A cut-down version of the 8080 processor using HCTTL and including a discrete ALU and instruction decoder

    319 parts1MIT
  • 2 layers160 × 100 mmMonitor, the printed circuit board in 3D

    Monitor

    Noah1989/loopmicro

    An 8-bit microcomputer for the 21st century

    113 parts2MIT
  • 2 layers249.9 × 89.4 mmCSCvon8, the printed circuit board in 3D

    CSCvon8

    DoctorWkt/CSCvon8

    A crazy small 8-bit CPU built with only seventeen 7400-series chips.

    55 parts112GPL-3.0
  • 2 layers103.6 × 79.8 mmBeanZee, the printed circuit board in 3D

    BeanZee

    PainfulDiodes/BeanZee

    A Z80 homebrew development board for experimentation

    30 parts23MIT
  • 2 layers214.6 × 123.2 mmFISC2, the printed circuit board in 3D

    FISC2

    DoctorWkt/2FISC

    FISC2: A TTL CPU with Some 16-bit Capabilities

    73 parts10GPL-3.0
  • 2 layers231.3 × 96.5 mmFISC, the printed circuit board in 3D

    FISC

    DoctorWkt/FISC

    An 8-bit TTL CPU with a Stack Pointer

    59 parts7GPL-3.0
  • 2 layers297 × 210 mmCpu, the printed circuit board in 3D

    Cpu

    tetrix450/masic-cpu

    MASIC CPU schematics and PCB

    197 partsMIT
  • 4 layers406.4 × 254 mmProcalu, the printed circuit board in 3D

    Procalu

    mrieker/pdp8i

    157 parts