// Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details package main import ( "fmt" "log" "errors" "flag" "time" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil" // "image/color" ) const IdleTicks = 1000 var consoleChan chan byte var cpu CPU var mem Mem var uart UART var framebuffer Framebuffer var sdspi SDSPI var irqc IRQC var Terminated = errors.New("terminated") var idleCounter int = IdleTicks func idle(canGoIdle bool) { if canGoIdle { if idleCounter > 0 { idleCounter -= 1 } } else { if idleCounter != IdleTicks { idleCounter = IdleTicks } } } type Game struct{ debug bool x,y int stepsPerFrame int lastFrameDuration time.Duration } func (g *Game) Update() error { startTime := time.Now() framebuffer.startFrame() for i := 0; i < g.stepsPerFrame; i++ { err := cpu.step() if err != nil { log.Printf("Stopped by error at PC %08X",cpu.PC) log.Print(err) return Terminated } if cpu.stopped { return Terminated } if idleCounter == 0 { break } } g.lastFrameDuration = time.Since(startTime) if inpututil.IsKeyJustReleased(ebiten.KeyF12) { g.debug = !g.debug } return nil } func (g *Game) Draw(screen *ebiten.Image) { screen.DrawImage(framebuffer.framebuffer, nil) if g.debug { buf := fmt.Sprintf("PC: %08X FP: %08X RP: %08X ESP: %2X %v", cpu.PC, cpu.FP, cpu.RP, cpu.ESP, g.lastFrameDuration) ebitenutil.DebugPrint(screen, buf) } /* screen.Set(g.x, g.y, color.RGBA{255,0,0,0}) screen.Set(g.x, g.y+1, color.RGBA{0,255,0,0}) screen.Set(g.x, g.y+2, color.RGBA{0,255,255,0}) screen.Set(g.x, g.y+3, color.RGBA{255,255,255,0}) g.x += 1 if g.x > 319 { g.x = 0 } */ // if idleCounter == 0 { ebitenutil.DebugPrint(screen, "idle") } } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { return 640, 400 } func main() { var codefile string = "" addrPtr := flag.Int("a",0,"starting address") tracePtr := flag.Bool("t",false,"trace") cardImgPtr := flag.String("i", "sdcard.img", "SD card image file") flag.Parse() if len(flag.Args()) > 0 { codefile = flag.Args()[0] } else { codefile = "rommon.prog" } log.SetFlags(0) oldState, err := SetRawConsole() if err != nil { panic(err) } defer RestoreConsole(oldState) cpu.initialize() mem.initialize(4096 * 1024 / 4) uart.cpu = &cpu mem.attachIO(&uart, 0) err = sdspi.openImage(*cardImgPtr) if err != nil { panic(err) } defer sdspi.closeImage() //sdspi.debug = true mem.attachIO(&sdspi, 1) framebuffer.initialize() mem.attachIO(&framebuffer, 2) irqc.initialize() mem.attachIO(&irqc, 3) cpu.mem = &mem cpu.PC = word(*addrPtr) if codefile != "" { mem.loadFromFile(codefile, *addrPtr) } consoleChan = make(chan byte) uart.consoleChan = make(chan byte) ebiten.SetWindowSize(800, 600) ebiten.SetWindowTitle("Tridora Framebuffer") g := Game{} cpu.trace = *tracePtr g.stepsPerFrame = 166666 // g.stepsPerFrame = 1 go func(ch chan byte) { for { buf := make([] byte,1); n, err := ConsoleRead(buf) if err != nil { fmt.Println("read error on stdin, closing channel") close(ch) return } if n > 0 {ch <- buf[0] } } }(uart.consoleChan) if err := ebiten.RunGame(&g); err != Terminated && err != nil { log.Panic(err) } }