Implemented basic graphics, firmware card

This commit is contained in:
Zellyn Hunter 2013-04-07 18:31:14 -07:00
parent 1d5e822fe8
commit 95f22d22b8
10 changed files with 504 additions and 120 deletions

19
cards/cards.go Normal file
View File

@ -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
}

72
cards/firmware.go Normal file
View File

@ -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
}

View File

@ -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?

View File

@ -1,7 +1,10 @@
package goapple2
import (
"fmt"
"github.com/zellyn/go6502/cpu"
"github.com/zellyn/goapple2/cards"
"github.com/zellyn/goapple2/videoscan"
)
@ -13,45 +16,49 @@ type Apple2 struct {
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)}
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<<i == a2.cardRomMask {
a2.cardRomHandler = i
return
}
}
}
}
func (a2 *Apple2) Handle12k(onOff bool, slot byte) {
if onOff {
a2.card12kMask |= slot
} else {
a2.card12kMask &^= slot
}
a2.card12kConflict = a2.card12kMask&(a2.card12kMask-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<<i == a2.card12kMask {
a2.card12kHandler = i
return
}
}
}
}

View File

@ -1 +1 @@
SDL_VIDEODRIVER=x11 go run sdl.go
SDL_VIDEODRIVER=x11 go run sdl.go $@

View File

@ -2,16 +2,26 @@
package main
import (
"flag"
"fmt"
"time"
"log"
"os"
"runtime"
"runtime/pprof"
"unsafe"
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
"github.com/zellyn/goapple2"
"github.com/zellyn/goapple2/cards"
"github.com/zellyn/goapple2/util"
"github.com/zellyn/goapple2/videoscan"
)
var (
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
steplimit = flag.Uint64("steplimit", 0, "limit on number of steps to take")
)
const (
BORDER_H = 20
BORDER_W = 20
@ -133,16 +143,16 @@ var KeyToApple = map[Key]byte{
Key{sdl.K_8, M_NONE}: '8',
Key{sdl.K_9, M_NONE}: '9',
Key{sdl.K_0, M_SHIFT}: '!',
Key{sdl.K_1, M_SHIFT}: '@',
Key{sdl.K_2, M_SHIFT}: '#',
Key{sdl.K_3, M_SHIFT}: '$',
Key{sdl.K_4, M_SHIFT}: '%',
Key{sdl.K_5, M_SHIFT}: '^',
Key{sdl.K_6, M_SHIFT}: '&',
Key{sdl.K_7, M_SHIFT}: '*',
Key{sdl.K_8, M_SHIFT}: '(',
Key{sdl.K_9, M_SHIFT}: ')',
Key{sdl.K_1, M_SHIFT}: '!',
Key{sdl.K_2, M_SHIFT}: '@',
Key{sdl.K_3, M_SHIFT}: '#',
Key{sdl.K_4, M_SHIFT}: '$',
Key{sdl.K_5, M_SHIFT}: '%',
Key{sdl.K_6, M_SHIFT}: '^',
Key{sdl.K_7, M_SHIFT}: '&',
Key{sdl.K_8, M_SHIFT}: '*',
Key{sdl.K_9, M_SHIFT}: '(',
Key{sdl.K_0, M_SHIFT}: ')',
Key{sdl.K_MINUS, M_NONE}: '-',
Key{sdl.K_MINUS, M_SHIFT}: '_',
@ -224,6 +234,7 @@ func ProcessEvents(events <-chan interface{}, a2 *goapple2.Apple2) (done bool) {
type SdlPlotter struct {
screen *sdl.Surface
oncePerFrame func()
}
func (s SdlPlotter) Plot(pd videoscan.PlotData) {
@ -241,32 +252,91 @@ func (s SdlPlotter) Plot(pd videoscan.PlotData) {
plot(x+i, y*2+1, color2, s.screen)
data >>= 1
}
if pd.Column == 39 && y == 191 {
}
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()
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
@ -38,12 +43,14 @@ type Scanner struct {
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
flashCount int // count up, and toggle flasher
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 {
change := last[row][column] != (data | s.graphicsBit)
if change || s.lastChange {
s.plotter.Plot(PlotData{
Row: byte(row),
Column: byte(column),
ColorBurst: color,
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 {

View File

@ -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{})