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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/CLAUDE.md +1 -1
- data/README.md +102 -12
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +376 -69
- data/doc/snapshots.md +148 -14
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +18 -4
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +4 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +23 -6
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +38 -9
- data/lib/badline/media.rb +15 -6
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +16 -2
- data/lib/badline/options/table.rb +18 -4
- data/lib/badline/options/validation.rb +27 -3
- data/lib/badline/options.rb +21 -7
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/ieee_drive.rb +5 -64
- data/lib/badline/pet/ieee_ports.rb +7 -0
- data/lib/badline/pet.rb +15 -4
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +111 -0
- data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
- data/lib/badline/roms/cbm2/basic128.rom +0 -0
- data/lib/badline/roms/cbm2/basic256.rom +0 -0
- data/lib/badline/roms/cbm2/characters-600.rom +0 -0
- data/lib/badline/roms/cbm2/characters-700.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
- data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
- data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
- data/lib/badline/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +7 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +38 -3
- data/lib/badline/snapshot/image.rb +62 -9
- data/lib/badline/snapshot/vice/cbm2.rb +93 -0
- data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
- data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
- data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
- data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
- data/lib/badline/snapshot/vice/cias.rb +13 -9
- data/lib/badline/snapshot/vice/p500.rb +96 -0
- data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
- data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
- data/lib/badline/snapshot/vice/pet.rb +7 -3
- data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
- data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
- data/lib/badline/snapshot/vice/pet_via.rb +6 -5
- data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
- data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
- data/lib/badline/snapshot/vice.rb +3 -0
- data/lib/badline/snapshot.rb +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- 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
|