badline 0.15.1 → 0.16.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/CLAUDE.md +8 -7
  4. data/README.md +102 -6
  5. data/doc/pinned-behaviour.md +67 -0
  6. data/doc/snapshot-format.md +213 -18
  7. data/doc/snapshots.md +14 -0
  8. data/lib/badline/c64/common.rb +1 -1
  9. data/lib/badline/chips/crtc/keyed_state.rb +46 -0
  10. data/lib/badline/chips/crtc.rb +163 -0
  11. data/lib/badline/chips/pia/control_lines.rb +138 -0
  12. data/lib/badline/chips/pia/keyed_parts.rb +56 -0
  13. data/lib/badline/chips/pia.rb +138 -0
  14. data/lib/badline/chips/vic/register_log.rb +1 -4
  15. data/lib/badline/core.rb +3 -0
  16. data/lib/badline/drives/ieee_bus.rb +28 -0
  17. data/lib/badline/drives/model1551/cable.rb +5 -2
  18. data/lib/badline/drives/model1551/tia.rb +13 -34
  19. data/lib/badline/drives/model1551.rb +26 -4
  20. data/lib/badline/drives.rb +1 -0
  21. data/lib/badline/frontend/app.rb +1 -1
  22. data/lib/badline/frontend/controls.rb +23 -3
  23. data/lib/badline/frontend/gamepads.rb +10 -3
  24. data/lib/badline/frontend/keys.rb +88 -0
  25. data/lib/badline/frontend/media_slots.rb +1 -0
  26. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
  27. data/lib/badline/frontend/menu/menu_media.rb +27 -28
  28. data/lib/badline/frontend/menu/menu_pages.rb +15 -2
  29. data/lib/badline/frontend/menu/menu_ports.rb +22 -1
  30. data/lib/badline/frontend/menu/pause_menu.rb +1 -0
  31. data/lib/badline/frontend/pet_keyboard.rb +124 -0
  32. data/lib/badline/frontend/screens.rb +2 -1
  33. data/lib/badline/frontend/timeline.rb +16 -9
  34. data/lib/badline/frontend.rb +1 -0
  35. data/lib/badline/kernal_trap/dos/writes.rb +3 -1
  36. data/lib/badline/kernal_trap/dos.rb +6 -1
  37. data/lib/badline/kernal_trap/frames.rb +117 -0
  38. data/lib/badline/kernal_trap/ieee.rb +148 -0
  39. data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
  40. data/lib/badline/kernal_trap/keyed_state.rb +4 -4
  41. data/lib/badline/kernal_trap/routine.rb +3 -53
  42. data/lib/badline/kernal_trap/serial.rb +4 -102
  43. data/lib/badline/kernal_trap/steps.rb +64 -0
  44. data/lib/badline/kernal_trap.rb +4 -0
  45. data/lib/badline/machine/keyboard_buffer.rb +8 -0
  46. data/lib/badline/machine/settling.rb +1 -1
  47. data/lib/badline/machine.rb +28 -6
  48. data/lib/badline/media/extensions.rb +5 -2
  49. data/lib/badline/media/pet_media.rb +40 -0
  50. data/lib/badline/media/program.rb +14 -2
  51. data/lib/badline/media/vic20_media.rb +2 -2
  52. data/lib/badline/media.rb +36 -16
  53. data/lib/badline/options/family.rb +17 -6
  54. data/lib/badline/options/help.rb +19 -3
  55. data/lib/badline/options/table.rb +14 -2
  56. data/lib/badline/options/validation.rb +68 -5
  57. data/lib/badline/options.rb +31 -19
  58. data/lib/badline/pet/bus/expansion.rb +106 -0
  59. data/lib/badline/pet/bus.rb +204 -0
  60. data/lib/badline/pet/canvas.rb +97 -0
  61. data/lib/badline/pet/cassettes.rb +63 -0
  62. data/lib/badline/pet/characters.rb +26 -0
  63. data/lib/badline/pet/cpu.rb +10 -0
  64. data/lib/badline/pet/crtc_video.rb +142 -0
  65. data/lib/badline/pet/discrete_video.rb +83 -0
  66. data/lib/badline/pet/ieee_ports.rb +66 -0
  67. data/lib/badline/pet/ieee_traps.rb +75 -0
  68. data/lib/badline/pet/keyboard_ports.rb +74 -0
  69. data/lib/badline/pet/keyed_modules.rb +85 -0
  70. data/lib/badline/pet/keyed_parts.rb +123 -0
  71. data/lib/badline/pet/keyed_state.rb +42 -0
  72. data/lib/badline/pet/model.rb +113 -0
  73. data/lib/badline/pet/rom_sets.rb +65 -0
  74. data/lib/badline/pet/sound.rb +128 -0
  75. data/lib/badline/pet/via.rb +10 -0
  76. data/lib/badline/pet/via_ports.rb +19 -0
  77. data/lib/badline/pet.rb +226 -0
  78. data/lib/badline/plus4/keyed_state.rb +5 -3
  79. data/lib/badline/plus4/tcbm_drive.rb +14 -0
  80. data/lib/badline/plus4.rb +1 -1
  81. data/lib/badline/roms/README +137 -0
  82. data/lib/badline/roms/pet/basic1.rom +0 -0
  83. data/lib/badline/roms/pet/basic2.rom +0 -0
  84. data/lib/badline/roms/pet/basic4.rom +0 -0
  85. data/lib/badline/roms/pet/characters1.rom +0 -0
  86. data/lib/badline/roms/pet/characters2.rom +0 -0
  87. data/lib/badline/roms/pet/editor1.rom +0 -0
  88. data/lib/badline/roms/pet/editor2-b.rom +0 -0
  89. data/lib/badline/roms/pet/editor2-n.rom +0 -0
  90. data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
  91. data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
  92. data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
  93. data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
  94. data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
  95. data/lib/badline/roms/pet/editor4-b.rom +0 -0
  96. data/lib/badline/roms/pet/editor4-n.rom +0 -0
  97. data/lib/badline/roms/pet/kernal1.rom +0 -0
  98. data/lib/badline/roms/pet/kernal2.rom +0 -0
  99. data/lib/badline/roms/pet/kernal4.rom +0 -0
  100. data/lib/badline/snapshot/container.rb +2 -0
  101. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  102. data/lib/badline/snapshot/field_doc.rb +16 -3
  103. data/lib/badline/snapshot/image.rb +14 -7
  104. data/lib/badline/snapshot/pet_setup.rb +71 -0
  105. data/lib/badline/snapshot/storage_setup.rb +7 -1
  106. data/lib/badline/snapshot.rb +9 -5
  107. data/lib/badline/storage/d80_image.rb +48 -0
  108. data/lib/badline/storage/d82_image.rb +21 -0
  109. data/lib/badline/storage/tap.rb +16 -3
  110. data/lib/badline/storage.rb +2 -0
  111. data/lib/badline/support/pet_checkpoint.rb +38 -0
  112. data/lib/badline/timing.rb +5 -4
  113. data/lib/badline/version.rb +1 -1
  114. data/lib/badline/vic20/attachments.rb +7 -2
  115. data/lib/badline/vic20/bus/keyed_state.rb +5 -4
  116. data/lib/badline/vic20/bus.rb +5 -0
  117. data/lib/badline/vic20.rb +1 -1
  118. metadata +55 -1
