Ist eine performantere Emulation des RV32I möglich?
Eigentlich eine Fortsetzung der Ideen und Gedanken von
hier: Bitte melde dich an, um diesen Link zu sehen.
und hier: Bitte melde dich an, um diesen Link zu sehen.
Evtl. lässt sich eine Diskusion anregen und auch einige Teil-Knobelaufgaben und Lösungen aus der Fragestellung ableiten.
Hintergrundinfos zum Prozessor:
Bitte melde dich an, um diesen Link zu sehen. (Bitte melde dich an, um diesen Link zu sehen.)
Bitte melde dich an, um diesen Link zu sehen.
Bitte melde dich an, um diesen Link zu sehen.
----
Die Idee ist eine GEORAM mit min. 4MB (vorerst) zwingend vorauszusetzen, da das "Sector"-Register dann 8-bit breit ist. (siehe Bitte melde dich an, um diesen Link zu sehen.)
Der restliche C64-Speicher (bis auf ZP, Stack, evtl. I/O und Bildschirm) bleibt auch so für Referenzentabellen frei, die zur Geschwindigkeitssteigerung genutzt werden könnten.
Konkret:
- der Dispatcher sollte in die ZP mit selfmod Code
- bei einem Lese/Speicherzugriff Befehl "lb, lh, lw..." oder "sb, sh, sw...", müssten der GEORAM Zustand "gespeichert", für den Speicherzugriff geändert und später wiederhergestellt werden, damit die Programmausführung dann normal (= i.d.R. mit "jmp .pc_") fortgesetzt werden kann.
-- der Speicher in der GEORAM ist durch die Track/Sector-Reihenfolge "wohlmöglich" verdreht, falls es darum geht eine Image-Datei vorzuhalten.
-- das Einstellen der Georam würde lediglich aus 2 zusätzlichen Schreibzugriffen (= 8 Zyklen) bestehen bspw. "sta $dfff: stx $dffe", da die Werte später nicht mehr gebraucht werden. (Der normale Programmfluss würde mit "lda .pc1_+1: sta $dfff: lda .pc2_+1: sta $dfffe", d.h. 14 Zyklen wiederhergestellt werden, d.h. ein Speicherzugriff auf die GEORAM würde insgesamt 22 Zyklen zusätzlich kosten.)
--- Hier könnte man sich überlegen, die Speicherzugriffe um 2 Zyklen günstiger und dafür den Pagewechsel während der Ausführung von Befehlen, welcher wahrscheinlich seltener vorkommen sollte, entsprechend teurer zu gestalten.
- das PC-Register ist verteilt in ".pc_+1", ".pc1_+1" und ".pc2_+1"
- Idee: für das r0 register könnten unterschiedliche Referenztabellen verwendet werden, d.h. bei Schreibzugriffen eine Tabelle, die r0 auf eine Dummy-Adresse leitet und bei Lesezugriffen, eine, die für r0 auf vier 0-Bytes zeigt.
- RV32E mit 16 Registern würde nur einen Vorteil beim Dekodieren des Feldes rs2 bringen, weil man sich dann dort ein Shift sparen würde (= die ersten 4-Bit sind direkt im dritten Byte sichtbar); aber sowit ich das sehe, hätte man keinen weiteren Vorteil, weil ja eh Referenztabellen (8-Bit breit) verwendet werden.
- Da genügend freier C64-Speicher vorhanden ist, ließen sich auch umfangreiche Shift-Tabellen dort unterbringen.
; ==================================================================
; reset cpu - in main memory
; ==================================================================
.reset ; init cpu - start execution at $000000
ldy #0 ; 2
sty .pc_+1 ; 3
sty $dfff ; 4
sty $dffe ; 4
jmp .pc_l1 ; 3 read directly first Opcode at $de00
; ==================================================================
; dispatcher - in zeropage
; ==================================================================
.nextPage
.pc1_
ldx .add1Lo ; 4 ; selfmod lo
stx .pc1_+1 ; 3
stx $dfff ; 4 ; 4MB georam - full 8-bit register ; unfortunately "inc $dfff ; 6" wouldn't work on readonly
bne .pc_l1 ; 2/3
.pc2_
ldx .add1Lo ; 4 ; selfmod lo
stx .pc2_+1 ; 3
stx $dffe ; 4 ; only 6-bits - no checking for bounds!
bne .pc_l1 ; 2/3
; ... no error handling ...
; ----
.pc_
ldy .add4Lo ; 4 selfmod lo
sty .pc_+1 ; 3
beq .nextPage ; 2/3
.pc_l1 ; **** other ideas do exist here ****
lax $de00,y ; 4 usage of lax, because RegA could be used later to decode rd
ldy .opcLo,x ; 4
sty .jv_+1 ; 3
.jv_
jmp .opcXX ; 3 selfmod lo
; ==================================================================
; opcode implementation - in main memory
; ==================================================================
!align 255,0
.opcXX
; ... get other bytes with e.g.
; 1.) ldy .pc_l1: lda $de00,y ... lda $de03,y
; 2.) ldy .pc_: lda $de00,y ... lda $de03,y
; 3.) ldy #0 ... #3: lda (.pc_l1),y
; ...
; ... returns usually with
; jmp .pc_
; ==================================================================
; tables - in main memory
; ==================================================================
!align 255,0
.add1lo ; table used for .pc1++ and .pc2++
!for i=0 to 255
!byte <(i+1)
!end
; ----
.add4lo ; table used for .pc += 4
!for i=0 to 255
!byte <(i+4)
!end
; ----
.opcLo ; relative to .opcXX
!byte <.opc00
!byte <.opc01
!byte <.opc02
; ...
Alles anzeigen
Die alternativen Ideen für die Stelle mit "****" sind:
; ==================================================================
; idea 1 - if more space is needed
; ==================================================================
; ...
.pc_l1
lax $de00,y ; 4
ldy .opcLo,x ; 4
sty .jv_+1 ; 3
ldy .opcHi,x ; 4 easy, needs additional table
sty .jv_+2 ; 3
.jv_
jmp .opcXX ; 3 selfmod
; = 21
; ...
; ==================================================================
; idea 2 - use table and indirect jump
; ==================================================================
; ...
.pc_l1
lax $de00,y ; 4
lsr ; 2 or "and #%11111100" ; 2
sta .jv_+1 ; 3
.jv_
jmp (.opcAddrTab) ; 5
; = 14
; ...
; ==================================================================
; idea 3 - use table and indirect jump but also decode rd-register
; ==================================================================
; ...
.pc_l1
lax $de00,y ; 4
asl ; 2 set carry = bit0 of rd
sta .jv_+1 ; 3
lda $de01,y ; 4 decode rd-register here or after the jump at .jv_
rol ; 2
tax ; 2
lda .rdTab,x ; 4
sta .rd ; 3
; if needed try to decode rs1 in a similar fashion
; lda $de02,y ; 4
; rol ; 2
; tax ; 2 ; or rol ; 2
; lda .rs1Tab,x ; 4 ; rol ; 2
; ; and #%01111100 ; 2
; sta .rs1 ; 3
.jv_
jmp (.opcAddrTab) ; 5
; = 14 (+15 for .rd; +15 for .rs1)
; ...
; ==================================================================
; idea 4 - use stack, so its not usable anymore, also irqs and jsrs are not allowed (or with preparation: ldx #$ff: txs; 4)
; ==================================================================
; ...
.pc_l1
lax $de00,y ; 4
txs ; 2
rts ; 6
; = 12
; ...
Alles anzeigen
Zu Idee 4, welche sehr theoretisch ist:
- Interessant ist ja, dass im lo-byte bei allen RV32I Opcodes "%11" steht, d.h. evtl. könnte man, falls man auf C64-Irqs verzichtet, "txs; rts" nutzen. Vor Sprüngen müsste man einen freien Bereich auf dem Stack wählen.
- Das erste Byte der RV32I-Opcodes lässt (grob drübergeschaut) einige Lücken von einigen Bytes zu (?).
Ist eigentlich auch egal, da bei der Idee 3 das rd-Feld, welches eigentlich immer gebraucht wird, gleich mitdekodiert werden kann. (Am besten nach dem jmp, da man bei einigen Befehlen eine andere Referenztabelle (für S-Type, B-Type; imm_i) verwenden müsste.)
Als letztes noch die Skizze für eine Aktion mit der "ALU":
; ==================================================================
; and example
; ==================================================================
; ...
ldx .rd
ldy .rs1
!for i=0 to 3
lda .rx+i,x
and .rx+i,y
sta .aluRes+i
!end
; ...
Alles anzeigen
Hier könnte man sich überlegen, inwiefern man den Code mehrfach im Speicher haben möchte und, wie mit .aluRes am besten umgegangen werden sollte. (Es fehlt ja irgendwie ein Register
, d.h. es würde hier das Zurückschreiben in ein Register oder in den Speicher (GEORAM) folgen.)
(...)
Noch eine Idee zu den Branch Befehlen:
- Es würde sich anbieten, erst die Prüfung durchzuführen, und dann nur bei Bedarf das abweichende Sprungziel (imm_b) zu dekodieren und auszurechnen (imm_b + pc).
- Falls die Prüfung ergibt, dass nicht gesprungen werden muss, genügt ein einfaches "jmp .pc_".
(...)
Nagut, genug als Anregung geschrieben.
Ideen, Rückmeldungen, Diskussionen, ... etc. ausdrücklich erwünscht!