badline 0.1.0 → 0.2.0

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.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -0
  3. data/CLAUDE.md +217 -0
  4. data/CODE_OF_CONDUCT.md +92 -0
  5. data/CONTRIBUTING.md +51 -0
  6. data/README.md +224 -31
  7. data/doc/pinned-behaviour.md +1117 -0
  8. data/exe/badline +22 -2
  9. data/exe/badline-sid +34 -0
  10. data/lib/badline/address_bus.rb +88 -23
  11. data/lib/badline/audio/aiff.rb +36 -0
  12. data/lib/badline/audio/bare_player.rb +129 -0
  13. data/lib/badline/audio/cli.rb +133 -0
  14. data/lib/badline/audio/console.rb +75 -0
  15. data/lib/badline/audio/jukebox.rb +82 -0
  16. data/lib/badline/audio/machine_player.rb +57 -0
  17. data/lib/badline/audio/options.rb +137 -0
  18. data/lib/badline/audio/pcm_writer.rb +57 -0
  19. data/lib/badline/audio/playback.rb +108 -0
  20. data/lib/badline/audio/renderer.rb +85 -0
  21. data/lib/badline/audio/sdl_sink.rb +102 -0
  22. data/lib/badline/audio/wav.rb +23 -0
  23. data/lib/badline/audio.rb +14 -0
  24. data/lib/badline/cartridge/action_replay.rb +111 -0
  25. data/lib/badline/cartridge/atomic_power.rb +24 -0
  26. data/lib/badline/cartridge/bank.rb +81 -0
  27. data/lib/badline/cartridge/comal80.rb +48 -0
  28. data/lib/badline/cartridge/dinamic.rb +34 -0
  29. data/lib/badline/cartridge/easy_flash.rb +122 -0
  30. data/lib/badline/cartridge/epyx_fastload.rb +71 -0
  31. data/lib/badline/cartridge/final_cartridge3.rb +77 -0
  32. data/lib/badline/cartridge/flash.rb +265 -0
  33. data/lib/badline/cartridge/freezer.rb +31 -0
  34. data/lib/badline/cartridge/fun_play.rb +38 -0
  35. data/lib/badline/cartridge/g_mod2.rb +62 -0
  36. data/lib/badline/cartridge/game_system.rb +40 -0
  37. data/lib/badline/cartridge/kcs_power.rb +72 -0
  38. data/lib/badline/cartridge/mach5.rb +34 -0
  39. data/lib/badline/cartridge/magic_desk.rb +6 -3
  40. data/lib/badline/cartridge/ocean.rb +11 -12
  41. data/lib/badline/cartridge/pagefox.rb +56 -0
  42. data/lib/badline/cartridge/retro_replay.rb +276 -0
  43. data/lib/badline/cartridge/rex_utility.rb +33 -0
  44. data/lib/badline/cartridge/rgcd.rb +48 -0
  45. data/lib/badline/cartridge/simons_basic.rb +40 -0
  46. data/lib/badline/cartridge/standard.rb +3 -19
  47. data/lib/badline/cartridge/super_games.rb +36 -0
  48. data/lib/badline/cartridge/westermann.rb +34 -0
  49. data/lib/badline/cartridge/zaxxon.rb +50 -0
  50. data/lib/badline/cartridge.rb +131 -15
  51. data/lib/badline/cia/interrupt_register.rb +88 -0
  52. data/lib/badline/cia/serial.rb +219 -0
  53. data/lib/badline/cia/timer.rb +43 -31
  54. data/lib/badline/cia.rb +109 -74
  55. data/lib/badline/color_memory.rb +14 -5
  56. data/lib/badline/computer.rb +68 -8
  57. data/lib/badline/control_ports.rb +54 -9
  58. data/lib/badline/cpu/addressing.rb +131 -0
  59. data/lib/badline/cpu/microcode.rb +171 -0
  60. data/lib/badline/cpu/operations.rb +88 -0
  61. data/lib/badline/cpu/stack_operations.rb +82 -0
  62. data/lib/badline/cpu.rb +108 -167
  63. data/lib/badline/cycleable.rb +3 -0
  64. data/lib/badline/datasette.rb +81 -0
  65. data/lib/badline/debug_register.rb +28 -0
  66. data/lib/badline/gui/application.rb +79 -18
  67. data/lib/badline/gui/gamepads.rb +97 -0
  68. data/lib/badline/gui/joy_map.rb +15 -7
  69. data/lib/badline/gui.rb +1 -0
  70. data/lib/badline/input/mouse1351.rb +41 -0
  71. data/lib/badline/input/paddles.rb +36 -0
  72. data/lib/badline/input.rb +4 -0
  73. data/lib/badline/instruction_set/arithmetic.rb +16 -21
  74. data/lib/badline/instruction_set/bitwise.rb +17 -22
  75. data/lib/badline/instruction_set/branch.rb +20 -19
  76. data/lib/badline/instruction_set/flag.rb +7 -7
  77. data/lib/badline/instruction_set/illegal.rb +14 -22
  78. data/lib/badline/instruction_set/inc_dec.rb +8 -10
  79. data/lib/badline/instruction_set/stack.rb +5 -57
  80. data/lib/badline/instruction_set/transfer.rb +9 -9
  81. data/lib/badline/instruction_set.rb +9 -31
  82. data/lib/badline/interrupts.rb +104 -0
  83. data/lib/badline/joystick.rb +9 -9
  84. data/lib/badline/kernal_trap/channel.rb +33 -0
  85. data/lib/badline/kernal_trap/drive.rb +239 -0
  86. data/lib/badline/kernal_trap/file.rb +4 -24
  87. data/lib/badline/kernal_trap/load.rb +2 -2
  88. data/lib/badline/kernal_trap/routine.rb +34 -0
  89. data/lib/badline/kernal_trap/save.rb +1 -1
  90. data/lib/badline/kernal_trap/serial.rb +139 -0
  91. data/lib/badline/kernal_trap.rb +4 -0
  92. data/lib/badline/keyboard.rb +40 -16
  93. data/lib/badline/media.rb +48 -10
  94. data/lib/badline/roms/eapi/LICENSE.md +15 -0
  95. data/lib/badline/roms/eapi/README +41 -0
  96. data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
  97. data/lib/badline/sid/decimator.rb +46 -0
  98. data/lib/badline/sid/envelope.rb +232 -0
  99. data/lib/badline/sid/filter.rb +213 -0
  100. data/lib/badline/sid/voice.rb +61 -0
  101. data/lib/badline/sid/waveform/combined.rb +126 -0
  102. data/lib/badline/sid/waveform/fast_forward.rb +105 -0
  103. data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
  104. data/lib/badline/sid/waveform.rb +289 -0
  105. data/lib/badline/sid.rb +274 -7
  106. data/lib/badline/status.rb +32 -27
  107. data/lib/badline/storage/crt_file.rb +18 -2
  108. data/lib/badline/storage/disk_image.rb +52 -5
  109. data/lib/badline/storage/host_directory.rb +18 -4
  110. data/lib/badline/storage/sid_file.rb +274 -0
  111. data/lib/badline/storage/song_lengths.rb +65 -0
  112. data/lib/badline/storage/t64.rb +67 -0
  113. data/lib/badline/storage/tap.rb +68 -0
  114. data/lib/badline/storage.rb +19 -0
  115. data/lib/badline/time_of_day.rb +105 -52
  116. data/lib/badline/version.rb +1 -1
  117. data/lib/badline/vic/bank.rb +14 -6
  118. data/lib/badline/vic/border_mask.rb +76 -0
  119. data/lib/badline/vic/collisions.rb +104 -0
  120. data/lib/badline/vic/color_patches.rb +33 -0
  121. data/lib/badline/vic/display_state.rb +92 -29
  122. data/lib/badline/vic/graphics_mode.rb +78 -53
  123. data/lib/badline/vic/graphics_shifter.rb +140 -0
  124. data/lib/badline/vic/register_log.rb +63 -0
  125. data/lib/badline/vic/registers.rb +3 -0
  126. data/lib/badline/vic/sequencer.rb +163 -137
  127. data/lib/badline/vic/sequencer_output.rb +116 -0
  128. data/lib/badline/vic/sprite/internal_bus.rb +49 -0
  129. data/lib/badline/vic/sprite/shifter.rb +129 -0
  130. data/lib/badline/vic/sprite.rb +275 -65
  131. data/lib/badline/vic/sprites.rb +220 -58
  132. data/lib/badline/vic.rb +373 -49
  133. data/lib/badline.rb +5 -0
  134. data/vendor/.gitkeep +0 -0
  135. metadata +103 -4
