From 95f22d22b88526ad8a20f26099fa63e4283b6150 Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Sun, 7 Apr 2013 18:31:14 -0700 Subject: [PATCH] Implemented basic graphics, firmware card --- cards/cards.go | 19 +++ cards/firmware.go | 72 +++++++++++ docs/apple2.org | 17 +++ goapple2.go | 237 +++++++++++++++++++++++++++++------- sdl/run.sh | 2 +- sdl/sdl.go | 106 +++++++++++++--- texty/texty.go | 8 +- util/util.go | 10 ++ videoscan/videoscan.go | 131 +++++++++++++------- videoscan/videoscan_test.go | 22 ++-- 10 files changed, 504 insertions(+), 120 deletions(-) create mode 100644 cards/cards.go create mode 100644 cards/firmware.go diff --git a/cards/cards.go b/cards/cards.go new file mode 100644 index 0000000..aa063cf --- /dev/null +++ b/cards/cards.go @@ -0,0 +1,19 @@ +package cards + +type Card interface { + String() string // The name of the card, for debug/display purposes + Read16(address byte) byte // Read from the $C0(8+slot)X addresses + Write16(address byte, value byte) // Write to the $C0(8+slot)X addresses + Slot() byte // Get the card's slot 0-7 + ROMDisabled() // Tell the card that its handling of $C(8-F)xx was disabled + Read256(address byte) byte // Read from the $C(slot)XX addresses + Write256(address byte, value byte) // Write to the $C(slot)XX addresses + Read(address uint16) byte // Read from any address ($C800-$FFFF) + Write(address uint16, value byte) // Write to any address ($C800-$FFFF) +} + +type CardManager interface { + HandleROM(onOff bool, slot byte) + Handle12k(onOff bool, slot byte) + EmptyRead() byte +} diff --git a/cards/firmware.go b/cards/firmware.go new file mode 100644 index 0000000..695574f --- /dev/null +++ b/cards/firmware.go @@ -0,0 +1,72 @@ +package cards + +import ( + "fmt" +) + +type FirmwareCard struct { + name string + rom [12288]byte + cm CardManager + slot byte + slotbit byte +} + +func NewFirmwareCard(rom []byte, name string, slot byte, cm CardManager) (*FirmwareCard, error) { + if len(rom) != 12288 { + return nil, fmt.Errorf("Wrong size ROM: expected 12288, got %d", len(rom)) + } + fc := &FirmwareCard{name: name, cm: cm, slot: slot, slotbit: 1 << slot} + copy(fc.rom[:], rom) + return fc, nil +} + +func (fc *FirmwareCard) String() string { + return fmt.Sprintf("%s (slot %d)", fc.name, fc.slot) +} + +func (fc *FirmwareCard) Slot() byte { + return fc.slot +} + +func (fc *FirmwareCard) ROMDisabled() { + // Firmware card doesn't have a $C(8-F)xx ROM +} + +func (fc *FirmwareCard) handleAccess(address byte) { + if address%2 == 1 { + // Card off + fc.cm.HandleROM(false, fc.slotbit) + } else { + // Card on + fc.cm.HandleROM(true, fc.slotbit) + } +} + +func (fc *FirmwareCard) Read16(address byte) byte { + fc.handleAccess(address) + return fc.cm.EmptyRead() +} + +func (fc *FirmwareCard) Write16(address byte, value byte) { + fc.handleAccess(address) +} + +func (fc *FirmwareCard) Read(address uint16) byte { + if address < 0xD000 { + panic(fmt.Sprintf("%s got read to $%04X (<$D000)", fc.String(), address)) + } + return fc.rom[address-0xD000] +} + +func (fc *FirmwareCard) Write(address uint16, value byte) { + // Firmware is ROM: do nothing +} + +func (fc *FirmwareCard) Read256(address byte) byte { + return fc.cm.EmptyRead() +} + +func (fc *FirmwareCard) Write256(address byte, value byte) { + // Firmware is ROM: do nothing +} diff --git a/docs/apple2.org b/docs/apple2.org index 228fe45..215f14c 100644 --- a/docs/apple2.org +++ b/docs/apple2.org @@ -468,3 +468,20 @@ sdlEventLoop(): + +* Firmware card +$D000-$FFFF +B2-B set (motherboard ROM) +- RESET+switch down +- odd addresses between $C081 and $C08F (slot 0) +B2-B reset (firmware card ROM) +- RESET+switch up +- even addresses between $C080 and $C08E + +F8 jumper jumpered: use F8 ROM on card when card is enabled. +F8 jumper not jumpered: never use F8 ROM on card: use motherboard. +(Apple sold jumpered+autostart ROM, non-jumpered+no-rom. [UtA2: 6-11]) + +* Questions +** Color +Can a previously rendered dot change colors? Gray? diff --git a/goapple2.go b/goapple2.go index 36ed866..2f787e4 100644 --- a/goapple2.go +++ b/goapple2.go @@ -1,57 +1,64 @@ package goapple2 import ( + "fmt" + "github.com/zellyn/go6502/cpu" + "github.com/zellyn/goapple2/cards" "github.com/zellyn/goapple2/videoscan" ) // Memory for the tests. Satisfies the cpu.Memory interface. type Apple2 struct { - mem [65536]byte - cpu cpu.Cpu - key byte // BUG(zellyn): make reads/writes atomic - keys chan byte - plotter videoscan.Plotter - scanner *videoscan.Scanner - done bool - tw TickWaiter -} - -// Cycle counter. Satisfies the cpu.Ticker interface. -type TickWaiter struct { - Wait chan byte -} - -func (t TickWaiter) Tick() { - <-t.Wait -} - -func NewTickWaiter() TickWaiter { - return TickWaiter{Wait: make(chan byte)} + mem [65536]byte + cpu cpu.Cpu + key byte // BUG(zellyn): make reads/writes atomic + keys chan byte + plotter videoscan.Plotter + scanner *videoscan.Scanner + Done bool + lastRead byte + cards [8]cards.Card "Peripheral cards" + cardMask byte + cardRomMask byte + cardRomConflict bool "True if more than one card is handling the 2k ROM area" + cardRomHandler byte + card12kMask byte + card12kConflict bool "True if more than one card is handling the 12k ROM area" + card12kHandler byte } func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 { - tw := NewTickWaiter() a2 := Apple2{ + // BUG(zellyn): this is not how the apple2 keyboard actually works keys: make(chan byte, 16), - tw: tw, } copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom) a2.scanner = videoscan.NewScanner(&a2, p, charRom) - a2.cpu = cpu.NewCPU(&a2, tw, cpu.VERSION_6502) + a2.cpu = cpu.NewCPU(&a2, &a2, cpu.VERSION_6502) a2.cpu.Reset() - go func() { - tw.Tick() - for !a2.done { - a2.cpu.Step() - } - }() return &a2 } -func (a2 *Apple2) Read(address uint16) byte { - // Keyboard read - if address == 0xC000 { +func (a2 *Apple2) AddCard(card cards.Card) error { + slot := card.Slot() + slotbit := byte(1 << slot) + if slotbit&a2.cardMask > 0 { + return fmt.Errorf("Slot %d already has a card: %s", slot, a2.cards[slot]) + } + a2.cardMask |= slotbit + a2.cards[slot] = card + return nil +} + +func (a2 *Apple2) handleCardRom(address uint16, value byte, write bool) byte { + return a2.EmptyRead() +} + +func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte { + switch address & 0xC0F0 { + // $C00X: Read keyboard + case 0xC000: if a2.key&0x80 == 0 { select { case key := <-a2.keys: @@ -60,23 +67,128 @@ func (a2 *Apple2) Read(address uint16) byte { } } return a2.key - } - if address == 0xC010 { + // $C01X: Reset keyboard + case 0xC010: a2.key &= 0x7F - return 0 // BUG(zellyn): return proper value (keydown on IIe, not sure on II+) + return a2.EmptyRead() } - return a2.mem[address] + switch address { + case 0xC050: // GRAPHICS + a2.scanner.SetGraphics(true) + case 0xC051: // TEXT + a2.scanner.SetGraphics(false) + case 0xC052: // NOMIX + a2.scanner.SetMix(false) + case 0xC053: // MIX + a2.scanner.SetMix(true) + case 0xC054: // PAGE 1 + a2.scanner.SetPage(1) + case 0xC055: // PAGE 2 + a2.scanner.SetPage(2) + case 0xC056: // LORES + a2.scanner.SetHires(false) + case 0xC057: // HIRES + a2.scanner.SetHires(true) + } + + if address < 0xC080 { + return a2.EmptyRead() + } + + if address < 0xC100 { + slot := byte((address - 0xC080) >> 4) + if a2.cards[slot] != nil { + if write { + a2.cards[slot].Write16(byte(address&0xF), value) + return 0 + } else { + return a2.cards[slot].Read16(byte(address & 0xF)) + } + } + return a2.EmptyRead() + } + + if address < 0xC800 { + slot := byte((address - 0xC000) >> 8) + if a2.cards[slot] != nil { + if write { + a2.cards[slot].Write256(byte(address&0xFF), value) + return 0 + } else { + return a2.cards[slot].Read256(byte(address & 0xFF)) + } + } + return a2.EmptyRead() + } + + // 0xCFFF disables 2k on all cards + if address == 0xCFFF { + for i := 0; a2.cardMask > 0; a2.cardMask >>= 1 { + if a2.cardMask&1 > 0 { + a2.cards[i].ROMDisabled() + } + i++ + } + return a2.EmptyRead() + } + + // Only addresses left are 0xC800-0xCFFE + if a2.cardRomMask == 0 { + return a2.EmptyRead() + } + if a2.cardRomConflict { + panic(fmt.Sprintf("More than one card trying to provide 2K ROM: Mask=$%02X", a2.cardRomMask)) + } + + if write { + a2.cards[a2.cardRomHandler].Write(address, value) + return 0 + } + return a2.cards[a2.cardRomHandler].Read(address) +} + +// EmptyRead returns the value last read from RAM, lingering on the bus. +func (a2 *Apple2) EmptyRead() byte { + return a2.lastRead +} + +func (a2 *Apple2) Read(address uint16) byte { + if address&0xF000 == 0xC000 { + return a2.handleC00X(address, 0, false) + } + if address >= 0xD000 && a2.cardRomMask > 0 { + if a2.card12kConflict { + panic(fmt.Sprintf("More than one card trying to provide 12K ROM: Mask=$%02X", a2.card12kMask)) + } + a2.lastRead = a2.cards[a2.card12kHandler].Read(address) + return a2.lastRead + } + + a2.lastRead = a2.mem[address] + return a2.lastRead +} + +func (a2 *Apple2) RamRead(address uint16) byte { + a2.lastRead = a2.mem[address] + return a2.lastRead } func (a2 *Apple2) Write(address uint16, value byte) { if address >= 0xD000 { + if a2.cardRomMask > 0 { + if a2.card12kConflict { + panic(fmt.Sprintf("More than one card trying to provide 12K ROM: Mask=$%02X", a2.card12kMask)) + } + a2.cards[a2.card12kHandler].Write(address, value) + } return } - if address == 0xC010 { - // Clear keyboard strobe - a2.key &= 0x7F + if address&0xF000 == 0xC000 { + a2.handleC00X(address, value, true) + return } a2.mem[address] = value + } func (a2 *Apple2) Keypress(key byte) { @@ -84,11 +196,50 @@ func (a2 *Apple2) Keypress(key byte) { } func (a2 *Apple2) Step() error { - a2.tw.Wait <- 0 + return a2.cpu.Step() +} + +func (a2 *Apple2) Tick() { a2.scanner.Scan1() - return nil } func (a2 *Apple2) Quit() { - a2.done = true + a2.Done = true +} + +func (a2 *Apple2) HandleROM(onOff bool, slot byte) { + if onOff { + a2.cardRomMask |= (1 << slot) + a2.cardRomHandler = slot + } else { + a2.cardRomMask &^= (1 << slot) + } + a2.cardRomConflict = a2.cardRomMask&(a2.cardRomMask-1) > 0 + if !onOff && !a2.cardRomConflict && a2.cardRomMask > 0 { + // Removed a card: figure out new handler + for i := byte(0); i < 7; i++ { + if 1< 0 + if !onOff && !a2.card12kConflict && a2.card12kMask > 0 { + // Removed a card: figure out new handler + for i := byte(0); i < 7; i++ { + if 1<>= 1 } - if pd.Column == 39 && y == 191 { - s.screen.Flip() +} + +func (s SdlPlotter) OncePerFrame() { + s.screen.Flip() + s.oncePerFrame() +} + +func typeProgram(a2 *goapple2.Apple2) { + lines := []string{ + "10 GR", + "20 POKE -16302,0", + "30 FOR Y=0 TO 47", + "40 FOR X=0 TO 39", + "50 COLOR=INT(RND(1)*16)", + "60 PLOT X,Y", + "70 NEXT", + "80 NEXT", + "RUN", + } + for _, line := range lines { + for _, ch := range line { + a2.Keypress(byte(ch)) + } + a2.Keypress(13) } } // Run the emulator func RunEmulator() { rom := util.ReadRomOrDie("../data/roms/apple2+.rom") + // charRom = util.ReadFullCharacterRomOrDie("../data/roms/apple2char.rom") charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom") screen, err := Init() if err != nil { panic(err) } - plotter := SdlPlotter{screen} - a2 := goapple2.NewApple2(plotter, rom, charRom) - for !ProcessEvents(sdl.Events, a2) { + var a2 *goapple2.Apple2 + oncePerFrame := func() { + a2.Done = a2.Done || ProcessEvents(sdl.Events, a2) + runtime.Gosched() + } + plotter := SdlPlotter{screen, oncePerFrame} + a2 = goapple2.NewApple2(plotter, rom, charRom) + + intBasicRom := util.ReadRomOrDie("../data/roms/apple2.rom") + firmwareCard, err := cards.NewFirmwareCard(intBasicRom, "Intbasic Firmware Card", 0, a2) + if err != nil { + panic(err) + } + if err := a2.AddCard(firmwareCard); err != nil { + log.Fatal(err) + } + + steps := *steplimit + + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + go typeProgram(a2) + + for !a2.Done { err := a2.Step() if err != nil { fmt.Println(err) break } - time.Sleep(1 * time.Nanosecond) // So the keyboard-reading goroutines can run + // runtime.Gosched() // So the keyboard-reading goroutines can run + if steps > 0 { + steps-- + if steps == 0 { + a2.Quit() + } + } } + a2.Quit() sdl.Quit() } func main() { + flag.Parse() RunEmulator() } diff --git a/texty/texty.go b/texty/texty.go index 37d65b0..a38a3e1 100644 --- a/texty/texty.go +++ b/texty/texty.go @@ -70,16 +70,16 @@ func (p TextPlotter) Plot(data videoscan.PlotData) { value := data.RawData ch, fg, bg := translateToTermbox(value) termbox.SetCell(x+1, y+1, ch, fg, bg) - if x == 39 && data.Row == 191 { - termbox.Flush() - } +} +func (p TextPlotter) OncePerFrame() { + termbox.Flush() } // Run the emulator func RunEmulator() { rom := util.ReadRomOrDie("../data/roms/apple2+.rom") + charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom") plotter := TextPlotter(0) - var charRom [2048]byte a2 := goapple2.NewApple2(plotter, rom, charRom) if err := termbox.Init(); err != nil { panic(err) diff --git a/util/util.go b/util/util.go index c320bc3..082cc9e 100644 --- a/util/util.go +++ b/util/util.go @@ -27,3 +27,13 @@ func ReadSmallCharacterRomOrDie(filename string) [2048]byte { } return value } + +func ReadFullCharacterRomOrDie(filename string) [2048]byte { + bytes := ReadRomOrDie(filename) + if len(bytes) != 2048 { + panic(fmt.Sprintf("Got %d bytes (not 2048) from file '%s'", len(bytes), filename)) + } + var value [2048]byte + copy(value[:], bytes) + return value +} diff --git a/videoscan/videoscan.go b/videoscan/videoscan.go index 93b4a9b..9bdf45f 100644 --- a/videoscan/videoscan.go +++ b/videoscan/videoscan.go @@ -2,11 +2,9 @@ package videoscan import ( "fmt" - - "github.com/zellyn/go6502/cpu" ) -const FLASH_CYCLES = 15 // 15/60 = four per second +const FLASH_INCREMENT = 8 // 128/8=16 / 60 // 15/60 = 3.75 per second // PlotData is the struct that holds plotting // information. Essentially, it holds the (binary) waveform of the @@ -16,15 +14,22 @@ type PlotData struct { Column byte "The column (0-39)" ColorBurst bool "Whether the color signal is active" Data uint16 "14 half color cycles of information" + LastData uint16 "The previous 14 half color cycles of information" RawData byte "The underlying raw byte" } type Plotter interface { Plot(PlotData) + OncePerFrame() +} + +// RAM memory interface +type RamReader interface { + RamRead(uint16) byte } type Scanner struct { - m cpu.Memory + m RamReader h uint16 v uint16 plotter Plotter @@ -35,15 +40,17 @@ type Scanner struct { hires bool // LORES/HIRES page2 bool // PAGE1/PAGE2 - hbl bool // Horizontal blanking - vbl bool // Vertical blanking - lastFour bool // Are we in the last 4 lines of the screen? - lastBit uint16 // Last bit of previous 14-cycle color data - flasher byte // if high bit is set, invert flashing text - flashCount int // count up, and toggle flasher + hbl bool // Horizontal blanking + vbl bool // Vertical blanking + lastFour bool // Are we in the last 4 lines of the screen? + lastData uint16 // The previous 14 half color cycles of information + lastBit uint16 // Last bit of previous 14-cycle color data + lastChange bool // Was there a change plotted last byte? + flasher byte // if high bit is set, invert flashing text + graphicsBit uint16 // Bit 14 high if we're in graphics mode } -func NewScanner(m cpu.Memory, p Plotter, rom [2048]byte) *Scanner { +func NewScanner(m RamReader, p Plotter, rom [2048]byte) *Scanner { s := &Scanner{ m: m, plotter: p, @@ -66,10 +73,7 @@ func (s *Scanner) inc() { switch s.v { case 0x1ff: s.v = 250 - if s.flashCount++; s.flashCount >= FLASH_CYCLES { - s.flashCount = 0 - s.flasher ^= 0x80 - } + s.flasher += FLASH_INCREMENT default: s.v++ } @@ -87,12 +91,15 @@ func (s *Scanner) inc() { s.hbl = ((s.h >> 3) & 7) <= 2 } +// The last-plotted color cycle information. +// Bits 0-13 are the color cycle waveform. Bit 14 is true if colorburst was on. +var last [192][40]uint16 + func (s *Scanner) Scan1() { - m := s.m.Read(s.address()) + m := s.m.RamRead(s.address()) row, column := s.row(), s.column() _, _, _ = m, row, column var data uint16 - color := s.graphics switch { case !s.graphics || (s.mix && s.lastFour): data = s.textData(m, row, column) @@ -103,14 +110,24 @@ func (s *Scanner) Scan1() { } s.lastBit = (data >> 13) & 1 if !s.hbl && !s.vbl { - s.plotter.Plot(PlotData{ - Row: byte(row), - Column: byte(column), - ColorBurst: color, - Data: data, - RawData: m, - }) + change := last[row][column] != (data | s.graphicsBit) + if change || s.lastChange { + s.plotter.Plot(PlotData{ + Row: byte(row), + Column: byte(column), + ColorBurst: s.graphics, + Data: data, + LastData: s.lastData, + RawData: m, + }) + last[row][column] = data | s.graphicsBit + } + s.lastChange = change + if column == 39 && row == 191 { + s.plotter.OncePerFrame() + } } + s.lastData = data s.inc() } @@ -122,20 +139,28 @@ func (s *Scanner) row() int { return int(s.v) - 0x100 } +var SUMS = [32]uint16{ + 104, 112, 120, 0, 8, 16, 24, 32, + 16, 24, 32, 40, 48, 56, 64, 72, + 56, 64, 72, 80, 88, 96, 104, 112, + 96, 104, 112, 120, 0, 8, 16, 24, +} + func (s *Scanner) address() uint16 { // Low three bits are just H0-H2 addr := s.h & 7 // Next four bits are H5,H4,H3 + offset = SUM-A6,SUM-A5,SUM-A4,SUM-A3 - bias := uint16(0xD) // 1 1 0 1 - hsum := (s.h >> 3) & 7 // 0 H5 H4 H3 - vsum := (s.v >> 6) & 3 // V4 V3 V4 V3 - vsum = vsum | (vsum << 2) - suma36 := (bias + hsum + vsum) & 0xF - addr |= (suma36 << 3) + // bias := uint16(0xD) // 1 1 0 1 + // hsum := (s.h >> 3) & 7 // 0 H5 H4 H3 + // vsum := (s.v >> 6) & 3 // V4 V3 V4 V3 + // vsum = vsum | (vsum << 2) + // suma36 := (bias + hsum + vsum) & 0xF + + addr |= SUMS[(s.h>>3)&7+(s.v>>3)&24] // Next three are V0,V1,V2 - addr |= ((s.v >> 3 & 7) << 7) + addr |= (s.v << 4) & 0x380 // ((s.v >> 3 & 7) << 7) page := uint16(1) if s.page2 { @@ -167,6 +192,11 @@ func (s *Scanner) address() uint16 { func (s *Scanner) SetGraphics(graphics bool) { s.graphics = graphics + if graphics { + s.graphicsBit = 1 << 14 + } else { + s.graphicsBit = 0 + } } func (s *Scanner) SetMix(mix bool) { @@ -191,7 +221,7 @@ func (s *Scanner) SetPage(page int) { func (s *Scanner) textData(m byte, row int, column int) uint16 { line := s.rom[int(m)*8+((row+800)%8)] // Invert if flash - if (m^0x80)&line&s.flasher > 0 { + if (m^0x80)&line&s.flasher > 127 { line ^= 0xff } line &= 0x7f // Mask out high bit @@ -199,22 +229,31 @@ func (s *Scanner) textData(m byte, row int, column int) uint16 { return s.hiresData(line, row, column) } +// Double each bit to go from pixel info to color info +var HIRES_DOUBLES = [128]uint16{ + 0x0, 0x3, 0xC, 0xF, 0x30, 0x33, 0x3C, 0x3F, + 0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF, + 0x300, 0x303, 0x30C, 0x30F, 0x330, 0x333, 0x33C, 0x33F, + 0x3C0, 0x3C3, 0x3CC, 0x3CF, 0x3F0, 0x3F3, 0x3FC, 0x3FF, + 0xC00, 0xC03, 0xC0C, 0xC0F, 0xC30, 0xC33, 0xC3C, 0xC3F, + 0xCC0, 0xCC3, 0xCCC, 0xCCF, 0xCF0, 0xCF3, 0xCFC, 0xCFF, + 0xF00, 0xF03, 0xF0C, 0xF0F, 0xF30, 0xF33, 0xF3C, 0xF3F, + 0xFC0, 0xFC3, 0xFCC, 0xFCF, 0xFF0, 0xFF3, 0xFFC, 0xFFF, + 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, + 0x30C0, 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, + 0x3300, 0x3303, 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, + 0x33C0, 0x33C3, 0x33CC, 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, + 0x3C00, 0x3C03, 0x3C0C, 0x3C0F, 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, + 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 0x3CF0, 0x3CF3, 0x3CFC, 0x3CFF, + 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 0x3F33, 0x3F3C, 0x3F3F, + 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC, 0x3FFF, +} + func (s *Scanner) hiresData(m byte, row int, column int) uint16 { - // Double each bit - var data uint16 - mm := uint16(m) - // BUG(zellyn): Use bitmagic to do this without looping - for i := byte(6); i != 0xff; i-- { - data |= ((mm >> i) & 1) * 3 - data <<= 2 + if m < 128 { + return HIRES_DOUBLES[m] } - // High bit set delays the signal by 1/4 color cycle = 1 bit, - // and extends the last bit to fill in the delay. - if m > 127 { - data <<= 1 - data |= s.lastBit - } - return data & 0x3fff + return ((HIRES_DOUBLES[m&0x7f] << 1) & 0x3ff) | s.lastBit } func (s *Scanner) loresData(m byte, row int, column int) uint16 { diff --git a/videoscan/videoscan_test.go b/videoscan/videoscan_test.go index 3c721de..aed86c1 100644 --- a/videoscan/videoscan_test.go +++ b/videoscan/videoscan_test.go @@ -2,19 +2,25 @@ package videoscan import ( "testing" - - "github.com/zellyn/go6502/tests" - apple2 "github.com/zellyn/goapple2" ) type fakePlotter struct { } -func (f *fakePlotter) Plot(apple2.PlotData) { +func (f *fakePlotter) Plot(PlotData) { +} +func (f *fakePlotter) OncePerFrame() { +} + +// Memory for the tests. Satisfies the videoscan.RamReader interfaces. +type K64 [65536]byte + +func (m *K64) RamRead(address uint16) byte { + return m[address] } func TestHorizontal(t *testing.T) { - var m tests.Memorizer + var m K64 var f fakePlotter s := NewScanner(&m, &f, [2048]byte{}) @@ -36,7 +42,7 @@ func TestHorizontal(t *testing.T) { } func TestVertical(t *testing.T) { - var m tests.Memorizer + var m K64 var f fakePlotter s := NewScanner(&m, &f, [2048]byte{}) scan65 := func() { @@ -63,7 +69,7 @@ func TestVertical(t *testing.T) { } func TestBlanking(t *testing.T) { - var m tests.Memorizer + var m K64 var f fakePlotter s := NewScanner(&m, &f, [2048]byte{}) @@ -92,7 +98,7 @@ func TestBlanking(t *testing.T) { } func TestSpecificLocationAddresses(t *testing.T) { - var m tests.Memorizer + var m K64 var f fakePlotter s := NewScanner(&m, &f, [2048]byte{})