badline 0.4.2 → 0.5.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/CLAUDE.md +10 -5
  4. data/README.md +54 -23
  5. data/doc/pinned-behaviour.md +221 -0
  6. data/exe/badline-ruby +7 -4
  7. data/lib/badline/address_bus/ultimax_pages.rb +38 -0
  8. data/lib/badline/address_bus.rb +19 -29
  9. data/lib/badline/audio/jukebox.rb +6 -4
  10. data/lib/badline/audio/machine_player.rb +10 -6
  11. data/lib/badline/cartridge/kcs_power.rb +3 -3
  12. data/lib/badline/cia.rb +5 -1
  13. data/lib/badline/computer.rb +86 -7
  14. data/lib/badline/cpu.rb +39 -0
  15. data/lib/badline/drive1541/bus.rb +134 -0
  16. data/lib/badline/drive1541/disk.rb +256 -0
  17. data/lib/badline/drive1541/disk_via.rb +33 -0
  18. data/lib/badline/drive1541/gcr.rb +57 -0
  19. data/lib/badline/drive1541/idle.rb +184 -0
  20. data/lib/badline/drive1541/mechanism.rb +277 -0
  21. data/lib/badline/drive1541/rotation.rb +111 -0
  22. data/lib/badline/drive1541/sector_reader.rb +76 -0
  23. data/lib/badline/drive1541/serial_port.rb +67 -0
  24. data/lib/badline/drive1541/track.rb +98 -0
  25. data/lib/badline/drive1541.rb +218 -0
  26. data/lib/badline/gui/application.rb +15 -3
  27. data/lib/badline/gui/drive_led_pane.rb +29 -0
  28. data/lib/badline/gui.rb +1 -0
  29. data/lib/badline/iec_bus.rb +93 -0
  30. data/lib/badline/kernal_trap/channel.rb +9 -1
  31. data/lib/badline/kernal_trap/serial.rb +9 -3
  32. data/lib/badline/media/true_drive.rb +54 -0
  33. data/lib/badline/media.rb +22 -1
  34. data/lib/badline/options.rb +24 -4
  35. data/lib/badline/region.rb +35 -10
  36. data/lib/badline/reu/dma.rb +228 -0
  37. data/lib/badline/reu/ram.rb +65 -0
  38. data/lib/badline/reu/trigger.rb +32 -0
  39. data/lib/badline/reu.rb +276 -0
  40. data/lib/badline/roms/README +10 -0
  41. data/lib/badline/roms/dos1541.rom +0 -0
  42. data/lib/badline/sdl/functions.rb +1 -0
  43. data/lib/badline/storage/d64_image.rb +18 -9
  44. data/lib/badline/storage/d81_image.rb +1 -0
  45. data/lib/badline/storage/disk_image/catalog.rb +52 -0
  46. data/lib/badline/storage/disk_image/writing.rb +16 -0
  47. data/lib/badline/storage/disk_image.rb +2 -0
  48. data/lib/badline/storage/g64_image.rb +222 -0
  49. data/lib/badline/storage/host_directory.rb +21 -1
  50. data/lib/badline/storage/listing.rb +118 -0
  51. data/lib/badline/storage/t64.rb +14 -0
  52. data/lib/badline/storage.rb +2 -0
  53. data/lib/badline/traps.rb +4 -0
  54. data/lib/badline/version.rb +1 -1
  55. data/lib/badline/via/control_lines.rb +3 -0
  56. data/lib/badline/via/fast_forward.rb +38 -0
  57. data/lib/badline/via/shift_register.rb +8 -3
  58. data/lib/badline/via/timer1.rb +30 -0
  59. data/lib/badline/via/timer2.rb +25 -0
  60. data/lib/badline/via.rb +2 -0
  61. data/lib/badline/vic/display_state.rb +7 -7
  62. data/lib/badline/vic/sprite/internal_bus.rb +11 -7
  63. data/lib/badline/vic/sprite/shifter.rb +10 -15
  64. data/lib/badline/vic/sprite/timing.rb +60 -0
  65. data/lib/badline/vic/sprite.rb +14 -12
  66. data/lib/badline/vic/sprites.rb +2 -2
  67. data/lib/badline/vic.rb +86 -39
  68. data/lib/badline.rb +3 -0
  69. metadata +26 -1