@@ -7,71 +7,72 @@ module Badline
7
7
  #
8
8
  # Opcodes:
9
9
  # $90 - relative - 2+ cycles
10
- def bcc(addr, _value)
11
- branch(addr) unless status.carry?
10
+ def bcc(_addr, _value)
11
+ take_branch unless status.carry?
12
12
  end
13
13
 
14
14
  # Branch if carry set (C=1).
15
15
  #
16
16
  # Opcodes:
17
17
  # $B0 - relative - 2+ cycles
18
- def bcs(addr, _value)
19
- branch(addr) if status.carry?
18
+ def bcs(_addr, _value)
19
+ take_branch if status.carry?
20
20
  end
21
21
 
22
22
  # Branch if equal (Z=1).
23
23
  #
24
24
  # Opcodes:
25
25
  # $F0 - relative - 2+ cycles
26
- def beq(addr, _value)
27
- branch(addr) if status.zero?
26
+ def beq(_addr, _value)
27
+ take_branch if status.zero?
28
28
  end
29
29
 
30
30
  # Branch if minus (N=1).
31
31
  #
32
32
  # Opcodes:
33
33
  # $30 - relative - 2+ cycles
34
- def bmi(addr, _value)
35
- branch(addr) if status.negative?
34
+ def bmi(_addr, _value)
35
+ take_branch if status.negative?
36
36
  end
