badline 0.16.1 → 0.17.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +102 -12
  5. data/doc/media.md +5 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +376 -69
  8. data/doc/snapshots.md +148 -14
  9. data/lib/badline/cbm2/acia.rb +10 -0
  10. data/lib/badline/cbm2/bus.rb +263 -0
  11. data/lib/badline/cbm2/cpu.rb +58 -0
  12. data/lib/badline/cbm2/crtc_video.rb +133 -0
  13. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  14. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  15. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  16. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  17. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  18. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  19. data/lib/badline/cbm2/keyed_state.rb +91 -0
  20. data/lib/badline/cbm2/model.rb +44 -0
  21. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  22. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  23. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  24. data/lib/badline/cbm2/p500/model.rb +39 -0
  25. data/lib/badline/cbm2/p500/pages.rb +57 -0
  26. data/lib/badline/cbm2/p500.rb +262 -0
  27. data/lib/badline/cbm2/sid_page.rb +19 -0
  28. data/lib/badline/cbm2.rb +267 -0
  29. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  30. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  31. data/lib/badline/chips/acia.rb +307 -0
  32. data/lib/badline/chips/crtc.rb +3 -2
  33. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  34. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  35. data/lib/badline/chips/riot/timer.rb +80 -0
  36. data/lib/badline/chips/riot.rb +162 -0
  37. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  38. data/lib/badline/chips/rriot.rb +132 -0
  39. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  40. data/lib/badline/chips/tpi.rb +233 -0
  41. data/lib/badline/chips/vic/bank.rb +60 -5
  42. data/lib/badline/chips/vic/sprite.rb +4 -3
  43. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  44. data/lib/badline/drives/datasette.rb +27 -3
  45. data/lib/badline/drives/model8050/disk.rb +163 -0
  46. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  47. data/lib/badline/drives/model8050/idle.rb +177 -0
  48. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  49. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  50. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  51. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  52. data/lib/badline/drives/model8050/parts.rb +37 -0
  53. data/lib/badline/drives/model8050.rb +243 -0
  54. data/lib/badline/frontend/app.rb +10 -3
  55. data/lib/badline/frontend/boot.rb +1 -1
  56. data/lib/badline/frontend/controls.rb +51 -15
  57. data/lib/badline/frontend/gamepads.rb +3 -3
  58. data/lib/badline/frontend/keys.rb +61 -2
  59. data/lib/badline/frontend/media_slots.rb +1 -1
  60. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  61. data/lib/badline/frontend/menu/menu_pages.rb +18 -4
  62. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  63. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  64. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  65. data/lib/badline/frontend/timeline.rb +19 -10
  66. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  67. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  68. data/lib/badline/kernal_trap/dos.rb +18 -5
  69. data/lib/badline/kernal_trap/ieee.rb +4 -2
  70. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  71. data/lib/badline/machine/ieee_drives.rb +194 -0
  72. data/lib/badline/machine/settling.rb +4 -0
  73. data/lib/badline/machine.rb +23 -6
  74. data/lib/badline/media/cbm2_media.rb +29 -0
  75. data/lib/badline/media/program.rb +6 -3
  76. data/lib/badline/media/true_drive.rb +38 -9
  77. data/lib/badline/media.rb +15 -6
  78. data/lib/badline/options/family.rb +15 -3
  79. data/lib/badline/options/help.rb +16 -2
  80. data/lib/badline/options/table.rb +18 -4
  81. data/lib/badline/options/validation.rb +27 -3
  82. data/lib/badline/options.rb +21 -7
  83. data/lib/badline/pet/cassettes.rb +22 -1
  84. data/lib/badline/pet/ieee_drive.rb +5 -64
  85. data/lib/badline/pet/ieee_ports.rb +7 -0
  86. data/lib/badline/pet.rb +15 -4
  87. data/lib/badline/plus4/acia.rb +4 -298
  88. data/lib/badline/plus4/keyed_parts.rb +0 -44
  89. data/lib/badline/plus4.rb +1 -0
  90. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  91. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  92. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  93. data/lib/badline/roms/README +111 -0
  94. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  95. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  96. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  97. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  98. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  99. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  100. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  101. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  102. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  103. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  104. data/lib/badline/snapshot/container.rb +7 -2
  105. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  106. data/lib/badline/snapshot/field_doc.rb +38 -3
  107. data/lib/badline/snapshot/image.rb +62 -9
  108. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  109. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  110. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  111. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  112. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  113. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  114. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  115. data/lib/badline/snapshot/vice/cias.rb +13 -9
  116. data/lib/badline/snapshot/vice/p500.rb +96 -0
  117. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  118. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  119. data/lib/badline/snapshot/vice/pet.rb +7 -3
  120. data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
  121. data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
  122. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  123. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  124. data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
  125. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  126. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  127. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  128. data/lib/badline/snapshot/vice.rb +3 -0
  129. data/lib/badline/snapshot.rb +11 -3
  130. data/lib/badline/storage/crt_file.rb +7 -1
  131. data/lib/badline/storage/d64_image.rb +7 -0
  132. data/lib/badline/storage/d80_image.rb +17 -1
  133. data/lib/badline/storage/d81_image.rb +14 -0
  134. data/lib/badline/storage/disk_image/bam.rb +18 -3
  135. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  136. data/lib/badline/storage/disk_image/writing.rb +35 -0
  137. data/lib/badline/storage/disk_image.rb +2 -0
  138. data/lib/badline/storage.rb +1 -0
  139. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  140. data/lib/badline/support/p500_checkpoint.rb +35 -0
  141. data/lib/badline/version.rb +1 -1
  142. data/lib/badline.rb +1 -0
  143. metadata +72 -2
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RIOT
6
+ class Timer
7
+ # Running the timer a stretch of cycles at once, as cycle! would one
8
+ # by one, for a drive sleeping through its idle loops.
9
+ module FastForward
10
+ # quiet_cycles for a timer whose flag can't pull IRQ.
11
+ QUIET = 1 << 40
12
+
13
+ # How many cycles the timer can run without pulling IRQ: until the
14
+ # count that sets its flag, and forever with its interrupt off.
15
+ def quiet_cycles
16
+ return QUIET unless @irq_enabled
17
+
18
+ cycles_to_flag - 1
19
+ end
20
+
21
+ # Runs +cycles+ cycles at once.
22
+ def fast_forward(cycles)
23
+ return if cycles.zero?
24
+ return count_expired(cycles) if @expired
25
+
26
+ to_flag = cycles_to_flag
27
+ if cycles < to_flag
28
+ count_down(cycles)
29
+ else
30
+ @counter = 0xff
31
+ @prescale = @divider
32
+ @expired = @flag = @fired = true
33
+ count_expired(cycles - to_flag) if cycles > to_flag
34
+ end
35
+ end
36
+
37
+ # What the counter and the flag don't hold, for comparing the
38
+ # timer at two points.
39
+ def state = [@divider, @expired, @irq_enabled]
40
+
41
+ private
42
+
43
+ # The cycles until the count that sets the flag: the count through
44
+ # 0 at the interval, or the next once expired.
45
+ def cycles_to_flag = @expired ? @counter + 1 : @prescale + (@counter * @divider)
46
+
47
+ # Counts at the interval, short of 0.
48
+ def count_down(cycles)
49
+ @fired = false
50
+ if cycles < @prescale
51
+ @prescale -= cycles
52
+ else
53
+ rest = cycles - @prescale
54
+ @counter -= 1 + (rest / @divider)
55
+ @prescale = @divider - (rest % @divider)
56
+ end
57
+ end
58
+
59
+ # Counts every cycle, setting the flag at each pass through 0.
60
+ def count_expired(cycles)
61
+ to_wrap = @counter + 1
62
+ @counter = (@counter - cycles) & 0xff
63
+ @fired = cycles >= to_wrap && ((cycles - to_wrap) % 256).zero?
64
+ @flag = true if cycles >= to_wrap
65
+ end
66
+ end
67
+
68
+ include FastForward
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RIOT
6
+ class Timer
7
+ # The timer's fields in a keyed snapshot module, under `prefix`, the
8
+ # divider by its A1-A0 code.
9
+ module KeyedState
10
+ def save_fields(out, prefix)
11
+ out.int("#{prefix}counter", @counter).int("#{prefix}interval", DIVIDERS.index(@divider))
12
+ out.int("#{prefix}prescale", @prescale)
13
+ out.bool("#{prefix}expired", @expired).bool("#{prefix}flag", @flag)
14
+ out.bool("#{prefix}irq_enabled", @irq_enabled).bool("#{prefix}fired", @fired)
15
+ end
16
+
17
+ def load_fields(input, prefix)
18
+ @counter = input.byte("#{prefix}counter", @counter)
19
+ @divider = DIVIDERS[input.masked("#{prefix}interval", DIVIDERS.index(@divider), 0x03)]
20
+ @prescale = input.int("#{prefix}prescale", @prescale).clamp(1, @divider)
21
+ @expired = input.bool("#{prefix}expired", @expired)
22
+ @flag = input.bool("#{prefix}flag", @flag)
23
+ @irq_enabled = input.bool("#{prefix}irq_enabled", @irq_enabled)
24
+ @fired = input.bool("#{prefix}fired", @fired)
25
+ end
26
+ end
27
+
28
+ include KeyedState
29
+ end
30
+
31
+ # The RIOT's fields in a keyed snapshot module (Snapshot::Keyed): the
32
+ # RAM, the port and direction registers, the edge detector and the
33
+ # level on PA7, and the timer under timer. The peripheral restores its
34
+ # own state.
35
+ module KeyedState
36
+ def save_fields(out)
37
+ out.bytes("ram", @ram)
38
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
39
+ out.bool("pa7_high", @pa7_high).bool("pa7_active", @pa7_active).bool("pa7_flag", @pa7_flag)
40
+ out.bool("edge_irq_enabled", @edge_irq_enabled).bool("rising_edge", @rising_edge)
41
+ @timer.save_fields(out, "timer.")
42
+ out
43
+ end
44
+
45
+ def load_fields(input)
46
+ input.bytes_into("ram", @ram)
47
+ @ora = input.byte("ora", @ora)
48
+ @orb = input.byte("orb", @orb)
49
+ @ddra = input.byte("ddra", @ddra)
50
+ @ddrb = input.byte("ddrb", @ddrb)
51
+ load_edge_fields(input)
52
+ @timer.load_fields(input, "timer.")
53
+ end
54
+
55
+ private
56
+
57
+ def load_edge_fields(input)
58
+ @pa7_high = input.bool("pa7_high", @pa7_high)
59
+ @pa7_active = input.bool("pa7_active", @pa7_active)
60
+ @pa7_flag = input.bool("pa7_flag", @pa7_flag)
61
+ @edge_irq_enabled = input.bool("edge_irq_enabled", @edge_irq_enabled)
62
+ @rising_edge = input.bool("rising_edge", @rising_edge)
63
+ end
64
+ end
65
+
66
+ include KeyedState
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RIOT
6
+ # The interval timer of the 6532 and the 6530: an 8-bit counter behind
7
+ # a prescaler that divides φ2 by 1, 8, 64 or 1024, and the timer's
8
+ # interrupt flag.
9
+ #
10
+ # A write loads the counter and counts it once on the write's own
11
+ # cycle, so the counter reads one less than written from the cycle
12
+ # after, and counts again every interval from the write. A write of N
13
+ # reads N - 1 until cycle T, 0 until cycle N x T, and $FF with the flag
14
+ # set on cycle N x T: 52 at ÷8 sets the flag on cycle 416. From there
15
+ # the counter counts every cycle, so it tells how long ago the flag
16
+ # set, and each count through 0 sets the flag again. Only a write puts
17
+ # the prescaler back.
18
+ #
19
+ # Reading or writing the counter clears the flag, except for a read on
20
+ # the cycle that sets it. Each access takes A3 as the interrupt enable.
21
+ class Timer
22
+ DIVIDERS = [1, 8, 64, 1024].freeze
23
+
24
+ attr_reader :counter, :flag
25
+ attr_accessor :irq_enabled
26
+
27
+ def initialize
28
+ @counter = 0
29
+ @divider = 1
30
+ @prescale = 1
31
+ @expired = true
32
+ @flag = false
33
+ @irq_enabled = false
34
+ @fired = false
35
+ end
36
+
37
+ def irq? = @flag && @irq_enabled
38
+
39
+ # +interval+ is A1-A0, picking ÷1, ÷8, ÷64 or ÷1024.
40
+ def write(value, interval, irq_enabled)
41
+ @irq_enabled = irq_enabled
42
+ @flag = false
43
+ @divider = DIVIDERS[interval]
44
+ @prescale = @divider
45
+ @expired = false
46
+ @counter = value
47
+ count
48
+ end
49
+
50
+ def read(irq_enabled)
51
+ @irq_enabled = irq_enabled
52
+ @flag = false unless @fired
53
+ @counter
54
+ end
55
+
56
+ def cycle!
57
+ @fired = false
58
+ unless @expired
59
+ @prescale -= 1
60
+ return unless @prescale.zero?
61
+
62
+ @prescale = @divider
63
+ end
64
+ count
65
+ end
66
+
67
+ private
68
+
69
+ def count
70
+ @counter = (@counter - 1) & 0xff
71
+ return unless @counter == 0xff
72
+
73
+ @expired = true
74
+ @flag = true
75
+ @fired = true
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/riot/timer"
4
+ require "badline/chips/riot/fast_forward"
5
+ require "badline/chips/riot/keyed_parts"
6
+
7
+ module Badline
8
+ module Chips
9
+ # The MOS 6532 RIOT (RAM, I/O, Timer): 128 bytes of RAM, two 8-bit
10
+ # ports, A and B, each with a direction register, a 1 making the pin an
11
+ # output, the interval timer (Timer) and an edge detector on PA7. The
12
+ # 8050 and 8250's interface processor has two.
13
+ #
14
+ # The RS pin picks the RAM (read_ram and write_ram, A6-A0) or the
15
+ # registers (peek and poke, A4-A0). With A2 low, A1-A0 pick port A, DDRA,
16
+ # port B and DDRB. With A2 high, a read with A0 low reads the timer and
17
+ # one with A0 high the interrupt flags. A write with A4 high loads the
18
+ # timer, A1-A0 picking the divider, and one with A4 low sets the edge
19
+ # detector, A1 the interrupt enable and A0 the active edge, rising when
20
+ # set; its data is ignored. A3 is the timer's interrupt enable on every
21
+ # timer access.
22
+ #
23
+ # Port A reads its pins, whatever drives them. Port B reads its output
24
+ # bits from its output register and its input bits from the pins. An
25
+ # input pin floats high, and a peripheral pulls lines low through read_a
26
+ # and read_b, as a VIA's does. PA7 takes its level from outside through
27
+ # pa7= as well, for the edge detector.
28
+ #
29
+ # The edge detector watches the level on PA7, input or output. Its
30
+ # active edge sets the PA7 flag whether or not its interrupt is enabled,
31
+ # and so can a change of the active edge. A read of the flags clears it.
32
+ class RIOT
33
+ attr_reader :peripheral, :timer, :ram
34
+
35
+ def initialize(peripheral: nil)
36
+ @peripheral = peripheral
37
+ @ram = Array.new(128, 0)
38
+ @timer = Timer.new
39
+ @pa7_high = true
40
+ @pa7_active = false
41
+ @pa7_flag = false
42
+ reset!
43
+ end
44
+
45
+ # The RES line clears the four I/O registers, which leaves every line
46
+ # an input, disables both interrupts and sets the active edge to
47
+ # falling. The timer counts on, and the flags stay as they are.
48
+ def reset!
49
+ @ora = @orb = @ddra = @ddrb = 0x00
50
+ @timer.irq_enabled = false
51
+ @edge_irq_enabled = false
52
+ @rising_edge = false
53
+ sense_pa7
54
+ end
55
+
56
+ def irq? = @timer.irq? || (@pa7_flag && @edge_irq_enabled)
57
+
58
+ def cycle!
59
+ @timer.cycle!
60
+ end
61
+
62
+ # Port A as driven by ORA and DDRA, input lines floating high.
63
+ def port_a_output = driven_lines(@ora, @ddra)
64
+
65
+ def port_b_output = driven_lines(@orb, @ddrb)
66
+
67
+ # The level something outside drives on PA7.
68
+ def pa7=(high)
69
+ @pa7_high = high
70
+ sense_pa7
71
+ end
72
+
73
+ # How many cycles the RIOT can run with no counter pulling IRQ.
74
+ def quiet_cycles = @timer.quiet_cycles
75
+
76
+ # Runs +cycles+ cycles at once, which moves only the timer.
77
+ def fast_forward(cycles)
78
+ @timer.fast_forward(cycles)
79
+ end
80
+
81
+ # Everything the RIOT holds but its timer's counter, for comparing it
82
+ # at two points.
83
+ def idle_state
84
+ [*@ram, @ora, @orb, @ddra, @ddrb, @pa7_high, @pa7_active, @pa7_flag, @edge_irq_enabled, @rising_edge,
85
+ *@timer.state, @timer.flag]
86
+ end
87
+
88
+ def read_ram(addr) = @ram[addr & 0x7f]
89
+
90
+ def write_ram(addr, value)
91
+ @ram[addr & 0x7f] = value
92
+ end
93
+
94
+ def peek(addr)
95
+ return read_timer(addr) if addr.anybits?(0x04)
96
+
97
+ case addr & 0x03
98
+ when 0x00 then pins_a
99
+ when 0x01 then @ddra
100
+ when 0x02 then read_port_b
101
+ else @ddrb
102
+ end
103
+ end
104
+
105
+ def poke(addr, value)
106
+ return write_timer(addr, value) if addr.anybits?(0x04)
107
+
108
+ case addr & 0x03
109
+ when 0x00 then @ora = value
110
+ when 0x01 then @ddra = value
111
+ when 0x02 then @orb = value
112
+ else @ddrb = value
113
+ end
114
+ sense_pa7
115
+ end
116
+
117
+ private
118
+
119
+ def driven_lines(register, direction)
120
+ (register & direction) | (~direction & 0xff)
121
+ end
122
+
123
+ def pins_a
124
+ lines = @pa7_high ? port_a_output : port_a_output & 0x7f
125
+ @peripheral ? lines & @peripheral.read_a(lines) : lines
126
+ end
127
+
128
+ def read_port_b
129
+ lines = port_b_output
130
+ lines &= @peripheral.read_b(lines) if @peripheral
131
+ (@orb & @ddrb) | (lines & ~@ddrb & 0xff)
132
+ end
133
+
134
+ # Bit 7 the timer flag, bit 6 the PA7 flag, which the read clears.
135
+ def read_timer(addr)
136
+ return @timer.read(addr.anybits?(0x08)) if addr.nobits?(0x01)
137
+
138
+ value = (@timer.flag ? 0x80 : 0x00) | (@pa7_flag ? 0x40 : 0x00)
139
+ @pa7_flag = false
140
+ value
141
+ end
142
+
143
+ def write_timer(addr, value)
144
+ if addr.anybits?(0x10)
145
+ @timer.write(value, addr & 0x03, addr.anybits?(0x08))
146
+ else
147
+ @edge_irq_enabled = addr.anybits?(0x02)
148
+ @rising_edge = addr.anybits?(0x01)
149
+ sense_pa7
150
+ end
151
+ end
152
+
153
+ # The detector is active while PA7 sits at the level its active edge
154
+ # leads to, and becoming active sets the flag.
155
+ def sense_pa7
156
+ active = (@pa7_high && port_a_output.anybits?(0x80)) == @rising_edge
157
+ @pa7_flag = true if active && !@pa7_active
158
+ @pa7_active = active
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RRIOT
6
+ # The RRIOT's fields in a keyed snapshot module (Snapshot::Keyed): the
7
+ # RAM, the port and direction registers, and the timer under timer.
8
+ # The ROM is the mask's, and the peripheral restores its own state.
9
+ module KeyedState
10
+ def save_fields(out)
11
+ out.bytes("ram", @ram)
12
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
13
+ @timer.save_fields(out, "timer.")
14
+ out
15
+ end
16
+
17
+ def load_fields(input)
18
+ input.bytes_into("ram", @ram)
19
+ @ora = input.byte("ora", @ora)
20
+ @orb = input.byte("orb", @orb)
21
+ @ddra = input.byte("ddra", @ddra)
22
+ @ddrb = input.byte("ddrb", @ddrb)
23
+ @timer.load_fields(input, "timer.")
24
+ end
25
+ end
26
+
27
+ include KeyedState
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/riot"
4
+ require "badline/chips/rriot/keyed_state"
5
+
6
+ module Badline
7
+ module Chips
8
+ # The MOS 6530 RRIOT (ROM, RAM, I/O, Timer): 1 KB of mask ROM, 64 bytes
9
+ # of RAM, two 8-bit ports, A and B, each with a direction register, a 1
10
+ # making the pin an output, and the 6532's interval timer (RIOT::Timer).
11
+ # It has no edge detector. The 8050 and 8250's floppy controller has
12
+ # one, its ROM the controller's program.
13
+ #
14
+ # Which addresses select the ROM, the RAM and the registers is part of
15
+ # the mask, so the board decodes them and calls read_rom (A9-A0),
16
+ # read_ram and write_ram (A5-A0), or peek and poke (A3-A0). With A2 low,
17
+ # A1-A0 pick port A, DDRA, port B and DDRB. With A2 high, a write loads
18
+ # the timer, A1-A0 picking the divider, and a read with A0 low reads the
19
+ # timer and one with A0 high the interrupt flag in bit 7, the other bits
20
+ # 0. A3 is the timer's interrupt enable on every timer access.
21
+ #
22
+ # Both ports read their output bits from the output register and their
23
+ # input bits from the pins. An input pin floats high, and a peripheral
24
+ # pulls lines low through read_a and read_b, as a VIA's does.
25
+ #
26
+ # The timer's interrupt comes out on PB7, which the mask can give to it:
27
+ # PB7 then reads low while the flag is up with its interrupt enabled,
28
+ # and the board takes IRQ from irq?. PB5 and PB6 can be chip selects
29
+ # instead of port lines, which the board decodes.
30
+ class RRIOT
31
+ attr_reader :peripheral, :timer, :ram, :rom
32
+
33
+ # +rom+ is the 1024 bytes of the mask ROM.
34
+ def initialize(rom:, peripheral: nil, irq_on_pb7: true)
35
+ @rom = rom
36
+ @peripheral = peripheral
37
+ @irq_on_pb7 = irq_on_pb7
38
+ @ram = Array.new(64, 0)
39
+ @timer = RIOT::Timer.new
40
+ reset!
41
+ end
42
+
43
+ # The RES line clears the four I/O registers, which leaves every line
44
+ # an input, and disables the interrupt. The timer counts on.
45
+ def reset!
46
+ @ora = @orb = @ddra = @ddrb = 0x00
47
+ @timer.irq_enabled = false
48
+ end
49
+
50
+ def irq? = @irq_on_pb7 && @timer.irq?
51
+
52
+ def cycle!
53
+ @timer.cycle!
54
+ end
55
+
56
+ # Port A as driven by ORA and DDRA, input lines floating high.
57
+ def port_a_output = driven_lines(@ora, @ddra)
58
+
59
+ # Port B as driven by ORB and DDRB, PB7 pulled low by the interrupt.
60
+ def port_b_output
61
+ lines = driven_lines(@orb, @ddrb)
62
+ irq? ? lines & 0x7f : lines
63
+ end
64
+
65
+ # How many cycles the RRIOT can run with no counter pulling IRQ.
66
+ def quiet_cycles = @timer.quiet_cycles
67
+
68
+ # Runs +cycles+ cycles at once, which moves only the timer.
69
+ def fast_forward(cycles)
70
+ @timer.fast_forward(cycles)
71
+ end
72
+
73
+ # Everything the RRIOT holds but its timer's counter, for comparing
74
+ # it at two points.
75
+ def idle_state = [*@ram, @ora, @orb, @ddra, @ddrb, *@timer.state, @timer.flag]
76
+
77
+ def read_rom(addr) = @rom[addr & 0x3ff]
78
+
79
+ def read_ram(addr) = @ram[addr & 0x3f]
80
+
81
+ def write_ram(addr, value)
82
+ @ram[addr & 0x3f] = value
83
+ end
84
+
85
+ def peek(addr)
86
+ return read_timer(addr) if addr.anybits?(0x04)
87
+
88
+ case addr & 0x03
89
+ when 0x00 then read_port_a
90
+ when 0x01 then @ddra
91
+ when 0x02 then read_port_b
92
+ else @ddrb
93
+ end
94
+ end
95
+
96
+ def poke(addr, value)
97
+ return @timer.write(value, addr & 0x03, addr.anybits?(0x08)) if addr.anybits?(0x04)
98
+
99
+ case addr & 0x03
100
+ when 0x00 then @ora = value
101
+ when 0x01 then @ddra = value
102
+ when 0x02 then @orb = value
103
+ else @ddrb = value
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def driven_lines(register, direction)
110
+ (register & direction) | (~direction & 0xff)
111
+ end
112
+
113
+ def read_port_a
114
+ lines = port_a_output
115
+ lines &= @peripheral.read_a(lines) if @peripheral
116
+ (@ora & @ddra) | (lines & ~@ddra & 0xff)
117
+ end
118
+
119
+ def read_port_b
120
+ lines = port_b_output
121
+ lines &= @peripheral.read_b(lines) if @peripheral
122
+ (@orb & @ddrb) | (lines & ~@ddrb & 0xff)
123
+ end
124
+
125
+ def read_timer(addr)
126
+ return @timer.read(addr.anybits?(0x08)) if addr.nobits?(0x01)
127
+
128
+ @timer.flag ? 0x80 : 0x00
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class TPI
6
+ # The registers, the interrupt latches and AIR, the priority
7
+ # stack, CA and CB, and the levels on the interrupt inputs, in a
8
+ # keyed snapshot module (BL-TPI1 and BL-TPI2).
9
+ module KeyedState
10
+ def save_fields(out)
11
+ out.ints("ports", [@pra, @prb, @prc]).ints("directions", [@ddra, @ddrb, @ddrc])
12
+ out.int("control", @control).int("latches", @latches).int("air", @air)
13
+ out.ints("stack", @stack).bool("ca", @ca).bool("cb", @cb).int("lines", @lines)
14
+ end
15
+
16
+ def load_fields(input)
17
+ ports = input.ints_into("ports", [@pra, @prb, @prc])
18
+ @pra = ports[0] & 0xff
19
+ @prb = ports[1] & 0xff
20
+ @prc = ports[2] & 0xff
21
+ directions = input.ints_into("directions", [@ddra, @ddrb, @ddrc])
22
+ @ddra = directions[0] & 0xff
23
+ @ddrb = directions[1] & 0xff
24
+ @ddrc = directions[2] & 0xff
25
+ @control = input.byte("control", @control)
26
+ @latches = input.masked("latches", @latches, 0x1f)
27
+ @air = input.masked("air", @air, 0x1f)
28
+ @stack.replace(input.ints("stack", []).last(STACK_DEPTH).map { |value| value & 0x1f })
29
+ @ca = input.bool("ca", @ca)
30
+ @cb = input.bool("cb", @cb)
31
+ @lines = input.masked("lines", @lines, 0x1f)
32
+ end
33
+ end
34
+
35
+ include KeyedState
36
+ end
37
+ end
38
+ end