@@ -6,8 +6,9 @@ module Badline
6
6
  # The timing and geometry a video standard gives the machine.
7
7
  module Region
8
8
  # The CPU clock, the VIC's raster, where the beam is blanked, the display
9
- # window's edges, the part of the raster a front end shows, and the
10
- # mains frequency that feeds the CIAs' TOD pins.
9
+ # window's edges, the part of the raster a front end shows, the mains
10
+ # frequency that feeds the CIAs' TOD pins, and where the VIC's sprite
11
+ # fetches and X counter sit in the line.
11
12
  #
12
13
  # Columns are the VIC's cycles within a line, counted from 0, and a
13
14
  # column draws 8 pixels, so the raster is `cycles_per_line * 8` pixels
@@ -16,8 +17,15 @@ module Badline
16
17
  # is the larger. `display_x_bounds` holds the display window's
17
18
  # [left, right] pixels per CSEL state, 38 columns then 40, and `crop`
18
19
  # the [left, top, width, height] a front end shows.
20
+ #
21
+ # `sprite_cycle` is Bauer's cycle for sprite 0's p-access, and each
22
+ # sprite after it takes two more. `sprite_display_cycle` is the cycle
23
+ # that turns a sprite's display on or off. `x_hold` is how many pixels
24
+ # the X counter spends past its run, repeating $184-$187, and a raster
25
+ # of more than 512 pixels needs them.
19
26
  Profile = Data.define(:name, :clock_hz, :cycles_per_line, :lines_per_frame, :hblank, :vblank,
20
- :display_x_bounds, :crop, :mains_hz)
27
+ :display_x_bounds, :crop, :mains_hz, :sprite_cycle, :sprite_display_cycle,
28
+ :x_hold)
21
29
 
22
30
  # The 6569 of a PAL C64: 63 cycles by 312 lines at 985,248 Hz, with
23
31
  # 50 Hz mains.
@@ -25,19 +33,36 @@ module Badline
25
33
  name: :pal, clock_hz: 985_248, cycles_per_line: 63, lines_per_frame: 312,
26
34
  hblank: [61, 9].freeze, vblank: [300, 15].freeze,
27
35
  display_x_bounds: [[135, 438].freeze, [128, 447].freeze].freeze,
28
- crop: [96, 20, 384, 272].freeze, mains_hz: 50
36
+ crop: [96, 20, 384, 272].freeze, mains_hz: 50,
37
+ sprite_cycle: 58, sprite_display_cycle: 58, x_hold: 0
29
38
  )
30
39
 
31
40
  # The 6567R8 of an NTSC C64: 65 cycles by 263 lines at 1,022,727 Hz,
32
- # with 60 Hz mains, and lines 13-40 blanked (Bauer). The machine does
33
- # not run it yet: the blanked columns and the display window are PAL's
34
- # stand-ins, and the crop leaves out lines 0-12, which show below the
35
- # last line.
41
+ # with 60 Hz mains. The sprite fetches and the compares ahead of them
42
+ # run a cycle later than on the 6569 (the spritesteal, spritex and
43
+ # phi1timing testprogs), and the X counter pauses near $184, as Bauer's
44
+ # 6567R8 diagram shows, reading $184-$187 for 8 pixels more. The
45
+ # blanked lines are the ones VICE's NTSC view leaves out, 12-27. It shows lines 0-11 below
46
+ # line 262, and the crop leaves those out.
36
47
  NTSC = Profile.new(
37
48
  name: :ntsc, clock_hz: 1_022_727, cycles_per_line: 65, lines_per_frame: 263,
38
- hblank: [61, 9].freeze, vblank: [13, 40].freeze,
49
+ hblank: [63, 9].freeze, vblank: [12, 27].freeze,
50
+ display_x_bounds: [[135, 438].freeze, [128, 447].freeze].freeze,
51
+ crop: [96, 28, 384, 235].freeze, mains_hz: 60,
52
+ sprite_cycle: 59, sprite_display_cycle: 59, x_hold: 8
53
+ )
54
+
55
+ # The 6567R56A of the first NTSC C64s: 64 cycles by 262 lines at
56
+ # 1,022,727 Hz. Its sprite fetches run a cycle later than the 6569's,
57
+ # as on the 6567R8, but it turns the display on in the same cycle as
58
+ # the 6569, and its X counter runs straight through 512 pixels, one
59
+ # coordinate for each.
60
+ NTSC_OLD = Profile.new(
61
+ name: :ntscold, clock_hz: 1_022_727, cycles_per_line: 64, lines_per_frame: 262,
62
+ hblank: [62, 9].freeze, vblank: [13, 27].freeze,
39
63
  display_x_bounds: [[135, 438].freeze, [128, 447].freeze].freeze,
40
- crop: [96, 41, 384, 222].freeze, mains_hz: 60
64
+ crop: [96, 28, 384, 234].freeze, mains_hz: 60,
65
+ sprite_cycle: 59, sprite_display_cycle: 58, x_hold: 0
41
66
  )
