badline 0.9.0 → 0.10.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/CLAUDE.md +6 -4
  4. data/README.md +39 -4
  5. data/doc/pinned-behaviour.md +146 -0
  6. data/doc/snapshots.md +13 -0
  7. data/lib/badline/address_bus/pla.rb +130 -0
  8. data/lib/badline/address_bus.rb +9 -88
  9. data/lib/badline/chrout_trap.rb +4 -3
  10. data/lib/badline/computer/attachments.rb +47 -7
  11. data/lib/badline/computer/saved_state.rb +1 -0
  12. data/lib/badline/computer.rb +44 -29
  13. data/lib/badline/control_ports.rb +10 -3
  14. data/lib/badline/core.rb +3 -0
  15. data/lib/badline/datasette.rb +39 -1
  16. data/lib/badline/drive1541/serial_port.rb +5 -8
  17. data/lib/badline/drive1541/sleep.rb +5 -6
  18. data/lib/badline/drive1541.rb +3 -3
  19. data/lib/badline/frontend/app.rb +62 -64
  20. data/lib/badline/frontend/boot.rb +33 -8
  21. data/lib/badline/frontend/controls.rb +12 -6
  22. data/lib/badline/frontend/drive_led.rb +9 -10
  23. data/lib/badline/frontend/frame_rate.rb +4 -4
  24. data/lib/badline/frontend/machine_window.rb +87 -0
  25. data/lib/badline/frontend/menu_media.rb +49 -24
  26. data/lib/badline/frontend/menu_pages.rb +23 -40
  27. data/lib/badline/frontend/menu_ports.rb +69 -0
  28. data/lib/badline/frontend/pacer.rb +6 -5
  29. data/lib/badline/frontend/pause_menu.rb +30 -33
  30. data/lib/badline/frontend/player_screen.rb +1 -1
  31. data/lib/badline/frontend/player_window.rb +1 -1
  32. data/lib/badline/frontend/screen.rb +39 -37
  33. data/lib/badline/frontend/sid_view.rb +1 -1
  34. data/lib/badline/frontend/sound.rb +11 -9
  35. data/lib/badline/frontend/timeline.rb +36 -5
  36. data/lib/badline/frontend.rb +2 -0
  37. data/lib/badline/iec_bus.rb +16 -28
  38. data/lib/badline/kernal_trap/drive.rb +3 -0
  39. data/lib/badline/kernal_trap/file.rb +1 -1
  40. data/lib/badline/kernal_trap/layout.rb +112 -0
  41. data/lib/badline/kernal_trap/load.rb +16 -27
  42. data/lib/badline/kernal_trap/routine.rb +11 -27
  43. data/lib/badline/kernal_trap/save.rb +13 -23
  44. data/lib/badline/kernal_trap/serial.rb +21 -19
  45. data/lib/badline/kernal_trap.rb +1 -0
  46. data/lib/badline/keyboard.rb +17 -14
  47. data/lib/badline/machine.rb +60 -0
  48. data/lib/badline/media/vic20_basic.rb +54 -0
  49. data/lib/badline/media/vic20_cartridge.rb +74 -0
  50. data/lib/badline/media/vic20_media.rb +132 -0
  51. data/lib/badline/media.rb +32 -18
  52. data/lib/badline/options/event.rb +5 -3
  53. data/lib/badline/options/help.rb +11 -3
  54. data/lib/badline/options/table.rb +11 -1
  55. data/lib/badline/options/validation.rb +55 -4
  56. data/lib/badline/options.rb +33 -33
  57. data/lib/badline/roms/README +27 -0
  58. data/lib/badline/roms/vic20/basic.rom +0 -0
  59. data/lib/badline/roms/vic20/character.rom +0 -0
  60. data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
  61. data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
  62. data/lib/badline/snapshot/container.rb +2 -0
  63. data/lib/badline/snapshot/image.rb +15 -0
  64. data/lib/badline/snapshot/state.rb +4 -3
  65. data/lib/badline/snapshot.rb +10 -0
  66. data/lib/badline/storage/crt_file.rb +16 -5
  67. data/lib/badline/storage/disk_image.rb +3 -0
  68. data/lib/badline/storage/host_directory.rb +3 -0
  69. data/lib/badline/storage/t64.rb +3 -0
  70. data/lib/badline/storage/tap.rb +38 -4
  71. data/lib/badline/storage/unavailable.rb +2 -0
  72. data/lib/badline/timing.rb +12 -0
  73. data/lib/badline/version.rb +1 -1
  74. data/lib/badline/via/shift_register.rb +43 -16
  75. data/lib/badline/vic/bank.rb +53 -16
  76. data/lib/badline/vic/registers.rb +20 -4
  77. data/lib/badline/vic.rb +34 -15
  78. data/lib/badline/vic20/attachments.rb +103 -0
  79. data/lib/badline/vic20/bus.rb +308 -0
  80. data/lib/badline/vic20/cpu.rb +10 -0
  81. data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
  82. data/lib/badline/vic20/port_wiring.rb +50 -0
  83. data/lib/badline/vic20/saved_state.rb +152 -0
  84. data/lib/badline/vic20/sound/output.rb +83 -0
  85. data/lib/badline/vic20/sound.rb +233 -0
  86. data/lib/badline/vic20/user_via_ports.rb +47 -0
  87. data/lib/badline/vic20/vic/painter.rb +239 -0
  88. data/lib/badline/vic20/vic/saved_state.rb +55 -0
  89. data/lib/badline/vic20/vic/text_window.rb +71 -0
  90. data/lib/badline/vic20/vic.rb +267 -0
  91. data/lib/badline/vic20.rb +220 -0
  92. metadata +28 -2
  93. data/lib/badline/address_bus/ultimax_pages.rb +0 -38
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Vic20
5
+ class VIC
6
+ # The text window's rows: where it opens and closes, the video matrix
7
+ # counter and the character line, and the registers that place the
8
+ # window and its memory.
9
+ module TextWindow
10
+ private
11
+
12
+ def new_frame
13
+ @window_open = @window_done = false
14
+ @row_start = @matrix = @char_line = @text_rows = 0
15
+ end
16
+
17
+ def open_window
18
+ @window_open = true
19
+ @char_line = 0
20
+ @matrix = @row_start
21
+ end
22
+
23
+ # Steps to the next line of the text row, or past the row's last line
24
+ # to the next row, closing the window after the last.
25
+ def next_text_line
26
+ @char_line += 1
27
+ if @char_line < (1 << @char_shift)
28
+ @matrix = @row_start
29
+ return
30
+ end
31
+
32
+ @char_line = 0
33
+ @row_start = @matrix
34
+ @text_rows += 1
35
+ return if @text_rows < @rows
36
+
37
+ @window_open = false
38
+ @window_done = true
39
+ end
40
+
41
+ # $9000 and $9002 move the window's fetches on the line, or only where
42
+ # they end once they've started.
43
+ def window_written
44
+ @origin_x = @registers[0] & 0x7f
45
+ @columns = @registers[2] & 0x7f
46
+ bases_written
47
+ return unless @window_open
48
+
49
+ if @column < @fetch_from
50
+ place_fetches
51
+ else
52
+ fetches_end
53
+ end
54
+ schedule
55
+ end
56
+
57
+ def rows_written(value)
58
+ @rows = (value >> 1) & 0x3f
59
+ @char_shift = value.anybits?(0x01) ? 4 : 3
60
+ end
61
+
62
+ # The video matrix at $9005 bits 7-4 and $9002 bit 7, address bits
63
+ # 13-9, and the characters at $9005 bits 3-0, bits 13-10.
64
+ def bases_written
65
+ @screen_base = ((@registers[5] & 0xf0) << 6) | ((@registers[2] & 0x80) << 2)
66
+ @char_base = (@registers[5] & 0x0f) << 10
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/vic20/vic/painter"
4
+ require "badline/vic20/vic/text_window"
5
+ require "badline/vic20/vic/saved_state"
6
+
7
+ module Badline
8
+ class Vic20
9
+ # The VIC-I, the 6561 on PAL machines: its sixteen registers, the
10
+ # raster, the text window's fetches over the V-bus and the picture. The
11
+ # writes to the sound's registers, $900A-$900E, go on to the Sound
12
+ # attached as #sound.
13
+ #
14
+ # The registers repeat through $9000-$90FF. $9003 bit 7 and $9004 read
15
+ # the raster line, its bit 0 and bits 8-1. $9006 and $9007 read the
16
+ # light pen, which nothing triggers yet, and $9008 and $9009 the two
17
+ # pots, which read $FF with nothing plugged in. The other registers
18
+ # read back what was written to them.
19
+ #
20
+ # The text window opens on the line where the raster's bits 8-1 match
21
+ # $9001, and closes after the number of text rows in $9003, each 8 lines
22
+ # of a character, or 16 with $9003 bit 0 set. On each of its lines the
23
+ # VIC fetches two bytes a character, $9002's count of them: the
24
+ # character code from the video matrix, with the colour nibble beside it
25
+ # on the colour bus, then the character's pattern byte for the line.
26
+ # The first code is fetched four cycles after the column in $9000, so
27
+ # the window starts 16 cycles into the line with the KERNAL's origin of
28
+ # 12, and none is fetched past the end of the line. The fetches go to a
29
+ # 14-bit address, the VIC's own (see Bus#video_fetch), and the byte each
30
+ # one reads is the V-bus's last byte. Outside the window the 6561E
31
+ # fetches nothing and the V-bus keeps the CPU's last byte there.
32
+ #
33
+ # The video matrix counter steps once a character. It starts each line
34
+ # of a text row where the row started, and the next row starts where
35
+ # the last line of the row left it. It starts the frame at zero.
36
+ #
37
+ # The picture is 4 pixels a cycle, so 284 a line. A character's 8
38
+ # pixels come out in the two cycles after its pattern fetch. The
39
+ # display's lines start 6 cycles after the raster count moves, as
40
+ # xvic's view of the frame does, so the character fetched 16 cycles
41
+ # into the line lands 48 pixels in. Lines 0-27 are blanked.
42
+ class VIC
43
+ include TextWindow
44
+ include SavedState
45
+
46
+ # What a VIC-I sets for the machine: the clock, the raster's cycles
47
+ # per line and lines per frame, and how many lines at the top of the
48
+ # frame it blanks. The 6560 of NTSC machines, with its half lines and
49
+ # the interlace of $9000 bit 7, would be another.
50
+ Profile = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :blanked_lines)
51
+
52
+ # The 6561: 71 cycles by 312 lines, lines 0-27 blanked (Marko
53
+ # Mäkelä's VIC-I.txt).
54
+ PAL = Profile.new(clock_hz: 1_108_405, cycles_per_line: 71, lines_per_frame: 312, blanked_lines: 28)
55
+
56
+ PIXELS_PER_CYCLE = 4
57
+
58
+ # The column whose pixels start a line of the display.
59
+ FIRST_PIXEL_COLUMN = 6
60
+
61
+ # How many cycles after the column in $9000 the first code is fetched.
62
+ FETCH_DELAY = 4
63
+
64
+ # The colours, as RGB, each from its luma and chroma: luma in five
65
+ # levels from black to white, and chroma at one strength in a phase
66
+ # that steps by 22.5 degrees from blue's. The levels and phases are
67
+ # the VIC-I's table on Wikipedia's "MOS Technology VIC" (Y, Pb, Pr),
68
+ # turned into RGB with the BT.601 matrix and the chroma scaled by 0.2.
69
+ PALETTE = [
70
+ 0x000000, 0xffffff, 0x82251d, 0x7ddae2, 0xb259bf, 0x4da640, 0x402e9a, 0xbfd165,
71
+ 0xb27240, 0xf2b27f, 0xc2655d, 0xbdffff, 0xf299ff, 0x8de57f, 0x806eda, 0xffffa5
72
+ ].freeze
73
+
74
+ attr_reader :rasterline, :column, :profile
75
+ attr_accessor :sound
76
+
77
+ def initialize(profile = PAL)
78
+ @profile = profile
79
+ @cycles_per_line = profile.cycles_per_line
80
+ @lines_per_frame = profile.lines_per_frame
81
+ @painter = Painter.new(@lines_per_frame, @cycles_per_line * PIXELS_PER_CYCLE, profile.blanked_lines)
82
+ @patterns = @painter.patterns
83
+ @colors = @painter.colors
84
+ @counts = @painter.counts
85
+ @starts = @painter.starts
86
+ @stale = @painter.stale
87
+ @bus = nil
88
+ @sound = nil
89
+ @color_cells = Array.new(0x400, 0)
90
+ power_on!
91
+ end
92
+
93
+ # The bus the VIC fetches from, the machine's, and the colour RAM
94
+ # whose cells come in on the colour bus beside each code.
95
+ def connect(bus)
96
+ @bus = bus
97
+ @color_cells = bus.color_ram.cells
98
+ end
99
+
100
+ # The power-on state: the registers cleared and the raster at the top
101
+ # of the frame. The VIC has no RES pin, so only power reaches it.
102
+ def power_on!
103
+ @registers = Array.new(16, 0)
104
+ @rasterline = 0
105
+ @column = 0
106
+ @origin_x = @origin_y = @columns = @rows = 0
107
+ @char_shift = 3
108
+ @screen_base = @char_base = 0
109
+ new_frame
110
+ @fetch_from = @fetch_to = 0
111
+ @fetched = @slot = 0
112
+ @code = 0
113
+ @painter.power_on!(@lines_per_frame - 1)
114
+ schedule
115
+ end
116
+
117
+ def cycle!
118
+ column = @column + 1
119
+ @column = column
120
+ return unless column == @next_event
121
+
122
+ if column > FIRST_PIXEL_COLUMN && column < @fetch_to && column >= @fetch_from
123
+ access(column)
124
+ column += 1
125
+ @next_event = column < @fetch_to ? column : @cycles_per_line
126
+ else
127
+ event(column)
128
+ end
129
+ end
130
+
131
+ # The sixteen registers as written.
132
+ def register_file = @registers.dup
133
+
134
+ # The display, a palette index a pixel, 284 to a line and every line
135
+ # of the frame.
136
+ def display = @painter.display
137
+
138
+ def width = @painter.width
139
+
140
+ def height = @lines_per_frame
141
+
142
+ # Whether each line of the display has changed since the front end
143
+ # last cleared them.
144
+ def dirty_lines = @painter.dirty_lines
145
+
146
+ def clear_dirty_lines! = @painter.clear_dirty_lines!
147
+
148
+ def palette = PALETTE
149
+
150
+ # Whether the VIC paints its display. It fetches either way.
151
+ def render = @painter.render
152
+
153
+ def render=(value)
154
+ @painter.render = value
155
+ end
156
+
157
+ def peek(addr)
158
+ register = addr & 0x0f
159
+ case register
160
+ when 0x03 then ((@rasterline & 0x01) << 7) | (@registers[3] & 0x7f)
161
+ when 0x04 then @rasterline >> 1
162
+ when 0x06, 0x07 then 0
163
+ when 0x08, 0x09 then 0xff
164
+ else @registers[register]
165
+ end
166
+ end
167
+
168
+ def poke(addr, value)
169
+ register = addr & 0x0f
170
+ @registers[register] = value
171
+ case register
172
+ when 0x00, 0x02 then window_written
173
+ when 0x01 then @origin_y = value
174
+ when 0x03 then rows_written(value)
175
+ when 0x05 then bases_written
176
+ when 0x0a, 0x0b, 0x0c, 0x0d then @sound&.write(register, value)
177
+ when 0x0e
178
+ @painter.aux_written(pixel_position, value >> 4)
179
+ @sound&.write(register, value)
180
+ when 0x0f then @painter.colors_written(pixel_position, value)
181
+ end
182
+ end
183
+
184
+ private
185
+
186
+ def event(column)
187
+ if column == @cycles_per_line
188
+ column = @column = 0
189
+ start_line
190
+ end
191
+ @painter.finish_line(@rasterline) if column == FIRST_PIXEL_COLUMN
192
+ access(column) if column >= @fetch_from && column < @fetch_to
193
+ schedule
194
+ end
195
+
196
+ # The next column with something to do: the end of the line, the
197
+ # start of a display line, or a fetch.
198
+ def schedule
199
+ column = @column
200
+ upcoming = column < FIRST_PIXEL_COLUMN ? FIRST_PIXEL_COLUMN : @cycles_per_line
201
+ fetch = column < @fetch_from ? @fetch_from : column + 1
202
+ upcoming = fetch if fetch < @fetch_to && fetch < upcoming
203
+ @next_event = upcoming
204
+ end
205
+
206
+ def start_line
207
+ line = @rasterline + 1
208
+ if line == @lines_per_frame
209
+ line = 0
210
+ new_frame
211
+ end
212
+ @rasterline = line
213
+ next_text_line if @window_open
214
+ open_window if !@window_open && !@window_done && (line >> 1) == @origin_y && @rows.positive?
215
+ place_fetches
216
+ end
217
+
218
+ def place_fetches
219
+ line = @rasterline
220
+ @slot = line * Painter::SLOTS
221
+ @fetched = @counts[line] = 0
222
+ @starts[line] = @origin_x * PIXELS_PER_CYCLE
223
+ unless @window_open
224
+ @fetch_from = @fetch_to = 0
225
+ return
226
+ end
227
+
228
+ @fetch_from = @origin_x + FETCH_DELAY
229
+ fetches_end
230
+ end
231
+
232
+ def fetches_end
233
+ last = @fetch_from + (2 * @columns)
234
+ @fetch_to = [last, @cycles_per_line].min
235
+ end
236
+
237
+ def access(column)
238
+ slot = @slot + @fetched
239
+ if (column - @fetch_from).even?
240
+ address = (@screen_base + @matrix) & 0x3fff
241
+ @code = @bus.video_fetch(address)
242
+ color = @color_cells[address & 0x3ff]
243
+ return if @colors[slot] == color
244
+
245
+ @colors[slot] = color
246
+ else
247
+ pattern = @bus.video_fetch((@char_base + (@code << @char_shift) + @char_line) & 0x3fff)
248
+ @matrix += 1
249
+ @fetched += 1
250
+ @counts[@rasterline] = @fetched
251
+ return if @patterns[slot] == pattern
252
+
253
+ @patterns[slot] = pattern
254
+ end
255
+ @stale[@rasterline] = true
256
+ end
257
+
258
+ # The pixel of the display line a register write in this cycle takes
259
+ # hold from: the second of the cycle's four.
260
+ def pixel_position
261
+ column = @column - FIRST_PIXEL_COLUMN
262
+ column += @cycles_per_line if column.negative?
263
+ (column * PIXELS_PER_CYCLE) + 1
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/vic20/attachments"
4
+ require "badline/vic20/bus"
5
+ require "badline/vic20/cpu"
6
+ require "badline/vic20/keyboard_via_ports"
7
+ require "badline/vic20/port_wiring"
8
+ require "badline/vic20/user_via_ports"
9
+ require "badline/vic20/vic"
10
+ require "badline/vic20/sound"
11
+ require "badline/vic20/saved_state"
12
+
13
+ module Badline
14
+ # The Commodore VIC-20 with the PAL 6561: a 6502 at the VIC's clock, the
15
+ # VIC-I and two 6522 VIAs. VIA 1, at $9110, pulls NMI, and VIA 2, at
16
+ # $9120, pulls IRQ. Nothing else interrupts the CPU, and nothing halts
17
+ # it: the VIC fetches in the half of the cycle the CPU leaves alone.
18
+ #
19
+ # It runs in the window, as `badline vic20`, with the VIC's picture and
20
+ # sound. The keyboard, the joystick and RESTORE reach the VIAs, the
21
+ # datasette and the serial bus hang off them (PortWiring), and device 8
22
+ # serves disks through the KERNAL traps or a true 1541.
23
+ class Vic20
24
+ include IntegerHelper
25
+ include KeyboardBuffer
26
+ include Attachments
27
+ include PortWiring
28
+ include SavedState
29
+
30
+ attr_reader :bus, :cpu, :vic, :via1, :via2, :keyboard, :joystick1, :cycles, :init_threshold, :sound,
31
+ :drive1541, :iec_bus, :datasette
32
+
33
+ # The RAM expansion it was built with, a key of Bus::RAM_CONFIGURATIONS.
34
+ attr_reader :ram_configuration
35
+
36
+ # The path of the disk or directory device 8 serves through the traps
37
+ # (Attachments), or an empty one.
38
+ def mounted_path = @drive.nil? ? "" : @drive.path
39
+
40
+ def ram = @bus.ram
41
+
42
+ def family = :vic20
43
+
44
+ # The one control port's joystick, which the front end's second
45
+ # joystick drives too.
46
+ def joystick2 = @joystick1
47
+
48
+ # No pot device plugs in yet.
49
+ def control_ports = nil
50
+
51
+ # +ram+ names the RAM expansion, one of Bus::RAM_CONFIGURATIONS.
52
+ def initialize(ram: :unexpanded, debug: false)
53
+ @ram_configuration = ram
54
+ @vic = VIC.new
55
+ @keyboard = Keyboard.new(matrix: KeyboardVIAPorts::MATRIX)
56
+ @joystick1 = Joystick.new
57
+ @iec_bus = IECBus.new
58
+ @datasette = Datasette.new
59
+ @via1 = VIA.new(start: 0x9000, peripheral: UserVIAPorts.new(joystick: @joystick1, serial_bus: @iec_bus,
60
+ datasette: @datasette))
61
+ keyboard_ports = KeyboardVIAPorts.new(keyboard: @keyboard, joystick: @joystick1)
62
+ @via2 = VIA.new(start: 0x9000, peripheral: keyboard_ports)
63
+ keyboard_ports.connect(@via2)
64
+ @bus = Bus.new(vic: @vic, via1: @via1, via2: @via2, blocks: Bus::RAM_CONFIGURATIONS.fetch(ram))
65
+ @vic.connect(@bus)
66
+ @cpu = CPU.new(@bus, debug:)
67
+ @cycles = 0
68
+ @sound = Sound.new(self, @vic.profile.clock_hz)
69
+ @vic.sound = @sound
70
+ @nmi_asserted = false
71
+ @init_handlers = []
72
+ @pending_keys = nil
73
+ @drive = nil
74
+ @serial_trap = nil
75
+ @save_trap = nil
76
+ @capture_output = nil
77
+ @drive1541 = nil
78
+ @init_threshold = boot_cycles
79
+ wire_ports
80
+ end
81
+
82
+ # The VIC and the VIAs clock ahead of the CPU, as the C64's chips do,
83
+ # so a register write lands on the cycle after the one it was issued
84
+ # on.
85
+ def cycle!
86
+ handle_init if @cycles == @init_threshold
87
+ feed_keyboard if @pending_keys
88
+
89
+ @vic.cycle!
90
+ @via1.cycle!
91
+ @via2.cycle!
92
+ @datasette.cycle!
93
+ @cpu.irq = @via2.irq?
94
+ drive_nmi
95
+ @cpu.cycle!
96
+ @drive1541&.host_cycle!
97
+
98
+ @cycles += 1
99
+ end
100
+
101
+ # Runs `count` cycles, one #cycle! after another.
102
+ def run_cycles(count)
103
+ i = 0
104
+ while i < count
105
+ cycle!
106
+ i += 1
107
+ end
108
+ end
109
+
110
+ # Runs cycles until the block returns true or the cycle count passes
111
+ # `limit`, checking before each cycle.
112
+ def run_until(limit)
113
+ cycle! until yield || @cycles > limit
114
+ end
115
+
116
+ # Runs the block once the KERNAL has booted (see init_threshold), or
117
+ # now if it has.
118
+ def on_init(&block)
119
+ if @cycles < @init_threshold
120
+ @init_handlers << block
121
+ else
122
+ block.call
123
+ end
124
+ end
125
+
126
+ # The chip whose #display a front end shows.
127
+ def video = @vic
128
+
129
+ # What a front end plays: the VIC's sound, which answers #record and
130
+ # #drain_samples.
131
+ def sound_source = @sound
132
+
133
+ # The PAL clock and raster, and the part of the display xvic shows: all
134
+ # 284 pixels of a line, from line 28, below the VIC's vertical blank.
135
+ # The VIC draws 4 pixels a cycle, half as many as the C64's VIC-II on
136
+ # about the same clock, so each pixel shows two window pixels wide, as
137
+ # xvic draws them.
138
+ def timing
139
+ profile = @vic.profile
140
+ width = profile.cycles_per_line * VIC::PIXELS_PER_CYCLE
141
+ blanked = profile.blanked_lines
142
+ Timing.new(clock_hz: profile.clock_hz, cycles_per_line: profile.cycles_per_line,
143
+ lines_per_frame: profile.lines_per_frame,
144
+ crop: [0, blanked, width, profile.lines_per_frame - blanked], pixel_width: 2)
145
+ end
146
+
147
+ # The RESTORE key pulls VIA 1's CA1 low while it is held. The KERNAL
148
+ # sets CA1 to interrupt on that falling edge, and its NMI handler
149
+ # ($FEAD) warm-starts BASIC if RUN/STOP is down too.
150
+ def press_restore
151
+ @via1.ca1 = false
152
+ end
153
+
154
+ def release_restore
155
+ @via1.ca1 = true
156
+ end
157
+
158
+ # Calls the block with the exit code a VICE testprog writes to $910F.
159
+ def install_debug_register(&) = @bus.install_debug_register(&)
160
+
161
+ # Stores a PRG's bytes from its load address on, as the CPU would, so
162
+ # bytes for an empty block go nowhere, and returns that address.
163
+ def load_prg(data)
164
+ load_addr = uint16(data[0], data[1])
165
+ bytes = data[2..] || []
166
+ bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
167
+ bytes.each_with_index { |byte, i| @bus.poke(load_addr + i, byte) }
168
+ load_addr
169
+ end
170
+
171
+ # The RES line reaches the CPU and both VIAs, and through the serial
172
+ # bus's RESET line, the drive. The VIC has no reset pin.
173
+ def reset!
174
+ @via1.reset!
175
+ @via2.reset!
176
+ via_written
177
+ @drive&.reset!
178
+ @drive1541&.reset!
179
+ @nmi_asserted = false
180
+ @cpu.reset!
181
+ end
182
+
183
+ # Switches the machine off and on: RAM and the VIC start from their
184
+ # power-on state, and the RES line resets everything else.
185
+ def power_cycle!
186
+ @bus.power_on!
187
+ @vic.power_on!
188
+ @sound.power_on!
189
+ reset!
190
+ end
191
+
192
+ def inspect
193
+ "#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
194
+ end
195
+
196
+ private
197
+
198
+ # The cycle by which the KERNAL has booted and BASIC printed READY,
199
+ # with a margin. The KERNAL's RAM test takes most of it, and runs up
200
+ # from $1000 until it finds no RAM, so each 8K block on from BLK1
201
+ # adds about 615,000 cycles. Unexpanded, READY is up by about 557,000.
202
+ def boot_cycles
203
+ tested = 0
204
+ tested += 1 while tested < 3 && @bus.ram?(%i[blk1 blk2 blk3][tested])
205
+ 700_000 + (650_000 * tested)
206
+ end
207
+
208
+ # VIA 1's IRQ output drives NMI, and the CPU takes an interrupt on its
209
+ # falling edge.
210
+ def drive_nmi
211
+ nmi = @via1.irq?
212
+ @cpu.nmi = true if nmi && !@nmi_asserted
213
+ @nmi_asserted = nmi
214
+ end
215
+
216
+ def handle_init
217
+ @init_handlers.each(&:call)
218
+ end
219
+ end
220
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: badline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -50,10 +50,10 @@ files:
50
50
  - lib/badline.rb