37
37
 
38
38
  # Branch if not equal (Z=0).
39
39
  #
40
40
  # Opcodes:
41
41
  # $D0 - relative - 2+ cycles
42
- def bne(addr, _value)
43
- branch(addr) unless status.zero?
42
+ def bne(_addr, _value)
43
+ take_branch unless status.zero?
44
44
  end
45
45
 
46
46
  # Branch if plus (N=0).
47
47
  #
48
48
  # Opcodes:
49
49
  # $10 - relative - 2+ cycles
50
- def bpl(addr, _value)
51
- branch(addr) unless status.negative?
50
+ def bpl(_addr, _value)
51
+ take_branch unless status.negative?
52
52
  end
53
53
 
54
54
  # Branch if overflow clear (V=0).
55
55
  #
56
56
  # Opcodes:
57
57
  # $50 - relative - 2+ cycles
58
- def bvc(addr, _value)
59
- branch(addr) unless status.overflow?
58
+ def bvc(_addr, _value)
59
+ take_branch unless status.overflow?
60
60
  end
61
61
 
62
62
  # Branch if overflow set (V=1).
63
63
  #
64
64
  # Opcodes:
65
65
  # $70 - relative - 2+ cycles
66
- def bvs(addr, _value)
67
- branch(addr) if status.overflow?
66
+ def bvs(_addr, _value)
67
+ take_branch if status.overflow?
68
68
  end
69
69
 
70
70
  private
71
71
 
72
- def branch(addr)
73
- cycle if high_byte(addr) != high_byte(@program_counter)
74
- cycle { @program_counter = addr }
72
+ # Marks the branch as taken. Operations#op_relative reads the flag
73
+ # and runs the extra cycles.
74
+ def take_branch
75
+ @branch_taken = true
75
76
  end
76
77
  end
77
78
  end
@@ -8,7 +8,7 @@ module Badline
8
8
  # Opcodes:
9
9
  # $18 - implied - 2 cycles
10
10
  def clc(_addr, _value)
11
- cycle { status.carry = false }
11
+ status.carry = false
12
12
  end
13
13
 
14
14
  # Clear decimal mode flag.
@@ -16,7 +16,7 @@ module Badline
16
16
  # Opcodes:
17
17
  # $D8 - implied - 2 cycles
18
18
  def cld(_addr, _value)
19
- cycle { status.decimal = false }
19
+ status.decimal = false
20
20
  end
21
21
 
22
22
  # Clear interrupt disable flag.
@@ -24,7 +24,7 @@ module Badline
24
24
  # Opcodes:
25
25
  # $58 - implied - 2 cycles
26
26
  def cli(_addr, _value)
27
- cycle { status.interrupt = false }
27
+ status.interrupt = false
28
28
  end
29
29
 
30
30
  # Clear overflow flag.
@@ -32,7 +32,7 @@ module Badline
32
32
  # Opcodes:
33
33
  # $B8 - implied - 2 cycles
34
34
  def clv(_addr, _value)
