Racket/6502-emu

Från Täpp-Anders
Hoppa till navigeringHoppa till sök
#lang racket

;; ================================================
;; Enkel 6502-emulator skriven i Racket
;; ================================================
;; Detta är en grundläggande men fullt fungerande emulator för 6502-processorn.
;; Den hanterar minne (64 KB), register, stack, flaggor och ett grundläggande
;; set av instruktioner. Du kan lätt utöka den med fler opkoder.
;;
;; Instruktioner som stöds i denna version (kan utökas i cpu-step):
;;   - $00 BRK          (stoppar exekveringen)
;;   - $A9 LDA #imm
;;   - $8D STA abs
;;   - $A2 LDX #imm
;;   - $E8 INX
;;   - $4C JMP abs
;;   - $D0 BNE rel      (enkel branch)
;;
;; NYTT: Funktioner för att skriva ut minne och flaggor har lagts till!

(struct cpu (a x y pc sp p mem) #:mutable #:transparent)

;; Flaggor (statusregister P)
(define CARRY      #b00000001)
(define ZERO       #b00000010)
(define INTERRUPT  #b00000100)
(define DECIMAL    #b00001000)
(define BREAK      #b00010000)
(define OVERFLOW   #b01000000)
(define NEGATIVE   #b10000000)

;; Skapa en ny CPU (PC startar godtyckligt, SP pekar mot stack-sidan)
(define (make-cpu)
  (cpu 0 0 0 #x8000 #xFF #b00100100 (make-vector 65536 0)))

;; Minnesåtkomst
(define (mem-read cpu addr)
  (vector-ref (cpu-mem cpu) (bitwise-and addr #xFFFF)))

(define (mem-write cpu addr val)
  (vector-set! (cpu-mem cpu) (bitwise-and addr #xFFFF) (bitwise-and val #xFF)))

;; Stack (6502-stack ligger på sidan 0x01)
(define (stack-push cpu val)
  (mem-write cpu (cpu-sp cpu) val)
  (set-cpu-sp! cpu (bitwise-and #xFF (- (cpu-sp cpu) 1))))

(define (stack-pop cpu)
  (set-cpu-sp! cpu (bitwise-and #xFF (+ (cpu-sp cpu) 1)))
  (mem-read cpu (cpu-sp cpu)))

;; Uppdatera N- och Z-flaggor (används av de flesta instruktioner)
(define (update-nz cpu val)
  (set-flag cpu NEGATIVE (not (zero? (bitwise-and val #x80))))
  (set-flag cpu ZERO      (zero? (bitwise-and val #xFF))))

(define (set-flag cpu mask val)
  (if val
      (set-cpu-p! cpu (bitwise-ior (cpu-p cpu) mask))
      (set-cpu-p! cpu (bitwise-and (cpu-p cpu) (bitwise-not mask)))))

(define (get-flag cpu mask)
  (not (zero? (bitwise-and (cpu-p cpu) mask))))

;; Hjälpfunktioner för att hämta data och öka PC
(define (fetch-byte cpu)
  (define addr (cpu-pc cpu))
  (set-cpu-pc! cpu (add1 addr))
  (mem-read cpu addr))

(define (fetch-word cpu)
  (define lo (fetch-byte cpu))
  (define hi (fetch-byte cpu))
  (bitwise-ior lo (arithmetic-shift hi 8)))

;; Huvudfunktion: exekvera en instruktion
(define (cpu-step cpu)
  (define opcode (fetch-byte cpu))
  (case opcode
    ;; BRK – stoppar emulatorn
    [(#x00)
     (set-flag cpu BREAK #t)
     'brk]

    ;; LDA #imm
    [(#xA9)
     (define val (fetch-byte cpu))
     (set-cpu-a! cpu val)
     (update-nz cpu val)]

    ;; STA abs
    [(#x8D)
     (define addr (fetch-word cpu))
     (mem-write cpu addr (cpu-a cpu))]

    ;; LDX #imm
    [(#xA2)
     (define val (fetch-byte cpu))
     (set-cpu-x! cpu val)
     (update-nz cpu val)]

    ;; INX
    [(#xE8)
     (define new-x (bitwise-and #xFF (add1 (cpu-x cpu))))
     (set-cpu-x! cpu new-x)
     (update-nz cpu new-x)]

    ;; JMP abs
    [(#x4C)
     (define addr (fetch-word cpu))
     (set-cpu-pc! cpu addr)]

    ;; BNE rel (branch if not equal / Z=0)
    [(#xD0)
     (define offset (fetch-byte cpu))
     (when (not (get-flag cpu ZERO))
       (set-cpu-pc! cpu (+ (cpu-pc cpu)
                           (if (> offset #x7F) (- offset #x100) offset))))]

    [else
     (error "Okänt opcode:" (format "~x" opcode))]))

;; Ladda ett program (lista av bytes) till minnet
(define (load-program cpu start-addr program-bytes)
  (for ([b program-bytes]
        [i (in-naturals)])
    (mem-write cpu (+ start-addr i) b)))

;; Kör emulatorn tills BRK eller max-steg nås
(define (run-cpu cpu [max-steps 100000])
  (let loop ([steps 0])
    (cond
      [(>= steps max-steps) 'timeout]
      [else
       (define result (cpu-step cpu))
       (if (eq? result 'brk)
           'halted-by-brk
           (loop (add1 steps)))])))

;; ================================================
;; NYA HJÄLPFUNKTIONER: Skriv ut minne och flaggor
;; ================================================

;; Skriver ut aktuellt CPU-tillstånd (alla register + flaggor i läsbart format)
(define (print-cpu-state cpu)
  (printf "=== CPU STATUS ===~n")
  (printf "PC: $~x   A: $~x   X: $~x   Y: $~x   SP: $~x~n"
          (cpu-pc cpu)
          (cpu-a cpu)
          (cpu-x cpu)
          (cpu-y cpu)
          (cpu-sp cpu))
  (printf "P:  $~x   " (cpu-p cpu))
  (printf "Flags: [N:~a V:~a - B:~a D:~a I:~a Z:~a C:~a]~n"
          (if (get-flag cpu NEGATIVE) "1" "0")
          (if (get-flag cpu OVERFLOW) "1" "0")
          (if (get-flag cpu BREAK)    "1" "0")
          (if (get-flag cpu DECIMAL)  "1" "0")
          (if (get-flag cpu INTERRUPT)"1" "0")
          (if (get-flag cpu ZERO)     "1" "0")
          (if (get-flag cpu CARRY)    "1" "0"))
  (newline))

;; Hex-dump av ett minnesområde (16 bytes per rad + ASCII-del)
;; Användning: (print-memory my-cpu #x0000 #x00FF) eller bara (print-memory my-cpu #x0600)
(define (print-memory cpu start [end #f])
  (when (not end)
    (set! end (+ start 255)))                 ; default: 256 bytes om inget slut anges
  (set! start (bitwise-and start #xFFFF))
  (set! end   (bitwise-and end   #xFFFF))
  (printf "=== MEMORY DUMP $~4x – $~4x ===~n" start end)
  (for ([addr (in-range start (add1 end) 16)])
    (when (<= addr end)
      (printf "$~4x: " addr)
      ;; Hex-delen
      (for ([i (in-range 16)])
        (define a (+ addr i))
        (if (<= a end)
            (printf "~2x " (mem-read cpu a))
            (printf "   ")))
      ;; ASCII-del
      (printf " | ")
      (for ([i (in-range 16)])
        (define a (+ addr i))
        (if (<= a end)
            (let ([b (mem-read cpu a)])
              (printf "~a" (if (and (>= b #x20) (<= b #x7E))
                               (integer->char b)
                               ".")))
            (printf " ")))
      (newline)))
  (newline))

;; ================================================
;; EXEMPEL: Ett litet program som demonstrerar emulatorn
;; ================================================
;; Programmet gör följande:
;;   LDA #$05      ; A = 5
;;   STA $0010     ; minne[0x0010] = 5
;;   LDX #$0A      ; X = 10
;;   INX           ; X = 11
;;   JMP $0610     ; hoppa till en BRK
;;   BRK

(define example-program
  (list #xA9 #x05          ; LDA #5
        #x8D #x10 #x00     ; STA $0010
        #xA2 #x0A          ; LDX #10
        #xE8               ; INX
        #x4C #x10 #x06     ; JMP $0610
        #x00))             ; BRK (vid 0x0610)

;; Hur du kör exemplet (med de nya utskriftsfunktionerna):

(define my-cpu (make-cpu))
(load-program my-cpu #x0600 example-program)
(print-cpu-state my-cpu)
(set-cpu-pc! my-cpu #x0600)
(print-cpu-state my-cpu)
(run-cpu my-cpu)
(print-cpu-state my-cpu)

;; Flera opkoder kan läggas till för bättre emulering!