51
51
  - lib/badline/address_bus.rb
52
52
  - lib/badline/address_bus/extra_sids.rb
53
+ - lib/badline/address_bus/pla.rb
53
54
  - lib/badline/address_bus/roms.rb
54
55
  - lib/badline/address_bus/saved_state.rb
55
56
  - lib/badline/address_bus/sid_slots.rb
56
- - lib/badline/address_bus/ultimax_pages.rb
57
57
  - lib/badline/addressable.rb
58
58
  - lib/badline/audio.rb
59
59
  - lib/badline/audio/aiff.rb
@@ -155,9 +155,11 @@ files:
155
155
  - lib/badline/frontend/gamepads.rb
156
156
  - lib/badline/frontend/info_view.rb
157
157
  - lib/badline/frontend/keys.rb
158
+ - lib/badline/frontend/machine_window.rb
158
159
  - lib/badline/frontend/menu_dialogs.rb
159
160
  - lib/badline/frontend/menu_media.rb
160
161
  - lib/badline/frontend/menu_pages.rb
162
+ - lib/badline/frontend/menu_ports.rb
161
163
  - lib/badline/frontend/name_field.rb
162
164
  - lib/badline/frontend/pacer.rb
163
165
  - lib/badline/frontend/pad_port.rb
@@ -206,17 +208,22 @@ files:
206
208
  - lib/badline/kernal_trap/drive/write_file.rb