data/doc/snapshots.md CHANGED
@@ -140,6 +140,20 @@ KERNAL and function ROMs come with the model. Its file names its machine
140
140
  doesn't open it, and badline doesn't open xplus4's. `--vice` and
141
141
  `computer.export_snapshot(path)` refuse a Plus/4.
142
142
 
143
+ ## The PET
144
+
145
+ Every PET and CBM model saves and restores the same way. A snapshot of
146
+ one holds its RAM, the screen RAM, an 8096's 64K board with its control
147
+ register, both PIAs, the VIA, the CPU, the CRTC on the boards that have
148
+ one, the picture with where the video is in its frame, the tapes in both
149
+ datasettes with their places on them, and the disk or directory device 8
150
+ serves through the IEEE-488 traps. It restores into a machine of the same
151
+ model with the same RAM, the same editor frame rate and the same ROMs,
152
+ which come with the model. Its file names its machine `PET`, as xpet's
153
+ do, and holds only badline's modules, so xpet doesn't open it, and
154
+ badline doesn't open xpet's. `--vice` and
155
+ `computer.export_snapshot(path)` refuse a PET.
156
+
143
157
  ## VICE
144
158
 
145
159
  Snapshots use VICE's `.vsf` format, and badline's own modules are named
@@ -10,7 +10,7 @@ module Badline
10
10
  def timing