42
67
 
43
68
  # Whether a column or line lies in a [first, last] blanking span.
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class REU
5
+ # One transfer between the C64 and the REU's RAM, clocked a cycle at a
6
+ # time while the REC holds the bus. The REC only reads the C64's bus
7
+ # while BA is high, so a read followed by BA low waits for BA to rise.
8
+ # After a write it goes on through the first BA-low cycle and stops at
9
+ # the second. See doc/pinned-behaviour.md, REU DMA.
10
+ class DMA
11
+ # Transfer types, as the command register's bottom two bits give
12
+ # them.
13
+ STASH = 0
14
+ FETCH = 1
15
+ SWAP = 2
16
+ VERIFY = 3
17
+
18
+ # What the REC did on the cycle before.
19
+ IDLE = 0
20
+ READ = 1
21
+ WRITE = 2
22
+
23
+ # Where the transfer stopped, what it left for the length register,
24
+ # and the status bits it ended with.
25
+ attr_reader :c64, :expansion, :length, :events
26
+
27
+ def initialize(ram, span, bus)
28
+ @ram = ram
29
+ @span = span
30
+ @bus = bus
31
+ @c64 = @expansion = @length = @events = 0
32
+ @holding = false
33
+ end
34
+
35
+ # Whether the REC held the bus on the last cycle clocked, which it
36
+ # does while it waits for BA too, since the CPU stays halted.
37
+ def holds_bus? = @holding
38
+
39
+ # A length of 0 moves 64K. control is the address control register,
40
+ # which can hold either address where it is.
41
+ def start(type, c64, expansion, length, control)
42
+ @type = type
43
+ @c64 = c64
44
+ @expansion = expansion
45
+ @length = length.zero? ? 0x10000 : length
46
+ @c64_step = control.anybits?(FIX_C64) ? 0 : 1
47
+ @expansion_step = control.anybits?(FIX_EXPANSION) ? 0 : 1
48
+ @events = 0
49
+ @holding = true
50
+ @last = IDLE
51
+ @waiting = false
52
+ @ba_low_after_writes = 0
53
+ @stopped_after_write = false
54
+ @swap_write_due = false
55
+ @swap_byte = 0
56
+ @held_read = 0
57
+ @moved_all = false
58
+ @extra_cycle = false
59
+ end
60
+
61
+ # One cycle of the transfer. The cycle the REC lets go of the bus on
62
+ # is the CPU's.
63
+ def cycle!(ba_low)
64
+ note_ba(ba_low)
65
+ settle_held_read(ba_low) if @held_read.positive?
66
+ return if @waiting && ba_low
67
+
68
+ @waiting = false
69
+ @moved_all ? wind_down : move(ba_low)
70
+ end
71
+
72
+ private
73
+
74
+ # The second BA-low cycle in a row after writes stops the REC, and so
75
+ # does any BA-low cycle after a read. It then waits for BA to rise.
76
+ def note_ba(ba_low)
77
+ if @last == READ
78
+ @waiting = ba_low
79
+ elsif @last == WRITE
80
+ @ba_low_after_writes = ba_low ? @ba_low_after_writes + 1 : 0
81
+ @stopped_after_write = @ba_low_after_writes == 2
82
+ if @stopped_after_write
83
+ @waiting = true
84
+ @ba_low_after_writes = 0
85
+ end
86
+ end
87
+ @last = IDLE
88
+ end
89
+
90
+ # A fetch or a swap whose last write ran into BA, or a verify that
91
+ # found a difference before its last byte, takes one more cycle before
92
+ # the REC lets go of the bus.
93
+ def wind_down
94
+ if @extra_cycle || (@stopped_after_write && (@type == FETCH || @type == SWAP))
95
+ @extra_cycle = @stopped_after_write = false
96
+ @last = READ
97
+ return
98
+ end
99
+
100
+ @holding = false
101
+ settle
102
+ end
103
+
104
+ def move(ba_low)
105
+ case @type
106
+ when STASH then stash
107
+ when FETCH then fetch
108
+ when SWAP then swap(ba_low)
109
+ else verify
110
+ end
111
+ end
112
+
113
+ # The REU's data bus latch keeps the last byte the REC stored.
114
+ def stash
115
+ byte = read_c64
116
+ @ram.poke(@expansion, byte)
117
+ @ram.latch = byte
118
+ step
119
+ @last = READ
120
+ end
121
+
122
+ def fetch
123
+ byte = @ram.peek(@expansion)
124
+ @ram.latch = byte
125
+ write_c64(byte)
126
+ step
127
+ @last = WRITE
128
+ end
129
+
130
+ # Two cycles a byte: the C64's byte is read and stored while the
131
+ # REU's is fetched, and the REU's is written to the C64 on the next.
132
+ def swap(ba_low)
133
+ if @swap_write_due
134
+ @swap_write_due = false
135
+ write_c64(@swap_byte)
136
+ step
137
+ @last = WRITE
138
+ return
139
+ end
140
+
141
+ @swap_byte = @ram.peek(@expansion)
142
+ @swap_write_due = true
143
+ @last = READ
144
+ return swap_read_on_ba if ba_low
145
+
146
+ @ram.poke(@expansion, read_c64)
147
+ end
148
+
149
+ # A swap's read on the first BA-low cycle after a write, while AEC is
150
+ # still high. On the last byte the read is made there, the write
151
+ # follows on the next cycle whatever BA does, and the REC lets go.
152
+ # Otherwise the read stays open and takes what is on the bus two
153
+ # cycles later, or on the cycle BA rises if that comes first. Pinned
154
+ # by REU/reutiming2/e5-m2, f3-m2 and f4-m2.
155
+ def swap_read_on_ba
156
+ return @held_read = 2 unless @length == 1
157
+
158
+ @ram.poke(@expansion, read_c64)
159
+ @last = IDLE
160
+ @ba_low_after_writes = 0
161
+ end
162
+
163
+ def settle_held_read(ba_low)
164
+ @held_read -= 1
165
+ return if @held_read.positive? && ba_low
166
+
167
+ @held_read = 0
168
+ @ram.poke(@expansion, read_c64)
169
+ end
170
+
171
+ # A difference ends the transfer, with one cycle more if bytes were
172
+ # left to compare.
173
+ def verify
174
+ same = @ram.peek(@expansion) == read_c64
175
+ step
176
+ @last = READ
177
+ return if same
178
+
179
+ @events |= FAULT
180
+ @extra_cycle = @length.positive?
181
+ @moved_all = true
182
+ end
183
+
184
+ # The REC reaches C64 memory as the CPU sees it, but at $00 and $01
185
+ # it reaches the RAM under the CPU's port.
186
+ def read_c64
187
+ @c64 < 0x02 ? @bus.ram.peek(@c64) : @bus.peek(@c64)
188
+ end
189
+
190
+ def write_c64(byte)
191
+ @c64 < 0x02 ? @bus.ram.poke(@c64, byte) : @bus.poke(@c64, byte)
192
+ end
193
+
194
+ # The REC counts the expansion address in its 19 low bits, from the
195
+ # top of its span back to 0, and leaves the bank bits above them as
196
+ # they were.
197
+ def step
198
+ @c64 = (@c64 + @c64_step) & 0xffff
199
+ low = (@expansion & 0x7ffff) + @expansion_step
200
+ @expansion = (@expansion & 0xf80000) | (low == @span ? 0 : low)
201
+ @length -= 1
202
+ @moved_all = true if @length.zero?
203
+ end
204
+
205
+ # The length register is left at 1 after a whole block. A fetch has
206
+ # already fetched the byte after its last into the latch.
207
+ def settle
208
+ return settle_verify if @type == VERIFY
209
+
210
+ @length += 1
211
+ @events |= END_OF_BLOCK
212
+ @ram.latch = @ram.peek(@expansion) if @type == FETCH
213
+ end
214
+
215
+ # A verify that compared every byte ends the block. One that found a
216
+ # difference on the byte before the last compares the last too, and
217
+ # ends the block if that one is the same.
218
+ def settle_verify
219
+ if @length.zero?
220
+ @length = 1
221
+ @events |= END_OF_BLOCK
222
+ elsif @length == 1 && @ram.peek(@expansion) == read_c64
223
+ @events |= END_OF_BLOCK
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class REU
5
+ # The REU's DRAM, as the REC addresses it. The REC counts 19 address
6
+ # bits, 17 on the 1700, and the 1764 fills only the first 256K of
7
+ # them: past that a read finds the latch on the REU's data bus, which
8
+ # holds the last byte the REC moved. A unit past 512K takes its upper
9
+ # address lines from the bank register.
10
+ class RAM
11
+ BANK_SIZE = 0x10000
12
+
13
+ # Where a bank of a 1764 that has just been switched on reads
14
+ # inverted, over the first 128K of each 256K. Over the second 128K
15
+ # it is the rest of the bank that reads inverted. Measured from
16
+ # REU/raminitpattern/dumpfile-256k-x1541.bin, a real 1764's dump.
17
+ INVERTED = [0x2a00...0x5400, 0x8000...0xac00, 0xd600...0x10000].freeze
18
+
19
+ attr_reader :size
20
+ attr_accessor :latch
21
+
22
+ def initialize(size, span)
23
+ @size = size
24
+ @mask = (size > 0x80000 ? size : span) - 1
25
+ @banks = Array.new(size / BANK_SIZE) { [] }
26
+ @power_on = [[], []]
27
+ @latch = 0xff
28
+ end
29
+
30
+ def peek(addr)
31
+ addr &= @mask
32
+ addr < @size ? bank(addr >> 16)[addr & 0xffff] : @latch
33
+ end
34
+
35
+ def poke(addr, value)
36
+ addr &= @mask
37
+ bank(addr >> 16)[addr & 0xffff] = value if addr < @size
38
+ end
39
+
40
+ private
41
+
42
+ # A bank takes its power-on contents the first time it is touched,
43
+ # so an REU costs only the memory a program uses.
44
+ def bank(number)
45
+ bank = @banks[number]
46
+ return bank unless bank.empty?
47
+
48
+ half = (number >> 1) & 1
49
+ @power_on[half] = power_on_bank(half == 1) if @power_on[half].empty?
50
+ @banks[number] = @power_on[half].dup
51
+ end
52
+
53
+ # Pairs of $FF and $00 bytes, starting with a single $FF, and each
54
+ # odd page the inverse of the even one before it.
55
+ def power_on_bank(upper)
56
+ Array.new(BANK_SIZE) do |offset|
57
+ byte = (offset + 1).anybits?(2) ? 0x00 : 0xff
58
+ byte ^= 0xff if offset.anybits?(0x100)
59
+ inverted = INVERTED.any? { |range| range.cover?(offset) }
60
+ inverted == upper ? byte : byte ^ 0xff
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class REU
5
+ # Stands in for the page at $FF00 in the write table, passing each
6
+ # write on to what is mapped there and telling the REC when $FF00 is
7
+ # written, which starts an armed transfer.
8
+ class Trigger
9
+ ADDRESS = 0xff00
10
+
11
+ def initialize(reu)
12
+ @reu = reu
13
+ @target = nil
14
+ end
15
+
16
+ # Puts the trigger in front of target and returns it.
17
+ def wrap(target)
18
+ @target = target
19
+ self
20
+ end
21
+
22
+ def peek(addr)
23
+ @target.peek(addr)
24
+ end
25
+
26
+ def poke(addr, value)
27
+ @target.poke(addr, value)
28
+ @reu.ff00_written if addr == ADDRESS
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/reu/ram"
4
+ require "badline/reu/dma"
5
+ require "badline/reu/trigger"
6
+
7
+ module Badline
8
+ # A RAM Expansion Unit: the Commodore 1700 (128K), 1764 (256K) and 1750
9
+ # (512K), and the bigger units built around the same 8726 RAM Expansion
10
+ # Controller (REC), such as the CMD 1750XL, up to 16M.
11
+ #
12
+ # The REC's eleven registers sit in I/O 2 from $DF00, repeated every 32
13
+ # bytes up to $DFFF, as the 1764 and 1750 manuals map them. It moves
14
+ # bytes between the C64 and its RAM by DMA, one bus cycle each, with the
15
+ # CPU held off the bus. A command with the execute bit set starts a
16
+ # transfer, or with bit 4 clear arms the REC to start one on the next
17
+ # write to $FF00, so a program can bank the ROMs out first. A transfer
18
+ # waits for the CPU's next read cycle with BA high, and Computer then
19
+ # clocks it through #dma_cycle! until it hands the bus back.
20
+ class REU
21
+ SIZES_KB = [128, 256, 512, 1024, 2048, 4096, 8192, 16_384].freeze
22
+
23
+ # The registers, by offset.
24
+ STATUS = 0x00
25
+ COMMAND = 0x01
26
+ C64_LOW = 0x02
27
+ C64_HIGH = 0x03
28
+ EXPANSION_LOW = 0x04
29
+ EXPANSION_HIGH = 0x05
30
+ EXPANSION_BANK = 0x06
31
+ LENGTH_LOW = 0x07
32
+ LENGTH_HIGH = 0x08
33
+ INTERRUPT_MASK = 0x09
34
+ ADDRESS_CONTROL = 0x0a
35
+
36
+ # Status: an interrupt is pending, the block ended, a verify found a
37
+ # difference (the manual's fault bit), and the unit is built from 256K
38
+ # chips, which only the 1700 isn't. Reading the status clears the top
39
+ # three.
40
+ INTERRUPT_PENDING = 0x80
41
+ END_OF_BLOCK = 0x40
42
+ FAULT = 0x20
43
+ CHIPS_256K = 0x10
44
+
45
+ # Command: execute, reload the registers after the transfer, don't wait
46
+ # for $FF00, and the transfer type in the bottom two bits.
47
+ EXECUTE = 0x80
48
+ AUTOLOAD = 0x20
49
+ NO_FF00_TRIGGER = 0x10
50
+ TRANSFER_TYPE = 0x03
51
+
52
+ # Interrupt mask: interrupts on at all, then on the end of a block and
53
+ # on a fault. The low five bits aren't there and read as 1.
54
+ INTERRUPTS_ON = 0x80
55
+ INTERRUPT_ON_END = 0x40
56
+ INTERRUPT_ON_FAULT = 0x20
57
+
58
+ # Address control: hold the C64 address or the expansion address where
59
+ # it is. The low six bits aren't there and read as 1.
60
+ FIX_C64 = 0x80
61
+ FIX_EXPANSION = 0x40
62
+
63
+ # The REC's bank register has three bits, and the other five read as
64
+ # 1. A unit past 512K latches all eight onto its own address lines.
65
+ BANK_STUCK = 0xf8
66
+
67
+ attr_reader :trigger
68
+
69
+ # bus is the bus the REC reaches C64 memory through, and vic the VIC,
70
+ # whose phi1 fetch is what a register read finds on the bus during a
71
+ # transfer.
72
+ def initialize(size_kb, bus:, vic:)
73
+ raise ArgumentError, "No #{size_kb}K REU. Pick one of #{SIZES_KB.join(', ')}." unless SIZES_KB.include?(size_kb)
74
+
75
+ # The 1700's REC counts 17 address bits, the others' 19.
76
+ span = size_kb == 128 ? 0x20000 : 0x80000
77
+ size = size_kb * 1024
78
+ @address_mask = [size, span].max - 1
79
+ @bank_bits = size > span ? 0xff : 0x07
80
+ @chips = size_kb == 128 ? 0 : CHIPS_256K
81
+ @ram = RAM.new(size, span)
82
+ @dma = DMA.new(@ram, span, bus)
83
+ @trigger = Trigger.new(self)
84
+ @vic = vic
85
+ @on_irq_change = nil
86
+ @on_dma = nil
87
+ @irq = false
88
+ reset!
89
+ end
90
+
91
+ def on_irq_change(&block)
92
+ @on_irq_change = block
93
+ end
94
+
95
+ # Called when the REC asks for the bus.
96
+ def on_dma(&block)
97
+ @on_dma = block
98
+ end
99
+
100
+ def irq? = @irq
101
+
102
+ # From the moment the REC asks for the bus until it hands it back.
103
+ def dma? = @requested || @running
104
+
105
+ # Whether the REC held the bus on the last cycle clocked. The CPU keeps
106
+ # it while the REC waits to start, and has it back from the cycle the
107
+ # REC lets go.
108
+ def holds_bus? = @running
109
+
110
+ def reset!
111
+ @status = @chips
112
+ @command = NO_FF00_TRIGGER
113
+ @c64 = @c64_start = 0
114
+ @expansion = @expansion_start = 0
115
+ @bank = @bank_start = 0
116
+ @length = @length_start = 0xffff
117
+ @interrupt_mask = 0x1f
118
+ @address_control = 0x3f
119
+ @armed = @requested = @running = false
120
+ interrupt(false)
121
+ end
122
+
123
+ def peek(addr)
124
+ return @vic.phi1_data if @running
125
+
126
+ register = addr & 0x1f
127
+ value = register_value(register)
128
+ read_status if register == STATUS
129
+ value
130
+ end
131
+
132
+ def poke(addr, value)
133
+ return if @running
134
+
135
+ register = addr & 0x1f
136
+ case register
137
+ when COMMAND then write_command(value)
138
+ when C64_LOW, C64_HIGH then @c64 = @c64_start = with_byte(@c64_start, value, register == C64_HIGH)
139
+ when EXPANSION_LOW, EXPANSION_HIGH
140
+ @expansion = @expansion_start = with_byte(@expansion_start, value, register == EXPANSION_HIGH)
141
+ when EXPANSION_BANK then @bank = @bank_start = value & @bank_bits
142
+ when LENGTH_LOW, LENGTH_HIGH
143
+ @length = @length_start = with_byte(@length_start, value, register == LENGTH_HIGH)
144
+ when INTERRUPT_MASK then write_interrupt_mask(value)
145
+ when ADDRESS_CONTROL then @address_control = value | 0x3f
146
+ end
147
+ end
148
+
149
+ # A write to $FF00 starts the transfer the command register armed.
150
+ def ff00_written
151
+ return unless @armed
152
+
153
+ @armed = false
154
+ request_bus
155
+ end
156
+
157
+ # Clocks the REC for a cycle, given the VIC's BA line and whether the
158
+ # CPU writes on it. A requested transfer starts on the first cycle the
159
+ # CPU reads with BA high.
160
+ def dma_cycle!(ba_low, cpu_writing)
161
+ if @requested
162
+ return if cpu_writing || ba_low
163
+
164
+ start_transfer
165
+ end
166
+ @dma.cycle!(ba_low)
167
+ end_transfer unless @dma.holds_bus?
168
+ end
169
+
170
+ # The REU's RAM, at an address as the REC's registers give it.
171
+ def ram_peek(addr) = @ram.peek(addr)
172
+
173
+ def ram_poke(addr, value) = @ram.poke(addr, value)
174
+
175
+ private
176
+
177
+ def register_value(register)
178
+ case register
179
+ when STATUS then @status
180
+ when COMMAND then @command
181
+ when C64_LOW then @c64 & 0xff
182
+ when C64_HIGH then @c64 >> 8
183
+ when EXPANSION_LOW then @expansion & 0xff
184
+ when EXPANSION_HIGH then @expansion >> 8
185
+ when EXPANSION_BANK then @bank | BANK_STUCK
186
+ when LENGTH_LOW then @length & 0xff
187
+ when LENGTH_HIGH then @length >> 8
188
+ when INTERRUPT_MASK then @interrupt_mask
189
+ when ADDRESS_CONTROL then @address_control
190
+ else 0xff
191
+ end
192
+ end
193
+
194
+ def read_status
195
+ @status &= CHIPS_256K
196
+ interrupt(false)
197
+ end
198
+
199
+ # Writing either byte of an address or the length sets the value the
200
+ # transfer starts from, and puts the counter back to it.
201
+ def with_byte(word, value, high)
202
+ high ? (word & 0x00ff) | (value << 8) : (word & 0xff00) | value
203
+ end
204
+
205
+ def write_command(value)
206
+ @command = value
207
+ return if value.nobits?(EXECUTE)
208
+
209
+ @armed = value.nobits?(NO_FF00_TRIGGER)
210
+ request_bus unless @armed
211
+ end
212
+
213
+ # An interrupt enabled over a block that already ended, or a fault
214
+ # already found, fires at once.
215
+ def write_interrupt_mask(value)
216
+ @interrupt_mask = value | 0x1f
217
+ interrupt_on(@status)
218
+ end
219
+
220
+ def interrupt_on(events)
221
+ return unless @interrupt_mask.anybits?(INTERRUPTS_ON)
222
+ return unless (events.anybits?(END_OF_BLOCK) && @interrupt_mask.anybits?(INTERRUPT_ON_END)) ||
223
+ (events.anybits?(FAULT) && @interrupt_mask.anybits?(INTERRUPT_ON_FAULT))
224
+
225
+ @status |= INTERRUPT_PENDING
226
+ interrupt(true)
227
+ end
228
+
229
+ def interrupt(level)
230
+ return if @irq == level
231
+
232
+ @irq = level
233
+ @on_irq_change&.call(level)
234
+ end
235
+
236
+ def request_bus
237
+ @requested = true
238
+ @on_dma&.call
239
+ end
240
+
241
+ def start_transfer
242
+ @requested = false
243
+ @running = true
244
+ @dma.start(@command & TRANSFER_TYPE, @c64, @expansion | (@bank << 16), @length, @address_control)
245
+ end
246
+
247
+ # The execute bit drops and the $FF00 trigger turns off. With autoload
248
+ # the addresses and the length go back to where the transfer started,
249
+ # and otherwise they stay where it stopped.
250
+ def end_transfer
251
+ @running = false
252
+ @command = (@command & ~EXECUTE & 0xff) | NO_FF00_TRIGGER
253
+ events = @dma.events
254
+ @status |= events
255
+ @command.anybits?(AUTOLOAD) ? reload : keep_counters
256
+ interrupt_on(events)
257
+ end
258
+
259
+ def reload
260
+ @c64 = @c64_start
261
+ @expansion = @expansion_start
262
+ @bank = @bank_start
263
+ @length = @length_start
264
+ end
265
+
266
+ def keep_counters
267
+ @c64 = @dma.c64 if @address_control.nobits?(FIX_C64)
268
+ @length = @dma.length & 0xffff
269
+ return if @address_control.anybits?(FIX_EXPANSION)
270
+
271
+ address = @dma.expansion & @address_mask
272
+ @expansion = address & 0xffff
273
+ @bank = (address >> 16) & 0xff
274
+ end
275
+ end
276
+ end
@@ -16,3 +16,13 @@ the C128's C64 mode.
16
16
  character.rom
17
17
  -------------
18
18
  The character generator ROM.
19
+
20
+ dos1541.rom
21
+ -----------
22
+ The 1541 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 325302-01
23
+ ($C000-$DFFF) followed by 901229-05 ($E000-$FFFF). Drive1541 loads it.
24
+
25
+ Both halves downloaded from
26
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
27
+ (1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
28
+ SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
Binary file
@@ -42,6 +42,7 @@ module Badline
42
42
  RenderSetLogicalSize = function("SDL_RenderSetLogicalSize", [POINTER, INT, INT], INT)
43
43
  SetRenderDrawColor = function("SDL_SetRenderDrawColor", [POINTER, UINT8, UINT8, UINT8, UINT8], INT)
44
44
  RenderClear = function("SDL_RenderClear", [POINTER], INT)
45
+ RenderFillRect = function("SDL_RenderFillRect", [POINTER, POINTER], INT)
45
46
  RenderCopy = function("SDL_RenderCopy", [POINTER, POINTER, POINTER, POINTER], INT)
46
47
  RenderPresent = function("SDL_RenderPresent", [POINTER], VOID)
47
48
  CreateRGBSurfaceFrom = function("SDL_CreateRGBSurfaceFrom",