207
209
  - lib/badline/kernal_trap/drive/writes.rb
208
210
  - lib/badline/kernal_trap/file.rb
211
+ - lib/badline/kernal_trap/layout.rb
209
212
  - lib/badline/kernal_trap/load.rb
210
213
  - lib/badline/kernal_trap/routine.rb
211
214
  - lib/badline/kernal_trap/save.rb
212
215
  - lib/badline/kernal_trap/serial.rb
213
216
  - lib/badline/keyboard.rb
214
217
  - lib/badline/keyboard_buffer.rb
218
+ - lib/badline/machine.rb
215
219
  - lib/badline/media.rb
216
220
  - lib/badline/media/disk_list.rb
217
221
  - lib/badline/media/disk_set.rb
218
222
  - lib/badline/media/queue.rb
219
223
  - lib/badline/media/true_drive.rb
224
+ - lib/badline/media/vic20_basic.rb
225
+ - lib/badline/media/vic20_cartridge.rb
226
+ - lib/badline/media/vic20_media.rb
220
227
  - lib/badline/memory.rb
221
228
  - lib/badline/model.rb
222
229
  - lib/badline/options.rb
@@ -247,6 +254,10 @@ files:
247
254
  - lib/badline/roms/eapi/eapi-am29f040-14
248
255
  - lib/badline/roms/kernal-sx64.rom
