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,57 +1,64 @@
package goapple2 package goapple2
import ( import (
"fmt"
"github.com/zellyn/go6502/cpu" "github.com/zellyn/go6502/cpu"
"github.com/zellyn/goapple2/cards"
"github.com/zellyn/goapple2/videoscan" "github.com/zellyn/goapple2/videoscan"
) )
// Memory for the tests. Satisfies the cpu.Memory interface. // Memory for the tests. Satisfies the cpu.Memory interface.
type Apple2 struct { type Apple2 struct {
mem [65536]byte mem [65536]byte
cpu cpu.Cpu cpu cpu.Cpu
key byte // BUG(zellyn): make reads/writes atomic key byte // BUG(zellyn): make reads/writes atomic
keys chan byte keys chan byte
plotter videoscan.Plotter plotter videoscan.Plotter
scanner *videoscan.Scanner scanner *videoscan.Scanner
done bool Done bool
tw TickWaiter lastRead byte
} cards [8]cards.Card "Peripheral cards"
cardMask byte
// Cycle counter. Satisfies the cpu.Ticker interface. cardRomMask byte
type TickWaiter struct { cardRomConflict bool "True if more than one card is handling the 2k ROM area"
Wait chan byte cardRomHandler byte
} card12kMask byte
card12kConflict bool "True if more than one card is handling the 12k ROM area"
func (t TickWaiter) Tick() { card12kHandler byte
<-t.Wait
}
func NewTickWaiter() TickWaiter {
return TickWaiter{Wait: make(chan byte)}
} }
func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 { func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 {
tw := NewTickWaiter()
a2 := Apple2{ a2 := Apple2{
// BUG(zellyn): this is not how the apple2 keyboard actually works
keys: make(chan byte, 16), keys: make(chan byte, 16),
tw: tw,
} }
copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom) copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom)
a2.scanner = videoscan.NewScanner(&a2, p, charRom) 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() a2.cpu.Reset()
go func() {
tw.Tick()
for !a2.done {
a2.cpu.Step()
}
}()
return &a2 return &a2
} }
func (a2 *Apple2) Read(address uint16) byte { func (a2 *Apple2) AddCard(card cards.Card) error {
// Keyboard read slot := card.Slot()
if address == 0xC000 { 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 { if a2.key&0x80 == 0 {
select { select {
case key := <-a2.keys: case key := <-a2.keys:
@ -60,23 +67,128 @@ func (a2 *Apple2) Read(address uint16) byte {
} }
} }
return a2.key return a2.key
} // $C01X: Reset keyboard
if address == 0xC010 { case 0xC010:
a2.key &= 0x7F 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) { func (a2 *Apple2) Write(address uint16, value byte) {
if address >= 0xD000 { 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 return
} }
if address == 0xC010 { if address&0xF000 == 0xC000 {
// Clear keyboard strobe a2.handleC00X(address, value, true)
a2.key &= 0x7F return
} }
a2.mem[address] = value a2.mem[address] = value
} }
func (a2 *Apple2) Keypress(key byte) { func (a2 *Apple2) Keypress(key byte) {
@ -84,11 +196,50 @@ func (a2 *Apple2) Keypress(key byte) {
} }
func (a2 *Apple2) Step() error { func (a2 *Apple2) Step() error {
a2.tw.Wait <- 0 return a2.cpu.Step()
}
func (a2 *Apple2) Tick() {
a2.scanner.Scan1() a2.scanner.Scan1()
return nil
} }
func (a2 *Apple2) Quit() { 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 package main
import ( import (
"flag"
"fmt" "fmt"
"time" "log"
"os"
"runtime"
"runtime/pprof"
"unsafe" "unsafe"
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl" "github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
"github.com/zellyn/goapple2" "github.com/zellyn/goapple2"
"github.com/zellyn/goapple2/cards"
"github.com/zellyn/goapple2/util" "github.com/zellyn/goapple2/util"
"github.com/zellyn/goapple2/videoscan" "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 ( const (
BORDER_H = 20 BORDER_H = 20
BORDER_W = 20 BORDER_W = 20
@ -133,16 +143,16 @@ var KeyToApple = map[Key]byte{
Key{sdl.K_8, M_NONE}: '8', Key{sdl.K_8, M_NONE}: '8',
Key{sdl.K_9, M_NONE}: '9', Key{sdl.K_9, M_NONE}: '9',
Key{sdl.K_0, M_SHIFT}: '!', Key{sdl.K_1, M_SHIFT}: '!',
Key{sdl.K_1, M_SHIFT}: '@', Key{sdl.K_2, M_SHIFT}: '@',
Key{sdl.K_2, M_SHIFT}: '#', Key{sdl.K_3, M_SHIFT}: '#',
Key{sdl.K_3, M_SHIFT}: '$', Key{sdl.K_4, M_SHIFT}: '$',
Key{sdl.K_4, M_SHIFT}: '%', Key{sdl.K_5, M_SHIFT}: '%',
Key{sdl.K_5, M_SHIFT}: '^', Key{sdl.K_6, M_SHIFT}: '^',
Key{sdl.K_6, M_SHIFT}: '&', Key{sdl.K_7, M_SHIFT}: '&',
Key{sdl.K_7, M_SHIFT}: '*', Key{sdl.K_8, M_SHIFT}: '*',
Key{sdl.K_8, M_SHIFT}: '(', Key{sdl.K_9, 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_NONE}: '-',
Key{sdl.K_MINUS, M_SHIFT}: '_', Key{sdl.K_MINUS, M_SHIFT}: '_',
@ -223,7 +233,8 @@ func ProcessEvents(events <-chan interface{}, a2 *goapple2.Apple2) (done bool) {
} }
type SdlPlotter struct { type SdlPlotter struct {
screen *sdl.Surface screen *sdl.Surface
oncePerFrame func()
} }
func (s SdlPlotter) Plot(pd videoscan.PlotData) { 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) plot(x+i, y*2+1, color2, s.screen)
data >>= 1 data >>= 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 // Run the emulator
func RunEmulator() { func RunEmulator() {
rom := util.ReadRomOrDie("../data/roms/apple2+.rom") rom := util.ReadRomOrDie("../data/roms/apple2+.rom")
// charRom = util.ReadFullCharacterRomOrDie("../data/roms/apple2char.rom")
charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom") charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom")
screen, err := Init() screen, err := Init()
if err != nil { if err != nil {
panic(err) panic(err)
} }
plotter := SdlPlotter{screen} var a2 *goapple2.Apple2
a2 := goapple2.NewApple2(plotter, rom, charRom) oncePerFrame := func() {
for !ProcessEvents(sdl.Events, a2) { 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() err := a2.Step()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
break 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() sdl.Quit()
} }
func main() { func main() {
flag.Parse()
RunEmulator() RunEmulator()
} }

View File

@ -70,16 +70,16 @@ func (p TextPlotter) Plot(data videoscan.PlotData) {
value := data.RawData value := data.RawData
ch, fg, bg := translateToTermbox(value) ch, fg, bg := translateToTermbox(value)
termbox.SetCell(x+1, y+1, ch, fg, bg) 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 // Run the emulator
func RunEmulator() { func RunEmulator() {
rom := util.ReadRomOrDie("../data/roms/apple2+.rom") rom := util.ReadRomOrDie("../data/roms/apple2+.rom")
charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom")
plotter := TextPlotter(0) plotter := TextPlotter(0)
var charRom [2048]byte
a2 := goapple2.NewApple2(plotter, rom, charRom) a2 := goapple2.NewApple2(plotter, rom, charRom)
if err := termbox.Init(); err != nil { if err := termbox.Init(); err != nil {
panic(err) panic(err)

View File

@ -27,3 +27,13 @@ func ReadSmallCharacterRomOrDie(filename string) [2048]byte {
} }
return value 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 ( import (
"fmt" "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 // PlotData is the struct that holds plotting
// information. Essentially, it holds the (binary) waveform of the // information. Essentially, it holds the (binary) waveform of the
@ -16,15 +14,22 @@ type PlotData struct {
Column byte "The column (0-39)" Column byte "The column (0-39)"
ColorBurst bool "Whether the color signal is active" ColorBurst bool "Whether the color signal is active"
Data uint16 "14 half color cycles of information" Data uint16 "14 half color cycles of information"
LastData uint16 "The previous 14 half color cycles of information"
RawData byte "The underlying raw byte" RawData byte "The underlying raw byte"
} }
type Plotter interface { type Plotter interface {
Plot(PlotData) Plot(PlotData)
OncePerFrame()
}
// RAM memory interface
type RamReader interface {
RamRead(uint16) byte
} }
type Scanner struct { type Scanner struct {
m cpu.Memory m RamReader
h uint16 h uint16
v uint16 v uint16
plotter Plotter plotter Plotter
@ -35,15 +40,17 @@ type Scanner struct {
hires bool // LORES/HIRES hires bool // LORES/HIRES
page2 bool // PAGE1/PAGE2 page2 bool // PAGE1/PAGE2
hbl bool // Horizontal blanking hbl bool // Horizontal blanking
vbl bool // Vertical blanking vbl bool // Vertical blanking
lastFour bool // Are we in the last 4 lines of the screen? lastFour bool // Are we in the last 4 lines of the screen?
lastBit uint16 // Last bit of previous 14-cycle color data lastData uint16 // The previous 14 half color cycles of information
flasher byte // if high bit is set, invert flashing text lastBit uint16 // Last bit of previous 14-cycle color data
flashCount int // count up, and toggle flasher 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{ s := &Scanner{
m: m, m: m,
plotter: p, plotter: p,
@ -66,10 +73,7 @@ func (s *Scanner) inc() {
switch s.v { switch s.v {
case 0x1ff: case 0x1ff:
s.v = 250 s.v = 250
if s.flashCount++; s.flashCount >= FLASH_CYCLES { s.flasher += FLASH_INCREMENT
s.flashCount = 0
s.flasher ^= 0x80
}
default: default:
s.v++ s.v++
} }
@ -87,12 +91,15 @@ func (s *Scanner) inc() {
s.hbl = ((s.h >> 3) & 7) <= 2 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() { func (s *Scanner) Scan1() {
m := s.m.Read(s.address()) m := s.m.RamRead(s.address())
row, column := s.row(), s.column() row, column := s.row(), s.column()
_, _, _ = m, row, column _, _, _ = m, row, column
var data uint16 var data uint16
color := s.graphics
switch { switch {
case !s.graphics || (s.mix && s.lastFour): case !s.graphics || (s.mix && s.lastFour):
data = s.textData(m, row, column) data = s.textData(m, row, column)
@ -103,14 +110,24 @@ func (s *Scanner) Scan1() {
} }
s.lastBit = (data >> 13) & 1 s.lastBit = (data >> 13) & 1
if !s.hbl && !s.vbl { if !s.hbl && !s.vbl {
s.plotter.Plot(PlotData{ change := last[row][column] != (data | s.graphicsBit)
Row: byte(row), if change || s.lastChange {
Column: byte(column), s.plotter.Plot(PlotData{
ColorBurst: color, Row: byte(row),
Data: data, Column: byte(column),
RawData: m, 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() s.inc()
} }
@ -122,20 +139,28 @@ func (s *Scanner) row() int {
return int(s.v) - 0x100 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 { func (s *Scanner) address() uint16 {
// Low three bits are just H0-H2 // Low three bits are just H0-H2
addr := s.h & 7 addr := s.h & 7
// Next four bits are H5,H4,H3 + offset = SUM-A6,SUM-A5,SUM-A4,SUM-A3 // Next four bits are H5,H4,H3 + offset = SUM-A6,SUM-A5,SUM-A4,SUM-A3
bias := uint16(0xD) // 1 1 0 1 // bias := uint16(0xD) // 1 1 0 1
hsum := (s.h >> 3) & 7 // 0 H5 H4 H3 // hsum := (s.h >> 3) & 7 // 0 H5 H4 H3
vsum := (s.v >> 6) & 3 // V4 V3 V4 V3 // vsum := (s.v >> 6) & 3 // V4 V3 V4 V3
vsum = vsum | (vsum << 2) // vsum = vsum | (vsum << 2)
suma36 := (bias + hsum + vsum) & 0xF // suma36 := (bias + hsum + vsum) & 0xF
addr |= (suma36 << 3)
addr |= SUMS[(s.h>>3)&7+(s.v>>3)&24]
// Next three are V0,V1,V2 // 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) page := uint16(1)
if s.page2 { if s.page2 {
@ -167,6 +192,11 @@ func (s *Scanner) address() uint16 {
func (s *Scanner) SetGraphics(graphics bool) { func (s *Scanner) SetGraphics(graphics bool) {
s.graphics = graphics s.graphics = graphics
if graphics {
s.graphicsBit = 1 << 14
} else {
s.graphicsBit = 0
}
} }
func (s *Scanner) SetMix(mix bool) { 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 { func (s *Scanner) textData(m byte, row int, column int) uint16 {
line := s.rom[int(m)*8+((row+800)%8)] line := s.rom[int(m)*8+((row+800)%8)]
// Invert if flash // Invert if flash
if (m^0x80)&line&s.flasher > 0 { if (m^0x80)&line&s.flasher > 127 {
line ^= 0xff line ^= 0xff
} }
line &= 0x7f // Mask out high bit 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) 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 { func (s *Scanner) hiresData(m byte, row int, column int) uint16 {
// Double each bit if m < 128 {
var data uint16 return HIRES_DOUBLES[m]
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
} }
// High bit set delays the signal by 1/4 color cycle = 1 bit, return ((HIRES_DOUBLES[m&0x7f] << 1) & 0x3ff) | s.lastBit
// and extends the last bit to fill in the delay.
if m > 127 {
data <<= 1
data |= s.lastBit
}
return data & 0x3fff
} }
func (s *Scanner) loresData(m byte, row int, column int) uint16 { func (s *Scanner) loresData(m byte, row int, column int) uint16 {

View File

@ -2,19 +2,25 @@ package videoscan
import ( import (
"testing" "testing"
"github.com/zellyn/go6502/tests"
apple2 "github.com/zellyn/goapple2"
) )
type fakePlotter struct { 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) { func TestHorizontal(t *testing.T) {
var m tests.Memorizer var m K64
var f fakePlotter var f fakePlotter
s := NewScanner(&m, &f, [2048]byte{}) s := NewScanner(&m, &f, [2048]byte{})
@ -36,7 +42,7 @@ func TestHorizontal(t *testing.T) {
} }
func TestVertical(t *testing.T) { func TestVertical(t *testing.T) {
var m tests.Memorizer var m K64
var f fakePlotter var f fakePlotter
s := NewScanner(&m, &f, [2048]byte{}) s := NewScanner(&m, &f, [2048]byte{})
scan65 := func() { scan65 := func() {
@ -63,7 +69,7 @@ func TestVertical(t *testing.T) {
} }
func TestBlanking(t *testing.T) { func TestBlanking(t *testing.T) {
var m tests.Memorizer var m K64
var f fakePlotter var f fakePlotter
s := NewScanner(&m, &f, [2048]byte{}) s := NewScanner(&m, &f, [2048]byte{})
@ -92,7 +98,7 @@ func TestBlanking(t *testing.T) {
} }
func TestSpecificLocationAddresses(t *testing.T) { func TestSpecificLocationAddresses(t *testing.T) {
var m tests.Memorizer var m K64
var f fakePlotter var f fakePlotter
s := NewScanner(&m, &f, [2048]byte{}) s := NewScanner(&m, &f, [2048]byte{})