badline 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +25 -6
- data/CONTRIBUTING.md +8 -3
- data/README.md +63 -4
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +15 -0
- data/lib/badline/address_bus/pla.rb +3 -16
- data/lib/badline/address_bus.rb +14 -0
- data/lib/badline/c128/banks.rb +35 -0
- data/lib/badline/c128/bus.rb +347 -0
- data/lib/badline/c128/bus_state.rb +53 -0
- data/lib/badline/c128/color_lines.rb +39 -0
- data/lib/badline/c128/cpu.rb +10 -0
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +197 -0
- data/lib/badline/c128/mmu_pages.rb +105 -0
- data/lib/badline/c128/model.rb +39 -0
- data/lib/badline/c128/modes.rb +48 -0
- data/lib/badline/c128/saved_state.rb +121 -0
- data/lib/badline/c128/vdc/memory.rb +54 -0
- data/lib/badline/c128/vdc/painter.rb +143 -0
- data/lib/badline/c128/vdc/raster.rb +97 -0
- data/lib/badline/c128/vdc/window.rb +215 -0
- data/lib/badline/c128/vdc.rb +268 -0
- data/lib/badline/c128.rb +322 -0
- data/lib/badline/chrout_trap.rb +3 -0
- data/lib/badline/cia/fast_forward.rb +42 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +2 -0
- data/lib/badline/computer/attachments.rb +10 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +5 -0
- data/lib/badline/drive/core.rb +172 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +9 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +13 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +34 -0
- data/lib/badline/drive1571.rb +225 -0
- data/lib/badline/frontend/app.rb +35 -22
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +8 -2
- data/lib/badline/frontend/menu_ports.rb +1 -1
- data/lib/badline/frontend/pause_menu.rb +1 -0
- data/lib/badline/frontend/screen.rb +33 -8
- data/lib/badline/frontend/screens.rb +90 -0
- data/lib/badline/frontend/timeline.rb +15 -4
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +58 -8
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +5 -0
- data/lib/badline/machine.rb +20 -9
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/media.rb +8 -6
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +7 -2
- data/lib/badline/options/table.rb +8 -2
- data/lib/badline/options/validation.rb +14 -1
- data/lib/badline/options.rb +20 -19
- data/lib/badline/roms/README +36 -0
- data/lib/badline/roms/c128/basic.rom +0 -0
- data/lib/badline/roms/c128/character.rom +0 -0
- data/lib/badline/roms/c128/kernal.rom +0 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot.rb +9 -2
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +108 -7
- data/lib/badline/vic20/attachments.rb +6 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +111 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- data/lib/badline.rb +1 -0
- metadata +59 -4
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/z80/flags"
|
|
4
|
+
require "badline/z80/registers"
|
|
5
|
+
require "badline/z80/bus_cycles"
|
|
6
|
+
require "badline/z80/arithmetic"
|
|
7
|
+
require "badline/z80/shifts"
|
|
8
|
+
require "badline/z80/loads"
|
|
9
|
+
require "badline/z80/exchanges"
|
|
10
|
+
require "badline/z80/jumps"
|
|
11
|
+
require "badline/z80/main_page"
|
|
12
|
+
require "badline/z80/bit_page"
|
|
13
|
+
require "badline/z80/extended_page"
|
|
14
|
+
require "badline/z80/block_transfers"
|
|
15
|
+
require "badline/z80/block_io"
|
|
16
|
+
require "badline/z80/interrupt_acceptance"
|
|
17
|
+
|
|
18
|
+
module Badline
|
|
19
|
+
class Z80
|
|
20
|
+
# The Z80 core, on whatever bus +bus+ is. The bus answers
|
|
21
|
+
# fetch(address) for an opcode fetch (M1), read(address) and
|
|
22
|
+
# write(address, value) for memory, input(port) and output(port, value)
|
|
23
|
+
# for I/O, and acknowledge for the byte a device puts on the data bus
|
|
24
|
+
# when the CPU takes a maskable interrupt.
|
|
25
|
+
#
|
|
26
|
+
# #step! runs one instruction, prefixes and all, or takes an interrupt,
|
|
27
|
+
# or idles one opcode fetch while halted. #cycles counts T-states, and
|
|
28
|
+
# each bus call happens while it holds the T-state its machine cycle
|
|
29
|
+
# starts on.
|
|
30
|
+
module Core
|
|
31
|
+
include Flags
|
|
32
|
+
include Registers
|
|
33
|
+
include BusCycles
|
|
34
|
+
include Arithmetic
|
|
35
|
+
include Shifts
|
|
36
|
+
include Loads
|
|
37
|
+
include Exchanges
|
|
38
|
+
include Jumps
|
|
39
|
+
include MainPage
|
|
40
|
+
include BitPage
|
|
41
|
+
include ExtendedPage
|
|
42
|
+
include BlockTransfers
|
|
43
|
+
include BlockIO
|
|
44
|
+
include InterruptAcceptance
|
|
45
|
+
|
|
46
|
+
attr_reader :bus, :cycles, :nmi
|
|
47
|
+
attr_accessor :int
|
|
48
|
+
|
|
49
|
+
def initialize(bus)
|
|
50
|
+
@bus = bus
|
|
51
|
+
@cycles = 0
|
|
52
|
+
@a = @f = 0xff
|
|
53
|
+
@b = @c = @d = @e = @h = @l = 0xff
|
|
54
|
+
@af_alt = @bc_alt = @de_alt = @hl_alt = 0xffff
|
|
55
|
+
@ix = @iy = @sp = 0xffff
|
|
56
|
+
@wz = @q = @last_q = 0
|
|
57
|
+
@prefix = 0
|
|
58
|
+
@int = @nmi = @nmi_pending = false
|
|
59
|
+
reset!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The RESET line: the program counter, I, R and the interrupt mode go
|
|
63
|
+
# to 0, and interrupts are disabled.
|
|
64
|
+
def reset!
|
|
65
|
+
@pc = @i = @r = @im = 0
|
|
66
|
+
@iff1 = @iff2 = false
|
|
67
|
+
@halted = @after_ei = @after_ld_a_ir = false
|
|
68
|
+
@nmi_pending = false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The NMI line. Asserting it latches an interrupt, taken after the
|
|
72
|
+
# current instruction, and holding it does nothing more.
|
|
73
|
+
def nmi=(asserted)
|
|
74
|
+
@nmi_pending = true if asserted && !@nmi
|
|
75
|
+
@nmi = asserted
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The WAIT line: a bus that holds it during an access lengthens that
|
|
79
|
+
# machine cycle by +states+ T-states.
|
|
80
|
+
def wait(states)
|
|
81
|
+
@cycles += states
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def step!
|
|
85
|
+
if @nmi_pending
|
|
86
|
+
take_nmi
|
|
87
|
+
elsif @int && @iff1 && !@after_ei
|
|
88
|
+
take_int
|
|
89
|
+
else
|
|
90
|
+
@after_ei = @after_ld_a_ir = false
|
|
91
|
+
@last_q = @q
|
|
92
|
+
@q = 0
|
|
93
|
+
@prefix = 0
|
|
94
|
+
@halted ? idle_fetch : execute(fetch_opcode)
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def inspect
|
|
100
|
+
"Cycles: #{@cycles}, PC: #{hex(@pc)}, SP: #{hex(@sp)}, AF: #{hex(af)}, BC: #{hex(bc)}, " \
|
|
101
|
+
"DE: #{hex(de)}, HL: #{hex(hl)}, IX: #{hex(@ix)}, IY: #{hex(@iy)}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def hex(value)
|
|
107
|
+
value.to_s(16).rjust(4, "0")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# EX AF,AF', EXX, EX DE,HL and EX (SP),HL. Only the last swaps IX or IY
|
|
6
|
+
# after a prefix.
|
|
7
|
+
module Exchanges
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def exchange_alternates
|
|
11
|
+
bc_alt = @bc_alt
|
|
12
|
+
de_alt = @de_alt
|
|
13
|
+
hl_alt = @hl_alt
|
|
14
|
+
@bc_alt = bc
|
|
15
|
+
@de_alt = de
|
|
16
|
+
@hl_alt = hl
|
|
17
|
+
self.bc = bc_alt
|
|
18
|
+
self.de = de_alt
|
|
19
|
+
self.hl = hl_alt
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def exchange_af
|
|
23
|
+
af_alt = @af_alt
|
|
24
|
+
@af_alt = af
|
|
25
|
+
self.af = af_alt
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def exchange_de_hl
|
|
29
|
+
d = @d
|
|
30
|
+
e = @e
|
|
31
|
+
@d = @h
|
|
32
|
+
@e = @l
|
|
33
|
+
@h = d
|
|
34
|
+
@l = e
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def exchange_stack_top
|
|
38
|
+
low = read_byte(@sp)
|
|
39
|
+
high = read_byte((@sp + 1) & 0xffff)
|
|
40
|
+
@cycles += 1
|
|
41
|
+
value = hl_or_index
|
|
42
|
+
write_byte((@sp + 1) & 0xffff, value >> 8)
|
|
43
|
+
write_byte(@sp, value & 0xff)
|
|
44
|
+
@cycles += 2
|
|
45
|
+
@wz = low | (high << 8)
|
|
46
|
+
self.hl_or_index = @wz
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# The interrupt mode each of ED 46, 4E, 56, 5E, 66, 6E, 76 and 7E sets.
|
|
6
|
+
# 4E and 6E set mode 0 on the NMOS Z80.
|
|
7
|
+
INTERRUPT_MODES = [0, 0, 1, 2, 0, 0, 1, 2].freeze
|
|
8
|
+
|
|
9
|
+
# The ED page: its second quarter, the block instructions, and 8
|
|
10
|
+
# T-states of nothing for the rest.
|
|
11
|
+
module ExtendedPage
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def execute_extended(opcode)
|
|
15
|
+
case opcode >> 6
|
|
16
|
+
when 1 then extended_operation(opcode)
|
|
17
|
+
when 2 then block_operation(opcode) if (opcode & 0x24) == 0x20
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def extended_operation(opcode)
|
|
22
|
+
row = (opcode >> 3) & 7
|
|
23
|
+
case opcode & 7
|
|
24
|
+
when 0 then input_register(row)
|
|
25
|
+
when 1 then output_register(row)
|
|
26
|
+
when 2 then add_pair_with_carry(row)
|
|
27
|
+
when 3 then row.nobits?(1) ? store_word(pair(row >> 1)) : set_pair(row >> 1, load_word)
|
|
28
|
+
when 4 then neg
|
|
29
|
+
when 5 then return_from_interrupt
|
|
30
|
+
when 6 then @im = INTERRUPT_MODES[row]
|
|
31
|
+
else special_register(row)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# IN r,(C), and IN F,(C) at index 6, which sets the flags alone.
|
|
36
|
+
def input_register(index)
|
|
37
|
+
port = bc
|
|
38
|
+
value = input(port)
|
|
39
|
+
@wz = (port + 1) & 0xffff
|
|
40
|
+
assign_flags((@f & CF) | SZ53P[value])
|
|
41
|
+
set_register(index, value) unless index == 6
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# OUT (C),r, and at index 6 OUT (C),0.
|
|
45
|
+
def output_register(index)
|
|
46
|
+
port = bc
|
|
47
|
+
output(port, index == 6 ? 0 : register(index))
|
|
48
|
+
@wz = (port + 1) & 0xffff
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_pair_with_carry(row)
|
|
52
|
+
row.nobits?(1) ? sbc16(pair(row >> 1)) : adc16(pair(row >> 1))
|
|
53
|
+
@cycles += 7
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# RETN and RETI both restore IFF1 from IFF2.
|
|
57
|
+
def return_from_interrupt
|
|
58
|
+
@iff1 = @iff2
|
|
59
|
+
return_from_call
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# LD I,A, LD R,A, LD A,I, LD A,R, RRD and RLD, and two that do
|
|
63
|
+
# nothing.
|
|
64
|
+
def special_register(row)
|
|
65
|
+
case row
|
|
66
|
+
when 0 then @i = load_ir(@a)
|
|
67
|
+
when 1 then @r = load_ir(@a)
|
|
68
|
+
when 2 then @a = load_a_from_ir(@i)
|
|
69
|
+
when 3 then @a = load_a_from_ir(@r)
|
|
70
|
+
when 4 then rotate_digits(false)
|
|
71
|
+
when 5 then rotate_digits(true)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def load_ir(value)
|
|
76
|
+
@cycles += 1
|
|
77
|
+
value
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# P/V copies IFF2, and an interrupt taken next clears it again.
|
|
81
|
+
def load_a_from_ir(value)
|
|
82
|
+
@cycles += 1
|
|
83
|
+
assign_flags((@f & CF) | SZ53[value] | (@iff2 ? PF : 0))
|
|
84
|
+
@after_ld_a_ir = true
|
|
85
|
+
value
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
CF = 0x01
|
|
6
|
+
NF = 0x02
|
|
7
|
+
PF = 0x04
|
|
8
|
+
XF = 0x08
|
|
9
|
+
HF = 0x10
|
|
10
|
+
YF = 0x20
|
|
11
|
+
ZF = 0x40
|
|
12
|
+
SF = 0x80
|
|
13
|
+
|
|
14
|
+
# Bits 5 and 3, which most instructions copy from a result.
|
|
15
|
+
XYF = YF | XF
|
|
16
|
+
|
|
17
|
+
# S, Z, Y and X as a result byte sets them.
|
|
18
|
+
SZ53 = Array.new(256) { |v| (v & (SF | XYF)) | (v.zero? ? ZF : 0) }.freeze
|
|
19
|
+
|
|
20
|
+
# P/V as parity: set when a byte has an even number of bits set.
|
|
21
|
+
PARITY = Array.new(256) { |v| (0x6996 >> ((v ^ (v >> 4)) & 0x0f)).nobits?(1) ? PF : 0 }.freeze
|
|
22
|
+
|
|
23
|
+
SZ53P = Array.new(256) { |v| SZ53[v] | PARITY[v] }.freeze
|
|
24
|
+
|
|
25
|
+
# The flags, which the core keeps in F as the chip does. Every
|
|
26
|
+
# instruction that sets them through the ALU also latches them in Q,
|
|
27
|
+
# which SCF and CCF read on the next instruction.
|
|
28
|
+
module Flags
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def assign_flags(value)
|
|
32
|
+
@f = value
|
|
33
|
+
@q = value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# Taking NMI and INT between instructions. Either wakes a halted CPU,
|
|
6
|
+
# whose program counter already points past the HALT.
|
|
7
|
+
module InterruptAcceptance
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# An opcode fetch whose byte is ignored, a T-state more, and a call
|
|
11
|
+
# to $0066. IFF2 keeps what IFF1 held, for RETN to restore.
|
|
12
|
+
def take_nmi
|
|
13
|
+
@nmi_pending = false
|
|
14
|
+
@halted = false
|
|
15
|
+
@iff1 = false
|
|
16
|
+
@bus.fetch(@pc)
|
|
17
|
+
refresh
|
|
18
|
+
@cycles += 5
|
|
19
|
+
push(@pc)
|
|
20
|
+
@pc = 0x66
|
|
21
|
+
@wz = 0x66
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The acknowledge cycle, an M1 with two wait states, reads a byte
|
|
25
|
+
# from the device. Mode 0 runs it as an opcode, which is a RST in
|
|
26
|
+
# practice, mode 1 calls $0038, and mode 2 calls through the vector
|
|
27
|
+
# at I and that byte.
|
|
28
|
+
def take_int
|
|
29
|
+
@halted = false
|
|
30
|
+
@f &= ~PF if @after_ld_a_ir
|
|
31
|
+
@iff1 = @iff2 = false
|
|
32
|
+
@after_ld_a_ir = false
|
|
33
|
+
@prefix = 0
|
|
34
|
+
data = @bus.acknowledge
|
|
35
|
+
refresh
|
|
36
|
+
@cycles += 6
|
|
37
|
+
return execute(data) if @im.zero?
|
|
38
|
+
|
|
39
|
+
@cycles += 1
|
|
40
|
+
push(@pc)
|
|
41
|
+
@pc = @im == 1 ? 0x38 : read_word((@i << 8) | data)
|
|
42
|
+
@wz = @pc
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# Jumps, calls and returns. Each leaves its target in WZ, and JP cc and
|
|
6
|
+
# CALL cc do whether they jump or not.
|
|
7
|
+
module Jumps
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# The conditions bits 5-3 pick: NZ Z NC C PO PE P M.
|
|
11
|
+
def condition?(index)
|
|
12
|
+
case index
|
|
13
|
+
when 0 then @f.nobits?(ZF)
|
|
14
|
+
when 1 then @f.anybits?(ZF)
|
|
15
|
+
when 2 then @f.nobits?(CF)
|
|
16
|
+
when 3 then @f.anybits?(CF)
|
|
17
|
+
when 4 then @f.nobits?(PF)
|
|
18
|
+
when 5 then @f.anybits?(PF)
|
|
19
|
+
when 6 then @f.nobits?(SF)
|
|
20
|
+
else @f.anybits?(SF)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# NOP, EX AF,AF', DJNZ, JR and JR cc.
|
|
25
|
+
def relative_jump(row)
|
|
26
|
+
case row
|
|
27
|
+
when 0 then nil
|
|
28
|
+
when 1 then exchange_af
|
|
29
|
+
when 2 then decrement_and_jump
|
|
30
|
+
when 3 then jump_relative(fetch_byte)
|
|
31
|
+
else
|
|
32
|
+
offset = fetch_byte
|
|
33
|
+
jump_relative(offset) if condition?(row - 4)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def decrement_and_jump
|
|
38
|
+
@cycles += 1
|
|
39
|
+
offset = fetch_byte
|
|
40
|
+
@b = (@b - 1) & 0xff
|
|
41
|
+
jump_relative(offset) unless @b.zero?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def jump_relative(offset)
|
|
45
|
+
@cycles += 5
|
|
46
|
+
@pc = (@pc + offset - (offset > 127 ? 256 : 0)) & 0xffff
|
|
47
|
+
@wz = @pc
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def jump(taken)
|
|
51
|
+
@wz = fetch_word
|
|
52
|
+
@pc = @wz if taken
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def call(taken)
|
|
56
|
+
@wz = fetch_word
|
|
57
|
+
return unless taken
|
|
58
|
+
|
|
59
|
+
@cycles += 1
|
|
60
|
+
push(@pc)
|
|
61
|
+
@pc = @wz
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def conditional_return(index)
|
|
65
|
+
@cycles += 1
|
|
66
|
+
return_from_call if condition?(index)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def return_from_call
|
|
70
|
+
@pc = pop
|
|
71
|
+
@wz = @pc
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def restart(address)
|
|
75
|
+
@cycles += 1
|
|
76
|
+
push(@pc)
|
|
77
|
+
@pc = address
|
|
78
|
+
@wz = address
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# Loads, PUSH, and IN and OUT through a port A and n address. The loads
|
|
6
|
+
# through a pointer leave the address after it in WZ, or for a store of
|
|
7
|
+
# A, A and the low byte of that address.
|
|
8
|
+
module Loads
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# LD r,r' with (HL) as index 6.
|
|
12
|
+
def load_register(target, source)
|
|
13
|
+
if source == 6
|
|
14
|
+
set_plain_register(target, read_byte(operand_address))
|
|
15
|
+
elsif target == 6
|
|
16
|
+
write_byte(operand_address, plain_register(source))
|
|
17
|
+
else
|
|
18
|
+
set_register(target, register(source))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# LD r,n, and LD (IX+d),n, which reads n while it adds d.
|
|
23
|
+
def load_immediate(index)
|
|
24
|
+
return set_register(index, fetch_byte) unless index == 6
|
|
25
|
+
return write_byte(hl, fetch_byte) if @prefix.zero?
|
|
26
|
+
|
|
27
|
+
displaced
|
|
28
|
+
value = fetch_byte
|
|
29
|
+
@cycles += 2
|
|
30
|
+
write_byte(@wz, value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def indirect_load(row)
|
|
34
|
+
case row
|
|
35
|
+
when 0 then store_a(bc)
|
|
36
|
+
when 1 then load_a(bc)
|
|
37
|
+
when 2 then store_a(de)
|
|
38
|
+
when 3 then load_a(de)
|
|
39
|
+
when 4 then store_word(hl_or_index)
|
|
40
|
+
when 5 then self.hl_or_index = load_word
|
|
41
|
+
when 6 then store_a(fetch_word)
|
|
42
|
+
else load_a(fetch_word)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def load_a(address)
|
|
47
|
+
@a = read_byte(address)
|
|
48
|
+
@wz = (address + 1) & 0xffff
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def store_a(address)
|
|
52
|
+
write_byte(address, @a)
|
|
53
|
+
@wz = (@a << 8) | ((address + 1) & 0xff)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def store_word(value)
|
|
57
|
+
address = fetch_word
|
|
58
|
+
write_word(address, value)
|
|
59
|
+
@wz = (address + 1) & 0xffff
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def load_word
|
|
63
|
+
address = fetch_word
|
|
64
|
+
@wz = (address + 1) & 0xffff
|
|
65
|
+
read_word(address)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def push_pair(index)
|
|
69
|
+
@cycles += 1
|
|
70
|
+
push(stack_pair(index))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# RET, EXX, JP (HL) and LD SP,HL.
|
|
74
|
+
def stack_and_exchange(index)
|
|
75
|
+
case index
|
|
76
|
+
when 0 then return_from_call
|
|
77
|
+
when 1 then exchange_alternates
|
|
78
|
+
when 2 then @pc = hl_or_index
|
|
79
|
+
else
|
|
80
|
+
@cycles += 2
|
|
81
|
+
@sp = hl_or_index
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def output_immediate
|
|
86
|
+
port = (@a << 8) | fetch_byte
|
|
87
|
+
output(port, @a)
|
|
88
|
+
@wz = (port & 0xff00) | ((port + 1) & 0xff)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def input_immediate
|
|
92
|
+
port = (@a << 8) | fetch_byte
|
|
93
|
+
@wz = (port + 1) & 0xffff
|
|
94
|
+
@a = input(port)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Z80
|
|
5
|
+
# The unprefixed opcodes, which DD and FD reuse with IX or IY for HL.
|
|
6
|
+
# An opcode splits into its quarter (bits 7-6), its row (5-3) and its
|
|
7
|
+
# column (2-0), and each quarter of the page is regular in rows and
|
|
8
|
+
# columns.
|
|
9
|
+
module MainPage
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def execute(opcode)
|
|
13
|
+
case opcode >> 6
|
|
14
|
+
when 0 then execute_low(opcode)
|
|
15
|
+
when 1 then opcode == 0x76 ? @halted = true : load_register((opcode >> 3) & 7, opcode & 7)
|
|
16
|
+
when 2 then alu((opcode >> 3) & 7, operand(opcode & 7))
|
|
17
|
+
else execute_high(opcode)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute_low(opcode)
|
|
22
|
+
row = (opcode >> 3) & 7
|
|
23
|
+
case opcode & 7
|
|
24
|
+
when 0 then relative_jump(row)
|
|
25
|
+
when 1 then opcode.nobits?(8) ? set_pair(row >> 1, fetch_word) : add_pair(row >> 1)
|
|
26
|
+
when 2 then indirect_load(row)
|
|
27
|
+
when 3 then step_pair(row)
|
|
28
|
+
when 4 then modify(row, true)
|
|
29
|
+
when 5 then modify(row, false)
|
|
30
|
+
when 6 then load_immediate(row)
|
|
31
|
+
else accumulator_operation(row)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def execute_high(opcode)
|
|
36
|
+
row = (opcode >> 3) & 7
|
|
37
|
+
case opcode & 7
|
|
38
|
+
when 0 then conditional_return(row)
|
|
39
|
+
when 1 then opcode.nobits?(8) ? set_stack_pair(row >> 1, pop) : stack_and_exchange(row >> 1)
|
|
40
|
+
when 2 then jump(condition?(row))
|
|
41
|
+
when 3 then miscellaneous(row)
|
|
42
|
+
when 4 then call(condition?(row))
|
|
43
|
+
when 5 then opcode.nobits?(8) ? push_pair(row >> 1) : call_or_prefix(row >> 1)
|
|
44
|
+
when 6 then alu(row, fetch_byte)
|
|
45
|
+
else restart(row << 3)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The operand of the 8-bit ALU opcodes: a register or (HL).
|
|
50
|
+
def operand(index)
|
|
51
|
+
index == 6 ? read_byte(operand_address) : register(index)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# INC and DEC on a register or (HL).
|
|
55
|
+
def modify(index, increase)
|
|
56
|
+
if index == 6
|
|
57
|
+
address = operand_address
|
|
58
|
+
value = read_byte(address)
|
|
59
|
+
@cycles += 1
|
|
60
|
+
write_byte(address, increase ? increment(value) : decrement(value))
|
|
61
|
+
else
|
|
62
|
+
set_register(index, increase ? increment(register(index)) : decrement(register(index)))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# INC and DEC on a pair, which touch no flags.
|
|
67
|
+
def step_pair(row)
|
|
68
|
+
@cycles += 2
|
|
69
|
+
index = row >> 1
|
|
70
|
+
set_pair(index, (pair(index) + (row.nobits?(1) ? 1 : -1)) & 0xffff)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_pair(index)
|
|
74
|
+
self.hl_or_index = add16(hl_or_index, pair(index))
|
|
75
|
+
@cycles += 7
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def accumulator_operation(row)
|
|
79
|
+
case row
|
|
80
|
+
when 0, 1, 2, 3 then rotate_a(row)
|
|
81
|
+
when 4 then daa
|
|
82
|
+
when 5 then complement
|
|
83
|
+
when 6 then assign_flags((@f & (SF | ZF | PF)) | CF | (((@last_q ^ @f) | @a) & XYF))
|
|
84
|
+
else assign_flags((@f & (SF | ZF | PF)) | ((@f & CF) << 4) | ((@f & CF) ^ CF) | (((@last_q ^ @f) | @a) & XYF))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def complement
|
|
89
|
+
@a ^= 0xff
|
|
90
|
+
assign_flags((@f & (SF | ZF | PF | CF)) | HF | NF | (@a & XYF))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def miscellaneous(row)
|
|
94
|
+
case row
|
|
95
|
+
when 0 then jump(true)
|
|
96
|
+
when 1 then @prefix.zero? ? execute_bits(fetch_opcode) : execute_index_bits
|
|
97
|
+
when 2 then output_immediate
|
|
98
|
+
when 3 then input_immediate
|
|
99
|
+
when 4 then exchange_stack_top
|
|
100
|
+
when 5 then exchange_de_hl
|
|
101
|
+
when 6 then @iff1 = @iff2 = false
|
|
102
|
+
else enable_interrupts
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def call_or_prefix(index)
|
|
107
|
+
case index
|
|
108
|
+
when 0 then call(true)
|
|
109
|
+
when 1 then index_prefix(1)
|
|
110
|
+
when 2 then extended_prefix
|
|
111
|
+
else index_prefix(2)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# DD and FD: the next opcode runs with IX or IY for HL. A prefix
|
|
116
|
+
# after a prefix replaces it.
|
|
117
|
+
def index_prefix(prefix)
|
|
118
|
+
@prefix = prefix
|
|
119
|
+
@last_q = 0
|
|
120
|
+
execute(fetch_opcode)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def extended_prefix
|
|
124
|
+
@prefix = 0
|
|
125
|
+
execute_extended(fetch_opcode)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def enable_interrupts
|
|
129
|
+
@iff1 = @iff2 = true
|
|
130
|
+
@after_ei = true
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|