249
256
  - lib/badline/roms/kernal.rom
257
+ - lib/badline/roms/vic20/basic.rom
258
+ - lib/badline/roms/vic20/character.rom
259
+ - lib/badline/roms/vic20/kernal-ntsc.rom
260
+ - lib/badline/roms/vic20/kernal-pal.rom
250
261
  - lib/badline/sdl.rb
251
262
  - lib/badline/sid.rb
252
263
  - lib/badline/sid/catch_up.rb
@@ -303,6 +314,7 @@ files:
303
314
  - lib/badline/storage/tap.rb
304
315
  - lib/badline/storage/unavailable.rb
305
316
  - lib/badline/time_of_day.rb
317
+ - lib/badline/timing.rb
306
318
  - lib/badline/traps.rb
307
319
  - lib/badline/version.rb
308
320
  - lib/badline/via.rb
@@ -333,6 +345,20 @@ files:
333
345
  - lib/badline/vic/sprite/timing.rb
334
346
  - lib/badline/vic/sprites.rb
335
347
  - lib/badline/vic/sprites_state.rb
348
+ - lib/badline/vic20.rb
349
+ - lib/badline/vic20/attachments.rb
350
+ - lib/badline/vic20/bus.rb
351
+ - lib/badline/vic20/cpu.rb
352
+ - lib/badline/vic20/keyboard_via_ports.rb
353
+ - lib/badline/vic20/port_wiring.rb
354
+ - lib/badline/vic20/saved_state.rb
355
+ - lib/badline/vic20/sound.rb
356
+ - lib/badline/vic20/sound/output.rb
357
+ - lib/badline/vic20/user_via_ports.rb
358
+ - lib/badline/vic20/vic.rb
359
+ - lib/badline/vic20/vic/painter.rb
360
+ - lib/badline/vic20/vic/saved_state.rb
361
+ - lib/badline/vic20/vic/text_window.rb
336
362
  - packaging/homebrew/badline.rb