11
11
  region = address_bus.region
12
12
  Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
13
- lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
13
+ lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1, dots: 1)
14
14
  end
15
15
 
16
16
  def load_prg(data)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class CRTC
6
+ # The CRTC's fields in a keyed snapshot module (Snapshot::Keyed): the
7
+ # registers, the one selected, and the counters: the character, the
8
+ # characters since horizontal sync, the line, the row, the lines of
9
+ # vertical sync left, whether the frame is in its vertical adjust,
10
+ # whether the row and the character show, and the memory address with
11
+ # the row's start. The output restores its own state.
12
+ module KeyedState
13
+ def save_fields(out)
14
+ out.bytes("registers", @registers).int("selected", @selected).int("column", @column)
15
+ out.int("since_hsync", @since_hsync).int("line", @line).int("row", @row).int("vsync_lines", @vsync_lines)
16
+ out.bool("adjusting", @adjusting).bool("row_display", @row_display).bool("column_display", @column_display)
17
+ out.int("row_start", @row_start).int("address", @address)
18
+ end
19
+
20
+ def load_fields(input)
21
+ input.bytes_into("registers", @registers)
22
+ MASKS.each_with_index { |mask, index| @registers[index] &= mask }
23
+ @selected = input.masked("selected", @selected, 0x1f)
24
+ @column = input.byte("column", @column)
25
+ @since_hsync = input.int("since_hsync", @since_hsync)
26
+ @line = input.masked("line", @line, 0x1f)
27
+ @row = input.masked("row", @row, 0x7f)
28
+ @vsync_lines = input.int("vsync_lines", @vsync_lines)
29
+ load_flag_fields(input)
30
+ @row_start = input.masked("row_start", @row_start, 0x3fff)
31
+ @address = input.masked("address", @address, 0x3fff)
32
+ end
33
+
34
+ private
35
+
36
+ def load_flag_fields(input)
37
+ @adjusting = input.bool("adjusting", @adjusting)
38
+ @row_display = input.bool("row_display", @row_display)
39
+ @column_display = input.bool("column_display", @column_display)
40
+ end
41
+ end
42
+
43
+ include KeyedState
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/crtc/keyed_state"
4
+
5
+ module Badline
6
+ module Chips
7
+ # The MOS 6545 CRTC, as the later PETs drive their screens with: an
8
+ # address register at even addresses selects one of eighteen registers,
9
+ # which the odd addresses write. Of them only the light pen's, R16 and
10
+ # R17, and the cursor address, R14 and R15, read back; every other read
11
+ # finds 0.
12
+ #
13
+ # Its counters run one character a cycle, and compare with the
14
+ # registers as they stand that cycle, so a write lands from the next
15
+ # character on:
16
+ #
17
+ # - The character counter runs from 0 to R0, then a line ends. The
18
+ # display shows from character 0 until the counter equals R1, and
19
+ # horizontal sync starts where it equals R2. A compare the counter has
20
+ # already passed doesn't happen that line: the display then runs on
21
+ # to the line's end.
22
+ # - The line counter (RA, five bits) counts a character row's lines. A
23
+ # row ends after the line where it equals R9; past R9 it runs on to
24
+ # 31 and wraps.
25
+ # - The row counter (seven bits) counts rows to R4. R5 more lines of
26
+ # vertical adjust follow, then the frame starts again. The display
27
+ # stops at the start of the row the counter equals R6, and comes back
28
+ # at the next frame; vertical sync starts with row R7 and lasts
29
+ # VSYNC_LINES lines.
30
+ # - The memory address (MA, fourteen bits) starts each frame at
31
+ # R12:R13 and counts one a character. Each line starts again from its
32
+ # row's start, which the last line of a row moves on to the address
33
+ # where the counter equals R1.
34
+ #
35
+ # Each displayed character goes to the output's #character with MA, RA
36
+ # and the characters since horizontal sync last started; the output
37
+ # also hears of each #hsync as it starts, each #line_end, the
38
+ # #rows_end where the display stops for the rest of the frame, and each
39
+ # #frame_start.
40
+ class CRTC
41
+ # The bits each register keeps.
42
+ MASKS = [0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xff, 0x1f, 0x7f, 0x1f,
43
+ 0x3f, 0xff, 0x3f, 0xff, 0x3f, 0xff].freeze
44
+
45
+ VSYNC_LINES = 16
46
+
47
+ attr_reader :registers, :column, :line, :row, :address
48
+
49
+ # The characters since horizontal sync last started.
50
+ attr_reader :since_hsync
51
+
52
+ # +registers+ is the register file, which the output may read too.
53
+ def initialize(output, registers: Array.new(MASKS.length, 0))
54
+ @output = output
55
+ @registers = registers
56
+ reset!
57
+ end
58
+
59
+ # The RES pin stops the counters at the start of a frame. The
60
+ # registers keep their values.
61
+ def reset!
62
+ @selected = 0
63
+ @column = 0
64
+ @since_hsync = 0
65
+ @vsync_lines = 0
66
+ start_frame
67
+ end
68
+
69
+ # Whether vertical sync is on.
70
+ def vsync? = @vsync_lines.positive?
71
+
72
+ # Whether the display shows the character the counters are at.
73
+ def display? = @row_display && @column_display
74
+
75
+ def peek(addr)
76
+ return 0 if addr.nobits?(0x01) || @selected < 14 || @selected >= MASKS.length
77
+
78
+ @registers[@selected]
79
+ end
80
+
81
+ def poke(addr, value)
82
+ if addr.nobits?(0x01)
83
+ @selected = value & 0x1f
84
+ elsif @selected < MASKS.length
85
+ @registers[@selected] = value & MASKS[@selected]
86
+ end
87
+ end
88
+
89
+ def cycle!
90
+ regs = @registers
91
+ column = @column
92
+ if column == regs[1]
93
+ @column_display = false
94
+ @row_start = @address if @line == regs[9]
95
+ end
96
+ @output.character(@address, @line, @since_hsync) if @column_display && @row_display
97
+ if column == regs[2]
98
+ @since_hsync = 1
99
+ @output.hsync
100
+ else
101
+ @since_hsync += 1
102
+ end
103
+ @address = (@address + 1) & 0x3fff
104
+ if column == regs[0]
105
+ @column = 0
106
+ end_line
107
+ else
108
+ @column = (column + 1) & 0xff
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def end_line
115
+ @output.line_end
116
+ @vsync_lines -= 1 if @vsync_lines.positive?
117
+ @column_display = true
118
+ if @adjusting
119
+ @line += 1
120
+ @line >= @registers[5] ? start_frame : @address = @row_start
121
+ elsif @line == @registers[9]
122
+ end_row
123
+ else
124
+ @line = (@line + 1) & 0x1f
125
+ @address = @row_start
126
+ end
127
+ end
128
+
129
+ def end_row
130
+ @line = 0
131
+ if @row == @registers[4]
132
+ return start_frame if @registers[5].zero?
133
+
134
+ @adjusting = true
135
+ else
136
+ @row = (@row + 1) & 0x7f
137
+ start_row
138
+ end
139
+ @address = @row_start
140
+ end
141
+
142
+ def start_frame
143
+ @line = 0
144
+ @row = 0
145
+ @adjusting = false
146
+ @row_display = true
147
+ @column_display = true
148
+ @row_start = ((@registers[12] << 8) | @registers[13]) & 0x3fff
149
+ @address = @row_start
150
+ start_row
151
+ @output.frame_start
152
+ end
153
+
154
+ def start_row
155
+ if @row == @registers[6] && @row_display
156
+ @row_display = false
157
+ @output.rows_end
158
+ end
159
+ @vsync_lines = VSYNC_LINES if @row == @registers[7]
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class PIA
6
+ # One side's control register, CRA or CRB, its two control lines, CA1
7
+ # and CA2 or CB1 and CB2, and their interrupt flags.
8
+ #
9
+ # bit 0 C1 interrupt enable
10
+ # bit 1 C1 active edge, rising when set
11
+ # bit 2 the port (1) or its direction register (0) at the same address
12
+ # bits 5-3 C2:
13
+ # 0e0 input, e the interrupt enable, falling edge
14
+ # 1e0 input, rising edge
15
+ # 100 handshake output
16
+ # 101 pulse output
17
+ # 110 low output
18
+ # 111 high output
19
+ # bit 6 C2 flag, read only, held clear while C2 is an output
20
+ # bit 7 C1 flag, read only
21
+ #
22
+ # A flag is set by its line's active edge whether or not its interrupt
23
+ # is enabled, and both are cleared by a read of the port. In handshake
24
+ # mode C2 goes low on the strobing access and back high on the next
25
+ # active C1 edge; in pulse mode it goes low for the cycle after it.
26
+ # Side A strobes on a read of the port, side B on a write.
27
+ class ControlLines
28
+ attr_reader :c1_high, :c2_high
29
+
30
+ # The level driven on C2, high while it is an input.
31
+ attr_reader :c2_output
32
+
33
+ def initialize(strobe_on_read:)
34
+ @strobe_on_read = strobe_on_read
35
+ @c1_high = @c2_high = true
36
+ reset!
37
+ end
38
+
39
+ # Every bit clear: C2 an input, both interrupts off and the
40
+ # direction register selected. The input levels carry over.
41
+ def reset!
42
+ @control = 0
43
+ @c1_flag = @c2_flag = false
44
+ @handshake_low = false
45
+ @pulse = 0
46
+ drive
47
+ end
48
+
49
+ # The control register as read, the flags in bits 7 and 6.
50
+ def register
51
+ value = @control
52
+ value |= 0x80 if @c1_flag
53
+ value |= 0x40 if @c2_flag
54
+ value
55
+ end
56
+
57
+ # Writes bits 0-5; the flags can't be written. A new C2 mode starts
58
+ # with any handshake or pulse over.
59
+ def register=(value)
60
+ mode = c2_mode
61
+ @control = value & 0x3f
62
+ @c2_flag = false if c2_output?
63
+ unless c2_mode == mode
64
+ @handshake_low = false
65
+ @pulse = 0
66
+ end
67
+ drive
68
+ end
69
+
70
+ def port_selected? = @control.anybits?(0x04)
71
+
72
+ def irq? = (@c1_flag && @control.anybits?(0x01)) || (@c2_flag && @control & 0x28 == 0x08)
73
+
74
+ # Only the pulse output needs the clock.
75
+ def pulsing? = @pulse.positive?
76
+
77
+ def cycle!
78
+ @pulse -= 1
79
+ drive if @pulse.zero?
80
+ end
81
+
82
+ # A new C1 level. An active edge sets the flag and ends a handshake.
83
+ def c1=(high)
84
+ return if high == @c1_high
85
+
86
+ @c1_high = high
87
+ return unless high == @control.anybits?(0x02)
88
+
89
+ @c1_flag = true
90
+ @handshake_low = false
91
+ drive
92
+ end
93
+
94
+ # A new C2 level. An active edge sets the flag while C2 is an input.
95
+ def c2=(high)
96
+ return if high == @c2_high
97
+
98
+ @c2_high = high
99
+ @c2_flag = true if !c2_output? && high == @control.anybits?(0x10)
100
+ end
101
+
102
+ # A read of the port, which clears both flags.
103
+ def port_read!
104
+ @c1_flag = @c2_flag = false
105
+ strobe if @strobe_on_read
106
+ end
107
+
108
+ def port_written!
109
+ strobe unless @strobe_on_read
110
+ end
111
+
112
+ private
113
+
114
+ def c2_output? = @control.anybits?(0x20)
115
+
116
+ def c2_mode = c2_output? ? @control & 0x38 : 0
117
+
118
+ def strobe
119
+ case @control & 0x38
120
+ when 0x20 then @handshake_low = true
121
+ when 0x28 then @pulse = 2
122
+ else return
123
+ end
124
+ drive
125
+ end
126
+
127
+ def drive
128
+ @c2_output = case @control & 0x38
129
+ when 0x20 then !@handshake_low
130
+ when 0x28 then @pulse.zero?
131
+ when 0x30 then false
132
+ else true
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class PIA
6
+ class ControlLines
7
+ # A side's control register, its lines' levels, its flags and the
8
+ # handshake or pulse under way, under `prefix`. The level driven on
9
+ # C2 follows from them on load.
10
+ module KeyedState
11
+ def save_fields(out, prefix)
12
+ out.int("#{prefix}control", @control).bool("#{prefix}c1_high", @c1_high).bool("#{prefix}c2_high", @c2_high)
13
+ out.bool("#{prefix}c1_flag", @c1_flag).bool("#{prefix}c2_flag", @c2_flag)
14
+ out.bool("#{prefix}handshake_low", @handshake_low).int("#{prefix}pulse", @pulse)
15
+ end
16
+
17
+ def load_fields(input, prefix)
18
+ @control = input.masked("#{prefix}control", @control, 0x3f)
19
+ @c1_high = input.bool("#{prefix}c1_high", @c1_high)
20
+ @c2_high = input.bool("#{prefix}c2_high", @c2_high)
21
+ @c1_flag = input.bool("#{prefix}c1_flag", @c1_flag)
22
+ @c2_flag = input.bool("#{prefix}c2_flag", @c2_flag)
23
+ @handshake_low = input.bool("#{prefix}handshake_low", @handshake_low)
24
+ @pulse = input.masked("#{prefix}pulse", @pulse, 0x03)
25
+ drive
26
+ end
27
+ end
28
+
29
+ include KeyedState
30
+ end
31
+
32
+ # The PIA's fields in a keyed snapshot module (Snapshot::Keyed): the
33
+ # output and direction registers, and each side's control lines under
34
+ # ca. and cb. The peripheral restores its own state.
35
+ module KeyedState
36
+ def save_fields(out)
37
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
38
+ @ca.save_fields(out, "ca.")
39
+ @cb.save_fields(out, "cb.")
40
+ out
41
+ end
42
+
43
+ def load_fields(input)
44
+ @ora = input.byte("ora", @ora)
45
+ @orb = input.byte("orb", @orb)
46
+ @ddra = input.byte("ddra", @ddra)
47
+ @ddrb = input.byte("ddrb", @ddrb)
48
+ @ca.load_fields(input, "ca.")
49
+ @cb.load_fields(input, "cb.")
50
+ end
51
+ end
52
+
53
+ include KeyedState
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/pia/control_lines"
4
+ require "badline/chips/pia/keyed_parts"
5
+
6
+ module Badline
7
+ module Chips
8
+ # The MOS 6520 PIA (Peripheral Interface Adapter), the PET's two: two
9
+ # 8-bit ports, A and B, each with a direction register, a 1 making the
10
+ # pin an output, and a control register for its pair of control lines
11
+ # (ControlLines). Each side has its own IRQ output, IRQA and IRQB. There
12
+ # are no timers.
13
+ #
14
+ # Four registers repeat on two address lines: port A or DDRA, CRA, port
15
+ # B or DDRB and CRB, the port or the direction register picked by bit 2
16
+ # of its control register.
17
+ #
18
+ # Port A reads its pins, whatever drives them. Port B reads its output
19
+ # bits from its output register and its input bits from the pins. An
20
+ # input pin floats high, and a peripheral pulls lines low through
21
+ # read_a and read_b, as a VIA's does. The control lines take levels
22
+ # from outside through ca1= and friends.
23
+ class PIA
24
+ attr_reader :peripheral
25
+
26
+ def initialize(peripheral: nil)
27
+ @peripheral = peripheral
28
+ @ca = ControlLines.new(strobe_on_read: true)
29
+ @cb = ControlLines.new(strobe_on_read: false)
30
+ reset!
31
+ end
32
+
33
+ # The RES line clears every register, which leaves every line an
34
+ # input and releases both IRQs.
35
+ def reset!
36
+ @ora = @orb = @ddra = @ddrb = 0x00
37
+ @ca.reset!
38
+ @cb.reset!
39
+ end
40
+
41
+ def irqa? = @ca.irq?
42
+
43
+ def irqb? = @cb.irq?
44
+
45
+ def irq? = @ca.irq? || @cb.irq?
46
+
47
+ def cycle!
48
+ @ca.cycle! if @ca.pulsing?
49
+ @cb.cycle! if @cb.pulsing?
50
+ end
51
+
52
+ def cra = @ca.register
53
+
54
+ def crb = @cb.register
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
+ def port_b_output = driven_lines(@orb, @ddrb)
60
+
61
+ def ca1=(high)
62
+ @ca.c1 = high
63
+ end
64
+
65
+ def ca2=(high)
66
+ @ca.c2 = high
67
+ end
68
+
69
+ def cb1=(high)
70
+ @cb.c1 = high
71
+ end
72
+
73
+ def cb2=(high)
74
+ @cb.c2 = high
75
+ end
76
+
77
+ def ca2_output = @ca.c2_output
78
+
79
+ def cb2_output = @cb.c2_output
80
+
81
+ def peek(addr)
82
+ case addr & 0x03
83
+ when 0x00 then @ca.port_selected? ? read_port_a : @ddra
84
+ when 0x01 then @ca.register
85
+ when 0x02 then @cb.port_selected? ? read_port_b : @ddrb
86
+ else @cb.register
87
+ end
88
+ end
89
+
90
+ def poke(addr, value)
91
+ case addr & 0x03
92
+ when 0x00 then write_port_a(value)
93
+ when 0x01 then @ca.register = value
94
+ when 0x02 then write_port_b(value)
95
+ else @cb.register = value
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def driven_lines(register, direction)
102
+ (register & direction) | (~direction & 0xff)
103
+ end
104
+
105
+ def read_port_a
106
+ lines = port_a_output
107
+ lines &= @peripheral.read_a(lines) if @peripheral
108
+ @ca.port_read!
109
+ lines
110
+ end
111
+
112
+ def read_port_b
113
+ lines = port_b_output
114
+ lines &= @peripheral.read_b(lines) if @peripheral
115
+ @cb.port_read!
116
+ (@orb & @ddrb) | (lines & ~@ddrb & 0xff)
117
+ end
118
+
119
+ def write_port_a(value)
120
+ if @ca.port_selected?
121
+ @ora = value
122
+ @ca.port_written!
123
+ else
124
+ @ddra = value
125
+ end
126
+ end
127
+
128
+ def write_port_b(value)
129
+ if @cb.port_selected?
130
+ @orb = value
131
+ @cb.port_written!
132
+ else
133
+ @ddrb = value
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -43,10 +43,7 @@ module Badline
43
43
  # register back at the value it held when the line started.
