badline 0.2.1 → 0.4.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/hooks/session-start.sh +57 -0
  3. data/.claude/settings.json +14 -0
  4. data/CHANGELOG.md +107 -0
  5. data/CLAUDE.md +51 -16
  6. data/CONTRIBUTING.md +95 -3
  7. data/README.md +119 -51
  8. data/SECURITY.md +9 -0
  9. data/doc/pinned-behaviour.md +293 -39
  10. data/exe/badline-ruby +59 -0
  11. data/lib/badline/address_bus.rb +28 -14
  12. data/lib/badline/addressable.rb +1 -1
  13. data/lib/badline/audio/aiff.rb +8 -3
  14. data/lib/badline/audio/bare_player.rb +20 -3
  15. data/lib/badline/audio/cli.rb +17 -12
  16. data/lib/badline/audio/console.rb +12 -58
  17. data/lib/badline/audio/machine_player.rb +9 -6
  18. data/lib/badline/audio/playback.rb +3 -0
  19. data/lib/badline/audio/renderer.rb +3 -3
  20. data/lib/badline/audio/sdl_sink.rb +20 -52
  21. data/lib/badline/audio/stream.rb +117 -0
  22. data/lib/badline/audio/terminal.rb +90 -0
  23. data/lib/badline/audio.rb +3 -2
  24. data/lib/badline/cartridge/easy_flash.rb +6 -2
  25. data/lib/badline/cartridge/epyx_fastload.rb +1 -1
  26. data/lib/badline/cartridge/geo_ram.rb +63 -0
  27. data/lib/badline/cartridge/mach5.rb +1 -1
  28. data/lib/badline/cartridge/magic_desk.rb +5 -1
  29. data/lib/badline/cartridge/ocean.rb +5 -2
  30. data/lib/badline/cartridge/rex_utility.rb +1 -1
  31. data/lib/badline/cartridge/rgcd.rb +1 -1
  32. data/lib/badline/cartridge/simons_basic.rb +2 -2
  33. data/lib/badline/cartridge/westermann.rb +2 -2
  34. data/lib/badline/cartridge.rb +9 -0
  35. data/lib/badline/checkpoint.rb +98 -0
  36. data/lib/badline/cia/interrupt_register.rb +34 -10
  37. data/lib/badline/cia.rb +19 -10
  38. data/lib/badline/color_memory.rb +2 -2
  39. data/lib/badline/computer.rb +34 -14
  40. data/lib/badline/cpu.rb +2 -2
  41. data/lib/badline/gui/application.rb +70 -26
  42. data/lib/badline/gui/gamepads.rb +22 -22
  43. data/lib/badline/gui/joy_map.rb +1 -1
  44. data/lib/badline/gui/key_map.rb +6 -1
  45. data/lib/badline/gui/pane.rb +7 -6
  46. data/lib/badline/gui/screen_pane.rb +9 -13
  47. data/lib/badline/gui/window.rb +35 -20
  48. data/lib/badline/gui.rb +1 -1
  49. data/lib/badline/input/mouse1351.rb +21 -6
  50. data/lib/badline/input/paddles.rb +11 -6
  51. data/lib/badline/kernal_trap/channel.rb +102 -7
  52. data/lib/badline/kernal_trap/drive/block_commands.rb +108 -0
  53. data/lib/badline/kernal_trap/drive/channels.rb +53 -0
  54. data/lib/badline/kernal_trap/drive/memory.rb +109 -0
  55. data/lib/badline/kernal_trap/drive/parameters.rb +28 -0
  56. data/lib/badline/kernal_trap/drive/status.rb +3 -0
  57. data/lib/badline/kernal_trap/drive/write_file.rb +41 -0
  58. data/lib/badline/kernal_trap/drive/writes.rb +92 -0
  59. data/lib/badline/kernal_trap/drive.rb +109 -131
  60. data/lib/badline/kernal_trap/file.rb +6 -14
  61. data/lib/badline/kernal_trap/load.rb +6 -0
  62. data/lib/badline/kernal_trap/save.rb +13 -7
  63. data/lib/badline/keyboard.rb +19 -3
  64. data/lib/badline/media.rb +44 -13
  65. data/lib/badline/memory.rb +2 -2
  66. data/lib/badline/options.rb +209 -0
  67. data/lib/badline/ram_expansion/banking.rb +30 -0
  68. data/lib/badline/ram_expansion/plus256k.rb +38 -0
  69. data/lib/badline/ram_expansion/plus60k.rb +31 -0
  70. data/lib/badline/ram_expansion/unexpanded.rb +22 -0
  71. data/lib/badline/ram_expansion.rb +21 -0
  72. data/lib/badline/region.rb +51 -0
  73. data/lib/badline/rom.rb +14 -5
  74. data/lib/badline/sdl/events.rb +47 -0
  75. data/lib/badline/sdl/functions.rb +73 -0
  76. data/lib/badline/sdl.rb +67 -0
  77. data/lib/badline/sid/catch_up.rb +110 -0
  78. data/lib/badline/sid/decimator.rb +6 -1
  79. data/lib/badline/sid/filter.rb +5 -5
  80. data/lib/badline/sid/waveform/fast_forward.rb +10 -10
  81. data/lib/badline/sid/waveform/noise_writeback.rb +32 -14
  82. data/lib/badline/sid/waveform.rb +8 -8
  83. data/lib/badline/sid.rb +19 -93
  84. data/lib/badline/storage/d64_image.rb +10 -0
  85. data/lib/badline/storage/d71_image.rb +12 -0
  86. data/lib/badline/storage/d81_image.rb +9 -0
  87. data/lib/badline/storage/disk_image/bam.rb +111 -0
  88. data/lib/badline/storage/disk_image/directory.rb +79 -0
  89. data/lib/badline/storage/disk_image/writing.rb +110 -0
  90. data/lib/badline/storage/disk_image.rb +49 -4
  91. data/lib/badline/storage/host_directory.rb +17 -4
  92. data/lib/badline/storage/sid_file.rb +36 -2
  93. data/lib/badline/storage.rb +18 -1
  94. data/lib/badline/time_of_day.rb +11 -2
  95. data/lib/badline/version.rb +1 -1
  96. data/lib/badline/via/control_lines.rb +104 -0
  97. data/lib/badline/via/interrupt_register.rb +58 -0
  98. data/lib/badline/via/shift_register.rb +135 -0
  99. data/lib/badline/via/timer1.rb +82 -0
  100. data/lib/badline/via/timer2.rb +84 -0
  101. data/lib/badline/via.rb +262 -0
  102. data/lib/badline/vic/bank.rb +4 -3
  103. data/lib/badline/vic/color_patches.rb +6 -3
  104. data/lib/badline/vic/display_state.rb +4 -0
  105. data/lib/badline/vic/graphics_shifter.rb +49 -8
  106. data/lib/badline/vic/registers.rb +19 -27
  107. data/lib/badline/vic/sequencer.rb +18 -18
  108. data/lib/badline/vic/sequencer_output.rb +4 -4
  109. data/lib/badline/vic/sprite/internal_bus.rb +4 -0
  110. data/lib/badline/vic/sprite/shifter.rb +25 -3
  111. data/lib/badline/vic/sprite.rb +5 -4
  112. data/lib/badline/vic/sprites.rb +8 -3
  113. data/lib/badline/vic.rb +98 -46
  114. data/lib/badline.rb +5 -0
  115. data/packaging/homebrew/badline.rb +34 -0
  116. metadata +37 -20
  117. data/exe/badline +0 -75
  118. data/exe/badline-sid +0 -34
  119. data/lib/badline/audio/options.rb +0 -137