35
- cycle { status.overflow = false }
35
+ status.overflow = false
36
36
  end
37
37
 
38
38
  # Set carry flag.
@@ -40,7 +40,7 @@ module Badline
40
40
  # Opcodes:
41
41
  # $38 - implied - 2 cycles
42
42
  def sec(_addr, _value)
43
- cycle { status.carry = true }
43
+ status.carry = true
44
44
  end
45
45
 
46
46
  # Set decimal mode flag.
@@ -48,7 +48,7 @@ module Badline
48
48
  # Opcodes:
49
49
  # $F8 - implied - 2 cycles
50
50
  def sed(_addr, _value)
51
- cycle { status.decimal = true }
51
+ status.decimal = true
52
52
  end
53
53
 
54
54
  # Set interrupt disable flag.
@@ -56,7 +56,7 @@ module Badline
56
56
  # Opcodes:
57
57
  # $78 - implied - 2 cycles
58
58
  def sei(_addr, _value)
59
- cycle { status.interrupt = true }
59
+ status.interrupt = true
60
60
  end
61
61
  end
62
62
  end
@@ -9,7 +9,7 @@ module Badline
9
9
  # Opcodes:
10
10
  # $4B - immediate - 2 cycles
11
11
  def alr(_addr, value)
12
- @a &= resolve(value)
12
+ @a &= value
13
13
  status.carry = @a[0]
14
14
  @a = (@a >> 1) & 0xff
15
15
  update_number_flags(@a)
@@ -25,13 +25,14 @@ module Badline
25
25
  end
26
26
 
27
27
  # Unstable instruction. AND's value with A|magic_const and X.
28
- # Magic constant varies by CPU.
28
+ # Magic constant varies by CPU. A VIC stall between the opcode and
29
+ # operand fetches clears its bits 0 and 4.
29
30
  #
30
31
  # Opcodes:
31
32
  # $8B - immediate - 2 cycles
32
33
  def ane(_addr, value)
33
- magic_const = 0xee
34
- @a = (a | magic_const) & x & resolve(value)
34
+ magic_const = stalled_before_this_cycle? ? @ane_constant & 0xee : @ane_constant
35
+ @a = (a | magic_const) & x & value
35
36
  update_number_flags(@a)
36
37
  end
37
38
 
@@ -40,7 +41,7 @@ module Badline
40
41
  # Opcodes:
41
42
  # $6B - immediate - 2 cycles
42
43
  def arr(_addr, value)
43
- tmp = a & resolve(value)
44
+ tmp = a & value
44
45
  result = (tmp >> 1) | (status.carry? ? 0x80 : 0)
45
46
  status.zero = result.zero?
46
47
  status.negative = status.carry
@@ -94,23 +95,12 @@ module Badline
94
95
  sbc(addr, inc(addr, value))
95
96
  end
96
97
 
97
- # Freezes the CPU by forcing an infinite loop.
98
- #
99
- # Opcodes:
100
- # $02, $12, $22, $32, $42, $52, $62, $72, $92, $B2, $D2, $F2
101
- def jam(_addr, _value)
102
- # TODO: Handle jam
103
- # It is possible to implement with loop { cycle }, but makes
104
- # testing problematic.
105
- 10.times { cycle }
106
- end
107
-
108
98
  # Loads A and S with value AND stack pointer.
109
99
  #
110
100
  # Opcodes:
111
101
  # $BB - absolute_y - 4 cycles
112
102
  def las(_addr, value)
113
- @a = @x = @stack_pointer = resolve(value) & stack_pointer
103
+ @a = @x = @stack_pointer = value & stack_pointer
114
104
  update_number_flags(@a)
115
105
  end
116
106
 
@@ -124,7 +114,7 @@ module Badline
124
114
  # $B7 - zeropage_y - 4 cycles
125
115
  # $BF - absolute_y - 4+ cycles
126
116
  def lax(_addr, value)
127
- @a = @x = resolve(value)
117
+ @a = @x = value
128
118
 
129
119
  update_number_flags(@a)
130
120
  end
@@ -136,7 +126,7 @@ module Badline
136
126
  # $AB - immediate - 2 cycles
137
127
  def lxa(_addr, value)
138
128
  magic_const = 0xee
139
- @a = @x = (a | magic_const) & resolve(value)
129
+ @a = @x = (a | magic_const) & value
140
130
  update_number_flags(@a)
141
131
  end