44
44
  def rewind
45
45
  @values.length.times { |reg| @values[reg] = @registers[reg] }
46
- @seed.each_index do |reg|
47
- value = @seed[reg]
48
- @values[reg] = value unless value.nil?
49
- end
46
+ @seed.each_with_index { |value, reg| @values[reg] = value unless value.nil? }
50
47
  @cursor = 0
51
48
  @next_x = @entries.empty? ? Float::INFINITY : @entries[0][0]
52
49
  end
data/lib/badline/core.rb CHANGED
@@ -18,6 +18,8 @@ require "badline/c64/banked_ram"
18
18
  require "badline/status"
19
19
  require "badline/input"
20
20
  require "badline/chips"
21
+ require "badline/chips/pia"
22
+ require "badline/chips/crtc"
21
23
  require "badline/drives"
22
24
  require "badline/c64/bus"
23
25
  require "badline/c64/reu"
@@ -27,6 +29,7 @@ require "badline/snapshot/storage_setup"
27
29
  require "badline/machine"
28
30
  require "badline/c64"
29
31
  require "badline/vic20"
32
+ require "badline/pet"
30
33
  require "badline/storage"
31
34
  require "badline/sid_tune"
32
35
  require "badline/c64/cartridge"
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ # The IEEE-488 bus between a PET and its drives: eight data lines, DIO1
6
+ # to DIO8, and the control lines EOI, DAV, NRFD, NDAC and ATN, each open
7
+ # collector and active low, so a line is low while anything on the bus
8
+ # pulls it low. The PET is the controller and always on the bus, and
9
+ # reads back every line it drives.
10
+ #
11
+ # No device plugs in yet, so the lines carry the controller's levels
12
+ # alone: nobody answers a handshake, and the KERNAL reports DEVICE NOT
13
+ # PRESENT for every device the traps don't serve.
14
+ class IEEEBus
15
+ # The lines as bits, the data lines in the low byte, DIO1 in bit 0.
16
+ DIO = 0x00ff
17
+ EOI = 0x0100
18
+ DAV = 0x0200
19
+ NRFD = 0x0400
20
+ NDAC = 0x0800
21
+ ATN = 0x1000
22
+
23
+ # The lines low on the bus while the controller pulls the lines in
24
+ # +controller+ low.
25
+ def low_lines(controller) = controller
26
+ end
27
+ end
28
+ end
@@ -10,7 +10,8 @@ module Badline
10
10
  # - the eight data lines join the two port As;