@@ -24,7 +24,9 @@ only catches the rows that happen to move.
24
24
  - [VIC graphics pipeline](#vic-graphics-pipeline)
25
25
  - [VIC phi1 bus](#vic-phi1-bus)
26
26
  - [VIC light pen](#vic-light-pen)
27
+ - [VIC-II 8565](#vic-ii-8565)
27
28
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
29
+ - [CIA 6526A interrupt register](#cia-6526a-interrupt-register)
28
30
  - [CIA serial shift register](#cia-serial-shift-register)
29
31
  - [6510 I/O port](#6510-io-port)
30
32
  - [SID oscillator](#sid-oscillator)
@@ -400,6 +402,14 @@ only catches the rows that happen to move.
400
402
  - Spec guard: *#collide_upto* in
401
403
  [`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb). Its first
402
404
  four examples each fail on a one-pixel change.
405
+ - $D01E and $D01F are read-only. A write, including the write-back of a
406
+ read-modify-write such as `LSR $D01E`, changes neither register.
407
+ - Pinned by `general/fuxxortest/ef2-inst4a`, which shifts each frame's
408
+ collision out of $D01E with `LSR`. Storing the shifted value back
409
+ leaves bit 0 set for the next frame, which reads as a collision one
410
+ pixel to the right of every real one.
411
+ - Spec guard: *with a collision register* in
412
+ [`vic/registers_spec.rb`](../spec/badline/vic/registers_spec.rb).
403
413
 
404
414
  ## VIC border and idle state
405
415
 
@@ -576,6 +586,31 @@ only catches the rows that happen to move.
576
586
  cells take their colour from the halted opcode.
577
587
  - Spec guard: *an FLI match in column 13* in
578
588
  [`vic_spec.rb`](../spec/badline/vic_spec.rb).
589
+ - On the 6569, a match that opens display state in a g-access column, which
590
+ is a DMA delay out of idle state, moves that column's idle g-access from
591
+ `$3fff` (or `$39ff`) to `$38ff`, but only when YSCROLL is nonzero, so
592
+ only when the trigger line's low three bits are nonzero. The address wins over
593
+ ECM's `$39ff`. Nothing else enters the condition: not the column, BMM,
594
+ RC, VC or the previous fetch. The column is the one just before the
595
+ first display-state g-access. This rule is empirical and fitted to the
596
+ two tests below. It is the same condition the Denise emulator converged
597
+ on, and no hardware explanation for the YSCROLL gate is known. The
598
+ readme lists `$38ff` for every 6569 it measured. Among 8565s it lists
599
+ `$3807`, `$38c7`, `$38d7` and `$38ff`, varying from chip to chip, and no
600
+ testbench row pins one, so the 8565 keeps `$3fff` there.
601
+ `VIC::DMA_DELAY_IDLE_ADDRESS` holds the 6569's address.
602
+ - Pinned by `vsp-tester`, which triggers on raster `$32` with YSCROLL 2
603
+ and reads the address back through sprite collisions. It passes on
604
+ `$38ff`, `$3807`, `$38c7` or `$38d7`, and reports `$3fff` (exit `$ff`)
605
+ without this rule.
606
+ - Pinned by `colorfetchbug/main`, whose only idle trigger is on raster
607
+ `$30` with YSCROLL 0 and whose reference shows `$3fff` there. Without
608
+ the YSCROLL gate it fails by 7 px. `sequencer-bug` (YSCROLL 3) reads
609
+ `$38ff` and passes either way.
610
+ - Spec guard: *reads the idle byte at $38ff when YSCROLL is nonzero* and
611
+ *reads the idle byte at $3fff when YSCROLL is 0*, plus the 8565's
612
+ *reads the idle byte at $3fff when YSCROLL is nonzero*, in
613
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
579
614
  - These rows can't be read as pixel counts. The sweep became readable by
580
615
  OCRing each reference PNG against `lib/badline/roms/character.rom` and
581
616
  matching every display row back to its offset in screen memory, so a diff
@@ -650,12 +685,24 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
650
685
  - Pinned by `modesplit` (48 → 1222 px painting whole groups, 428 with
651
686
  MCM read at pixel 4), the `videomode` rows and `vicii_reg_timing`
652
687
  (pass → 274).
653
- - `videomode2` and `videomode-y` disagree on where a falling BMM shows:
654
- pixel 6 in `videomode2`, pixel 5 in `videomode-y` and in `modesplit`'s
655
- ECM+BMM → ECM split. The readme says these delays vary with the chip
656
- and its temperature. badline keeps VICE's pixel 6, which passes
657
- `videomode2` and leaves `videomode-y` 1 px off and all 48 px left in
658
- `modesplit`, one pixel on each of its first-section lines.
688
+ - A falling BMM shows at pixel 5 instead of 6 unless it leaves hi-res
689
+ bitmap: out of multicolour bitmap, and out of the invalid ECM+BMM
690
+ modes, whether ECM falls with it or stays.
691
+ - Pinned by `modesplit` (48 px → pass), whose first section drops BMM
692
+ out of ECM+BMM, by the E+B row of `vicii_reg_timing-a5` and `-ff`
693
+ (7 px each → pass), which drops ECM and BMM together, and by
694
+ `videomode-y` (1 px → pass), which drops it out of multicolour
695
+ bitmap. The hi-res exception is pinned by `videomode2` and the BMM
696
+ row of `vicii_reg_timing` (pass → 1 and 7 px without it).
697
+ - Two references disagree, and both are marked unsafe in the
698
+ testlist: `videomode-v` drops BMM out of multicolour bitmap and
699
+ `videomode-w` out of ECM+BMM, and both want pixel 6 (+1 px each).
700
+ The readme says these delays vary with the chip and its
701
+ temperature.
702
+ - Spec guard: *takes a BMM falling out of hi-res bitmap at pixel 6*,
703
+ *out of ECM+BMM at pixel 5* and *out of multicolour bitmap at
704
+ pixel 5* in
705
+ [`vic/graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
659
706
  - An MCM that falls out of the invalid ECM+MCM text mode is a pixel
660
707
  later on both counts. The lookup stays black through pixel 4 and
661
708
  changes at pixel 5, and the pairs are read through pixel 7, with
@@ -722,12 +769,125 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
722
769
  reference, as `makeref` corrects it: the test fixes up the tail bytes of
723
770
  pre-R03 dumps before comparing. All five pages match, the raster-read
724
771
  pages included (see [VIC raster IRQ phase](#vic-raster-irq-phase)).
725
- - The 6569's offset is 2 half-pixels and the 8565's is 1, so
726
- `lp-trigger/test2new`, which wants the 8565, would fail by design.
727
- badline models only the 6569, and `bin/testbench` skips `vicii-new` rows.
772
+ - The 6569's offset is 2 half-pixels and the 8565's is 1 (see
773
+ [VIC-II 8565](#vic-ii-8565)).
728
774
  - Pinned by `lplatency`, `lp-trigger`, and the `fldscroll` tests, which sync
729
775
  through the light pen instead of the double IRQ.
730
776
 
777
+ ## VIC-II 8565
778
+
779
+ The 8565 (`VIC.new(model: :mos8565)`, `Computer.new(vic_model:
780
+ :mos8565)`), fitted to the C64C, runs every rule above except the ones
781
+ below. They follow VICE x64sc's model checks (`color_latency` clear), and
782
+ each is gated on the model and was knocked out: removing it fails the rows
783
+ named, all in `testbench-vicii-new`, and moves no 6569 row. Pixel counts
784
+ are the row's diff with the rule removed.
785
+
786
+ - **Grey dots.** Where the 6569 still shows a colour register's old value
787
+ on the first pixel after a write, the 8565 shows light grey
788
+ (`VIC::GREY_DOT`, `$f`), for the border, the background registers
789
+ (`VIC::ColorPatches`) and the sprite colours (`VIC#log_sprite_change`).
790
+ A write that leaves the value as it was shows its dot too.
791
+ - The background and border dot is pinned by `rmwtest` (1655 px), every
792
+ spritesplit row whose `$d021` staircase it crosses (60 px each) and
793
+ `vicii_reg_timing` (275 px, 282/289 for `-a5`/`-ff`). The sprite dot
794
+ by `ss-hires-color` and `ss-mc-color0`/`1`/`2` (44 px each) and
795
+ `vicii_reg_timing` (136 px). The same-value dot by `rmwtest` (775 px)
796
+ and `vicii_reg_timing` (22 px), whose read-modify-write instructions
797
+ store each value twice.
798
+ - Spec guard: the *on the 8565* group in
799
+ [`color_patches_spec.rb`](../spec/badline/vic/color_patches_spec.rb),
800
+ *shows a grey dot on the pixel before a new sprite color* in
801
+ [`sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb) and *shows a
802
+ grey dot for a background write of the same color* in
803
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
804
+ - **Sprite multicolour.** `$d01c` reaches the sprite sequencers a pixel
805
+ sooner, 6 pixels on instead of 7, and works on the multicolour
806
+ flip-flop (VICE `update_sprite_mc_bits_8565`). The X match leaves the
807
+ flip-flop set in hi-res as well as clear after the first multicolour
808
+ pixel. A change flips it only when it lands between the two halves of
809
+ an expanded pixel, where a switch to hi-res sets it instead. A hi-res
810
+ pixel with the flip-flop clear sets it and loads nothing, so the latch
811
+ holds its last pixel.
812
+ - The delay is pinned by `ss-hires-mc`, `ss-mc-hires` and their `-exp`
813
+ twins (528/88/1452/88 px at 7). The flip-flop after the X match by
814
+ `ss-hires-mc`, `ss-pri`, `ss-unexp-exp-hires` (168 px each) and
815
+ `ss-hires-mc-exp`, `ss-pri-exp`, `ss-exp-unexp-hires` (336 px each).
816
+ The flip on a change by `ss-hires-mc-exp` (1364 px) and
817
+ `ss-mc-hires-exp` (44 px), and the hi-res hold by `ss-mc-hires` and
818
+ `ss-mc-hires-exp` (44 px each).
819
+ - Spec guard: *reaches the multicolor flip-flop six pixels on* in
820
+ [`sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb) and the *with
821
+ an 8565* group of *mid-line writes* in
822
+ [`sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
823
+ - **The g-access addresses with the `$d011` of the column before**,
824
+ whole, instead of the 6569's hold of BMM and, on the character ROM, ECM.
825
+ - Pinned by `modesplit` (598 px), `vicii_reg_timing` (92, 99/106 px),
826
+ `fetchsplit` (180 px) and every videomode row but `videomode1` (3 to
827
+ 16 px).
828
+ - Spec guard: *addresses with the $d011 of the column before* in
829
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
830
+ - **ECM and BMM take hold at the next group's pixel 0**, rising or
831
+ falling, where the 6569 takes them at pixel 4 as they rise and 5 or 6 as
832
+ they fall. MCM keeps the 6569's timing, but an MCM that falls out of
833
+ ECM+MCM does so on time instead of a pixel late.
834
+ - The pixel 0 is pinned by `modesplit` (916 px), `vicii_reg_timing` (86,
835
+ 100/114 px) and every videomode row (6 to 13 px). The on-time MCM by
836
+ `videomode1` and `videomode-z` (2 px each).
837
+ - Spec guard: the *on the 8565* group in
838
+ [`graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
839
+ - **Out of an invalid mode into multicolour bitmap, a background pixel 0
840
+ is still black, and into hi-res text any pixel 0 is.** Into multicolour
841
+ text and ECM text it shows on time.
842
+ - The multicolour bitmap case is pinned by `videomode-v` and `videomode2`
843
+ (1 px each) and `modesplit` (92 → 124 px), whose `%00` and `%01` pairs
844
+ after an ECM+MCM or ECM+BMM+MCM split keep their first pixel black.
845
+ `videomode-y` shows a `%01` pair on time into multicolour text.
846
+ - The hi-res text case is empirical, pinned by the E+B row of
847
+ `vicii_reg_timing-a5` and `-ff` (pass and 7 px, 7 and 14 px without
848
+ it), which drops ECM+BMM back to text. All three `vicii_reg_timing`
849
+ references keep that pixel 0 black, a foreground pixel in `-a5` and
850
+ `-ff`. `$d021` is black there, so no reference tells a background
851
+ pixel 0 apart, and the rule blackens both.
852
+ - `videomode-w` shows ECM+BMM into ECM text on time (1 px if it goes
853
+ black), while `modesplit`'s section 1 keeps the same move black (48 of
854
+ its 92 px). The two references disagree about the same move, and the
855
+ videomode one is kept.
856
+ - Spec guard: *keeps a background pixel 0 black out of an invalid mode
857
+ into multicolour bitmap*, *keeps pixel 0 black out of an invalid mode
858
+ into hi-res text*, *shows pixel 0 on time out of an invalid mode into
859
+ multicolour text* and *shows pixel 0 on time out of an invalid mode into
860
+ ECM text* in
861
+ [`graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
862
+ - **The light pen latches one extra half-pixel**, where the 6569 adds two.
863
+ - Pinned by `lp-trigger/test2new`, which measures the trigger delay and
864
+ fails with two.
865
+ - Spec guard: *adds one extra half-pixel on the 8565* in
866
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
867
+
868
+ What the 8565 references don't settle, and so what stays as it is:
869
+
870
+ - The pixel 0 of a group that leaves ECM or BMM is not consistent across
871
+ the references. Moving every falling edge to pixel 1 passes `modesplit`
872
+ but fails every videomode row but `videomode1` and all three
873
+ `vicii_reg_timing` rows, and the same move out of hi-res bitmap into
874
+ text is the new mode in `vicii_reg_timing-a5` and the old one in `-ff`.
875
+ The videomode readme says these delays "may depend on the type of VICII,
876
+ and the temperature of the chip". So `modesplit` (92 px) and
877
+ `vicii_reg_timing-ff` (7 px, the hi-res bitmap into text row) stay FAIL.
878
+ No emulator in the testbench results passes `vicii_reg_timing-a5` or
879
+ `-ff` on the 8565, and only Hoxs64 passes `modesplit`, against the
880
+ `8565early` reference.
881
+ - `fetchsplit` (154 px) stays FAIL: its readme says its 8565 artefacts
882
+ differ from chip to chip and change as the machine warms up, and its
883
+ `M1` and `M7` captures differ. The differences sit in the first character
884
+ after a `$dd00` bank switch on some lines. Delaying every rising, or
885
+ every falling, bank bit by a cycle on the 8565 makes it 656 or 752 px.
886
+ - VICE's idle g-access also reads ECM from the `$d011` of the column
887
+ before on the 8565. No testprog tells it apart, so the 8565 keeps the
888
+ 6569's idle access, without the 6569's `$38ff` read where a DMA delay
889
+ starts.
890
+
731
891
  ## CIA 6526 timer pipeline
732
892
 
733
893
  - Start and stop go through two stages. A force load lands one tick after
@@ -759,13 +919,17 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
759
919
  `CIA/ciavarious/cia3` K/L, `cia3a` D/H, `cia4` X, `cia8` A/C/F/J/L
760
920
  (the readme's old-versus-new CIA cells) and `CIA/cia-timer/cia-timer-oldcias`,
761
921
  and ported from VICE's `CIA_IM_TBB`.
762
- - The modelled revision is the **6526**, not the 6526A, matching
763
- `Lorenz.d81`. That is all the `(*1)` cells of `cia1ta`/`cia1tb` measure,
764
- and it is an ICR difference, not a counter one. Those cells read the ICR
765
- on the very cycle the underflow flag rises, so the source bit is up while
766
- IR is not: they read `$01`/`$02`, where a 6526A reads `$81`/`$82`. Counter
767
- readback is identical on both revisions. `Lorenznew.d81` expects the
768
- 6526A and must not be mixed in.
922
+ - Every rule in this section is the **6526**'s, the chip `CIA.new`
923
+ builds unless given `model: :mos6526a`, and the one `Lorenz.d81`
924
+ expects. That is all the `(*1)` cells of `cia1ta`/`cia1tb` measure, and
925
+ it is an ICR difference, not a counter one. Those cells read the ICR on
926
+ the very cycle the underflow flag rises, so the source bit is up while
927
+ IR is not: they read `$01`/`$02`, where a 6526A reads `$81`/`$82`.
928
+ Counter readback is identical on both revisions. `Lorenznew.d81` expects
929
+ the 6526A and must not be mixed into the `lorenz` chain. Its tests run
930
+ one at a time on a 6526A machine in `testbench-cia-new` instead. See
931
+ [CIA 6526A interrupt register](#cia-6526a-interrupt-register) for what
932
+ changes.
769
933
  - Timer B's cascade decodes CRB, not CRA. Every count source (ø2, a CNT
770
934
  edge, a cascaded timer A underflow) drives the same two-stage
771
935
  count-enable pipeline, so a timer A underflow decrements timer B two
@@ -793,6 +957,65 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
793
957
  acknowledge* and *6526 timer B bug* groups guard the three ICR rules
794
958
  above, on a bare CIA.
795
959
 
960
+ ## CIA 6526A interrupt register
961
+
962
+ The 6526A (`CIA.new(model: :mos6526a)`, `Computer.new(cia_model:
963
+ :mos6526a)`), fitted to the C64C, differs from the 6526 only in its
964
+ interrupt control register. Its timers and shift register run the rules
965
+ above unchanged: every `testbench-cia-new` row passes on the 6526's
966
+ timer and serial code, the long counter sweeps `cia1tanew`, `cia1tbnew`,
967
+ `cia2tanew` and `cia2tbnew` included. VICE's `ciatimer.c` has no model
968
+ check either. Each rule below is gated on the model in
969
+ [`cia/interrupt_register.rb`](../lib/badline/cia/interrupt_register.rb),
970
+ and each was knocked out: removing it fails the rows named.
971
+
972
+ - **IR on the flag's cycle.** A source flag whose mask bit is set raises
973
+ IR on the cycle the flag rises, not a cycle later. The exception is a
974
+ flag that rises on the cycle after an ICR read: that one raises IR a
975
+ cycle later, as on the 6526 (VICE's `rdi + 1 == rclk`).
976
+ - The early IR is pinned by `CIA/irqdelay/irqdelay-new`,
977
+ `irqdelay-oneshot-new`, `irqdelay2-new`, `irqdelay-cia1-4-new` and
978
+ `irqdelay-cia1-oneshot-4-new`, `interrupts/branchquirk/branchquirk-new`
979
+ and `branchquirk-nminew`, `interrupts/cia-int/cia-int-irq-new` and
980
+ `cia-int-nmi-new`, `interrupts/irq-ackn-bug/cia1new` and `cia2new`,
981
+ `interrupts/irqnmi/irqnmi-new`, `CIA/CIA-AcountsB/cmp-b-counts-a-new`,
982
+ `CIA/cia-timer/cia-timer-newcias`, `CIA/timerbasics/timer_test1_new`
983
+ and `CIA/dd0dtest/dd0dtest`.
984
+ - The exception after a read is pinned by `cia-int-irq-new`,
985
+ `cia-int-nmi-new` and `dd0dtest` test 17, which fail when every flag
986
+ raises IR at once.
987
+ - **A read sees an IR about to rise.** A read on the cycle before IR is
988
+ due returns IR set, and cancels the assert as on the 6526. On the 6526,
989
+ only a read on the next cycle sees it.
990
+ - Pinned by `dd0dtest` test 17. The two reads of `inc $dd0d,x` land on
991
+ the cycles before and of timer A's flag, so the second sees `$81` and
992
+ the RMW writes `$81`/`$82` back, arming timer B, whose NMI handler
993
+ then reads `$82`. Without the rule the second read sees `$01`, timer B
994
+ stays masked and no NMI comes.
995
+ - **The acknowledge holds every bit.** A read releases the interrupt line
996
+ at once, but the bits it read, sources and IR alike, stay readable on
997
+ the next cycle, along with anything flagged since. They clear the cycle
998
+ after that. The 6526 holds only IR (see *Old-CIA acknowledge* above).
999
+ - Pinned by `dd0dtest` tests 18 and 19. Their `inc $dd0d,x` reads `$81`
1000
+ on the flag cycle and again on the next cycle, where a 6526 reads
1001
+ `$80`, so the RMW writes `$82` and arms timer B.
1002
+ - **A mask write raises IR a cycle later**, where the 6526 takes two.
1003
+ - Pinned by Lorenz `imrnew`.
1004
+ - **No timer B bug.** An underflow on the cycle after an ICR read keeps
1005
+ its flag.
1006
+ - Pinned by `CIA/ciavarious/cia3new`, `cia3anew`, `cia4new` and
1007
+ `cia8new` (the readme's old-versus-new cells) and
1008
+ `CIA/cia-timer/cia-timer-newcias`.
1009
+ - Two of VICE's model checks are left out, because no testprog tells
1010
+ them apart. VICE stops a mask write from raising IR on the cycle after
1011
+ an ICR read, and applies the 6526's mask cancel (*Old-CIA mask cancel*
1012
+ above) only to the 6526. Adding the first, or dropping the second for
1013
+ the 6526A, passes every `testbench-cia-new` row, so the 6526A keeps the
1014
+ 6526's behaviour on both.
1015
+ - Spec guard: *the 6526A interrupt register* in
1016
+ [`cia_spec.rb`](../spec/badline/cia_spec.rb), one example per rule,
1017
+ each failing when its rule is knocked out.
1018
+
796
1019
  ## CIA serial shift register
797
1020
 
798
1021
  - The register counts itself empty on the 15th timer A underflow, when the
@@ -858,6 +1081,15 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
858
1081
  before the KERNAL does.
859
1082
  - Spec guard: *when powered on* in
860
1083
  [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1084
+ - A write to `$00` or `$01` goes to the port, and the RAM below takes the
1085
+ byte the VIC fetched in the phi1 half of the same cycle, as in VICE's
1086
+ `zero_store`. Only the VIC reads that RAM.
1087
+ - Pinned by `general/ram0001/test1`, which puts the byte in `$3fff` for
1088
+ the idle fetch, and `general/fuxxortest/ef2-inst4a`, which uses two
1089
+ sprite pointer fetches. Both read the RAM back through sprite
1090
+ collisions.
1091
+ - Spec guard: *when a program writes to the port* in
1092
+ [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
861
1093
 
862
1094
  ## SID oscillator
863
1095
 
@@ -902,6 +1134,15 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
902
1134
  [`sid_spec.rb`](../spec/badline/sid_spec.rb): a catch-up that
903
1135
  fast-forwards across a rise carries the shift still in flight into the
904
1136
  next span.
1137
+ - Setting the test bit onto noise combined with another waveform writes
1138
+ that combination's output back into the LFSR first, from the accumulator
1139
+ it had, before the bit clears it.
1140
+ - Pinned by `SID/noiselfsrinit`'s `simple` and `scan` (both chips). Their
1141
+ `$f8`/`$80` pairs set the test bit onto all four waveforms from noise
1142
+ alone, and zero the register only if this writes back. The release that
1143
+ follows goes to noise alone, which writes nothing back (below).
1144
+ - Spec guard: *as the test bit rises* in
1145
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
905
1146
  - The test bit does not clear the LFSR. It stalls it halfway through a
906
1147
  shift with bit 22 forced high. While the bit is held, every bit bleeds up
907
1148
  to `$7fffff`: after `$950000` cycles on the 8580 (`SID/bitfade`'s
@@ -921,28 +1162,33 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
921
1162
  that varies with the chip's temperature (`SID/wf12nsr`'s
922
1163
  `quicktest.prg` on a 6581), which nothing scored depends on.
923
1164
  - Spec guard: *bleeding through a held test bit* in
924
- [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb). Before it does, the old waveform's output is
1165
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
1166
+ - Before the bit clocks in on release, the old waveform's output is
925
1167
  written back only for some waveform changes. Noise has to have been
926
- combined before the release and still be selected after it. A change to
927
- noise alone writes nothing back unless all four waveforms were selected
928
- before. A change to pulse+noise writes nothing back, nor does a change
929
- from pulse+noise to sawtooth+noise. On the 6581, trading triangle for
930
- sawtooth or back writes nothing back. The rule follows libresidfp's
931
- `do_writeback`. What is written is the writeback shape below, not the
932
- OSC3 read. One more case is not in that rule: on the 6581, a change that
933
- keeps only pulse+noise of the old waveform (`D`/`E`/`F`→`C`, `D`→`E`)
934
- writes the three lowest noise lines low (`$f8`).
935
- - Pinned by `SID/wb_testsuite` (the `9`/`A`/`D`/`E`→`8` rows on both
1168
+ combined before the release and still be combined after it, so a change
1169
+ to noise alone writes nothing back. A change to pulse+noise writes
1170
+ nothing back, nor does a change from pulse+noise to sawtooth+noise. On
1171
+ the 6581, trading triangle for sawtooth or back writes nothing back. The
1172
+ rule follows libresidfp's `do_writeback`. What is written is the
1173
+ writeback shape below, not the OSC3 read. Two more cases are not in that
1174
+ rule. On the 6581, a change that keeps only pulse+noise of the old
1175
+ waveform (`D`/`E`/`F`→`C`, `D`→`E`) writes the three lowest noise lines
1176
+ low (`$f8`). On the 8580, a change from pulse+noise to all four
1177
+ (`C`→`F`) writes every line low.
1178
+ - Pinned by `SID/wb_testsuite` (the `9`/`A`/`D`/`E`/`F`→`8` rows on both
936
1179
  chips, and the 6581's `9`↔`A`, `9`/`A`→`C` and `D`→`A` rows) and by
937
1180
  `SID/noisewriteback`'s `noise_writeback_test1`. The 6581's `$f8` case
938
1181
  is pinned by its `D`→`C`, `D`→`E`, `E`→`C` and `F`→`C` rows, and
939
1182
  dropping it fails all four. The 8580's `C`→`A` row pins the
940
1183
  pulse+noise to sawtooth+noise exception: without it the release writes
941
- the 8580's `$fc` and the row fails.
942
- - Known conflict: the 6581's `F`→`8` row expects no writeback, but
943
- `SID/noiselfsrinit`'s `simple` and `scan` zero the register with
944
- `$f8`/`$80` pairs, which needs `F`→`8` to write back. The row stays
945
- failing.
1184
+ the 8580's `$fc` and the row fails. The 8580's `C`→`F` row pins the
1185
+ all-low case: writing the old waveform's `$fc` back makes the first read
1186
+ `$fc`, where the row expects the `$6c` that `F`→`F` reads. Both 8580
1187
+ samplings in `SID/wb_testsuite/samplings` read `$6c` for `C`→`F`.
1188
+ - `F`→`8` wrote back until the test bit's rise (above) took over
1189
+ zeroing the register for `SID/noiselfsrinit`. Every sampling in
1190
+ `SID/wb_testsuite/samplings`, both chips, reads `F`→`8` as a plain
1191
+ shift.
946
1192
  - Spec guard: *as the test bit falls* in
947
1193
  [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
948
1194
  - A combined waveform shorts the shapers onto the lines the oscillator reads
@@ -986,11 +1232,15 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
986
1232
  fails wfc and the `C`→`9`/`C`→`E` rows; applying the lone-line rule to
987
1233
  the read as well makes `noise_writeback_test2` read `$00` on both
988
1234
  chips.
989
- - Not fitted: the 6581's pulse+noise row of `SID/wf12nsr`. The program
990
- never sets voice 3's pulse width, and the 6581 reference reads as if
991
- the pulse was low when the test bit fell, grounding every line. From
992
- power-on the width is zero and the pulse high. Forcing a width of
993
- `$800` before the row makes it pass.
1235
+ - Can't pass: the 6581's pulse+noise row of `SID/wf12nsr`. The program
1236
+ never writes voice 3's pulse width, so the row depends on whatever width
1237
+ the machine it was recorded on held. The 6581 reference reads `$00`
1238
+ right after the release into pulse+noise at frequency `$ffff`, as if
1239
+ the pulse was still low and grounding every line. After power-on or
1240
+ reset the width is zero and the pulse is high. With the width set
1241
+ before the program runs, the row passes from `$1a0` up and fails from
1242
+ `$198` down. The 8580 program passes from a zero width. `SID/wf12nsr`'s `quicktest.prg` doesn't set the width
1243
+ either. The row stays failing.
994
1244
  - Spec guard: *writes a lone line of a noise combination back low*, *with
995
1245
  pulse+noise on the 8580* and *as the test bit falls* in
996
1246
  [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
@@ -1098,9 +1348,13 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
1098
1348
  - A tune's init and play routines must run with the ROMs banked to match
1099
1349
  the address they live at, following libsidplayfp's iomap: `$37` below
1100
1350
  `$a000`, `$36` under BASIC, `$34` in the `$d000` I/O window, `$35` under
1101
- the KERNAL. Restore `$01` afterwards so the caller's banking survives.
1102
- This entry is the rule, and it should outlive whichever code carries it.
1351
+ the KERNAL. A routine below `$a000` gets `$36` too when the tune's image
1352
+ reaches under BASIC, as VSID does, since it may call into that part.
1353
+ Restore `$01` afterwards so the caller's banking survives. This entry is
1354
+ the rule, and it should outlive whichever code carries it.
1103
1355
  - Derived against six OneLoad64 tunes (Galway, Tel, Gray, Dunn, Cooksey).
1356
+ Green Beret's init sits at `$9fe3` and calls its own code at `$aaca`,
1357
+ which forces the `$36` below `$a000`.
1104
1358
  `SIDFile#bank_for` is the one copy of the map: `Driver` wraps both calls
1105
1359
  in a `$01` save/bank/restore, and `BarePlayer#dispatch` pokes it before
1106
1360
  handing the CPU the stub.
data/exe/badline-ruby ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ require "badline/options"
8
+
9
+ begin
10
+ options = Badline::Options.parse(ARGV)
11
+ rescue Badline::Options::Error => e
12
+ warn "badline-ruby: #{e.message}"
13
+ warn "Try 'badline-ruby --help' for more information."
14
+ exit 1
15
+ end
16
+
17
+ if options.help?
18
+ puts options.help
19
+ exit
20
+ end
21
+
22
+ jit_available = defined?(RubyVM::YJIT) && !RubyVM::YJIT.enabled?
23
+ RubyVM::YJIT.enable if options.jit? && jit_available
24
+
25
+ require "badline"
26
+
27
+ if options.headless?
28
+ begin
29
+ Badline::Audio::CLI.new(options, sink: Badline::Audio::SDLSink.method(:new),
30
+ console: Badline::Audio::Console.method(:new)).run
31
+ rescue Badline::Audio::CLI::Error => e
32
+ warn "badline-ruby: #{e.message}"
33
+ exit 1
34
+ rescue Interrupt
35
+ exit 130
36
+ end
37
+ exit
38
+ end
39
+
40
+ begin
41
+ require "badline/gui"
42
+ rescue Badline::SDL::Error => e
43
+ warn "badline-ruby: can't open the window: #{e.message}. " \
44
+ "Install SDL2 with 'brew install sdl2' or 'apt install libsdl2-2.0-0'."
45
+ exit 1
46
+ end
47
+
48
+ begin
49
+ application = Badline::GUI::Application.new(media_path: options.media_path, autostart: options.autostart?,
50
+ song: options.song, sid_model: options.sid_model,
51
+ sound: options.sound?, disk: { read_only: options.read_only? })
52
+ rescue Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
53
+ Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
54
+ Badline::Cartridge::UnsupportedTypeError => e
55
+ warn "badline-ruby: #{options.media_path}: #{e.message}"
56
+ exit 1
57
+ end
58
+
59
+ application.run
@@ -49,10 +49,14 @@ module Badline
49
49
 
50
50
  attr_reader :io_port, :ram, :basic_rom, :character_rom, :kernal_rom,
51
51
  :vic, :sid, :color_ram, :cia1, :cia2, :keyboard, :joystick1, :joystick2,
52
- :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette
52
+ :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region, :video_ram
53
53
 
54
- def initialize(sid_model: :mos6581)
54
+ # ram_expansion fits a +60K (:plus60k) or +256K (:plus256k).
55
+ def initialize(sid_model: :mos6581, cia_model: :mos6526, vic_model: :mos6569, region: Region::PAL,
56
+ ram_expansion: nil)
57
+ @region = region
55
58
  @ram = Memory.new(RAM_POWER_ON, length: 2**16, start: 0)
59
+ @ram_expansion = RAMExpansion.build(ram_expansion, @ram) { update_overlays! }
56
60
  @cartridge = nil
57
61
  @debug_register = nil
58
62
 
@@ -64,9 +68,9 @@ module Badline
64
68
  @joystick1 = Joystick.new
65
69
  @joystick2 = Joystick.new
66
70
  @control_ports = ControlPorts.new(keyboard: @keyboard, joystick1: @joystick1, joystick2: @joystick2)
67
- @vic = VIC.new(self)
68
- @cia1 = CIA.new(start: 0xdc00, peripheral: @control_ports)
69
- @cia2 = CIA.new(start: 0xdd00)
71
+ @vic = VIC.new(self, model: vic_model, region:)
72
+ @cia1 = CIA.new(start: 0xdc00, peripheral: @control_ports, model: cia_model, region:)
73
+ @cia2 = CIA.new(start: 0xdd00, model: cia_model, region:)
70
74
  @control_ports.port_a_source = @cia1
71
75
  @cia1.on_port_b4_change { |high| @vic.lightpen_level(high) }
72
76
  @sid = SID.new(model: sid_model, pots: @control_ports)
@@ -97,11 +101,14 @@ module Badline
97
101
 
98
102
  def power_on!
99
103
  @ram.clear!(RAM_POWER_ON)
104
+ @ram_expansion.power_on!
100
105
  end
101
106
 
102
- # The 6510's RES line clears the port's direction and output registers.
103
- # The floating bits keep their charge.
104
- def reset_port!
107
+ # The RES line clears the 6510 port's direction and output registers,
108
+ # and a RAM expansion's bank register. The port's floating bits keep
109
+ # their charge.
110
+ def reset!
111
+ @ram_expansion.reset!
105
112
  @port_ddr = 0x00
106
113
  @port_out = 0x00
107
114
  update_port!
@@ -124,8 +131,12 @@ module Badline
124
131
  @read_pages[addr >> 8].peek(addr)
125
132
  end
126
133
 
134
+ # A write to $00 or $01 goes to the port, but the RAM below is still
135
+ # write-enabled, and takes whatever is left on the bus: the byte the VIC
136
+ # fetched in the phi1 half of the cycle.
127
137
  def poke(addr, value)
128
138
  if addr < 0x02
139
+ @ram.poke(addr, @vic.phi1_data)
129
140
  addr.zero? ? @port_ddr = value : @port_out = value
130
141
  update_port!
131
142
  else
@@ -162,8 +173,8 @@ module Badline
162
173
  def update_overlays!
163
174
  @ultimax = @cartridge ? @cartridge.ultimax? : false
164
175
  @phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
165
- @read_pages.fill(@ram)
166
- @write_pages.fill(@ram)
176
+ @ram_expansion.map(@read_pages, @write_pages)
177
+ @video_ram = @ram_expansion.video_ram
167
178
 
168
179
  @ultimax ? map_ultimax_pages : map_banked_pages
169
180
  end
@@ -180,10 +191,12 @@ module Badline
180
191
  end
181
192
  end
182
193
 
194
+ # EXROM and GAME select the cartridge whether or not it has a chip
195
+ # there, and an empty socket leaves the bus floating.
183
196
  def map_rom_overlays
184
- @read_pages.fill(@cartridge.roml, 0x80, 0x20) if roml?
197
+ @read_pages.fill(@cartridge.roml || @open_bus, 0x80, 0x20) if roml?
185
198
  if romh?
186
- @read_pages.fill(@cartridge.romh, 0xa0, 0x20)
199
+ @read_pages.fill(@cartridge.romh || @open_bus, 0xa0, 0x20)
187
200
  elsif basic?
188
201
  @read_pages.fill(basic_rom, 0xa0, 0x20)
189
202
  end
@@ -236,6 +249,7 @@ module Badline
236
249
  }.each do |chip, pages|
237
250
  pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
238
251
  end
252
+ @ram_expansion.map_io(@read_pages, @write_pages)
239
253
  @read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
240
254
  map_cartridge_io if @cartridge
241
255
  end
@@ -254,11 +268,11 @@ module Badline
254
268
  end
255
269
 
256
270
  def roml?
257
- @cartridge&.roml && @cartridge.exrom.zero? && io_port.kernal? && io_port.basic?
271
+ @cartridge&.exrom&.zero? && io_port.kernal? && io_port.basic?
258
272
  end
259
273
 
260
274
  def romh?
261
- @cartridge&.romh && @cartridge.exrom.zero? && @cartridge.game.zero? && io_port.kernal?
275
+ @cartridge&.exrom&.zero? && @cartridge.game.zero? && io_port.kernal?
262
276
  end
263
277
 
264
278
  # In 16K mode LORAM alone leaves $d000 as RAM, where it still maps I/O.
@@ -51,7 +51,7 @@ module Badline
51
51
 
52
52
  private
53
53
 
54
- def index(addr)
54
+ def offset_of(addr)
55
55
  i = addr - @start
56
56
  raise OutOfBoundsError, "#{addr.inspect} (#{range})" unless i >= 0 && i < @length
57
57
 
@@ -25,11 +25,16 @@ module Badline
25
25
  end
26
26
 
27
27
  # Sign bit, 15-bit biased exponent, then a 64-bit mantissa that keeps
28
- # its leading one rather than hiding it.
28
+ # its leading one rather than hiding it, packed as two 32-bit halves.
29
29
  def extended(value)
30
30
  exponent = EXPONENT_BIAS + value.bit_length - 1
31
- mantissa = value << (64 - value.bit_length)
32
- [exponent, mantissa >> 32, mantissa & 0xffff_ffff].pack("nNN")
31
+ [exponent, *mantissa(value, 64 - value.bit_length)].pack("nNN")
32
+ end
33
+
34
+ def mantissa(value, shift)
35
+ return [value << (shift - 32), 0] if shift >= 32
36
+
37
+ [value >> (32 - shift), (value & ((1 << (32 - shift)) - 1)) << shift]
33
38
  end
34
39
  end
35
40
  end