142
132
 
@@ -191,8 +181,7 @@ module Badline
191
181
  # Opcodes:
192
182
  # $CB - immediate - 2 cycles
193
183
  def sbx(_addr, value)
194
- v = resolve(value)
195
- result = (@x & @a) - v
184
+ result = (@x & @a) - value
196
185
  status.carry = result >= 0
197
186
  @x = result & 0xff
198
187
  update_number_flags(@x)
@@ -263,6 +252,9 @@ module Badline
263
252
 
264
253
  private
265
254
 
255
+ # A VIC stall right before the dummy read, the second-to-last cycle,
256
+ # drops the & (H+1) from the stored value but not from the high byte
257
+ # of a page-crossing target.
266
258
  def store_high_and(register, addr)
267
259
  base_high = if boundary_crossed
268
260
  (high_byte(addr) - 1) & 0xff
@@ -271,7 +263,7 @@ module Badline
271
263
  end
272
264
  result = register & ((base_high + 1) & 0xff)
273
265
  target = boundary_crossed ? uint16(low_byte(addr), result) : addr
274
- write_byte(target, result)
266
+ write_byte(target, stalled_before_previous_cycle? ? register : result)
275
267
  end
276
268
  end
277
269
  end
@@ -11,9 +11,8 @@ module Badline
11
11
  # $D6 - zeropage_x - 6 cycles
12
12
  # $DE - absolute_x - 7 cycles
13
13
  def dec(addr, value)
14
- v = resolve(value)
15
- result = (v - 1) & 0xff
16
- write_modified(addr, v, result)
14
+ result = (value - 1) & 0xff
15
+ write_modified(addr, result)
17
16
  update_number_flags(result)
18
17
  end
19
18
 
@@ -22,7 +21,7 @@ module Badline
22
21
  # Opcodes:
23
22
  # $CA - implied - 2 cycles
24
23
  def dex(_addr, _value)
25
- cycle { @x = (@x - 1) & 0xff }
24
+ @x = (@x - 1) & 0xff
26
25
  update_number_flags(@x)
27
26
  end
28
27
 
@@ -31,7 +30,7 @@ module Badline
31
30
  # Opcodes:
32
31
  # $88 - implied - 2 cycles
33
32
  def dey(_addr, _value)
34
- cycle { @y = (@y - 1) & 0xff }
33
+ @y = (@y - 1) & 0xff
35
34
  update_number_flags(@y)
36
35
  end
37
36
 
@@ -43,9 +42,8 @@ module Badline
43
42
  # $F6 - zeropage_x - 6 cycles
44
43
  # $FE - absolute_x - 7 cycles
45
44
  def inc(addr, value)
46
- v = resolve(value)
47
- result = (v + 1) & 0xff
48
- write_modified(addr, v, result)
45
+ result = (value + 1) & 0xff
46
+ write_modified(addr, result)
49
47
  update_number_flags(result)
50
48
  end
51
49
 
@@ -54,7 +52,7 @@ module Badline
54
52
  # Opcodes:
55
53
  # $E8 - implied - 2 cycles
56
54
  def inx(_addr, _value)
57
- cycle { @x = (@x + 1) & 0xff }
55
+ @x = (@x + 1) & 0xff
58
56
  update_number_flags(@x)
59
57
  end
60
58
 
@@ -63,7 +61,7 @@ module Badline
63
61
  # Opcodes:
64
62
  # $C8 - implied - 2 cycles
65
63
  def iny(_addr, _value)
66
- cycle { @y = (@y + 1) & 0xff }
64
+ @y = (@y + 1) & 0xff
67
65
  update_number_flags(@y)
68
66
  end
69
67
  end
@@ -24,7 +24,7 @@ module Badline
24
24
  # Opcodes:
25
25
  # $68 - implied - 4 cycles
26
26
  def pla(_addr, _value)
27
- cycle { @a = stack_pull }
27
+ @a = stack_pull
28
28
  update_number_flags(@a)
29
29
  end
30
30
 
@@ -33,71 +33,19 @@ module Badline
33
33
  # Opcodes:
34
34
  # $28 - implied - 4 cycles
35
35
  def plp(_addr, _value)