11
11
  # - the drive's STATUS0 and STATUS1 (PC0-1) reach the computer's PB0-1;
12
12
  # - the drive's ACK (PC3) reaches the computer's PC7;
13
- # - the computer's DAV (PC6) reaches the drive's PC7.
13
+ # - the computer's DAV (PC6) reaches the drive's PC7;
14
+ # - the drive's DEV (PC2) picks the interface's address (Model1551).
14
15
  #
15
16
  # The drive pulls every line up, so a line neither side drives low
16
17
  # reads 1. The computer's other port B and C pins read 0.
@@ -23,13 +24,14 @@ module Badline
23
24
  # wakes a drive asleep in its idle loop (atn_moved?).
24
25
  class Cable
25
26
  STATUS = 0x03
27
+ DEV = 0x04
26
28
  ACK = 0x08
27
29
  DAV = 0x40
28
30
 
29
31
  attr_reader :host, :drive
30
32
 
31
33
  # Plugs the cable into +host+, the computer's Chips::TIA, and
32
- # +drive+, the drive's (Model1551::TIA#chip), for +unit+, the drive.
34
+ # +drive+, the drive's (Model1551::TIA), for +unit+, the drive.
33
35
  def initialize(host, drive, unit)
34
36
  @host = host
35
37
  @drive = drive
@@ -93,6 +95,7 @@ module Badline
93
95
  drive_inputs = drive.inputs
94
96
  drive_inputs[0] = @data & drive_data
95
97
  drive_inputs[2] = (drive_inputs[2] & 0x7f) | (@dav << 1)
98
+ @unit.dev_line(drive_lines.anybits?(DEV))
96
99
  return if @moved || (host_data == @data && (host_lines & DAV) == @dav)
97
100
 
98
101
  @moved = true