From a15f3e1fb1df6131e506eb9cf50e6c0bd08197e3 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Fri, 14 Jun 2024 12:43:08 +0200 Subject: [PATCH 01/19] Successfully loads and executes the BDOS, fails to open CCP --- build.py | 1 + src/arch/nano6502/build.py | 42 ++ src/arch/nano6502/buildimage.py | 65 +++ src/arch/nano6502/nano6502.S | 789 ++++++++++++++++++++++++++++++++ src/arch/nano6502/nano6502.ld | 27 ++ 5 files changed, 924 insertions(+) create mode 100644 src/arch/nano6502/build.py create mode 100755 src/arch/nano6502/buildimage.py create mode 100644 src/arch/nano6502/nano6502.S create mode 100644 src/arch/nano6502/nano6502.ld diff --git a/build.py b/build.py index 45e69528..34f1cfeb 100644 --- a/build.py +++ b/build.py @@ -30,6 +30,7 @@ "vic20.d64": "src/arch/commodore+vic20_diskimage", "x16.zip": "src/arch/x16+diskimage", "sorbus.zip": "src/arch/sorbus+diskimage", + "nano6502.zip": "src/arch/nano6502+diskimage", }, deps=[ "tests" diff --git a/src/arch/nano6502/build.py b/src/arch/nano6502/build.py new file mode 100644 index 00000000..4340011e --- /dev/null +++ b/src/arch/nano6502/build.py @@ -0,0 +1,42 @@ +from build.ab import normalrule +from tools.build import mkdfs, mkcpmfs +from build.llvm import llvmrawprogram +from config import ( + MINIMAL_APPS, + MINIMAL_APPS_SRCS, + BIG_APPS, + BIG_APPS_SRCS, + SCREEN_APPS, + PASCAL_APPS, +) + +llvmrawprogram( + name="nano6502", + srcs=["./nano6502.S"], + deps=["include", "src/lib+bioslib"], + linkscript="./nano6502.ld", +) + +mkcpmfs( + name="cpmfs", + format="generic-1m", + items={"0:ccp.sys@sr": "src+ccp"} + | MINIMAL_APPS + | MINIMAL_APPS_SRCS + | BIG_APPS + | BIG_APPS_SRCS + | SCREEN_APPS + | PASCAL_APPS, +) + +normalrule( + name="diskimage", + ins=[ + ".+cpmfs", + ".+nano6502", + "src/bdos", + ], + outs=["nano6502.img"], + commands=["rm -f {outs[0]}","./src/arch/nano6502/buildimage.py"], + label="IMG", +) diff --git a/src/arch/nano6502/buildimage.py b/src/arch/nano6502/buildimage.py new file mode 100755 index 00000000..7a1fc677 --- /dev/null +++ b/src/arch/nano6502/buildimage.py @@ -0,0 +1,65 @@ +#!/usr/bin/python3 + +import sys +import os + +bdos_offset = 512*256 +cpmfs_offset = 512*256*2 + +bios_filename = '.obj/src/arch/nano6502/+nano6502/+nano6502' +bdos_filename = '.obj/src/bdos/+bdos/+bdos' +cpmfs_filename = '.obj/src/arch/nano6502/+cpmfs/src/arch/nano6502/+cpmfs.img' + +output_filename = './nano6502.img' + +size = os.path.getsize(bios_filename) + +infile=open(bios_filename, "rb") +outfile=open(output_filename, "wb") + +byte=infile.read(1) +# Write BIOS +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = bdos_offset - size; +out = 0 + +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() + +# Write BDOS +size = os.path.getsize(bdos_filename) + +infile = open(bdos_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = cpmfs_offset - size; +out = 0 + +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() + +# Write CPMFS +size = os.path.getsize(cpmfs_filename) + +infile = open(cpmfs_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +infile.close() +outfile.close() diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S new file mode 100644 index 00000000..865165b3 --- /dev/null +++ b/src/arch/nano6502/nano6502.S @@ -0,0 +1,789 @@ +; CP/M-65 Copyright © 2022 David Given +; This file is licensed under the terms of the 2-clause BSD license. Please +; see the COPYING file in the root project directory for the full text. + +#include "zif.inc" +#include "cpm65.inc" +#include "driver.inc" +#include "jumptables.inc" +IO_page_uart = $01 +IO_page_sdcard = $03 +IO_page_video = $04 +IO_page_reg = $00 +ROM_sel_reg = $02 + +tty_write = $fe06 +tty_cls = $fe09 +tty_busy = $fe07 + +sd_base = $fe00 +sd_addr_0 = $fe00 +sd_addr_1 = $fe01 +sd_addr_2 = $fe02 +sd_addr_3 = $fe03 +sd_busy = $fe04 +sd_read_strobe = $fe05 +sd_write_strobe = $fe06 +sd_page = $fe07 +sd_done = $fe0a + +uart_tx_data = $fe00 +uart_tx_done = $fe01 +uart_rx_data = $fe02 +uart_rx_avail = $fe03 + + +; Offset to second byte of SDCARD adress +; Wasteful, but it's not like we are going to use the full card anyway +BDOS_OFFSET = $1 +BDOS_BLOCKS = $0a ; Number of 512 byte blocks to load +CPMFS_OFFSET = $2 + +ZEROPAGE + +.global ptr +.global ptr1 +ptr: .word 0 +ptr1: .word 0 +dma: .fill 2 +current_bank: .byte 0 +current_drive: .byte 0 +sd_offset: .byte 0 +sd_page_zp: .byte 0 +sd_sector: .fill 4 +temp: .byte 0 +; --- Initialisation code --------------------------------------------------- + +; Called once on startup and then never again. + +zproc _start + ; Disable ROM + lda #01 + sta ROM_sel_reg + + lda #'a' + jsr uart_out + ; Clear screen + + lda #IO_page_video + sta IO_page_reg + sta tty_cls + + lda #'b' + jsr uart_out + ; Print banner + + ldy #banner_end - banner + zrepeat +banner_wait: + lda tty_busy + bne banner_wait + lda banner - 1, y + sta tty_write + dey + zuntil_eq + + lda #'c' + jsr uart_out + ; Load the BDOS image. + lda #BDOS_OFFSET + sta sd_offset + lda mem_base + clc + adc #$30 + jsr uart_out + lda mem_base + jsr load_bdos + + lda #'d' + jsr uart_out + ; Relocate it. + + lda mem_base + ldx zp_base + jsr bios_RELOCATE + + + lda #'e' + jsr uart_out + ; Open CPMFS + + lda #CPMFS_OFFSET + sta sd_offset + + ; Initialize drivers + jsr initdrivers + + lda #'f' + jsr uart_out + ; Avoid junk character in buffer at startup + lda #$00 + sta pending_key + + ; Compute the entry address and jump. + + lda mem_base + pha + lda #COMHDR_ENTRY-1 ; rts addresses are one before the target + pha + + lda #biosentry + + ;jmp mem_base + COMHDR_ENTRY + + rts ; indirect jump +zendproc + +banner: ; reversed! + .byte 13, 10 + .ascii "-------------------- 2056onan rof 56-M/PC --------------------" +banner_end: + ; Filler + .byte 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 +; Stuff above of here must be 0x80 long, currently OK + +; ---- Drive filenames ----- +#cpmfs_filename: +# .ascii "CPMFS" +# .byte 00 + +; --- Drivers ------------------------------------------------------- +.data +.global drvtop +drvtop: .word drv_TTY + +defdriver TTY, DRVID_TTY, drvstrat_TTY, 0 + +; TTY driver strategy routine +; Y = TTY opcode + +zproc drvstrat_TTY + jmpdispatch jmptable_lo, jmptable_hi + +jmptable_lo: + jmptablo tty_const + jmptablo tty_conin + jmptablo tty_conout +jmptable_hi: + jmptabhi tty_const + jmptabhi tty_conin + jmptabhi tty_conout +zendproc + + +;defdriver SCREEN, DRVID_SCREEN, drvstrat_SCREEN, 0 + +; SCREEN driver strategy routine +; Y = TTY opcode + +;zproc drvstrat_SCREEN +; jmpdispatch screen_jmptable_lo, screen_jmptable_hi + +;screen_jmptable_lo: +; jmptablo screen_version +; jmptablo screen_getsize +; jmptablo screen_clear +; jmptablo screen_setcursor +; jmptablo screen_getcursor +; jmptablo screen_putchar +; jmptablo screen_putstring +; jmptablo screen_getchar +; jmptablo screen_showcursor +; jmptablo screen_scrollup +; jmptablo screen_scrolldown +; jmptablo screen_cleartoeol +; jmptablo screen_setstyle +;screen_jmptable_hi: +; jmptabhi screen_version +; jmptabhi screen_getsize +; jmptabhi screen_clear +; jmptabhi screen_setcursor +; jmptabhi screen_getcursor +; jmptabhi screen_putchar +; jmptabhi screen_putstring +; jmptabhi screen_getchar +; jmptabhi screen_showcursor +; jmptabhi screen_scrollup +; jmptabhi screen_scrolldown +; jmptabhi screen_cleartoeol +; jmptabhi screen_setstyle +;zendproc + +; SERIAL driver strategy routine +; Y = TTY opcode + +;defdriver "SERIAL", DRVID_SERIAL, drvstrat_SERIAL, 0 + +;zproc drvstrat_SERIAL +; jmpdispatch serial_jmptable_lo, serial_jmptable_hi + +;serial_jmptable_lo: +; jmptablo serial_inp +; jmptablo serial_out +; jmptablo serial_open +; jmptablo serial_close +; jmptablo serial_outp +; jmptablo serial_in +;serial_jmptable_hi: +; jmptabhi serial_inp +; jmptabhi serial_out +; jmptabhi serial_open +; jmptabhi serial_close +; jmptabhi serial_outp +; jmptabhi serial_in +;zendproc + +; --- SERIAL driver ------------------------------------------------------- + +;zproc serial_inp +; jsr ACIA3_Scan +; rts +;zendproc + +;zproc serial_out +; jmp ACIA3_Output +;zendproc + +;zproc serial_open +; jmp ACIA3_init +;zendproc + +;zproc serial_close +; rts +;zendproc + +;zproc serial_outp +; jsr ACIA3_Output +; clc +; rts +;zendproc + +;zproc serial_in +; jmp ACIA3_Input +;zendproc + +; --- SCREEN driver ------------------------------------------------------- + +;zproc screen_version +; lda #0 +; rts +;zendproc + +;zproc screen_getsize +; ; Screen size is 80x30 +; lda #79 +; ldx #29 +; rts +;zendproc + +;zproc screen_clear +; jmp jmp_cls +;zendproc + +;zproc screen_setcursor +; jmp jmp_setcursor +;zendproc + +;zproc screen_getcursor +; lda vidcursor_x +; ldx vidcursor_y +; rts +;zendproc + +;zproc screen_putchar +; ora vid_style +; ldy #0 +; sta (vidpointer),Y + +; lda #63 +; cmp vidcursor_x +; beq screen_putchar_done +; inc vidcursor_x +; lda vidcursor_x +; ldx vidcursor_y +; jsr screen_setcursor +;screen_putchar_done: +; rts +;zendproc + +;zproc screen_putstring +; sta ptr+0 +; stx ptr+1 +; ldy #0 +;putstring_loop: +; lda (ptr),y +; beq putstring_endloop +; jsr jmp_out +; iny +; jmp putstring_loop +;putstring_endloop: +; clc +; rts +;zendproc + +;zproc screen_getchar +; jsr jmp_scan +; cmp #00 +; beq nodata +; cmp #$08 +; bne notbs +; lda #127 +;notbs: +; clc +; rts +;nodata: +; sec +; rts +;zendproc + +;zproc screen_showcursor +; jmp jmp_showcursor +;zendproc + +;zproc screen_scrollup +; jmp jmp_scrollup +;zendproc + +;zproc screen_scrolldown +; jmp jmp_scrolldown +;zendproc + +;zproc screen_cleartoeol +; jmp jmp_cleartoeol +;zendproc + +;zproc screen_setstyle +; cmp #$01 +; bne normal_style +; lda #$80 +; sta vid_style +; rts +;normal_style: +; lda #$00 +; sta vid_style +; rts +;zendproc + +; --- TTY driver ------------------------------------------------------- + +; Returns 0x00 if no key is pending, 0xff if one is. +; C if no key is pending, !C if key pending +zproc tty_const + lda pending_key + zif_eq + lda #IO_page_uart + sta IO_page_reg + lda uart_rx_avail + zif_eq + lda #$00 + sec + rts + zendif + lda uart_rx_data + sta pending_key + zendif + lda #$ff + clc + rts +zendproc + +; Blocks and waits for the next keypress; returns it in A. + +zproc tty_conin + lda pending_key + zif_eq + lda #IO_page_uart + sta IO_page_reg +tty_input_wait: + lda uart_rx_avail + beq tty_input_wait + lda uart_rx_data + rts + zendif + + ldx #0 + stx pending_key + rts +zendproc + +zproc tty_conout + cmp #127 + zif_eq + lda #8 + zendif + pha + lda #IO_page_video + sta IO_page_reg + pla + sta tty_write + rts +zendproc + +; -- Rest of the BIOS --- + +; Sets the current DMA address. + +zproc bios_SETDMA + sta dma+0 + stx dma+1 + rts +zendproc + +; Select a disk. +; A is the disk number. +; Returns the DPH in XA. +; Sets carry on error. + +zproc bios_SELDSK + cmp #0 + zif_ne + sec ; invalid drive + rts + zendif + + lda #dph + clc + rts +zendproc + +; Set the current absolute sector number. +; XA is a pointer to a three-byte number. + +zproc bios_SETSEC + ; CP/M sector length is 128 bytes + ; SD card sector length is 512 bytes + ; Setup adressing... + sta ptr + stx ptr+1 + + ; Set SD card IO page + lda #IO_page_sdcard + sta IO_page_reg + + ; Get page offset + ldy #0 + lda (ptr),y + and #$03 + sta sd_page_zp + sta sd_page + + ; Divide sector address by 4 to get correct SD sector + ldx #2 +divloop: + ldy #2 + lda (ptr),y + lsr + sta (ptr),y + + dey + lda (ptr),y + ror + sta (ptr),y + + dey + lda (ptr),y + ror + sta (ptr),y + dex + bne divloop + + lda #0 + sta sd_offset+3 + + ldy #2 + lda (ptr),y + sta sd_offset+2 + + ldy #1 + lda (ptr),y + sta sd_offset+1 + + ldy #0 + sta (ptr),y + sta sd_offset+0 + + clc + lda sd_offset+1 + adc CPMFS_OFFSET + sta sd_offset+1 + bcc offset_done + inc sd_offset+2 + offset_done: + jsr sync_sd_offset + + rts +zendproc + +zproc bios_READ + ;jsr jmp_locate + ;lda dma;#dma + ;jsr jmp_read_sector + + ; Setup pointer + lda dma + sta ptr + lda dma+1 + sta ptr+1 + + ; Perform SD-card read + jsr sdcard_read + + ; Copy data to dma + ldx #$80 + ldy #0 +readcopyloop: + lda sd_base, X + sta (ptr),Y + iny + bne readcopycont + inc ptr+1 +readcopycont: + inx + bne readcopyloop + + rts +zendproc + +zproc bios_WRITE + ;jsr jmp_locate + ;lda dma;#dma + ;jsr jmp_write_sector + ; Setup pointer + lda dma + sta ptr + lda dma+1 + sta ptr+1 + + ; Perform SD-card read + jsr sdcard_read + + ; Copy data from dma + ldx #$80 + ldy #0 +writecopyloop: + lda (ptr),Y + sta sd_base, X + iny + bne writecopycont + inc ptr+1 +writecopycont: + inx + bne writecopyloop + + ; Write to SD-card + lda #01 + sta sd_write_strobe + + rts +zendproc + +zproc bios_GETTPA + ;ldy current_bank + lda mem_base;, Y + ldx mem_end;, Y + rts +zendproc + +zproc bios_SETTPA + ldy current_bank + cpy #0 + bne settpa_end + sta mem_base;, Y + stx mem_end;, Y +settpa_end: + rts +zendproc + +zproc bios_GETZP + ;ldy current_bank + lda zp_base;, Y + ldx zp_end;, Y + rts +zendproc + +zproc bios_SETZP + sta zp_base;, Y + stx zp_end;, Y + rts +zendproc + +zproc bios_SETBANK + sta current_bank + rts +zendproc + +zproc sdcard_read + lda #IO_page_sdcard + sta IO_page_reg + + jsr sync_sd_offset + + ; Read strobe + lda #1 + sta sd_read_strobe +sd_read_busy: + lda sd_busy + bne sd_read_busy + + rts +zendproc + +zproc load_bdos + ; Push load adress unto stack + pha + + ; Set IO page + lda #IO_page_sdcard + sta IO_page_reg + + ; Set offset to BDOS address + lda #BDOS_OFFSET + sta sd_sector+1 + lda #0 + sta sd_sector + sta sd_sector+2 + sta sd_sector+3 + sta sd_page_zp + + jsr sync_sd_offset + + ; Read the BDOS blocks + lda #BDOS_BLOCKS+1 + sta ptr1 + ldy #0 + lda #0 + sta ptr + pla + sta ptr+1 +bdos_sector_read: + ; read strobe + lda #1 + sta sd_read_strobe +bdos_read_wait: + lda sd_busy + bne bdos_read_wait + + ; Copy data to RAM +bdos_bank_loop: + ldx #$80 +bdos_copy_loop: + lda sd_base,X + sta (ptr),Y + iny + bne bdos_copy_cont + inc ptr+1 +bdos_copy_cont: + inx + bne bdos_copy_loop + + inc sd_page_zp + jsr sync_sd_offset + lda sd_page_zp + cmp #04 + bne bdos_bank_loop + +bdos_copy_done: + dec ptr1 + lda ptr1 + clc + adc #$30 + jsr uart_out + lda ptr1 + beq bdos_all_done + + ; Increase SD sector address + inc sd_sector + bne bdos_inc_done + inc sd_sector+1 + bne bdos_inc_done + inc sd_sector+2 + bne bdos_inc_done + inc sd_sector+3 +bdos_inc_done: + lda #0 + sta sd_page_zp + jsr sync_sd_offset + jmp bdos_sector_read +bdos_all_done: + rts +zendproc + +zproc sync_sd_offset + lda #'S' + jsr uart_out + lda sd_sector+1 + clc + adc #$30 + jsr uart_out + lda #13 + jsr uart_out + lda #10 + jsr uart_out + + ; Set IO page + lda #IO_page_sdcard + sta IO_page_reg + + ; Wait until SD-card is not busy +sd_offset_wait: + lda sd_busy + bne sd_offset_wait + + lda sd_sector + sta sd_addr_0 + lda sd_sector+1 + sta sd_addr_1 + lda sd_sector+2 + sta sd_addr_2 + lda sd_sector+3 + sta sd_addr_3 + lda sd_page_zp + sta sd_page + rts +zendproc + +zproc uart_out + pha + ; Preserve io page + lda IO_page_reg + sta temp + + lda #IO_page_uart + sta IO_page_reg + +uart_out_wait: + lda uart_tx_done + beq uart_out_wait + + pla + sta uart_tx_data + + lda temp + sta IO_page_reg + rts +zendproc + +.data +;current_bank: .byte 0 +zp_base: .byte __ZP0_START__;, __ZP1_START__ +zp_end: .byte __ZP0_END__;, __ZP1_END__ + +mem_base: .byte __TPA0_START__@mos16hi +mem_end: .byte __TPA0_END__@mos16hi + +; DPH for drives + +define_drive dph, 128*64, 2048, 128, 0 + +directory_buffer = _start +NOINIT + +pending_key: .byte 0 ; pending keypress from system +sector_num: .fill 3 ; current absolute sector number + +; vim: filetype=asm sw=4 ts=4 et + diff --git a/src/arch/nano6502/nano6502.ld b/src/arch/nano6502/nano6502.ld new file mode 100644 index 00000000..979a0726 --- /dev/null +++ b/src/arch/nano6502/nano6502.ld @@ -0,0 +1,27 @@ +MEMORY { + zp : ORIGIN = 0x05, LENGTH = 0xf0 + ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0800 +} + +SECTIONS { + .zp : { + *(.zp .zp.*) + __ZP0_START__ = .; + __ZP0_END__ = 0xff; + } >zp + + .text : { *(.text .text.*) } >ram + .data : { *(.data .data.* .rodata .rodata.*) } > ram + .noinit (NOLOAD) : { + *(.noinit .noinit.*) + . = ALIGN(256); + __TPA0_START__ = .; + __TPA0_END__ = 0xFE00; + + } >ram +} + +OUTPUT_FORMAT { + TRIM(ram) +} + From 074cfa86fa93181fa60ce93ca19e8fa14ee51502 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Fri, 14 Jun 2024 18:08:12 +0200 Subject: [PATCH 02/19] It now boots! But any writing instantly corrupts the filesystem... --- src/arch/nano6502/nano6502.S | 237 ++++++++++++++++++++++------------- 1 file changed, 151 insertions(+), 86 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 865165b3..aa6c76d6 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -37,7 +37,7 @@ uart_rx_avail = $fe03 ; Wasteful, but it's not like we are going to use the full card anyway BDOS_OFFSET = $1 BDOS_BLOCKS = $0a ; Number of 512 byte blocks to load -CPMFS_OFFSET = $2 +CPMFS_OFFSET = $3 ZEROPAGE @@ -51,7 +51,7 @@ current_drive: .byte 0 sd_offset: .byte 0 sd_page_zp: .byte 0 sd_sector: .fill 4 -temp: .byte 0 +kuken: .byte 0 ; --- Initialisation code --------------------------------------------------- ; Called once on startup and then never again. @@ -61,16 +61,12 @@ zproc _start lda #01 sta ROM_sel_reg - lda #'a' - jsr uart_out ; Clear screen lda #IO_page_video sta IO_page_reg sta tty_cls - lda #'b' - jsr uart_out ; Print banner ldy #banner_end - banner @@ -83,20 +79,12 @@ banner_wait: dey zuntil_eq - lda #'c' - jsr uart_out ; Load the BDOS image. lda #BDOS_OFFSET sta sd_offset lda mem_base - clc - adc #$30 - jsr uart_out - lda mem_base jsr load_bdos - lda #'d' - jsr uart_out ; Relocate it. lda mem_base @@ -104,8 +92,6 @@ banner_wait: jsr bios_RELOCATE - lda #'e' - jsr uart_out ; Open CPMFS lda #CPMFS_OFFSET @@ -114,8 +100,6 @@ banner_wait: ; Initialize drivers jsr initdrivers - lda #'f' - jsr uart_out ; Avoid junk character in buffer at startup lda #$00 sta pending_key @@ -130,8 +114,6 @@ banner_wait: lda #biosentry - ;jmp mem_base + COMHDR_ENTRY - rts ; indirect jump zendproc @@ -141,6 +123,7 @@ banner: ; reversed! banner_end: ; Filler .byte 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 + .byte 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 ; Stuff above of here must be 0x80 long, currently OK ; ---- Drive filenames ----- @@ -404,6 +387,7 @@ tty_input_wait: ldx #0 stx pending_key + clc rts zendproc @@ -415,8 +399,12 @@ zproc tty_conout pha lda #IO_page_video sta IO_page_reg +conout_wait: + lda tty_busy + bne conout_wait pla sta tty_write + clc rts zendproc @@ -427,6 +415,7 @@ zendproc zproc bios_SETDMA sta dma+0 stx dma+1 + clc rts zendproc @@ -458,6 +447,9 @@ zproc bios_SETSEC sta ptr stx ptr+1 + ;lda #'S' + ;jsr uart_out + ; Set SD card IO page lda #IO_page_sdcard sta IO_page_reg @@ -467,7 +459,25 @@ zproc bios_SETSEC lda (ptr),y and #$03 sta sd_page_zp - sta sd_page + + ;clc + ;adc #$30 + ;jsr uart_out + + ; Print requested sector number + ;ldy #2 + ;lda (ptr),y + ;jsr uart_out + + ;ldy #1 + ;lda (ptr),y + ;jsr uart_out + + ;ldy #0 + ;lda (ptr),y + ;jsr uart_out + + ; Divide sector address by 4 to get correct SD sector ldx #2 @@ -477,12 +487,12 @@ divloop: lsr sta (ptr),y - dey + ldy #1 lda (ptr),y ror sta (ptr),y - dey + ldy #0 lda (ptr),y ror sta (ptr),y @@ -490,32 +500,48 @@ divloop: bne divloop lda #0 - sta sd_offset+3 + sta sd_sector+3 ldy #2 lda (ptr),y - sta sd_offset+2 + sta sd_sector+2 ldy #1 lda (ptr),y - sta sd_offset+1 + sta sd_sector+1 ldy #0 - sta (ptr),y - sta sd_offset+0 + lda (ptr),y + sta sd_sector+0 clc - lda sd_offset+1 - adc CPMFS_OFFSET - sta sd_offset+1 + lda sd_sector+1 + adc #CPMFS_OFFSET + sta sd_sector+1 bcc offset_done - inc sd_offset+2 + inc sd_sector+2 offset_done: - jsr sync_sd_offset + ;lda sd_sector+0 + ;clc + ;adc #$30 + ;jsr uart_out + + ;lda sd_sector+1 + ;clc + ;adc #$30 + ;jsr uart_out + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + + jsr sync_sd_offset + + clc rts zendproc - + zproc bios_READ ;jsr jmp_locate ;lda dma;#dma + #sta ptr+1 + + ;lda #'R' + ;jsr uart_out + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out ; Perform SD-card read jsr sdcard_read @@ -536,14 +570,15 @@ zproc bios_READ ldy #0 readcopyloop: lda sd_base, X - sta (ptr),Y + sta (dma),Y + ;jsr uart_out iny - bne readcopycont - inc ptr+1 -readcopycont: + ;bne readcopycont + ;inc ptr+1 +;readcopycont: inx bne readcopyloop - + clc rts zendproc @@ -553,10 +588,17 @@ zproc bios_WRITE ;ldx dma+1;#>dma ;jsr jmp_write_sector ; Setup pointer - lda dma - sta ptr - lda dma+1 - sta ptr+1 + ;lda dma + ;sta ptr + ;lda dma+1 + ;sta ptr+1 + + ;lda #'W' + ;jsr uart_out + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out ; Perform SD-card read jsr sdcard_read @@ -565,54 +607,58 @@ zproc bios_WRITE ldx #$80 ldy #0 writecopyloop: - lda (ptr),Y + lda (dma),Y sta sd_base, X iny - bne writecopycont - inc ptr+1 -writecopycont: + ;bne writecopycont + ;inc ptr+1 +;writecopycont: inx bne writecopyloop +prewritewait: + lda sd_busy + bne prewritewait ; Write to SD-card lda #01 sta sd_write_strobe - +postwritewait: + lda sd_busy + bne postwritewait + + clc rts zendproc zproc bios_GETTPA - ;ldy current_bank - lda mem_base;, Y - ldx mem_end;, Y + lda mem_base + ldx mem_end + clc rts zendproc zproc bios_SETTPA - ldy current_bank - cpy #0 - bne settpa_end - sta mem_base;, Y - stx mem_end;, Y -settpa_end: + sta mem_base + stx mem_end + clc rts zendproc zproc bios_GETZP - ;ldy current_bank - lda zp_base;, Y - ldx zp_end;, Y + lda zp_base + ldx zp_end + clc rts zendproc zproc bios_SETZP - sta zp_base;, Y - stx zp_end;, Y + sta zp_base + stx zp_end + clc rts zendproc zproc bios_SETBANK - sta current_bank rts zendproc @@ -622,12 +668,16 @@ zproc sdcard_read jsr sync_sd_offset +sd_read_busy_a: + lda sd_busy + bne sd_read_busy_a + ; Read strobe lda #1 sta sd_read_strobe -sd_read_busy: +sd_read_busy_b: lda sd_busy - bne sd_read_busy + bne sd_read_busy_b rts zendproc @@ -688,11 +738,11 @@ bdos_copy_cont: bdos_copy_done: dec ptr1 - lda ptr1 - clc - adc #$30 - jsr uart_out - lda ptr1 + ;lda ptr1 + ;clc + ;adc #$30 + ;jsr uart_out + ;lda ptr1 beq bdos_all_done ; Increase SD sector address @@ -713,16 +763,31 @@ bdos_all_done: zendproc zproc sync_sd_offset - lda #'S' - jsr uart_out - lda sd_sector+1 - clc - adc #$30 - jsr uart_out - lda #13 - jsr uart_out - lda #10 - jsr uart_out + ;lda #'O' + ;jsr uart_out + + ;lda sd_sector + 3 + ;jsr uart_out + + ;lda sd_sector + 2 + ;jsr uart_out + + ;lda sd_sector + 1 + ;jsr uart_out + + ;lda sd_sector + ;jsr uart_out + + ;lda sd_page_zp + ;jsr uart_out + + + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + + ; Set IO page lda #IO_page_sdcard @@ -750,7 +815,7 @@ zproc uart_out pha ; Preserve io page lda IO_page_reg - sta temp + sta kuken lda #IO_page_uart sta IO_page_reg @@ -762,7 +827,7 @@ uart_out_wait: pla sta uart_tx_data - lda temp + lda kuken sta IO_page_reg rts zendproc From 4db445907fc208716eaab9d91f40cb66fb8b4350 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sat, 15 Jun 2024 08:18:01 +0200 Subject: [PATCH 03/19] Cleanup --- src/arch/nano6502/nano6502.S | 209 ++++++++++++++--------------------- 1 file changed, 80 insertions(+), 129 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index aa6c76d6..86603c06 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -447,9 +447,6 @@ zproc bios_SETSEC sta ptr stx ptr+1 - ;lda #'S' - ;jsr uart_out - ; Set SD card IO page lda #IO_page_sdcard sta IO_page_reg @@ -460,45 +457,28 @@ zproc bios_SETSEC and #$03 sta sd_page_zp - ;clc - ;adc #$30 - ;jsr uart_out - - ; Print requested sector number - ;ldy #2 - ;lda (ptr),y - ;jsr uart_out - - ;ldy #1 - ;lda (ptr),y - ;jsr uart_out - - ;ldy #0 - ;lda (ptr),y - ;jsr uart_out - - - ; Divide sector address by 4 to get correct SD sector ldx #2 -divloop: - ldy #2 - lda (ptr),y - lsr - sta (ptr),y + zrepeat + ldy #2 + lda (ptr),y + lsr + sta (ptr),y - ldy #1 - lda (ptr),y - ror - sta (ptr),y - - ldy #0 - lda (ptr),y - ror - sta (ptr),y - dex - bne divloop + ldy #1 + lda (ptr),y + ror + sta (ptr),y + + ldy #0 + lda (ptr),y + ror + sta (ptr),y + dex + zuntil_eq + ; Store calculated sector + ; Could be turned into loop... lda #0 sta sd_sector+3 @@ -518,24 +498,11 @@ divloop: lda sd_sector+1 adc #CPMFS_OFFSET sta sd_sector+1 - bcc offset_done - inc sd_sector+2 - offset_done: - ;lda sd_sector+0 - ;clc - ;adc #$30 - ;jsr uart_out - - ;lda sd_sector+1 - ;clc - ;adc #$30 - ;jsr uart_out - - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - + + zif_cs + inc sd_sector+2 + zendif + jsr sync_sd_offset clc @@ -543,41 +510,20 @@ divloop: zendproc zproc bios_READ - ;jsr jmp_locate - ;lda dma;#dma - ;jsr jmp_read_sector - - ; Setup pointer - #lda #dma - #sta ptr+1 - - ;lda #'R' - ;jsr uart_out - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - ; Perform SD-card read jsr sdcard_read ; Copy data to dma ldx #$80 ldy #0 -readcopyloop: - lda sd_base, X - sta (dma),Y - ;jsr uart_out - iny - ;bne readcopycont - ;inc ptr+1 -;readcopycont: - inx - bne readcopyloop + + zrepeat + lda sd_base, x + sta (dma),y + iny + inx + zuntil_eq + clc rts zendproc @@ -595,6 +541,18 @@ zproc bios_WRITE ;lda #'W' ;jsr uart_out + ;lda #'B' + ;jsr uart_out + ;lda sd_addr_3 + ;jsr uart_out + ;lda sd_addr_2 + ;jsr uart_out + ;lda sd_addr_1 + ;jsr uart_out + ;lda sd_addr_0 + ;jsr uart_out + ;lda sd_page + ;jsr uart_out ;lda #13 ;jsr uart_out ;lda #10 @@ -606,19 +564,37 @@ zproc bios_WRITE ; Copy data from dma ldx #$80 ldy #0 -writecopyloop: - lda (dma),Y - sta sd_base, X - iny - ;bne writecopycont - ;inc ptr+1 -;writecopycont: - inx - bne writecopyloop + zrepeat + lda (dma), y + sta sd_base, x + inx + iny + cpy #$80 + zuntil_eq + prewritewait: lda sd_busy bne prewritewait + ;lda #'W' + ;jsr uart_out + ;lda #'A' + ;jsr uart_out + ;lda sd_addr_3 + ;jsr uart_out + ;lda sd_addr_2 + ;jsr uart_out + ;lda sd_addr_1 + ;jsr uart_out + ;lda sd_addr_0 + ;jsr uart_out + ;lda sd_page + ;jsr uart_out + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + ; Write to SD-card lda #01 sta sd_write_strobe @@ -668,16 +644,17 @@ zproc sdcard_read jsr sync_sd_offset -sd_read_busy_a: - lda sd_busy - bne sd_read_busy_a + zrepeat + lda sd_busy + zuntil_eq ; Read strobe lda #1 sta sd_read_strobe -sd_read_busy_b: - lda sd_busy - bne sd_read_busy_b + + zrepeat + lda sd_busy + zuntil_eq rts zendproc @@ -763,40 +740,14 @@ bdos_all_done: zendproc zproc sync_sd_offset - ;lda #'O' - ;jsr uart_out - - ;lda sd_sector + 3 - ;jsr uart_out - - ;lda sd_sector + 2 - ;jsr uart_out - - ;lda sd_sector + 1 - ;jsr uart_out - - ;lda sd_sector - ;jsr uart_out - - ;lda sd_page_zp - ;jsr uart_out - - - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - - - ; Set IO page lda #IO_page_sdcard sta IO_page_reg ; Wait until SD-card is not busy -sd_offset_wait: - lda sd_busy - bne sd_offset_wait + zrepeat + lda sd_busy + zuntil_eq lda sd_sector sta sd_addr_0 From b42912f988d0de159b2b009f2b74e3eb3a3d0158 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sat, 15 Jun 2024 08:37:08 +0200 Subject: [PATCH 04/19] More cleanup --- src/arch/nano6502/nano6502.S | 172 ++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 83 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 86603c06..bd745f40 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -116,14 +116,100 @@ banner_wait: rts ; indirect jump zendproc + +zproc load_bdos + ; Push load adress unto stack + pha + + ; Set IO page + lda #IO_page_sdcard + sta IO_page_reg + + ; Set offset to BDOS address + lda #BDOS_OFFSET + sta sd_sector+1 + lda #0 + sta sd_sector + sta sd_sector+2 + sta sd_sector+3 + sta sd_page_zp + jsr sync_sd_offset + + ; Read the BDOS blocks + lda #BDOS_BLOCKS+1 + sta ptr1 + ldy #0 + lda #0 + sta ptr + pla + sta ptr+1 + + zrepeat + ; read strobe + lda #1 + sta sd_read_strobe + + ; Wait for read to finish + zrepeat + lda sd_busy + zuntil_eq + + ; Copy data to RAM + zrepeat + ;bdos_bank_loop: + ldx #$80 + ;bdos_copy_loop: + zrepeat + lda sd_base,X + sta (ptr),Y + iny + zif_eq + ;bne bdos_copy_cont + inc ptr+1 + zendif + ;bdos_copy_cont: + inx + zuntil_eq + ;bne bdos_copy_loop + + inc sd_page_zp + jsr sync_sd_offset + lda sd_page_zp + cmp #04 + zuntil_eq + + dec ptr1 + + ; Increase SD sector address + zif_ne + inc sd_sector + zif_eq + inc sd_sector+1 + zif_eq + inc sd_sector+2 + zif_eq + inc sd_sector+3 + zendif + zendif + zendif + lda #0 + sta sd_page_zp + jsr sync_sd_offset + zendif + + lda ptr1 + zuntil_eq + + rts +zendproc + + banner: ; reversed! .byte 13, 10 .ascii "-------------------- 2056onan rof 56-M/PC --------------------" banner_end: - ; Filler - .byte 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 - .byte 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 + .byte 00 ; Stuff above of here must be 0x80 long, currently OK ; ---- Drive filenames ----- @@ -659,86 +745,6 @@ zproc sdcard_read rts zendproc -zproc load_bdos - ; Push load adress unto stack - pha - - ; Set IO page - lda #IO_page_sdcard - sta IO_page_reg - - ; Set offset to BDOS address - lda #BDOS_OFFSET - sta sd_sector+1 - lda #0 - sta sd_sector - sta sd_sector+2 - sta sd_sector+3 - sta sd_page_zp - - jsr sync_sd_offset - - ; Read the BDOS blocks - lda #BDOS_BLOCKS+1 - sta ptr1 - ldy #0 - lda #0 - sta ptr - pla - sta ptr+1 -bdos_sector_read: - ; read strobe - lda #1 - sta sd_read_strobe -bdos_read_wait: - lda sd_busy - bne bdos_read_wait - - ; Copy data to RAM -bdos_bank_loop: - ldx #$80 -bdos_copy_loop: - lda sd_base,X - sta (ptr),Y - iny - bne bdos_copy_cont - inc ptr+1 -bdos_copy_cont: - inx - bne bdos_copy_loop - - inc sd_page_zp - jsr sync_sd_offset - lda sd_page_zp - cmp #04 - bne bdos_bank_loop - -bdos_copy_done: - dec ptr1 - ;lda ptr1 - ;clc - ;adc #$30 - ;jsr uart_out - ;lda ptr1 - beq bdos_all_done - - ; Increase SD sector address - inc sd_sector - bne bdos_inc_done - inc sd_sector+1 - bne bdos_inc_done - inc sd_sector+2 - bne bdos_inc_done - inc sd_sector+3 -bdos_inc_done: - lda #0 - sta sd_page_zp - jsr sync_sd_offset - jmp bdos_sector_read -bdos_all_done: - rts -zendproc - zproc sync_sd_offset ; Set IO page lda #IO_page_sdcard From 3e251bdbc2942c77683e9e4d550530953fa3f90f Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sat, 15 Jun 2024 13:25:53 +0200 Subject: [PATCH 05/19] Screen driver now works, QE runs really nicely. But writing is still broken... --- src/arch/nano6502/nano6502.S | 509 ++++++++++++++++++++++------------ src/arch/nano6502/nano6502.ld | 8 +- 2 files changed, 336 insertions(+), 181 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index bd745f40..26cc9b9c 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -15,6 +15,15 @@ ROM_sel_reg = $02 tty_write = $fe06 tty_cls = $fe09 tty_busy = $fe07 +video_line = $fe00 +video_cursor_x = $fe01 +video_cursor_y = $fe02 +video_curvis = $fe03 +video_scroll_up = $fe04 +video_scroll_down = $fe05 +video_clear_to_eol = $fe08 +video_tty_enable = $fe0a +video_line_data = $fe80 sd_base = $fe00 sd_addr_0 = $fe00 @@ -84,6 +93,13 @@ banner_wait: sta sd_offset lda mem_base jsr load_bdos + + ; Reset values of ptr + lda #0 + sta ptr + sta ptr+1 + sta ptr1 + sta ptr1+1 ; Relocate it. @@ -222,7 +238,7 @@ banner_end: .global drvtop drvtop: .word drv_TTY -defdriver TTY, DRVID_TTY, drvstrat_TTY, 0 +defdriver TTY, DRVID_TTY, drvstrat_TTY, drv_SCREEN ; TTY driver strategy routine ; Y = TTY opcode @@ -241,43 +257,43 @@ jmptable_hi: zendproc -;defdriver SCREEN, DRVID_SCREEN, drvstrat_SCREEN, 0 +defdriver SCREEN, DRVID_SCREEN, drvstrat_SCREEN, 0 ; SCREEN driver strategy routine ; Y = TTY opcode -;zproc drvstrat_SCREEN -; jmpdispatch screen_jmptable_lo, screen_jmptable_hi - -;screen_jmptable_lo: -; jmptablo screen_version -; jmptablo screen_getsize -; jmptablo screen_clear -; jmptablo screen_setcursor -; jmptablo screen_getcursor -; jmptablo screen_putchar -; jmptablo screen_putstring -; jmptablo screen_getchar -; jmptablo screen_showcursor -; jmptablo screen_scrollup -; jmptablo screen_scrolldown -; jmptablo screen_cleartoeol -; jmptablo screen_setstyle -;screen_jmptable_hi: -; jmptabhi screen_version -; jmptabhi screen_getsize -; jmptabhi screen_clear -; jmptabhi screen_setcursor -; jmptabhi screen_getcursor -; jmptabhi screen_putchar -; jmptabhi screen_putstring -; jmptabhi screen_getchar -; jmptabhi screen_showcursor -; jmptabhi screen_scrollup -; jmptabhi screen_scrolldown -; jmptabhi screen_cleartoeol -; jmptabhi screen_setstyle -;zendproc +zproc drvstrat_SCREEN + jmpdispatch screen_jmptable_lo, screen_jmptable_hi + +screen_jmptable_lo: + jmptablo screen_version + jmptablo screen_getsize + jmptablo screen_clear + jmptablo screen_setcursor + jmptablo screen_getcursor + jmptablo screen_putchar + jmptablo screen_putstring + jmptablo screen_getchar + jmptablo screen_showcursor + jmptablo screen_scrollup + jmptablo screen_scrolldown + jmptablo screen_cleartoeol + jmptablo screen_setstyle +screen_jmptable_hi: + jmptabhi screen_version + jmptabhi screen_getsize + jmptabhi screen_clear + jmptabhi screen_setcursor + jmptabhi screen_getcursor + jmptabhi screen_putchar + jmptabhi screen_putstring + jmptabhi screen_getchar + jmptabhi screen_showcursor + jmptabhi screen_scrollup + jmptabhi screen_scrolldown + jmptabhi screen_cleartoeol + jmptabhi screen_setstyle +zendproc ; SERIAL driver strategy routine ; Y = TTY opcode @@ -334,105 +350,143 @@ zendproc ; --- SCREEN driver ------------------------------------------------------- -;zproc screen_version -; lda #0 -; rts -;zendproc +zproc screen_version + lda #0 + rts +zendproc -;zproc screen_getsize -; ; Screen size is 80x30 -; lda #79 -; ldx #29 -; rts -;zendproc +zproc screen_getsize + ; Screen size is 80x30 + lda #79 + ldx #29 + rts +zendproc -;zproc screen_clear -; jmp jmp_cls -;zendproc +zproc screen_clear + jsr video_init + sta tty_cls + rts +zendproc -;zproc screen_setcursor -; jmp jmp_setcursor -;zendproc +zproc screen_setcursor + jsr video_init + sta video_cursor_x + jsr video_wait + stx video_cursor_y +zendproc -;zproc screen_getcursor -; lda vidcursor_x -; ldx vidcursor_y -; rts -;zendproc +zproc screen_getcursor + jsr video_init + lda video_cursor_x + ldx video_cursor_y + rts +zendproc -;zproc screen_putchar -; ora vid_style -; ldy #0 -; sta (vidpointer),Y - -; lda #63 -; cmp vidcursor_x -; beq screen_putchar_done -; inc vidcursor_x -; lda vidcursor_x -; ldx vidcursor_y -; jsr screen_setcursor -;screen_putchar_done: -; rts -;zendproc +zproc screen_putchar + jsr video_init + pha + lda video_cursor_y + sta video_line + ldx video_cursor_x + pla + ora vid_style + sta video_line_data,x + + lda video_cursor_x + cmp #79 + zif_ne + inc video_cursor_x + zendif -;zproc screen_putstring -; sta ptr+0 -; stx ptr+1 -; ldy #0 -;putstring_loop: -; lda (ptr),y -; beq putstring_endloop -; jsr jmp_out -; iny -; jmp putstring_loop -;putstring_endloop: -; clc -; rts -;zendproc + rts +zendproc -;zproc screen_getchar -; jsr jmp_scan -; cmp #00 -; beq nodata -; cmp #$08 -; bne notbs -; lda #127 -;notbs: -; clc -; rts -;nodata: -; sec -; rts -;zendproc +zproc screen_putstring + sta ptr+0 + stx ptr+1 + ldy #0 +putstring_loop: + lda (ptr),y + beq putstring_endloop + jsr screen_putchar + iny + jmp putstring_loop +putstring_endloop: + clc + rts +zendproc -;zproc screen_showcursor -; jmp jmp_showcursor -;zendproc +zproc screen_getchar + lda #IO_page_uart + sta IO_page_reg + lda uart_rx_avail + beq nodata + lda uart_rx_data + cmp #$08 + bne notbs + lda #127 +notbs: + clc + rts +nodata: + sec + rts +zendproc -;zproc screen_scrollup -; jmp jmp_scrollup -;zendproc +zproc screen_showcursor + jsr video_init + sta video_curvis + rts +zendproc -;zproc screen_scrolldown -; jmp jmp_scrolldown -;zendproc +zproc screen_scrollup + jsr video_init + sta video_scroll_up + + jsr video_wait + lda #0 + sta video_cursor_x + jsr video_wait + lda #29 + sta video_cursor_y + jsr video_wait + sta video_clear_to_eol + rts +zendproc -;zproc screen_cleartoeol -; jmp jmp_cleartoeol -;zendproc +zproc screen_scrolldown + jsr video_init + sta video_scroll_down + + jsr video_wait + + lda #0 + sta video_cursor_x + jsr video_wait + lda #0 + sta video_cursor_y + jsr video_wait + sta video_clear_to_eol + rts +zendproc -;zproc screen_setstyle -; cmp #$01 -; bne normal_style -; lda #$80 -; sta vid_style -; rts -;normal_style: -; lda #$00 -; sta vid_style -; rts -;zendproc +zproc screen_cleartoeol + jsr video_wait + sta video_clear_to_eol + rts +zendproc + +zproc screen_setstyle + cmp #$01 + bne normal_style + lda #$80 + sta vid_style + rts +normal_style: + lda #$00 + sta vid_style + rts +zendproc ; --- TTY driver ------------------------------------------------------- @@ -501,7 +555,6 @@ zendproc zproc bios_SETDMA sta dma+0 stx dma+1 - clc rts zendproc @@ -533,6 +586,25 @@ zproc bios_SETSEC sta ptr stx ptr+1 + ; Debug prints + ;lda #'S' + ;jsr uart_out + + ;ldy #1 + ;lda (ptr),y + ;jsr uart_hex + ;ldy #0 + ;lda (ptr),y + ;jsr uart_hex + + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + + + + ; Set SD card IO page lda #IO_page_sdcard sta IO_page_reg @@ -580,6 +652,7 @@ zproc bios_SETSEC lda (ptr),y sta sd_sector+0 + ; Add offset clc lda sd_sector+1 adc #CPMFS_OFFSET @@ -596,6 +669,24 @@ zproc bios_SETSEC zendproc zproc bios_READ + ; Debug prints + ;lda #'R' + ;jsr uart_out + + ;lda sd_addr_1 + ;jsr uart_hex + ;lda sd_addr_0 + ;jsr uart_hex + ;lda sd_page + ;jsr uart_hex + + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + + + ; Perform SD-card read jsr sdcard_read @@ -603,6 +694,10 @@ zproc bios_READ ldx #$80 ldy #0 + ; Set IO page + lda #IO_page_sdcard + sta IO_page_reg + zrepeat lda sd_base, x sta (dma),y @@ -615,30 +710,17 @@ zproc bios_READ zendproc zproc bios_WRITE - ;jsr jmp_locate - ;lda dma;#dma - ;jsr jmp_write_sector - ; Setup pointer - ;lda dma - ;sta ptr - ;lda dma+1 - ;sta ptr+1 - + ; Debug prints ;lda #'W' ;jsr uart_out - ;lda #'B' - ;jsr uart_out - ;lda sd_addr_3 - ;jsr uart_out - ;lda sd_addr_2 - ;jsr uart_out + ;lda sd_addr_1 - ;jsr uart_out + ;jsr uart_hex ;lda sd_addr_0 - ;jsr uart_out + ;jsr uart_hex ;lda sd_page - ;jsr uart_out + ;jsr uart_hex + ;lda #13 ;jsr uart_out ;lda #10 @@ -647,46 +729,35 @@ zproc bios_WRITE ; Perform SD-card read jsr sdcard_read + ; Set IO page + lda #IO_page_sdcard + sta IO_page_reg + + zrepeat + lda sd_busy + zuntil_eq + ; Copy data from dma ldx #$80 ldy #0 zrepeat lda (dma), y sta sd_base, x - inx iny - cpy #$80 + inx zuntil_eq -prewritewait: - lda sd_busy - bne prewritewait - - ;lda #'W' - ;jsr uart_out - ;lda #'A' - ;jsr uart_out - ;lda sd_addr_3 - ;jsr uart_out - ;lda sd_addr_2 - ;jsr uart_out - ;lda sd_addr_1 - ;jsr uart_out - ;lda sd_addr_0 - ;jsr uart_out - ;lda sd_page - ;jsr uart_out - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - - ; Write to SD-card + zrepeat + lda sd_busy + zuntil_eq + + ; Write back to SD-card lda #01 sta sd_write_strobe -postwritewait: - lda sd_busy - bne postwritewait + + zrepeat + lda sd_busy + zuntil_eq clc rts @@ -724,6 +795,22 @@ zproc bios_SETBANK rts zendproc +zproc video_init + pha + lda #IO_page_video + sta IO_page_reg + jsr video_wait + pla + rts +zendproc + +zproc video_wait + zrepeat + lda tty_busy + zuntil_eq + rts +zendproc + zproc sdcard_read lda #IO_page_sdcard sta IO_page_reg @@ -741,7 +828,15 @@ zproc sdcard_read zrepeat lda sd_busy zuntil_eq + + ; Read strobe + lda #1 + sta sd_read_strobe + zrepeat + lda sd_busy + zuntil_eq + rts zendproc @@ -765,6 +860,42 @@ zproc sync_sd_offset sta sd_addr_3 lda sd_page_zp sta sd_page + + lda sd_addr_0 + cmp sd_sector + zif_ne + lda #'X' + jsr uart_out + zendif + + lda sd_addr_1 + cmp sd_sector+1 + zif_ne + lda #'Y' + jsr uart_out + zendif + + lda sd_addr_2 + cmp sd_sector+2 + zif_ne + lda #'Z' + jsr uart_out + zendif + + lda sd_addr_3 + cmp sd_sector+3 + zif_ne + lda #'Q' + jsr uart_out + zendif + + lda sd_page + cmp sd_page_zp + zif_ne + lda #'W' + jsr uart_out + zendif + rts zendproc @@ -789,23 +920,47 @@ uart_out_wait: rts zendproc +zproc uart_hex + pha + lsr a + lsr a + lsr a + lsr a + jsr h4 + pla +h4: + and #0x0f + ora #'0' + cmp #'9'+1 + zif_cs + adc #6 + zendif + pha + jsr uart_out + pla + rts +zendproc + .data ;current_bank: .byte 0 -zp_base: .byte __ZP0_START__;, __ZP1_START__ -zp_end: .byte __ZP0_END__;, __ZP1_END__ +vid_style: .byte 0 +zp_base: .byte __ZEROPAGE_START__;, __ZP1_START__ +zp_end: .byte __ZEROPAGE_END__;, __ZP1_END__ -mem_base: .byte __TPA0_START__@mos16hi -mem_end: .byte __TPA0_END__@mos16hi +mem_base: .byte __TPA0_START__@mos16hi, __TPA0_START__@mos16hi +mem_end: .byte __TPA0_END__@mos16hi, __TPA0_START__@mos16hi ; DPH for drives define_drive dph, 128*64, 2048, 128, 0 -directory_buffer = _start +;directory_buffer = _start NOINIT pending_key: .byte 0 ; pending keypress from system -sector_num: .fill 3 ; current absolute sector number - +sector_num: .fill 3 +.global directory_buffer +directory_buffer: .fill 128 + ; vim: filetype=asm sw=4 ts=4 et diff --git a/src/arch/nano6502/nano6502.ld b/src/arch/nano6502/nano6502.ld index 979a0726..bf0378ce 100644 --- a/src/arch/nano6502/nano6502.ld +++ b/src/arch/nano6502/nano6502.ld @@ -1,13 +1,13 @@ MEMORY { - zp : ORIGIN = 0x05, LENGTH = 0xf0 + zp : ORIGIN = 0x10, LENGTH = 0xf0 ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0800 } SECTIONS { .zp : { *(.zp .zp.*) - __ZP0_START__ = .; - __ZP0_END__ = 0xff; + __ZEROPAGE_START__ = .; + __ZEROPAGE_END__ = 0xff; } >zp .text : { *(.text .text.*) } >ram @@ -16,7 +16,7 @@ SECTIONS { *(.noinit .noinit.*) . = ALIGN(256); __TPA0_START__ = .; - __TPA0_END__ = 0xFE00; + __TPA0_END__ = 0xFD00; } >ram } From 0e2e9b85c449e07bc3ce3b0a151d58554a7ec32f Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sat, 15 Jun 2024 13:54:45 +0200 Subject: [PATCH 06/19] Some more cleanup. Everything works great except writing... seems like the first sector is overwritten --- src/arch/nano6502/nano6502.S | 149 ++++------------------------------- 1 file changed, 15 insertions(+), 134 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 26cc9b9c..a936c09f 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -54,9 +54,7 @@ ZEROPAGE .global ptr1 ptr: .word 0 ptr1: .word 0 -dma: .fill 2 -current_bank: .byte 0 -current_drive: .byte 0 +dma: .word 0 sd_offset: .byte 0 sd_page_zp: .byte 0 sd_sector: .fill 4 @@ -471,7 +469,7 @@ zproc screen_scrolldown zendproc zproc screen_cleartoeol - jsr video_wait + jsr video_init sta video_clear_to_eol rts zendproc @@ -586,25 +584,6 @@ zproc bios_SETSEC sta ptr stx ptr+1 - ; Debug prints - ;lda #'S' - ;jsr uart_out - - ;ldy #1 - ;lda (ptr),y - ;jsr uart_hex - ;ldy #0 - ;lda (ptr),y - ;jsr uart_hex - - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - - - - ; Set SD card IO page lda #IO_page_sdcard sta IO_page_reg @@ -669,24 +648,6 @@ zproc bios_SETSEC zendproc zproc bios_READ - ; Debug prints - ;lda #'R' - ;jsr uart_out - - ;lda sd_addr_1 - ;jsr uart_hex - ;lda sd_addr_0 - ;jsr uart_hex - ;lda sd_page - ;jsr uart_hex - - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - - - ; Perform SD-card read jsr sdcard_read @@ -694,10 +655,6 @@ zproc bios_READ ldx #$80 ldy #0 - ; Set IO page - lda #IO_page_sdcard - sta IO_page_reg - zrepeat lda sd_base, x sta (dma),y @@ -710,33 +667,9 @@ zproc bios_READ zendproc zproc bios_WRITE - ; Debug prints - ;lda #'W' - ;jsr uart_out - - ;lda sd_addr_1 - ;jsr uart_hex - ;lda sd_addr_0 - ;jsr uart_hex - ;lda sd_page - ;jsr uart_hex - - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - ; Perform SD-card read jsr sdcard_read - ; Set IO page - lda #IO_page_sdcard - sta IO_page_reg - - zrepeat - lda sd_busy - zuntil_eq - ; Copy data from dma ldx #$80 ldy #0 @@ -747,19 +680,13 @@ zproc bios_WRITE inx zuntil_eq - zrepeat - lda sd_busy - zuntil_eq - + jsr sd_wait + ; Write back to SD-card lda #01 sta sd_write_strobe - zrepeat - lda sd_busy - zuntil_eq - - clc + jsr sd_wait rts zendproc @@ -812,31 +739,16 @@ zproc video_wait zendproc zproc sdcard_read - lda #IO_page_sdcard - sta IO_page_reg - jsr sync_sd_offset - zrepeat - lda sd_busy - zuntil_eq - + jsr sd_wait + ; Read strobe lda #1 sta sd_read_strobe - zrepeat - lda sd_busy - zuntil_eq - - ; Read strobe - lda #1 - sta sd_read_strobe + jsr sd_wait - zrepeat - lda sd_busy - zuntil_eq - rts zendproc @@ -846,9 +758,7 @@ zproc sync_sd_offset sta IO_page_reg ; Wait until SD-card is not busy - zrepeat - lda sd_busy - zuntil_eq + jsr sd_wait lda sd_sector sta sd_addr_0 @@ -861,41 +771,13 @@ zproc sync_sd_offset lda sd_page_zp sta sd_page - lda sd_addr_0 - cmp sd_sector - zif_ne - lda #'X' - jsr uart_out - zendif - - lda sd_addr_1 - cmp sd_sector+1 - zif_ne - lda #'Y' - jsr uart_out - zendif - - lda sd_addr_2 - cmp sd_sector+2 - zif_ne - lda #'Z' - jsr uart_out - zendif - - lda sd_addr_3 - cmp sd_sector+3 - zif_ne - lda #'Q' - jsr uart_out - zendif - - lda sd_page - cmp sd_page_zp - zif_ne - lda #'W' - jsr uart_out - zendif + rts +zendproc +zproc sd_wait + zrepeat + lda sd_busy + zuntil_eq rts zendproc @@ -942,7 +824,6 @@ h4: zendproc .data -;current_bank: .byte 0 vid_style: .byte 0 zp_base: .byte __ZEROPAGE_START__;, __ZP1_START__ zp_end: .byte __ZEROPAGE_END__;, __ZP1_END__ From a7d812b61273ca4bc60b968c2d5bc99676f279c2 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 13:06:09 +0200 Subject: [PATCH 07/19] Writing now works! --- src/arch/nano6502/nano6502.S | 191 +++++++++++++++++++--------------- src/arch/nano6502/nano6502.ld | 4 +- 2 files changed, 108 insertions(+), 87 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index a936c09f..93ae312d 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -35,6 +35,7 @@ sd_read_strobe = $fe05 sd_write_strobe = $fe06 sd_page = $fe07 sd_done = $fe0a +sd_data = $fe80 uart_tx_data = $fe00 uart_tx_done = $fe01 @@ -55,10 +56,6 @@ ZEROPAGE ptr: .word 0 ptr1: .word 0 dma: .word 0 -sd_offset: .byte 0 -sd_page_zp: .byte 0 -sd_sector: .fill 4 -kuken: .byte 0 ; --- Initialisation code --------------------------------------------------- ; Called once on startup and then never again. @@ -92,13 +89,6 @@ banner_wait: lda mem_base jsr load_bdos - ; Reset values of ptr - lda #0 - sta ptr - sta ptr+1 - sta ptr1 - sta ptr1+1 - ; Relocate it. lda mem_base @@ -578,88 +568,41 @@ zendproc ; XA is a pointer to a three-byte number. zproc bios_SETSEC - ; CP/M sector length is 128 bytes - ; SD card sector length is 512 bytes - ; Setup adressing... + ; Save sector number sta ptr stx ptr+1 - ; Set SD card IO page - lda #IO_page_sdcard - sta IO_page_reg - - ; Get page offset ldy #0 - lda (ptr),y - and #$03 - sta sd_page_zp - - ; Divide sector address by 4 to get correct SD sector - ldx #2 zrepeat - ldy #2 - lda (ptr),y - lsr - sta (ptr),y - - ldy #1 - lda (ptr),y - ror - sta (ptr),y - - ldy #0 lda (ptr),y - ror - sta (ptr),y - dex + sta cpm_sector,y + iny + cpy #3 zuntil_eq - - ; Store calculated sector - ; Could be turned into loop... - lda #0 - sta sd_sector+3 - - ldy #2 - lda (ptr),y - sta sd_sector+2 - - ldy #1 - lda (ptr),y - sta sd_sector+1 - - ldy #0 - lda (ptr),y - sta sd_sector+0 - - ; Add offset - clc - lda sd_sector+1 - adc #CPMFS_OFFSET - sta sd_sector+1 - - zif_cs - inc sd_sector+2 - zendif - - jsr sync_sd_offset clc rts zendproc zproc bios_READ + ;lda #'R' + ;jsr uart_out + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + ; Perform SD-card read jsr sdcard_read ; Copy data to dma - ldx #$80 ldy #0 zrepeat - lda sd_base, x + lda sd_data, y sta (dma),y iny - inx + cpy #$80 zuntil_eq clc @@ -667,23 +610,29 @@ zproc bios_READ zendproc zproc bios_WRITE + ;lda #'W' + ;jsr uart_out + ;lda sd_page + ;jsr uart_hex + ;lda #13 + ;jsr uart_out + ;lda #10 + ;jsr uart_out + + ; Perform SD-card read jsr sdcard_read ; Copy data from dma - ldx #$80 ldy #0 zrepeat lda (dma), y - sta sd_base, x + sta sd_data, y iny - inx + cpy #$80 zuntil_eq - jsr sd_wait - ; Write back to SD-card - lda #01 sta sd_write_strobe jsr sd_wait @@ -739,12 +688,10 @@ zproc video_wait zendproc zproc sdcard_read + jsr calculate_sd_address jsr sync_sd_offset - jsr sd_wait - ; Read strobe - lda #1 sta sd_read_strobe jsr sd_wait @@ -752,6 +699,76 @@ zproc sdcard_read rts zendproc +zproc calculate_sd_address + ; CP/M sector length is 128 bytes + ; SD card sector length is 512 bytes + ; Setup adressing... + + ; DEBUG + ;lda #'S' + ;jsr uart_out + ;ldy #1 + ;lda (ptr),y + ;jsr uart_hex + ;dey + ;lda (ptr),y + ;jsr uart_hex + + ; Get page offset + lda cpm_sector + and #$03 + sta sd_page_zp + + ; Divide sector address by 4 to get correct SD sector + ldx #2 + zrepeat + ldy #2 + lda cpm_sector,y + lsr + sta cpm_sector,y + + ldy #1 + lda cpm_sector,y + ror + sta cpm_sector,y + + ldy #0 + lda cpm_sector,y + ror + sta cpm_sector,y + dex + zuntil_eq + + ; Store calculated sector + ; Could be turned into loop... + lda #0 + sta sd_sector+3 + + ldy #2 + lda cpm_sector,y + sta sd_sector+2 + + ldy #1 + lda cpm_sector,y + sta sd_sector+1 + + ldy #0 + lda cpm_sector,y + sta sd_sector+0 + + ; Add offset + clc + lda sd_sector+1 + adc #CPMFS_OFFSET + sta sd_sector+1 + + zif_cs + inc sd_sector+2 + zendif + + rts +zendproc + zproc sync_sd_offset ; Set IO page lda #IO_page_sdcard @@ -830,18 +847,22 @@ zp_end: .byte __ZEROPAGE_END__;, __ZP1_END__ mem_base: .byte __TPA0_START__@mos16hi, __TPA0_START__@mos16hi mem_end: .byte __TPA0_END__@mos16hi, __TPA0_START__@mos16hi - + +sd_offset: .byte 0 +sd_page_zp: .byte 0 +sd_sector: .fill 4 +cpm_sector: .fill 3 +kuken: .byte 0 +; ; DPH for drives define_drive dph, 128*64, 2048, 128, 0 -;directory_buffer = _start NOINIT pending_key: .byte 0 ; pending keypress from system -sector_num: .fill 3 -.global directory_buffer -directory_buffer: .fill 128 + +directory_buffer = _start ; vim: filetype=asm sw=4 ts=4 et diff --git a/src/arch/nano6502/nano6502.ld b/src/arch/nano6502/nano6502.ld index bf0378ce..8c77690b 100644 --- a/src/arch/nano6502/nano6502.ld +++ b/src/arch/nano6502/nano6502.ld @@ -1,6 +1,6 @@ MEMORY { zp : ORIGIN = 0x10, LENGTH = 0xf0 - ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0800 + ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0600 } SECTIONS { @@ -16,7 +16,7 @@ SECTIONS { *(.noinit .noinit.*) . = ALIGN(256); __TPA0_START__ = .; - __TPA0_END__ = 0xFD00; + __TPA0_END__ = 0xFE00; } >ram } From 075c2863abbfae13292629791f1904501f52ae1c Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 13:14:32 +0200 Subject: [PATCH 08/19] Changed disk size to 4MB --- src/arch/nano6502/build.py | 2 +- src/arch/nano6502/nano6502.S | 6 ++++-- src/arch/nano6502/nano6502.ld | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/arch/nano6502/build.py b/src/arch/nano6502/build.py index 4340011e..e97334e7 100644 --- a/src/arch/nano6502/build.py +++ b/src/arch/nano6502/build.py @@ -19,7 +19,7 @@ mkcpmfs( name="cpmfs", - format="generic-1m", + format="sorbus", items={"0:ccp.sys@sr": "src+ccp"} | MINIMAL_APPS | MINIMAL_APPS_SRCS diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 93ae312d..5671811e 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -856,13 +856,15 @@ kuken: .byte 0 ; ; DPH for drives -define_drive dph, 128*64, 2048, 128, 0 +;define_drive dph, 128*64, 2048, 128, 0 +define_drive dph, 32768, 2048, 1024, 256 +directory_buffer = _start NOINIT pending_key: .byte 0 ; pending keypress from system -directory_buffer = _start +;directory_buffer = _start ; vim: filetype=asm sw=4 ts=4 et diff --git a/src/arch/nano6502/nano6502.ld b/src/arch/nano6502/nano6502.ld index 8c77690b..c314d68a 100644 --- a/src/arch/nano6502/nano6502.ld +++ b/src/arch/nano6502/nano6502.ld @@ -1,6 +1,6 @@ MEMORY { zp : ORIGIN = 0x10, LENGTH = 0xf0 - ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0600 + ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0700 } SECTIONS { From 60d1337a867875548d387bbc34610f0f1d08d3b6 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 14:31:52 +0200 Subject: [PATCH 09/19] Added 3 additional 4 Mb drives --- src/arch/nano6502/build.py | 7 ++ src/arch/nano6502/buildimage.py | 64 ++++++++++++++- src/arch/nano6502/nano6502.S | 136 +++++++++++++++++++++----------- src/arch/nano6502/nano6502.ld | 2 +- 4 files changed, 161 insertions(+), 48 deletions(-) diff --git a/src/arch/nano6502/build.py b/src/arch/nano6502/build.py index e97334e7..cd2a4271 100644 --- a/src/arch/nano6502/build.py +++ b/src/arch/nano6502/build.py @@ -29,10 +29,17 @@ | PASCAL_APPS, ) +mkcpmfs( + name="emptycpmfs", + format="sorbus", + items="", +) + normalrule( name="diskimage", ins=[ ".+cpmfs", + ".+emptycpmfs", ".+nano6502", "src/bdos", ], diff --git a/src/arch/nano6502/buildimage.py b/src/arch/nano6502/buildimage.py index 7a1fc677..565008d5 100755 --- a/src/arch/nano6502/buildimage.py +++ b/src/arch/nano6502/buildimage.py @@ -4,12 +4,13 @@ import os bdos_offset = 512*256 -cpmfs_offset = 512*256*2 +cpmfs_offset = 512*256 +cpmfs_size = 1024*4096 bios_filename = '.obj/src/arch/nano6502/+nano6502/+nano6502' bdos_filename = '.obj/src/bdos/+bdos/+bdos' cpmfs_filename = '.obj/src/arch/nano6502/+cpmfs/src/arch/nano6502/+cpmfs.img' - +cpmfs_empty_filename = '.obj/src/arch/nano6502/+emptycpmfs/src/arch/nano6502/+emptycpmfs.img' output_filename = './nano6502.img' size = os.path.getsize(bios_filename) @@ -61,5 +62,64 @@ outfile.write(byte) byte=infile.read(1) +padding = cpmfs_size - size; +out=0 +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() + +# Write empty drive B +size = os.path.getsize(cpmfs_empty_filename) + +infile = open(cpmfs_empty_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = cpmfs_size - size; +out=0 +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() + +# Write empty drive C +size = os.path.getsize(cpmfs_empty_filename) + +infile = open(cpmfs_empty_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = cpmfs_size - size; +out=0 +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; +infile.close() + +# Write empty drive D +size = os.path.getsize(cpmfs_empty_filename) + +infile = open(cpmfs_empty_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = cpmfs_size - size; +out=0 +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + infile.close() outfile.close() diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 5671811e..61e7ac1d 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -47,7 +47,10 @@ uart_rx_avail = $fe03 ; Wasteful, but it's not like we are going to use the full card anyway BDOS_OFFSET = $1 BDOS_BLOCKS = $0a ; Number of 512 byte blocks to load -CPMFS_OFFSET = $3 +CPMFS_A_OFFSET = $2 +CPMFS_B_OFFSET = $22 +CPMFS_C_OFFSET = $42 +CPMFS_D_OFFSET = $62 ZEROPAGE @@ -98,7 +101,7 @@ banner_wait: ; Open CPMFS - lda #CPMFS_OFFSET + lda #CPMFS_A_OFFSET sta sd_offset ; Initialize drivers @@ -553,14 +556,54 @@ zendproc zproc bios_SELDSK cmp #0 - zif_ne - sec ; invalid drive + zif_eq + lda #CPMFS_A_OFFSET + sta sd_offset + lda #dph_a + clc rts zendif - lda #dph - clc + cmp #1 + zif_eq + lda #CPMFS_B_OFFSET + sta sd_offset + lda #dph_b + clc + rts + zendif + + cmp #2 + zif_eq + lda #CPMFS_C_OFFSET + sta sd_offset + lda #dph_c + clc + rts + zendif + + cmp #3 + zif_eq + lda #CPMFS_D_OFFSET + sta sd_offset + lda #dph_d + clc + rts + zendif + + ;zif_ne + ; sec ; invalid drive + ; rts + ;zendif + + ;lda #dph + ;clc + sec rts zendproc @@ -759,7 +802,7 @@ zproc calculate_sd_address ; Add offset clc lda sd_sector+1 - adc #CPMFS_OFFSET + adc sd_offset sta sd_sector+1 zif_cs @@ -798,47 +841,47 @@ zproc sd_wait rts zendproc -zproc uart_out - pha - ; Preserve io page - lda IO_page_reg - sta kuken +;zproc uart_out +; pha +; ; Preserve io page +; lda IO_page_reg +; sta kuken - lda #IO_page_uart - sta IO_page_reg +; lda #IO_page_uart +; sta IO_page_reg -uart_out_wait: - lda uart_tx_done - beq uart_out_wait +;uart_out_wait: +; lda uart_tx_done +; beq uart_out_wait - pla - sta uart_tx_data +; pla +; sta uart_tx_data - lda kuken - sta IO_page_reg - rts -zendproc +; lda kuken +; sta IO_page_reg +; rts +;zendproc -zproc uart_hex - pha - lsr a - lsr a - lsr a - lsr a - jsr h4 - pla -h4: - and #0x0f - ora #'0' - cmp #'9'+1 - zif_cs - adc #6 - zendif - pha - jsr uart_out - pla - rts -zendproc +;zproc uart_hex +; pha +; lsr a +; lsr a +; lsr a +; lsr a +; jsr h4 +; pla +;h4: +; and #0x0f +; ora #'0' +; cmp #'9'+1 +; zif_cs +; adc #6 +; zendif +; pha +; jsr uart_out +; pla +; rts +;zendproc .data vid_style: .byte 0 @@ -857,7 +900,10 @@ kuken: .byte 0 ; DPH for drives ;define_drive dph, 128*64, 2048, 128, 0 -define_drive dph, 32768, 2048, 1024, 256 +define_drive dph_a, 32768, 2048, 1024, 256 +define_drive dph_b, 32768, 2048, 1024, 256 +define_drive dph_c, 32768, 2048, 1024, 256 +define_drive dph_d, 32768, 2048, 1024, 256 directory_buffer = _start NOINIT diff --git a/src/arch/nano6502/nano6502.ld b/src/arch/nano6502/nano6502.ld index c314d68a..485e39c9 100644 --- a/src/arch/nano6502/nano6502.ld +++ b/src/arch/nano6502/nano6502.ld @@ -1,6 +1,6 @@ MEMORY { zp : ORIGIN = 0x10, LENGTH = 0xf0 - ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0700 + ram (rw) : ORIGIN = 0x0300, LENGTH = 0x0E00 } SECTIONS { From afae0733ec4d8ed7a8b560d433572058a09c14d9 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 15:39:51 +0200 Subject: [PATCH 10/19] Changed to 16 1MB drives --- src/arch/nano6502/build.py | 4 +- src/arch/nano6502/buildimage.py | 70 +++---- src/arch/nano6502/nano6502.S | 321 ++++++++++++++++---------------- 3 files changed, 190 insertions(+), 205 deletions(-) diff --git a/src/arch/nano6502/build.py b/src/arch/nano6502/build.py index cd2a4271..febbeb9c 100644 --- a/src/arch/nano6502/build.py +++ b/src/arch/nano6502/build.py @@ -19,7 +19,7 @@ mkcpmfs( name="cpmfs", - format="sorbus", + format="generic-1m", items={"0:ccp.sys@sr": "src+ccp"} | MINIMAL_APPS | MINIMAL_APPS_SRCS @@ -31,7 +31,7 @@ mkcpmfs( name="emptycpmfs", - format="sorbus", + format="generic-1m", items="", ) diff --git a/src/arch/nano6502/buildimage.py b/src/arch/nano6502/buildimage.py index 565008d5..84778b13 100755 --- a/src/arch/nano6502/buildimage.py +++ b/src/arch/nano6502/buildimage.py @@ -5,23 +5,26 @@ bdos_offset = 512*256 cpmfs_offset = 512*256 -cpmfs_size = 1024*4096 +cpmfs_size = 1024*1024 bios_filename = '.obj/src/arch/nano6502/+nano6502/+nano6502' bdos_filename = '.obj/src/bdos/+bdos/+bdos' cpmfs_filename = '.obj/src/arch/nano6502/+cpmfs/src/arch/nano6502/+cpmfs.img' cpmfs_empty_filename = '.obj/src/arch/nano6502/+emptycpmfs/src/arch/nano6502/+emptycpmfs.img' output_filename = './nano6502.img' +output_sysonly_filename = './nano6502_sysonly.img' size = os.path.getsize(bios_filename) infile=open(bios_filename, "rb") outfile=open(output_filename, "wb") +sysfile=open(output_sysonly_filename, "wb") byte=infile.read(1) # Write BIOS while byte: outfile.write(byte) + sysfile.write(byte) byte=infile.read(1) padding = bdos_offset - size; @@ -29,6 +32,7 @@ while padding: outfile.write(out.to_bytes(1,"little")) + sysfile.write(out.to_bytes(1,"little")) padding = padding - 1; infile.close() @@ -41,6 +45,7 @@ byte=infile.read(1) while byte: outfile.write(byte) + sysfile.write(byte) byte=infile.read(1) padding = cpmfs_offset - size; @@ -48,6 +53,7 @@ while padding: outfile.write(out.to_bytes(1,"little")) + sysfile.write(out.to_bytes(1,"little")) padding = padding - 1; infile.close() @@ -60,66 +66,36 @@ byte=infile.read(1) while byte: outfile.write(byte) + sysfile.write(byte) byte=infile.read(1) padding = cpmfs_size - size; out=0 while padding: outfile.write(out.to_bytes(1,"little")) + sysfile.write(out.to_bytes(1,"little")) padding = padding - 1; infile.close() +sysfile.close() -# Write empty drive B -size = os.path.getsize(cpmfs_empty_filename) +# Write empty drives B-O +for i in range(15): + size = os.path.getsize(cpmfs_empty_filename) -infile = open(cpmfs_empty_filename, "rb") + infile = open(cpmfs_empty_filename, "rb") -byte=infile.read(1) -while byte: - outfile.write(byte) byte=infile.read(1) + while byte: + outfile.write(byte) + byte=infile.read(1) -padding = cpmfs_size - size; -out=0 -while padding: - outfile.write(out.to_bytes(1,"little")) - padding = padding - 1; - -infile.close() + padding = cpmfs_size - size; + out=0 + while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; -# Write empty drive C -size = os.path.getsize(cpmfs_empty_filename) + infile.close() -infile = open(cpmfs_empty_filename, "rb") - -byte=infile.read(1) -while byte: - outfile.write(byte) - byte=infile.read(1) - -padding = cpmfs_size - size; -out=0 -while padding: - outfile.write(out.to_bytes(1,"little")) - padding = padding - 1; -infile.close() - -# Write empty drive D -size = os.path.getsize(cpmfs_empty_filename) - -infile = open(cpmfs_empty_filename, "rb") - -byte=infile.read(1) -while byte: - outfile.write(byte) - byte=infile.read(1) - -padding = cpmfs_size - size; -out=0 -while padding: - outfile.write(out.to_bytes(1,"little")) - padding = padding - 1; - -infile.close() outfile.close() diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 61e7ac1d..006eccc0 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -34,7 +34,6 @@ sd_busy = $fe04 sd_read_strobe = $fe05 sd_write_strobe = $fe06 sd_page = $fe07 -sd_done = $fe0a sd_data = $fe80 uart_tx_data = $fe00 @@ -43,14 +42,25 @@ uart_rx_data = $fe02 uart_rx_avail = $fe03 -; Offset to second byte of SDCARD adress -; Wasteful, but it's not like we are going to use the full card anyway +; Offset to second byte of SDCARD address BDOS_OFFSET = $1 BDOS_BLOCKS = $0a ; Number of 512 byte blocks to load -CPMFS_A_OFFSET = $2 -CPMFS_B_OFFSET = $22 -CPMFS_C_OFFSET = $42 -CPMFS_D_OFFSET = $62 +CPMFS_A_OFFSET = $02 +CPMFS_B_OFFSET = $0A +CPMFS_C_OFFSET = $12 +CPMFS_D_OFFSET = $1A +CPMFS_E_OFFSET = $22 +CPMFS_F_OFFSET = $2A +CPMFS_G_OFFSET = $32 +CPMFS_H_OFFSET = $3A +CPMFS_I_OFFSET = $42 +CPMFS_J_OFFSET = $4A +CPMFS_K_OFFSET = $52 +CPMFS_L_OFFSET = $5A +CPMFS_M_OFFSET = $62 +CPMFS_N_OFFSET = $6A +CPMFS_O_OFFSET = $72 +CPMFS_P_OFFSET = $7A ZEROPAGE @@ -214,16 +224,11 @@ zendproc banner: ; reversed! .byte 13, 10 - .ascii "-------------------- 2056onan rof 56-M/PC --------------------" + .ascii "2056onan rof 56-M/PC" banner_end: .byte 00 ; Stuff above of here must be 0x80 long, currently OK -; ---- Drive filenames ----- -#cpmfs_filename: -# .ascii "CPMFS" -# .byte 00 - ; --- Drivers ------------------------------------------------------- .data .global drvtop @@ -286,59 +291,6 @@ screen_jmptable_hi: jmptabhi screen_setstyle zendproc -; SERIAL driver strategy routine -; Y = TTY opcode - -;defdriver "SERIAL", DRVID_SERIAL, drvstrat_SERIAL, 0 - -;zproc drvstrat_SERIAL -; jmpdispatch serial_jmptable_lo, serial_jmptable_hi - -;serial_jmptable_lo: -; jmptablo serial_inp -; jmptablo serial_out -; jmptablo serial_open -; jmptablo serial_close -; jmptablo serial_outp -; jmptablo serial_in -;serial_jmptable_hi: -; jmptabhi serial_inp -; jmptabhi serial_out -; jmptabhi serial_open -; jmptabhi serial_close -; jmptabhi serial_outp -; jmptabhi serial_in -;zendproc - -; --- SERIAL driver ------------------------------------------------------- - -;zproc serial_inp -; jsr ACIA3_Scan -; rts -;zendproc - -;zproc serial_out -; jmp ACIA3_Output -;zendproc - -;zproc serial_open -; jmp ACIA3_init -;zendproc - -;zproc serial_close -; rts -;zendproc - -;zproc serial_outp -; jsr ACIA3_Output -; clc -; rts -;zendproc - -;zproc serial_in -; jmp ACIA3_Input -;zendproc - ; --- SCREEN driver ------------------------------------------------------- zproc screen_version @@ -595,14 +547,128 @@ zproc bios_SELDSK rts zendif - ;zif_ne - ; sec ; invalid drive - ; rts - ;zendif - - ;lda #dph - ;clc + cmp #4 + zif_eq + lda #CPMFS_E_OFFSET + sta sd_offset + lda #dph_e + clc + rts + zendif + + cmp #5 + zif_eq + lda #CPMFS_F_OFFSET + sta sd_offset + lda #dph_f + clc + rts + zendif + + cmp #6 + zif_eq + lda #CPMFS_G_OFFSET + sta sd_offset + lda #dph_g + clc + rts + zendif + + cmp #7 + zif_eq + lda #CPMFS_H_OFFSET + sta sd_offset + lda #dph_h + clc + rts + zendif + + cmp #8 + zif_eq + lda #CPMFS_I_OFFSET + sta sd_offset + lda #dph_i + clc + rts + zendif + + cmp #9 + zif_eq + lda #CPMFS_J_OFFSET + sta sd_offset + lda #dph_j + clc + rts + zendif + + cmp #10 + zif_eq + lda #CPMFS_K_OFFSET + sta sd_offset + lda #dph_k + clc + rts + zendif + + cmp #11 + zif_eq + lda #CPMFS_L_OFFSET + sta sd_offset + lda #dph_l + clc + rts + zendif + + cmp #12 + zif_eq + lda #CPMFS_M_OFFSET + sta sd_offset + lda #dph_m + clc + rts + zendif + + cmp #13 + zif_eq + lda #CPMFS_N_OFFSET + sta sd_offset + lda #dph_d + clc + rts + zendif + + cmp #14 + zif_eq + lda #CPMFS_O_OFFSET + sta sd_offset + lda #dph_o + clc + rts + zendif + + cmp #15 + zif_eq + lda #CPMFS_P_OFFSET + sta sd_offset + lda #dph_p + clc + rts + zendif + + + ; Invalid drive sec rts zendproc @@ -628,13 +694,6 @@ zproc bios_SETSEC zendproc zproc bios_READ - ;lda #'R' - ;jsr uart_out - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - ; Perform SD-card read jsr sdcard_read @@ -653,16 +712,6 @@ zproc bios_READ zendproc zproc bios_WRITE - ;lda #'W' - ;jsr uart_out - ;lda sd_page - ;jsr uart_hex - ;lda #13 - ;jsr uart_out - ;lda #10 - ;jsr uart_out - - ; Perform SD-card read jsr sdcard_read @@ -745,18 +794,7 @@ zendproc zproc calculate_sd_address ; CP/M sector length is 128 bytes ; SD card sector length is 512 bytes - ; Setup adressing... - ; DEBUG - ;lda #'S' - ;jsr uart_out - ;ldy #1 - ;lda (ptr),y - ;jsr uart_hex - ;dey - ;lda (ptr),y - ;jsr uart_hex - ; Get page offset lda cpm_sector and #$03 @@ -783,7 +821,6 @@ zproc calculate_sd_address zuntil_eq ; Store calculated sector - ; Could be turned into loop... lda #0 sta sd_sector+3 @@ -841,76 +878,48 @@ zproc sd_wait rts zendproc -;zproc uart_out -; pha -; ; Preserve io page -; lda IO_page_reg -; sta kuken - -; lda #IO_page_uart -; sta IO_page_reg - -;uart_out_wait: -; lda uart_tx_done -; beq uart_out_wait - -; pla -; sta uart_tx_data - -; lda kuken -; sta IO_page_reg -; rts -;zendproc - -;zproc uart_hex -; pha -; lsr a -; lsr a -; lsr a -; lsr a -; jsr h4 -; pla -;h4: -; and #0x0f -; ora #'0' -; cmp #'9'+1 -; zif_cs -; adc #6 -; zendif -; pha -; jsr uart_out -; pla -; rts -;zendproc - .data vid_style: .byte 0 -zp_base: .byte __ZEROPAGE_START__;, __ZP1_START__ -zp_end: .byte __ZEROPAGE_END__;, __ZP1_END__ +zp_base: .byte __ZEROPAGE_START__; +zp_end: .byte __ZEROPAGE_END__; -mem_base: .byte __TPA0_START__@mos16hi, __TPA0_START__@mos16hi -mem_end: .byte __TPA0_END__@mos16hi, __TPA0_START__@mos16hi +mem_base: .byte __TPA0_START__@mos16hi +mem_end: .byte __TPA0_END__@mos16hi sd_offset: .byte 0 sd_page_zp: .byte 0 sd_sector: .fill 4 cpm_sector: .fill 3 -kuken: .byte 0 + ; ; DPH for drives +; + +define_drive dph_a, 128*64, 2048, 128, 0 +define_drive dph_b, 128*64, 2048, 128, 0 +define_drive dph_c, 128*64, 2048, 128, 0 +define_drive dph_d, 128*64, 2048, 128, 0 + +define_drive dph_e, 128*64, 2048, 128, 0 +define_drive dph_f, 128*64, 2048, 128, 0 +define_drive dph_g, 128*64, 2048, 128, 0 +define_drive dph_h, 128*64, 2048, 128, 0 + +define_drive dph_i, 128*64, 2048, 128, 0 +define_drive dph_j, 128*64, 2048, 128, 0 +define_drive dph_k, 128*64, 2048, 128, 0 +define_drive dph_l, 128*64, 2048, 128, 0 -;define_drive dph, 128*64, 2048, 128, 0 -define_drive dph_a, 32768, 2048, 1024, 256 -define_drive dph_b, 32768, 2048, 1024, 256 -define_drive dph_c, 32768, 2048, 1024, 256 -define_drive dph_d, 32768, 2048, 1024, 256 +define_drive dph_m, 128*64, 2048, 128, 0 +define_drive dph_n, 128*64, 2048, 128, 0 +define_drive dph_o, 128*64, 2048, 128, 0 +define_drive dph_p, 128*64, 2048, 128, 0 directory_buffer = _start NOINIT pending_key: .byte 0 ; pending keypress from system -;directory_buffer = _start ; vim: filetype=asm sw=4 ts=4 et From 544547082c498fb890915fed1459cc4be90f9f98 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 15:44:34 +0200 Subject: [PATCH 11/19] Cleaned up build settings --- build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.py b/build.py index 34f1cfeb..d0066d27 100644 --- a/build.py +++ b/build.py @@ -30,7 +30,8 @@ "vic20.d64": "src/arch/commodore+vic20_diskimage", "x16.zip": "src/arch/x16+diskimage", "sorbus.zip": "src/arch/sorbus+diskimage", - "nano6502.zip": "src/arch/nano6502+diskimage", + "nano6502.img": "src/arch/nano6502+diskimage", + "nano6502_sysonly.img": "src/arch/nano6502+diskimage", }, deps=[ "tests" From fa81825e8c24bd1c200f121cab4a49913bc98ab8 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 17:04:05 +0200 Subject: [PATCH 12/19] Further clean up of build. Now generates two images, one with all file systems and with only the A drive --- build.py | 2 +- src/arch/nano6502/build.py | 13 +++++ src/arch/nano6502/buildimage.py | 37 ++++++++---- src/arch/nano6502/buildsysimage.py | 94 ++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 13 deletions(-) create mode 100755 src/arch/nano6502/buildsysimage.py diff --git a/build.py b/build.py index d0066d27..6c60ddf8 100644 --- a/build.py +++ b/build.py @@ -31,7 +31,7 @@ "x16.zip": "src/arch/x16+diskimage", "sorbus.zip": "src/arch/sorbus+diskimage", "nano6502.img": "src/arch/nano6502+diskimage", - "nano6502_sysonly.img": "src/arch/nano6502+diskimage", + "nano6502_sysonly.img": "src/arch/nano6502+sysimage", }, deps=[ "tests" diff --git a/src/arch/nano6502/build.py b/src/arch/nano6502/build.py index febbeb9c..3a42e8e9 100644 --- a/src/arch/nano6502/build.py +++ b/src/arch/nano6502/build.py @@ -47,3 +47,16 @@ commands=["rm -f {outs[0]}","./src/arch/nano6502/buildimage.py"], label="IMG", ) + +normalrule( + name="sysimage", + ins=[ + ".+cpmfs", + ".+nano6502", + "src/bdos", + ], + outs=["nano6502_sysonly.img"], + commands=["rm -f {outs[0]}","./src/arch/nano6502/buildsysimage.py"], + label="IMG", +) + diff --git a/src/arch/nano6502/buildimage.py b/src/arch/nano6502/buildimage.py index 84778b13..12583d96 100755 --- a/src/arch/nano6502/buildimage.py +++ b/src/arch/nano6502/buildimage.py @@ -3,6 +3,7 @@ import sys import os +bootsector_size = 512 bdos_offset = 512*256 cpmfs_offset = 512*256 cpmfs_size = 1024*1024 @@ -11,28 +12,45 @@ bdos_filename = '.obj/src/bdos/+bdos/+bdos' cpmfs_filename = '.obj/src/arch/nano6502/+cpmfs/src/arch/nano6502/+cpmfs.img' cpmfs_empty_filename = '.obj/src/arch/nano6502/+emptycpmfs/src/arch/nano6502/+emptycpmfs.img' -output_filename = './nano6502.img' -output_sysonly_filename = './nano6502_sysonly.img' +output_filename = '.obj/src/arch/nano6502/+diskimage/nano6502.img' size = os.path.getsize(bios_filename) -infile=open(bios_filename, "rb") outfile=open(output_filename, "wb") -sysfile=open(output_sysonly_filename, "wb") +# Write boot sector +# Boot sector format +# Magic number: 0x6E, 0x61, 0x6E, 0x6F +# SD sector to load - 4 bytes +# Number of pages to load - 1 byte +# Page to load data to - 1 byte +# Total 10 bytes +# Padding 502 bytes + +bootsector_data = [0x6E, 0x61, 0x6E, 0x6F, 0x00, 0x00, 0x00, 0x01, 0x04, 0x03] + +for d in bootsector_data: + outfile.write(d.to_bytes(1, "little")) + +out=0 +for i in range(bootsector_size - len(bootsector_data)): + outfile.write(out.to_bytes(1, "little")) + +# Write BIOS + +infile=open(bios_filename, "rb") byte=infile.read(1) + # Write BIOS while byte: outfile.write(byte) - sysfile.write(byte) byte=infile.read(1) -padding = bdos_offset - size; +padding = bdos_offset - size - bootsector_size out = 0 while padding: outfile.write(out.to_bytes(1,"little")) - sysfile.write(out.to_bytes(1,"little")) padding = padding - 1; infile.close() @@ -45,7 +63,6 @@ byte=infile.read(1) while byte: outfile.write(byte) - sysfile.write(byte) byte=infile.read(1) padding = cpmfs_offset - size; @@ -53,7 +70,6 @@ while padding: outfile.write(out.to_bytes(1,"little")) - sysfile.write(out.to_bytes(1,"little")) padding = padding - 1; infile.close() @@ -66,18 +82,15 @@ byte=infile.read(1) while byte: outfile.write(byte) - sysfile.write(byte) byte=infile.read(1) padding = cpmfs_size - size; out=0 while padding: outfile.write(out.to_bytes(1,"little")) - sysfile.write(out.to_bytes(1,"little")) padding = padding - 1; infile.close() -sysfile.close() # Write empty drives B-O for i in range(15): diff --git a/src/arch/nano6502/buildsysimage.py b/src/arch/nano6502/buildsysimage.py new file mode 100755 index 00000000..1612aa87 --- /dev/null +++ b/src/arch/nano6502/buildsysimage.py @@ -0,0 +1,94 @@ +#!/usr/bin/python3 + +import sys +import os + +bootsector_size = 512 +bdos_offset = 512*256 +cpmfs_offset = 512*256 +cpmfs_size = 1024*1024 + +bios_filename = '.obj/src/arch/nano6502/+nano6502/+nano6502' +bdos_filename = '.obj/src/bdos/+bdos/+bdos' +cpmfs_filename = '.obj/src/arch/nano6502/+cpmfs/src/arch/nano6502/+cpmfs.img' +cpmfs_empty_filename = '.obj/src/arch/nano6502/+emptycpmfs/src/arch/nano6502/+emptycpmfs.img' +output_filename = '.obj/src/arch/nano6502/+sysimage/nano6502_sysonly.img' + +size = os.path.getsize(bios_filename) + +outfile=open(output_filename, "wb") + +# Write boot sector +# Boot sector format +# Magic number: 0x6E, 0x61, 0x6E, 0x6F +# SD sector to load - 4 bytes +# Number of pages to load - 1 byte +# Page to load data to - 1 byte +# Total 10 bytes +# Padding 502 bytes + +bootsector_data = [0x6E, 0x61, 0x6E, 0x6F, 0x00, 0x00, 0x00, 0x01, 0x04, 0x03] + +for d in bootsector_data: + outfile.write(d.to_bytes(1, "little")) + +out=0 +for i in range(bootsector_size - len(bootsector_data)): + outfile.write(out.to_bytes(1, "little")) + +# Write BIOS + +infile=open(bios_filename, "rb") +byte=infile.read(1) + +# Write BIOS +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = bdos_offset - size - bootsector_size +out = 0 + +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() + +# Write BDOS +size = os.path.getsize(bdos_filename) + +infile = open(bdos_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = cpmfs_offset - size; +out = 0 + +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() + +# Write CPMFS +size = os.path.getsize(cpmfs_filename) + +infile = open(cpmfs_filename, "rb") + +byte=infile.read(1) +while byte: + outfile.write(byte) + byte=infile.read(1) + +padding = cpmfs_size - size; +out=0 +while padding: + outfile.write(out.to_bytes(1,"little")) + padding = padding - 1; + +infile.close() +outfile.close() From afd76638b59578d95ceed865180914a7b0dd49b6 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Sun, 16 Jun 2024 17:17:24 +0200 Subject: [PATCH 13/19] Added drive activity indication LED driving --- src/arch/nano6502/nano6502.S | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 006eccc0..85154117 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -7,6 +7,7 @@ #include "driver.inc" #include "jumptables.inc" IO_page_uart = $01 +IO_page_led = $02 IO_page_sdcard = $03 IO_page_video = $04 IO_page_reg = $00 @@ -36,6 +37,8 @@ sd_write_strobe = $fe06 sd_page = $fe07 sd_data = $fe80 +led_base = $fe00 + uart_tx_data = $fe00 uart_tx_done = $fe01 uart_rx_data = $fe02 @@ -694,6 +697,9 @@ zproc bios_SETSEC zendproc zproc bios_READ + ; Turn on drive indication LED + jsr toggle_led + ; Perform SD-card read jsr sdcard_read @@ -706,12 +712,18 @@ zproc bios_READ iny cpy #$80 zuntil_eq - + + ; Turn off drive indication LED + jsr toggle_led + clc rts zendproc zproc bios_WRITE + ; Turn on drive indication LED + jsr toggle_led + ; Perform SD-card read jsr sdcard_read @@ -728,6 +740,10 @@ zproc bios_WRITE sta sd_write_strobe jsr sd_wait + + ; Turn off drive indication LED + jsr toggle_led + rts zendproc @@ -878,6 +894,19 @@ zproc sd_wait rts zendproc +zproc toggle_led + ; Set LED IO page + lda #IO_page_led + sta IO_page_reg + + ; Toggle first LED + lda led_base + eor #$01 + sta led_base + + rts +zendproc + .data vid_style: .byte 0 zp_base: .byte __ZEROPAGE_START__; From 878d307be3f6bdf13fa3e9286465f524876349a9 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Mon, 24 Jun 2024 16:44:43 +0200 Subject: [PATCH 14/19] Moved driver code around to a more sensible configuration --- src/arch/nano6502/buildimage.py | 2 +- src/arch/nano6502/buildsysimage.py | 2 +- src/arch/nano6502/nano6502.S | 125 +++++++++++++++-------------- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/arch/nano6502/buildimage.py b/src/arch/nano6502/buildimage.py index 12583d96..f30157eb 100755 --- a/src/arch/nano6502/buildimage.py +++ b/src/arch/nano6502/buildimage.py @@ -27,7 +27,7 @@ # Total 10 bytes # Padding 502 bytes -bootsector_data = [0x6E, 0x61, 0x6E, 0x6F, 0x00, 0x00, 0x00, 0x01, 0x04, 0x03] +bootsector_data = [0x6E, 0x61, 0x6E, 0x6F, 0x00, 0x00, 0x00, 0x01, 0x05, 0x03] for d in bootsector_data: outfile.write(d.to_bytes(1, "little")) diff --git a/src/arch/nano6502/buildsysimage.py b/src/arch/nano6502/buildsysimage.py index 1612aa87..403956f6 100755 --- a/src/arch/nano6502/buildsysimage.py +++ b/src/arch/nano6502/buildsysimage.py @@ -27,7 +27,7 @@ # Total 10 bytes # Padding 502 bytes -bootsector_data = [0x6E, 0x61, 0x6E, 0x6F, 0x00, 0x00, 0x00, 0x01, 0x04, 0x03] +bootsector_data = [0x6E, 0x61, 0x6E, 0x6F, 0x00, 0x00, 0x00, 0x01, 0x05, 0x03] for d in bootsector_data: outfile.write(d.to_bytes(1, "little")) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index 85154117..a5859cfb 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -237,6 +237,8 @@ banner_end: .global drvtop drvtop: .word drv_TTY +; --- TTY driver ------------------------------------------------------- + defdriver TTY, DRVID_TTY, drvstrat_TTY, drv_SCREEN ; TTY driver strategy routine @@ -255,6 +257,67 @@ jmptable_hi: jmptabhi tty_conout zendproc +; Returns 0x00 if no key is pending, 0xff if one is. +; C if no key is pending, !C if key pending +zproc tty_const + lda pending_key + zif_eq + lda #IO_page_uart + sta IO_page_reg + lda uart_rx_avail + zif_eq + lda #$00 + sec + rts + zendif + lda uart_rx_data + sta pending_key + zendif + lda #$ff + clc + rts +zendproc + +; Blocks and waits for the next keypress; returns it in A. + +zproc tty_conin + lda pending_key + zif_eq + lda #IO_page_uart + sta IO_page_reg +tty_input_wait: + lda uart_rx_avail + beq tty_input_wait + lda uart_rx_data + rts + zendif + + ldx #0 + stx pending_key + clc + rts +zendproc + +zproc tty_conout + cmp #127 + zif_eq + lda #8 + zendif + pha + lda #IO_page_video + sta IO_page_reg +conout_wait: + lda tty_busy + bne conout_wait + pla + sta tty_write + clc + rts +zendproc + + + +; --- SCREEN driver ------------------------------------------------------- defdriver SCREEN, DRVID_SCREEN, drvstrat_SCREEN, 0 @@ -294,8 +357,6 @@ screen_jmptable_hi: jmptabhi screen_setstyle zendproc -; --- SCREEN driver ------------------------------------------------------- - zproc screen_version lda #0 rts @@ -434,66 +495,6 @@ normal_style: rts zendproc -; --- TTY driver ------------------------------------------------------- - -; Returns 0x00 if no key is pending, 0xff if one is. -; C if no key is pending, !C if key pending -zproc tty_const - lda pending_key - zif_eq - lda #IO_page_uart - sta IO_page_reg - lda uart_rx_avail - zif_eq - lda #$00 - sec - rts - zendif - lda uart_rx_data - sta pending_key - zendif - lda #$ff - clc - rts -zendproc - -; Blocks and waits for the next keypress; returns it in A. - -zproc tty_conin - lda pending_key - zif_eq - lda #IO_page_uart - sta IO_page_reg -tty_input_wait: - lda uart_rx_avail - beq tty_input_wait - lda uart_rx_data - rts - zendif - - ldx #0 - stx pending_key - clc - rts -zendproc - -zproc tty_conout - cmp #127 - zif_eq - lda #8 - zendif - pha - lda #IO_page_video - sta IO_page_reg -conout_wait: - lda tty_busy - bne conout_wait - pla - sta tty_write - clc - rts -zendproc - ; -- Rest of the BIOS --- ; Sets the current DMA address. From 1a6e42919413c6c6199937a598893cdec7499046 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Tue, 9 Jul 2024 11:38:28 +0200 Subject: [PATCH 15/19] Changes to screen driver to match SoC updates + timeout added to getchar --- src/arch/nano6502/nano6502.S | 85 +++++++++++++++++------------------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/arch/nano6502/nano6502.S b/src/arch/nano6502/nano6502.S index a5859cfb..04b60425 100644 --- a/src/arch/nano6502/nano6502.S +++ b/src/arch/nano6502/nano6502.S @@ -1,4 +1,5 @@ ; CP/M-65 Copyright © 2022 David Given +; nano6502 BIOS Copyright © 2024 Henrik Löfgren ; This file is licensed under the terms of the 2-clause BSD license. Please ; see the COPYING file in the root project directory for the full text. @@ -10,12 +11,14 @@ IO_page_uart = $01 IO_page_led = $02 IO_page_sdcard = $03 IO_page_video = $04 +IO_page_timer = $05 IO_page_reg = $00 ROM_sel_reg = $02 tty_write = $fe06 tty_cls = $fe09 tty_busy = $fe07 + video_line = $fe00 video_cursor_x = $fe01 video_cursor_y = $fe02 @@ -24,8 +27,14 @@ video_scroll_up = $fe04 video_scroll_down = $fe05 video_clear_to_eol = $fe08 video_tty_enable = $fe0a +video_scroll_enable = $fe0b video_line_data = $fe80 +timer_idle = $fe00 +timer_start_strobe = $fe01 +timer_cs_lsb = $fe02 +timer_cs_msb = $fe03 + sd_base = $fe00 sd_addr_0 = $fe00 sd_addr_1 = $fe01 @@ -303,13 +312,7 @@ zproc tty_conout zif_eq lda #8 zendif - pha - lda #IO_page_video - sta IO_page_reg -conout_wait: - lda tty_busy - bne conout_wait - pla + jsr video_init sta tty_write clc rts @@ -392,42 +395,59 @@ zendproc zproc screen_putchar jsr video_init pha - lda video_cursor_y - sta video_line - ldx video_cursor_x + lda #0 + sta video_scroll_enable pla ora vid_style - sta video_line_data,x - - lda video_cursor_x - cmp #79 - zif_ne - inc video_cursor_x - zendif - + sta tty_write + lda #1 + sta video_scroll_enable rts zendproc zproc screen_putstring sta ptr+0 stx ptr+1 + jsr video_init ldy #0 + lda #0 + sta video_scroll_enable putstring_loop: lda (ptr),y beq putstring_endloop - jsr screen_putchar + ora vid_style + sta tty_write + jsr video_wait iny jmp putstring_loop putstring_endloop: + lda #1 + sta video_scroll_enable clc rts zendproc zproc screen_getchar + ; Setup timer + pha + lda #IO_page_timer + sta IO_page_reg + pla + sta timer_cs_lsb + stx timer_cs_msb + sta timer_start_strobe + +screen_getchar_wait: lda #IO_page_uart sta IO_page_reg lda uart_rx_avail - beq nodata + bne screen_getchar_data + lda #IO_page_timer + sta IO_page_reg + lda timer_idle + beq screen_getchar_wait + jmp nodata +screen_getchar_data: lda uart_rx_data cmp #$08 bne notbs @@ -698,9 +718,6 @@ zproc bios_SETSEC zendproc zproc bios_READ - ; Turn on drive indication LED - jsr toggle_led - ; Perform SD-card read jsr sdcard_read @@ -714,17 +731,11 @@ zproc bios_READ cpy #$80 zuntil_eq - ; Turn off drive indication LED - jsr toggle_led - clc rts zendproc zproc bios_WRITE - ; Turn on drive indication LED - jsr toggle_led - ; Perform SD-card read jsr sdcard_read @@ -742,9 +753,6 @@ zproc bios_WRITE jsr sd_wait - ; Turn off drive indication LED - jsr toggle_led - rts zendproc @@ -895,19 +903,6 @@ zproc sd_wait rts zendproc -zproc toggle_led - ; Set LED IO page - lda #IO_page_led - sta IO_page_reg - - ; Toggle first LED - lda led_base - eor #$01 - sta led_base - - rts -zendproc - .data vid_style: .byte 0 zp_base: .byte __ZEROPAGE_START__; From 418f206acb8b0d0827fdf91a9e1d898f656e469b Mon Sep 17 00:00:00 2001 From: Henrik L Date: Tue, 9 Jul 2024 13:35:56 +0200 Subject: [PATCH 16/19] Added nano6502 specific utilities to user 1 on A: --- src/arch/nano6502/build.py | 5 +- src/arch/nano6502/utils/build.py | 25 +++++++ src/arch/nano6502/utils/colorbg.S | 119 ++++++++++++++++++++++++++++++ src/arch/nano6502/utils/colorfg.S | 119 ++++++++++++++++++++++++++++++ src/arch/nano6502/utils/ledtest.S | 86 +++++++++++++++++++++ 5 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 src/arch/nano6502/utils/build.py create mode 100644 src/arch/nano6502/utils/colorbg.S create mode 100644 src/arch/nano6502/utils/colorfg.S create mode 100644 src/arch/nano6502/utils/ledtest.S diff --git a/src/arch/nano6502/build.py b/src/arch/nano6502/build.py index 3a42e8e9..5c9abd11 100644 --- a/src/arch/nano6502/build.py +++ b/src/arch/nano6502/build.py @@ -20,7 +20,10 @@ mkcpmfs( name="cpmfs", format="generic-1m", - items={"0:ccp.sys@sr": "src+ccp"} + items={"0:ccp.sys@sr": "src+ccp", + "1:colorfg.com": "src/arch/nano6502/utils+colorfg", + "1:colorbg.com": "src/arch/nano6502/utils+colorbg", + "1:ledtest.com": "src/arch/nano6502/utils+ledtest"} | MINIMAL_APPS | MINIMAL_APPS_SRCS | BIG_APPS diff --git a/src/arch/nano6502/utils/build.py b/src/arch/nano6502/utils/build.py new file mode 100644 index 00000000..e0d40f1e --- /dev/null +++ b/src/arch/nano6502/utils/build.py @@ -0,0 +1,25 @@ +from build.llvm import llvmprogram + +llvmprogram( + name="colorfg", + srcs=["./colorfg.S"], + deps=[ + "include", + ], +) + +llvmprogram( + name="colorbg", + srcs=["./colorbg.S"], + deps=[ + "include", + ], +) + +llvmprogram( + name="ledtest", + srcs=["./ledtest.S"], + deps=[ + "include", + ], +) diff --git a/src/arch/nano6502/utils/colorbg.S b/src/arch/nano6502/utils/colorbg.S new file mode 100644 index 00000000..dea594ef --- /dev/null +++ b/src/arch/nano6502/utils/colorbg.S @@ -0,0 +1,119 @@ +; --------------------------------------------------------------------------- +; +; nano6502 text color setting utility +; +; Copyright (C) 2024 Henrik Löfgren +; This file is licensed under the terms of the 2-cluse BSD license. Please +; see the COPYING file in the root project directory for the full test. +; +; --------------------------------------------------------------------------- + +#include "zif.inc" +#include "cpm65.inc" + +; Color RAM bank addresses +IO_page_reg = $00 +IO_page_video = $04 + +video_fg_r = $fe10 +video_fg_g = $fe11 +video_fg_b = $fe12 +video_bg_r = $fe13 +video_bg_g = $fe14 +video_bg_b = $fe15 + +ZEROPAGE + +bg_color: .fill 3 + +zproc main + ; Read hex color values from command line + + ldx #1 + ldy #0 + +read_bg: + ; Read first digit + lda cpm_fcb,x + + ; Convert + jsr hexconv + ; Exit if not valid hex + zif_cs + rts + zendif + + ; Store and shift to high nibble + asl + asl + asl + asl + sta bg_color,y + + inx + ; Read second digit + lda cpm_fcb,x + + ; Convert + jsr hexconv + ; Exit if not valid hex + zif_cs + rts + zendif + ; Combine with low nibble + ora bg_color,y + sta bg_color,y + + iny + inx + cpx #7 + bne read_bg + + ; Set color + lda #IO_page_video + sta IO_page_reg + lda bg_color + sta video_bg_r + lda bg_color+1 + sta video_bg_g + lda bg_color+2 + sta video_bg_b + + rts +zendproc + +; Convert ASCII hex digit to number, print usage if not valid data +zproc hexconv + cmp #'0' + bcs hexconv_a + jmp print_usage +hexconv_a: + cmp #'9'+1 + bcs hexconv_b + sec + sbc #0x30 + clc + rts +hexconv_b: + cmp #'F'+1 + bcc hexconv_c + jmp print_usage +hexconv_c: + sec + sbc #0x37 + clc + rts +print_usage: + lda #usage_string + ldy #BDOS_WRITE_STRING + jsr BDOS + sec + rts +zendproc + +usage_string: + .ascii "colorbg - set background color on the nano6502 computer" + .byte 13, 10 + .ascii "Usage: colorbg RRGGBB. Eg. color FFFFFF for white background." + .byte 13, 10, 0 diff --git a/src/arch/nano6502/utils/colorfg.S b/src/arch/nano6502/utils/colorfg.S new file mode 100644 index 00000000..8009a8f9 --- /dev/null +++ b/src/arch/nano6502/utils/colorfg.S @@ -0,0 +1,119 @@ +; --------------------------------------------------------------------------- +; +; nano6502 text color setting utility +; +; Copyright (C) 2024 Henrik Löfgren +; This file is licensed under the terms of the 2-cluse BSD license. Please +; see the COPYING file in the root project directory for the full test. +; +; --------------------------------------------------------------------------- + +#include "zif.inc" +#include "cpm65.inc" + +; Color RAM bank addresses +IO_page_reg = $00 +IO_page_video = $04 + +video_fg_r = $fe10 +video_fg_g = $fe11 +video_fg_b = $fe12 +video_bg_r = $fe13 +video_bg_g = $fe14 +video_bg_b = $fe15 + +ZEROPAGE + +fg_color: .fill 3 + +zproc main + ; Read hex color values from command line + + ldx #1 + ldy #0 + +read_fg: + ; Read first digit + lda cpm_fcb,x + + ; Convert + jsr hexconv + ; Exit if not valid hex + zif_cs + rts + zendif + + ; Store and shift to high nibble + asl + asl + asl + asl + sta fg_color,y + + inx + ; Read second digit + lda cpm_fcb,x + + ; Convert + jsr hexconv + ; Exit if not valid hex + zif_cs + rts + zendif + ; Combine with low nibble + ora fg_color,y + sta fg_color,y + + iny + inx + cpx #7 + bne read_fg + + ; Set color + lda #IO_page_video + sta IO_page_reg + lda fg_color + sta video_fg_r + lda fg_color+1 + sta video_fg_g + lda fg_color+2 + sta video_fg_b + + rts +zendproc + +; Convert ASCII hex digit to number, print usage if not valid data +zproc hexconv + cmp #'0' + bcs hexconv_a + jmp print_usage +hexconv_a: + cmp #'9'+1 + bcs hexconv_b + sec + sbc #0x30 + clc + rts +hexconv_b: + cmp #'F'+1 + bcc hexconv_c + jmp print_usage +hexconv_c: + sec + sbc #0x37 + clc + rts +print_usage: + lda #usage_string + ldy #BDOS_WRITE_STRING + jsr BDOS + sec + rts +zendproc + +usage_string: + .ascii "colorfg - set foreground color on the nano6502 computer" + .byte 13, 10 + .ascii "Usage: colorfg RRGGBB. Eg. color FFFFFF for white text." + .byte 13, 10, 0 diff --git a/src/arch/nano6502/utils/ledtest.S b/src/arch/nano6502/utils/ledtest.S new file mode 100644 index 00000000..3881c6a8 --- /dev/null +++ b/src/arch/nano6502/utils/ledtest.S @@ -0,0 +1,86 @@ +; --------------------------------------------------------------------------- +; +; nano6502 LED tester, blinks onboard LEDs for 2 seconds +; +; Copyright (C) 2024 Henrik Löfgren +; This file is licensed under the terms of the 2-cluse BSD license. Please +; see the COPYING file in the root project directory for the full test. +; +; --------------------------------------------------------------------------- + +#include "zif.inc" +#include "cpm65.inc" + +; Color RAM bank addresses +IO_page_reg = $00 +IO_page_LED = $02 +IO_page_timer = $05 + +timer_set_lsb = $fe02 +timer_set_msb = $fe03 +timer_start_strobe = $fe01 +timer_idle = $fe00 +timer_reset_strobe = $fe04 + +led_row = $fe00 +led_b = $fe03 + +ZEROPAGE + +leds: .fill 1 + +zproc main + ldx #$0a + lda #$11 + sta leds +loop: + lda #IO_page_LED + sta IO_page_reg + + lda #30 + sta led_b + + lda leds + sta led_row + ror + sta leds + jsr delay + lda #IO_page_LED + sta IO_page_reg + + lda #00 + sta led_b + + lda leds + sta led_row + ror + sta leds + + jsr delay + + dex + bne loop + + lda #IO_page_LED + sta IO_page_reg + + lda #00 + sta led_row + + rts +zendproc + +zproc delay + lda #IO_page_timer + sta IO_page_reg + lda #$0A ; 0.1 sec + sta timer_set_lsb + lda #$00 + sta timer_set_msb + sta timer_reset_strobe + sta timer_start_strobe +delaywait: + lda timer_idle + beq delaywait + rts +zendproc From 0dd57df29e8c16f61d424dcdcfcdeffc07fa6ea7 Mon Sep 17 00:00:00 2001 From: Henrik L Date: Tue, 9 Jul 2024 14:11:47 +0200 Subject: [PATCH 17/19] Added nano6502 screenshot --- doc/nano6502.png | Bin 0 -> 78086 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/nano6502.png diff --git a/doc/nano6502.png b/doc/nano6502.png new file mode 100644 index 0000000000000000000000000000000000000000..5bee43d1f48e7ecc81d1aa6d972bf3dd01ea6cf0 GIT binary patch literal 78086 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�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

gP|!iya?{%In)a0Zbz!Madsh^q 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 literal 0 HcmV?d00001 From 483e71838595c28afe7a268f40940cf7b81cb391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20L=C3=B6fgren?= <106430829+venomix666@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:25:50 +0200 Subject: [PATCH 18/19] Update README.md with nano6502 information --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 2a131fa9..008f1073 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ to the 6502. So far it runs on: - Olimex' neo6502 6502-based computer. + - nano6502 6502-based SoC for the Tang Nano 20K FPGA board; TPA is 55kB. + Unlike the original, it supports relocatable binaries, so allowing unmodified binaries to run on any system: this is necessary as 6502 systems tend to be much less standardised than 8080 and Z80 systems. (The systems above all load @@ -57,6 +59,7 @@ No, it won't let you run 8080 programs on the 6502! CP/M-65 running on an Tangerine Oric 1 CP/M-65 running on the Sorbus Computer CP/M-65 running on the Olimex neo6502 +CP/M-65 running on the nano6502 @@ -309,6 +312,22 @@ the same time. - The console is 53x30. It has a SCREEN driver. +### neo6502 notes + + - The [nano6502](https://github.com/venomix666/nano6502/) is a 65C02-based SoC for the Tang Nano 20K FPGA board. + + - The CPU is running at 25.175 MHz (i.e. the pixel clock). + + - It is using CPMFS directly on the microSD-card, with 15x1Mb partitions (drives `A` to `O`). + + - The text output is over HDMI, with 640x480 video output and a 80x30 console. It has a SCREEN driver. + + - The input is currently using the built in USB serial port. This way, this port can be run with only the Tang Nano 20K board without any specific carrier board. + + - To use, write the `nano6502.img` file into the SD-card using `dd` or your preferred SD-card image writer. If you are updating the image and want to preserve the data on all drives except `A`, write the `nano6502_sysonly.img` instead. + + - User area 1 on drive `A` contains utilities for setting the text and background colors, and a demo application which blinks the onboard LEDs. + ### Supported programs Commands include `DUMP`, `STAT`, `COPY`, `SUBMIT`, `ASM`, `QE` and `BEDIT` plus From 176ddba3ad78b3e972b06c264458fafa57c19b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20L=C3=B6fgren?= <106430829+venomix666@users.noreply.github.com> Date: Mon, 15 Jul 2024 10:46:35 +0200 Subject: [PATCH 19/19] Fixed typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 008f1073..b22c2f6a 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,7 @@ the same time. - The console is 53x30. It has a SCREEN driver. -### neo6502 notes +### nano6502 notes - The [nano6502](https://github.com/venomix666/nano6502/) is a 65C02-based SoC for the Tang Nano 20K FPGA board.