36
- cycle { status.value = stack_pull & 0b11101111 }
37
- end
38
-
39
- # Jump to absolute address.
40
- #
41
- # Opcodes:
42
- # $4C - absolute - 3 cycles
43
- def jmp(addr, _value)
44
- @program_counter = addr
45
- end
46
-
47
- # Jump to subroutine. The operand high byte is fetched only after the
48
- # return address has been pushed, so a JSR executing inside the stack
49
- # jumps via the value its own push just wrote.
50
- #
51
- # Opcodes:
52
- # $20 - absolute - 6 cycles
53
- def jsr(addr, _value)
54
- return_addr = (program_counter - 1) & 0xffff
55
- cycle { @memory.peek(stack_address) } # internal cycle: dummy stack read
56
- write_byte(stack_address, high_byte(return_addr))
57
- write_byte(stack_address(-1), low_byte(return_addr))
58
- @stack_pointer = (@stack_pointer - 2) & 0xff
59
- @program_counter = uint16(addr, read_byte(return_addr))
60
- end
61
-
62
- # Return from interrupt.
63
- #
64
- # Opcodes:
65
- # $40 - implied - 6 cycles
66
- def rti(_addr, _value)
67
- cycle do
68
- @stack_pointer = (@stack_pointer + 1) & 0xff
69
- @status.value = memory[stack_address]
70
- @status.break = false
71
- end
72
- @program_counter = uint16(stack_pull, stack_pull)
73
- end
74
-
75
- # Return from subroutine.
76
- #
77
- # Opcodes:
78
- # $60 - implied - 6 cycles
79
- def rts(_addr, _value)
80
- cycle do
81
- @program_counter = (uint16(stack_pull, stack_pull) + 1) & 0xffff
82
- end
36
+ status.value = stack_pull & 0b11101111
83
37
  end
84
38
 
85
39
  private
86
40
 
87
41
  def stack_pull
88
- cycle { @stack_pointer = (@stack_pointer + 1) & 0xff }
89
- read_byte(stack_address)
42
+ @stack_pointer = (@stack_pointer + 1) & 0xff
43
+ @memory.peek(stack_address)
90
44
  end
91
45
 
92
46
  def stack_push(value)
93
47
  write_byte(stack_address, value)
94
- cycle { @stack_pointer = (@stack_pointer - 1) & 0xff }
95
- end
96
-
97
- def stack_push16(value)
98
- write_byte(stack_address, high_byte(value))
99
- write_byte(stack_address(-1), low_byte(value))
100
- cycle { @stack_pointer = (@stack_pointer - 2) & 0xff }
48
+ @stack_pointer = (@stack_pointer - 1) & 0xff
101
49
  end
102
50
  end
103
51
  end
@@ -16,7 +16,7 @@ module Badline
16
16
  # $B9 - absolute_y - 4+ cycles
17
17
  # $BD - absolute_x - 4+ cycles
18
18
  def lda(_addr, value)
19
- @a = resolve(value)
19
+ @a = value
20
20
  update_number_flags(@a)
21
21
  end
22
22
 
@@ -29,7 +29,7 @@ module Badline
29
29
  # $B6 - zeropage_y - 4 cycles
30
30
  # $BE - absolute_y - 4+ cycles
31
31
  def ldx(_addr, value)
32
- @x = resolve(value)
32
+ @x = value
33
33
  update_number_flags(@x)
34
34
  end
35
35
 
@@ -42,7 +42,7 @@ module Badline
42
42
  # $B4 - zeropage_x - 4 cycles
43
43
  # $BC - absolute_x - 4+ cycles
44
44
  def ldy(_addr, value)
45
- @y = resolve(value)
45
+ @y = value
46
46
  update_number_flags(@y)
47
47
  end
48
48
 
@@ -85,7 +85,7 @@ module Badline
85
85
  # Opcodes:
86
86
  # $AA - implied - 2 cycles
87
87
  def tax(_addr, _value)
88
- cycle { @x = a }
88
+ @x = a
89
89
  update_number_flags(@x)
90
90
  end
91
91
 
@@ -94,7 +94,7 @@ module Badline
94
94
  # Opcodes:
95
95
  # $A8 - implied - 2 cycles
96
96
  def tay(_addr, _value)
97
- cycle { @y = a }
97
+ @y = a
98
98
  update_number_flags(@y)
99
99
  end
100
100
 
@@ -103,7 +103,7 @@ module Badline
103
103
  # Opcodes:
104
104
  # $BA - implied - 2 cycles
105
105
  def tsx(_addr, _value)
