-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port to Ben Eater's 6502 Breadboard Computer (#149)
* Port to Ben Eater's 6502 Breadboard Computer * Make "eater" complete and hosted * Get the examples to compile and run. * Define missing abort() function. * Move delay() from libcrt0 to libc. * Define getchar() and __putchar() in C. * Convert CR into LF in getchar(). * Rename putchar_raw() to __chrout() for consistency with other platforms. * Rename getchar_raw() to __chrin(). * Improvements to the "eater" target * Add include guards to chrin.h and chrout.h * Add -mlto-zp option to clang.cfg * Remove unnecessary .data_initializers section from link.ld
- Loading branch information
Showing
16 changed files
with
494 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
platform(eater COMPLETE HOSTED PARENT common) | ||
|
||
if(NOT CMAKE_CROSSCOMPILING) | ||
return() | ||
endif() | ||
|
||
install(FILES | ||
chrin.h | ||
chrout.h | ||
eater.h | ||
TYPE INCLUDE) | ||
install(FILES link.ld TYPE LIB) | ||
|
||
add_platform_library(eater-crt0 | ||
crt0/reset.S | ||
crt0/serial.S | ||
crt0/systick.S | ||
) | ||
merge_libraries(eater-crt0 | ||
common-crt0 | ||
common-init-stack | ||
common-copy-zp-data | ||
common-zero-bss | ||
common-exit-loop | ||
) | ||
|
||
add_platform_library(eater-c | ||
abort.c | ||
delay.c | ||
getchar.c | ||
putchar.c | ||
) | ||
|
||
target_compile_options(eater-crt0 PUBLIC -mcpu=mosw65c02) | ||
target_link_libraries(eater-crt0 PRIVATE common-asminc) | ||
|
||
target_include_directories(eater-c BEFORE PUBLIC .) | ||
target_compile_options(eater-c PUBLIC -mcpu=mosw65c02) | ||
target_link_libraries(eater-c PRIVATE common-asminc) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
|
||
This directory contains support for [Ben Eater's Breadboard 6502](https://eater.net/6502) project. | ||
|
||
Features of the port | ||
-------------------- | ||
|
||
This port has the following features: | ||
|
||
* .text and .rodata segments are placed into ROM, between $8000 and $FFFF. | ||
* .data and .bss segments are placed into RAM, between $0200 and $3FFF. | ||
* The C stack grows downwards from $4000. | ||
* LLVM registers are stored between $E0 and $FF in the zero page. | ||
* Support for serial input and output via the W65C51N Asynchronous | ||
Communications Interface Adapter (ACIA). | ||
* System millisecond tick counter based on the T2 timer in the | ||
W65C22 Versatile Interface Adapter (VIA). | ||
|
||
Serial console | ||
-------------- | ||
|
||
The W65C51N Asynchronous Communications Interface Adapter (ACIA) | ||
at address $5000 in the breadboard's memory map is used to implement the | ||
\_\_putchar() and getchar() functions for the C library. | ||
|
||
The ACIA is configured for 19200 bps N-8-1 communications and RTS | ||
handshaking. The handshaking lines on the W65C51N should be hooked | ||
up as follows: | ||
|
||
* Connect pin 8 (RTS) of the W65C51N to pin 10 (T2IN) of the MAX232. | ||
* Connect pin 7 (T2OUT) of the MAX232 to pin 8 (CTS) on the DB9 connector. | ||
* Connect pins 9 (CTS), 16 (DCD), and 17 (DSR) of the W65C51N to ground. | ||
* Everything else should be connected up the same way as in | ||
[Ben Eater's standard schematic](https://eater.net/schematics/6502-serial.png), | ||
including the diodes D1 and D2 on the IRQ lines for the chips. | ||
|
||
Serial output uses busy-waiting after transmitting each character, | ||
due to bugs in the W65C51N chip. | ||
|
||
Interrupts and RTS handshaking are used to reduce the chance of missing | ||
incoming characters. A 256-byte buffer is used to hold incoming data | ||
until it can be read. If the buffer fills up, then RTS will be disabled | ||
and communications with the host will stop until the program starts | ||
reading bytes again with getchar(). | ||
|
||
System tick counter | ||
------------------- | ||
|
||
The system tick counter can be read by program code with the millis() | ||
function: | ||
|
||
unsigned long millis(void); | ||
|
||
The counter starts at zero at startup time and increments every millisecond. | ||
The value will wrap around after 49.7 days. | ||
|
||
The millis() function is intended for implementing timeouts and delays without | ||
using hard-coded delay loops. The built-in delay() function makes it easy | ||
to introduce a simple delay up to 65535 milliseconds into your program: | ||
|
||
void delay(unsigned ms); | ||
|
||
Interrupt handling | ||
------------------ | ||
|
||
The crt0 code takes care of serial and system tick interrupts automatically. | ||
After the standard interrupt sources have been handled, the crt0 code | ||
will call the irq() function, which must be declared as follows: | ||
|
||
__attribute__((interrupt)) void irq(void) | ||
{ | ||
... | ||
} | ||
|
||
Non-maskable interrupts can also be handled if the program declares the | ||
nmi() function as follows: | ||
|
||
__attribute__((interrupt)) void nmi(void) | ||
{ | ||
... | ||
} | ||
|
||
The linker script provides stub definitions for irq() and nmi() in case | ||
you don't need them. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
|
||
void abort(void) { | ||
puts("Aborted"); | ||
_exit(134); // 128 + SIGABRT | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#ifndef _CHRIN_H_ | ||
#define _CHRIN_H_ | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
// Get a character from the serial console. | ||
int __chrin(void); | ||
|
||
// Get a character from the serial console, returns -1 if none available. | ||
int __chrin_no_wait(void); | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
||
#endif // not _CHRIN_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#ifndef _CHROUT_H_ | ||
#define _CHROUT_H_ | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
void __chrout(char c); | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
||
#endif // not _CHROUT_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-D__eater__ | ||
-mcpu=mosw65c02 | ||
-mlto-zp=218 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
; Copyright (c) 2023 Rhys Weatherley | ||
; | ||
; Licensed under the Apache License, Version 2.0 with LLVM Exceptions, | ||
; See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license | ||
; information. | ||
|
||
.include "imag.inc" | ||
|
||
; Initialize the system from the reset vector. | ||
.global __do_reset | ||
.section .init.50,"axR",@progbits | ||
__do_reset: | ||
; Fix the D and I flags. | ||
cld | ||
cli | ||
; Set up the initial 6502 stack pointer. | ||
ldx #$ff | ||
txs | ||
|
||
.text | ||
; IRQBRK handler. | ||
.global _irqbrk | ||
.section .text._irqbrk,"axR",@progbits | ||
_irqbrk: | ||
pha ; __systick_isr and __serial_isr use A and X. | ||
phx | ||
cld ; Just in case. | ||
jsr __systick_isr ; Handle the system millisecond tick timer interrupt. | ||
jsr __serial_isr ; Handle serial receive interrupts. | ||
plx | ||
pla | ||
jmp irq ; Jump to the user-supplied IRQ handler. | ||
|
||
; Default IRQ and NMI handler if the user's program hasn't defined one. | ||
.global _irq_default | ||
.section .text._irq_default,"axR",@progbits | ||
_irq_default: | ||
rti |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
; Copyright (c) 2023 Rhys Weatherley | ||
; | ||
; Licensed under the Apache License, Version 2.0 with LLVM Exceptions, | ||
; See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license | ||
; information. | ||
|
||
#define ACIA_DATA 0x5000 | ||
#define ACIA_STATUS 0x5001 | ||
#define ACIA_CMD 0x5002 | ||
#define ACIA_CTRL 0x5003 | ||
|
||
#define RX_HIGH 240 ; Buffer high water mark. | ||
#define RX_LOW 224 ; Buffer low water mark. | ||
|
||
; Initialize the serial port. | ||
.global __do_init_serial | ||
.section .init.270,"axR",@progbits | ||
__do_init_serial: | ||
lda ACIA_STATUS ; Clear any reported errors. | ||
lda ACIA_DATA ; Empty the receive buffer if something is in it. | ||
stz ACIA_STATUS ; Force the ACIA to reset itself. | ||
lda #$1f ; 19200-N-8-1 | ||
sta ACIA_CTRL | ||
lda #$09 ; ACIA_TIC1 | ACIA_DTR | ||
sta ACIA_CMD | ||
|
||
; Handle interrupts for serial receive. | ||
.text | ||
.global __serial_isr | ||
.section .text.__serial_isr,"axR",@progbits | ||
__serial_isr: | ||
lda ACIA_STATUS ; Is there an ACIA interrupt pending? | ||
bpl __serial_isr_end | ||
and #$08 ; Is RDRF set, indicating a received byte? | ||
beq __serial_isr_end | ||
lda ACIA_DATA ; Retrieve the character that just arrived. | ||
ldx __serial_rx_in ; Add it to the serial receive buffer. | ||
sta __serial_rx_buffer,x | ||
inc __serial_rx_in ; Advance the buffer pointer. | ||
lda __serial_rx_in ; Is the buffer above the high water mark? | ||
sec | ||
sbc __serial_rx_out | ||
cmp #RX_HIGH | ||
bcc __serial_isr_end | ||
lda #$01 ; Disable receive interrupts. | ||
sta ACIA_CMD | ||
__serial_isr_end: | ||
rts | ||
|
||
; Get a character from the serial receive buffer. | ||
.global __chrin | ||
.section .text.__chrin,"ax",@progbits | ||
__chrin: | ||
ldx __serial_rx_out ; Do we have a character? | ||
cpx __serial_rx_in | ||
beq __chrin ; No, then go back and wait again. | ||
inc __serial_rx_out ; Increment the buffer's output pointer. | ||
lda __serial_rx_in ; Are we now below the low water mark? | ||
sec | ||
sbc __serial_rx_out | ||
cmp #RX_LOW | ||
bcs __chrin_have_char | ||
lda #$09 ; Turn receive interrupts back on. | ||
sta ACIA_CMD | ||
__chrin_have_char: | ||
lda __serial_rx_buffer,x | ||
ldx #0 | ||
rts | ||
|
||
; Get a character from the serial receive buffer, without waiting. | ||
.global __chrin_no_wait | ||
.section .text.__chrin_no_wait,"ax",@progbits | ||
__chrin_no_wait: | ||
ldx __serial_rx_out ; Do we have a character? | ||
cpx __serial_rx_in | ||
beq __chrin_no_char | ||
inc __serial_rx_out ; Increment the buffer's output pointer. | ||
lda __serial_rx_in ; Are we now below the low water mark? | ||
sec | ||
sbc __serial_rx_out | ||
cmp #RX_LOW | ||
bcs __chrin_no_wait_have_char | ||
lda #$09 ; Turn receive interrupts back on. | ||
sta ACIA_CMD | ||
__chrin_no_wait_have_char: | ||
lda __serial_rx_buffer,x | ||
ldx #0 | ||
rts | ||
__chrin_no_char: | ||
lda #$ff ; No character available, return -1. | ||
tax | ||
rts | ||
|
||
; Put a character to the serial port. | ||
.global __chrout | ||
.section .text.__chrout,"ax",@progbits | ||
__chrout: | ||
sta ACIA_DATA ; Transmit A using the ACIA. | ||
ldx #$70 ; Wait ~560us for the byte to be transmitted. | ||
__chrout_wait_for_tx: | ||
dex | ||
bne __chrout_wait_for_tx | ||
rts | ||
|
||
; Control variables for the serial input buffer in the zero page. | ||
.section .zp.bss,"zaw",@nobits | ||
__serial_rx_in: | ||
.fill 1 | ||
__serial_rx_out: | ||
.fill 1 | ||
|
||
; Location of the serial input buffer in noinit RAM. | ||
.section .noinit,"aw",@nobits | ||
__serial_rx_buffer: | ||
.fill 256 |
Oops, something went wrong.