337
363
  homepage: https://github.com/elektronaut/badline
338
364
  licenses:
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Badline
4
- class AddressBus
5
- # The page tables of an Ultimax cartridge, which AddressBus lays out
6
- # in place of the $01 banking while the cartridge holds GAME low and
7
- # EXROM high.
8
- module UltimaxPages
9
- private
10
-
11
- # Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
12
- # I/O always visible and open address space everywhere else. The ROML
13
- # and ROMH selects fire on writes as well, so cartridge RAM or flash in
14
- # either window takes the writes there.
15
- def map_ultimax_pages
16
- @read_pages.fill(@open_bus, 0x10, 0xf0)
17
- @write_pages.fill(@open_bus, 0x10, 0xf0)
18
- @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
19
- map_ultimax_writes(@cartridge.roml, 0x80)
20
- @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
21
- map_ultimax_writes(@cartridge.romh, 0xe0)
22
- map_ultimax_a000
23
- map_io_pages
24
- end
25
-
26
- def map_ultimax_writes(bank, first_page)
27
- @write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
28
- end
29
-
30
- def map_ultimax_a000
31
- return unless (window = @cartridge.ultimax_a000)
32
-
33
- @read_pages.fill(window, 0xa0, 0x20)
34
- @write_pages.fill(window, 0xa0, 0x20)
35
- end
36
- end
37
- end
38
- end