106
- cycle { @x = stack_pointer }
106
+ @x = stack_pointer
107
107
  update_number_flags(@x)
108
108
  end
109
109
 
@@ -112,7 +112,7 @@ module Badline
112
112
  # Opcodes:
113
113
  # $8A - implied - 2 cycles
114
114
  def txa(_addr, _operand)
115
- cycle { @a = x }
115
+ @a = x
116
116
  update_number_flags(@a)
117
117
  end
118
118
 
@@ -121,7 +121,7 @@ module Badline
121
121
  # Opcodes:
122
122
  # $9A - implied - 2 cycles
123
123
  def txs(_addr, _operand)
124
- cycle { @stack_pointer = x }
124
+ @stack_pointer = x
125
125
  end
126
126
 
127
127
  # Transfer Y register to accumulator.
@@ -129,7 +129,7 @@ module Badline
129
129
  # Opcodes:
130
130
  # $98 - implied - 2 cycles
131
131
  def tya(_addr, _operand)
132
- cycle { @a = y }
132
+ @a = y
133
133
  update_number_flags(@a)
134
134
  end
135
135
  end
@@ -22,18 +22,8 @@ module Badline
22
22
  include InstructionSet::Stack
23
23
  include InstructionSet::Transfer
24
24
 
25
- # Forces a software interrupt (break).
26
- # Pushes PC+2 and status to stack, sets break flag, and jumps via IRQ vector.
27
- #
28
- # Opcodes:
29
- # $00 - implied - 7 cycles
30
- def brk(_addr, _value)
31
- status.break = true
32
- handle_interrupt(0xfffe, brk: true, pre_cycles: 1)
33
- status.break = false
34
- end
35
-
36
- # No operation. Does nothing but consume a clock cycle.
25
+ # No operation. The illegal forms with an operand still read it, in
26
+ # the steps before this is called.
37
27
  #
38
28
  # Opcodes:
39
29
  # $EA - implied - 2 cycles
@@ -42,35 +32,23 @@ module Badline
42
32
  # $0C - absolute - 4 cycles (illegal)
43
33
  # $1C, $3C, $5C, $7C, $DC, $FC - absolute_x - 4+ cycles (illegal)
44
34
  # $1A, $3A, $5A, $7A, $DA, $FA - implied - 2 cycles (illegal)
45
- def nop(_addr, _value)
46
- cycle
47
- end
35
+ def nop(_addr, _value); end
48
36
 
49
37
  private
50
38
 
51
- def resolve(value)
52
- if value == :lazy
53
- realize_value(@instruction, @operand, @address)
54
- elsif value.is_a?(Proc)
55
- value.call
56
- else
57
- value
58
- end
59
- end
60
-
61
39
  def update_number_flags(value)
62
40
  status.zero = value.zero?
63
- status.negative = value.anybits?(0x80)
41
+ status.negative = value & 0x80 != 0
64
42
  value
65
43
  end
66
44
 
67
- # Read-modify-write instructions put the unmodified value back on the bus before writing the result.
68
- def write_modified(addr, original, result)
45
+ # Stores a read-modify-write result. For memory, the write happens in
46
+ # the steps that follow (see Operations#op_rmw_read).
47
+ def write_modified(addr, result)
69
48
  if addr == :accumulator
70
- cycle { @a = result }
49
+ @a = result
71
50
  else
72
- write_byte(addr, original)
73
- write_byte(addr, result)
51
+ @rmw_result = result
74
52
  end
75
53
  end
76
54
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Interrupts
5
+ BREAK_MASK = 0x10
6
+
7
+ private
8
+
9
+ # Samples the IRQ line and the NMI latch once per cycle. An instruction
10
+ # boundary commits the sample taken on its second-to-last cycle.
11
+ def poll
12
+ if @skip_poll
13
+ @skip_poll = false
14
+ return
15
+ end
16
+ @irq_pending = @irq_sample
17
+ @nmi_pending = @nmi_sample
18
+ @irq_sample = @irq && !@status.interrupt?
19
+ @nmi_sample = @nmi
20
+ end
21
+
22
+ # A cycle the VIC stalls through BA keeps sampling the lines, OR-ed with
23
+ # the sample it already holds, but the boundary pipeline doesn't advance
24
+ # and a skipped poll stays owed to the next real cycle.
25
+ def sample_while_stalled
26
+ @irq_sample ||= @irq && !interrupt_disabled_after_stall?
27
+ @nmi_sample = true if @nmi
28
+ end
29
+
30
+ # A stalled CLI or SEI execute cycle masks with the I it is about to
31
+ # set. Every other step masks with the current I, PLP included: its
32
+ # pulled I only arrives with the stalled stack read.
33
+ def interrupt_disabled_after_stall?
34
+ return @status.interrupt? unless @plan.equal?(CPU::IMPLIED_PLAN)
35
+
36
+ case @operation
37
+ when :cli then false
38
+ when :sei then true
39
+ else @status.interrupt?
40
+ end
41
+ end
42
+
43
+ # Runs in the opcode fetch cycle in place of the fetch, with no bus
44
+ # access, and switches to the interrupt plan.
45
+ def start_interrupt
46
+ @interrupt = @boundary_nmi ? 0xfffa : 0xfffe
47
+ @brk = false
48
+ @plan = CPU::INTERRUPT_PLAN
49
+ @writes = CPU::INTERRUPT_WRITES
50
+ @index = 1
51
+ end
52
+
53
+ # BRK reads the byte after its opcode and ignores it, and selects the
54
+ # IRQ vector.
55
+ def op_brk_dummy
56
+ @memory.peek(@program_counter)
57
+ @interrupt = 0xfffe
58
+ @brk = true
59
+ end
60
+
61
+ def op_int_push_pch
62
+ @address = @brk ? (@program_counter + 1) & 0xffff : @program_counter
63
+ @memory.poke(stack_address, high_byte(@address))
64
+ @stack_pointer = (@stack_pointer - 1) & 0xff
65
+ end
66
+
67
+ def op_int_push_pcl
68
+ @memory.poke(stack_address, low_byte(@address))
69
+ @stack_pointer = (@stack_pointer - 1) & 0xff
70
+ end
71
+
72
+ def op_int_push_status
73
+ value = @brk ? @status.value | BREAK_MASK : @status.value & ~BREAK_MASK
74
+ @memory.poke(stack_address, value)
75
+ @stack_pointer = (@stack_pointer - 1) & 0xff
76
+ @status.interrupt = true
77
+ # An NMI that arrives before cycle 4 of a BRK or IRQ takes it over:
78
+ # the sequence finishes with the NMI vector.
79
+ @interrupt = 0xfffa if @interrupt == 0xfffe && @nmi_pending
80
+ @nmi = false if @interrupt == 0xfffa
81
+ end
82
+
83
+ def op_int_vector_low
84
+ @address = @memory.peek(@interrupt)
85
+ end
86
+
87
+ # Clears any interrupt that is pending, so the handler's first
88
+ # instruction always runs before the next interrupt. This applies to
89
+ # BRK too.
90
+ def op_int_vector_high
91
+ high = @memory.peek((@interrupt + 1) & 0xffff)
92
+ @program_counter = @address | (high << 8)
93
+ @irq_sample = @irq_pending = false
94
+ @nmi_sample = @nmi_pending = false
95
+ @interrupt = nil
96
+ if @brk
97
+ @status.break = false
98
+ end_instruction
99
+ else
100
+ end_sequence
101
+ end
102
+ end
103
+ end
104
+ end
@@ -4,22 +4,22 @@ module Badline
4
4
  class Joystick
5
5
  DIRECTIONS = { up: 0, down: 1, left: 2, right: 3, fire: 4 }.freeze
6
6
 
7
+ # Kept up to date as switches move, since the CIA samples the fire line
8
+ # every cycle to drive the light pen input.
9
+ attr_reader :port_bits
10
+
7
11
  def initialize
8
- @pressed = {}
12
+ @port_bits = 0xff
9
13
  end
10
14
 
11
15
  def press(direction)
12
- @pressed[direction] = true if DIRECTIONS.key?(direction)
16
+ bit = DIRECTIONS[direction]
17
+ @port_bits &= ~(1 << bit) if bit
13
18
  end
14
19
 
15
20
  def release(direction)
16
- @pressed.delete(direction)
17
- end
18
-
19
- def port_bits
20
- bits = 0xff
21
- DIRECTIONS.each { |dir, bit| bits &= ~(1 << bit) if @pressed[dir] }
22
- bits
21
+ bit = DIRECTIONS[direction]
22
+ @port_bits |= 1 << bit if bit
23
23
  end
24
24
  end
25
25
  end