Share
Embed
Transcript
95 slides
-
جຊεϖοΫ ߲ ֓ཁ CPU LR35902 4.19MHz 8bit RAM 8kB VRAM
8kB ROM 256k~32MBit Display 4֊ௐϞϊΫϩɺ160×144υοτ εϓϥΠτ 8×8 ࠷େ40ݸදࣔ / 1ϥΠϯ্ʹ ࠷େ10ݸදࣔ എܠ 256×256υοτ αϯυ ۣܗ2ch+ܗϝϞϦԻݯ1ch+ϊΠζ1ch ௨৴ϙʔτ γϦΞϧ௨৴ϙʔτࡌ ׂࠐΈػೳ ύουೖྗׂࠐΈɺγϦΞϧ௨৴ׂࠐΈɺλΠϚʔׂࠐΈ LCDCׂࠐΈɺVblankׂࠐΈ -
-$% Y (16 "16 $16 73". L# 83". L# $BSUSJEHF
CJUόε 30. 3". ϒϩοΫਤ -3 .)[ -
Ϩδελ CJU CJU ֓ཁ " ' ൚༻ϑϥάϨδελ # $ ൚༻Ϩδελ
% & ʏ ) - ʏ 41 ελοΫϙΠϯλ 1$ ϓϩάϥϜΧϯλ -
ϑϥάʢ'ʣϨδελ CJU ུশ ໊শ ৄࡉ ; θϩ ԋࢉ݁Ռ͕̌ͳΒ̍
/ αϒτϥΫτ Ճࢉ໋ྩͳΒ̌ݮࢉ໋ྩͳΒ̍ ) ϋʔϑΩϟϦʔ ԼҐ̐ϏοτͰ͔Β܁Γ্͛ ্Ґ̐Ϗοτ͔Β܁ΓԼ͕͛ൃੜͨ͠ Β̍ $ ΩϟϦʔ ԋࢉͰΦʔόʔϑϩʔ͕ൃੜͨ͠Β̍ ະ༻ -
Ϩδελ type Registers struct { A byte B byte C
byte D byte E byte H byte L byte F byte } -
Ϩδελ type CPU struct { PC types.Word SP types.Word Regs
Registers …omitted… } -
$16ͷجຊಈ࡞ func (cpu *CPU) Step() Cycle { opcode := cpu.fetch()
inst := instructions[opcode] operands := cpu.fetchOperands( inst.OperandsSize ) inst.Execute(cpu, operands) return inst.Cycles } -
$16ͷجຊಈ࡞ func (cpu *CPU) Step() Cycle { opcode := cpu.fetch()
inst := instructions[opcode] operands := cpu.fetchOperands( inst.OperandsSize ) inst.Execute(cpu, operands) return inst.Cycles } -
ϑΣον func (cpu *CPU) fetch() byte { d := cpu.bus.ReadByte(cpu.PC)
cpu.PC++ return d } -
ϑΣον func (cpu *CPU) fetch() byte { d := cpu.bus.ReadByte(cpu.PC)
cpu.PC++ return d } 1$ʹ֨ೲ͞ΕͨΞυϨε ͔ΒσʔλΛऔಘ -
Y Y Y Y Y'' 1$ ϓϩάϥϜΧϯλ ʜ Y -%#
O Y" ʜ ϝϞϦ ϑΣον 1$Yͷ߹ -
ϑΣον func (cpu *CPU) fetch() byte { d := cpu.bus.ReadByte(cpu.PC)
cpu.PC++ return d } -
1$ ϓϩάϥϜΧϯλ ϑΣον Y Y Y Y Y'' ʜ Y
-%# O Y" ʜ ϝϞϦ -
σίʔυ func (cpu *CPU) Step() Cycle { opcode := cpu.fetch()
inst := instructions[opcode] operands := cpu.fetchOperands( inst.OperandsSize ) inst.Execute(cpu, operands) return inst.Cycles } -
σίʔυ type inst struct { Opcode byte //ɹ໋ྩίʔυɹ Description string
// ໋ྩ໊ OperandsSize uint // Ҿͷ Cycles uint // ࣮ߦʹඞཁͳαΠΫϧ Execute func(cpu *CPU, operands []byte) } -
σίʔυ var instructions = []*inst{ &inst{0x0, "NOP", 0, 1, func(cpu
*CPU, operands []byte) { ɹɹ cpu.nop() } }, &inst{0x1, "LD BC,nn", 2, 3, func(cpu *CPU, operands []byte) { cpu.ldn_nn(…) } }, …omitted… &inst{0x6, "LD B,n", 1, 2, func(cpu *CPU, operands []byte) { cpu.ldnn_n(&cpu.Regs.B, operands) } }, …omitted… } -
σίʔυ var instructions = []*inst{ &inst{0x0, "NOP", 0, 1, func(cpu
*CPU, operands []byte) { ɹɹ cpu.nop() } }, &inst{0x1, "LD BC,nn", 2, 3, func(cpu *CPU, operands []byte) { cpu.ldn_nn(…) } }, …omitted… &inst{0x6, "LD B,n", 1, 2, func(cpu *CPU, operands []byte) { cpu.ldnn_n(&cpu.Regs.B, operands) } }, …omitted… } -%# O Φϖϥϯυ $16αΠΫϧ MEOO@Oؔ -
σίʔυ func (cpu *CPU) Step() Cycle { opcode := cpu.fetch()
inst := instructions[opcode] operands := cpu.fetchOperands( inst.OperandsSize ) inst.Execute(cpu, operands) return inst.Cycles } -
σίʔυ func (cpu *CPU) fetchOperands(size uint) []byte { operands :=
[]byte{} for i := 0; i < int(size); i++ { operands = append(operands, cpu.fetch()) } return operands } -
1$ ϓϩάϥϜΧϯλ Y Y Y Y Y'' ʜ Y -%#
O Y" ʜ ϝϞϦ σίʔυ ΦϖϥϯυଈY" ͕Ϧʔυ͞ΕΔ -
࣮ߦ func (cpu *CPU) Step() Cycle { opcode := cpu.fetch()
inst := instructions[opcode] operands := cpu.fetchOperands( inst.OperandsSize ) inst.Execute(cpu, operands) return inst.Cycles } -
࣮ߦ // LD nn,n // Description: // Put value nn
into n. // Use with: // nn = B,C,D,E,H,L,BC,DE,HL,SP func (cpu *CPU) ldnn_n(reg *types.Register, operands []byte) { *reg = operands[0] } -
ϝϞϦϚοϓ L#30.CBOL L#TXJUDIBCMF30. L#73". L#TXJUDIBCMF3". L#83". 83".NJSSPS 0". 4QSJUF 3".
QFSJQIFSBM *OUFSOBM3". *32&OBCMF3FHJTUFS Y Y Y Y" Y$ Y& Y'& Y'&" Y'' Y'''' -
ϝϞϦϚοϓ L#30.CBOL L#TXJUDIBCMF30. L#73". L#TXJUDIBCMF3". L#83". 83".NJSSPS 0". 4QSJUF 3".
QFSJQIFSBM *OUFSOBM3". *32&OBCMF3FHJTUFS Y Y Y Y" Y$ Y& Y'& Y'&" Y'' Y'''' ΧʔτϦοδ෦ ͷϝϞϦ -
Ϧʔυ func (b *Bus) ReadByte(addr types.Word) byte { switch {
case addr >= 0x0000 && addr <= 0x7FFF: if b.bootmode && addr < 0x0100 { return BIOS[addr] } return b.cartridge.ReadByte(addr) // Video RAM case addr >= 0x8000 && addr <= 0x9FFF: return b.vRAM.Read(addr - 0x8000) case addr >= 0xA000 && addr <= 0xBFFF: return b.cartridge.ReadByte(addr) // Working RAM case addr >= 0xC000 && addr <= 0xDFFF: return b.wRAM.Read(addr - 0xC000) case … omitted … default: } return 0 } -
(16ͷجຊಈ࡞ func (g *GPU) Step(cycles uint) { …omitted… g.clock +=
cycles if g.clock >= 456 { if g.ly < 144 { g.buildBGTile() } else if g.ly == 144 { g.buildSprites() } else if g.ly >= 144+10 { g.ly = 0 …omitted… } g.ly++ g.clock -= 456 } } -
(16ͷجຊಈ࡞ func (g *GPU) Step(cycles uint) { …omitted… g.clock +=
cycles if g.clock >= 456 { if g.ly < 144 { g.buildBGTile() } else if g.ly == 144 { g.buildSprites() } else if g.ly >= 144+10 { g.ly = 0 …omitted… } g.ly++ g.clock -= 456 } } (16αΠΫϧ -
func (g *GPU) Step(cycles uint) { …omitted… g.clock += cycles
if g.clock >= 456 { if g.ly < 144 { g.buildBGTile() } else if g.ly == 144 { g.buildSprites() } else if g.ly >= 144+10 { g.ly = 0 …omitted… } g.ly++ g.clock -= 456 } } (16ͷجຊಈ࡞ ΫϩοΫΛMJOF ΧϯτΞοϓ ඳըதϥΠϯΛࣔ͢ ϨδελΛΠϯΫϦϝϯτ -
(16ͷجຊಈ࡞ func (g *GPU) Step(cycles uint) { …omitted… g.clock +=
cycles if g.clock >= 456 { if g.ly < 144 { g.buildBGTile() } else if g.ly == 144 { g.buildSprites() } else if g.ly >= 144+10 { g.ly = 0 …omitted… } g.ly++ g.clock -= 456 } } දࣔൣғͰ͋Ε എܠMJOFߏங -
(16ͷجຊಈ࡞ func (g *GPU) Step(cycles uint) { …omitted… g.clock +=
cycles if g.clock >= 456 { if g.ly < 144 { g.buildBGTile() } else if g.ly == 144 { g.buildSprites() } else if g.ly >= 144+10 { g.ly = 0 …omitted… } g.ly++ g.clock -= 456 } } ࠷ऴϥΠϯͰ εϓϥΠτΛඳը -
(16ͷجຊಈ࡞ func (g *GPU) Step(cycles uint) { …omitted… g.clock +=
cycles if g.clock >= 456 { if g.ly < 144 { g.buildBGTile() } else if g.ly == 144 { g.buildSprites() } else if g.ly >= 144+10 { g.ly = 0 …omitted… } g.ly++ g.clock -= 456 } } 7CMBOLظؒྃ -
̔#ZUFº#ZUFͰºαΠζͷը૾Λදݱ "EESFTTT %BUB Y Y Y Y' Y Y'' Y
Y'' Y Y'' Y" Y& Y$ Y$ Y& Y "EESFTTT %BUB Y Y Y Y' Y Y#' Y Y#' Y Y'' Y# Y& Y% Y$ Y' Y എܠͷඳը ̌ʙ̏ͷύϨοτ*% ̎CJUͷ৭ใ -
λΠϧϚοϓ QY UJMF QY UJMF Y Y#'' YdY#''Λ YλΠϧͷ λΠϧ൪߸ͰຒΊΔ
QY UJMF QY UJMF දࣔՄೳൣғ දࣔൣғ -
എܠͷඳը func (g *GPU) buildBGTile() { var tileID int for
x := 0; x < constants.ScreenWidth; x++ { tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32 tileX := x+int(g.scrollX))/8%32 tileID = g.getTileID( tileY, uint(tileX, g.getBGTilemapAddr() ) paletteID := g.getBGPaletteID( tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8 ) rgba := g.getBGPalette(uint(paletteID)) base := ((g.ly)*constants.ScreenWidth + uint(x)) * 4 g.imageData[base] = rgba.R g.imageData[base+1] = rgba.G g.imageData[base+2] = rgba.B g.imageData[base+3] = rgba.A } } -
എܠͷඳը func (g *GPU) buildBGTile() { var tileID int for
x := 0; x < constants.ScreenWidth; x++ { tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32 tileX := x+int(g.scrollX))/8%32 tileID = g.getTileID( tileY, uint(tileX, g.getBGTilemapAddr() ) paletteID := g.getBGPaletteID( tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8 ) rgba := g.getBGPalette(uint(paletteID)) base := ((g.ly)*constants.ScreenWidth + uint(x)) * 4 g.imageData[base] = rgba.R g.imageData[base+1] = rgba.G g.imageData[base+2] = rgba.B g.imageData[base+3] = rgba.A } } ϥΠϯ -
func (g *GPU) buildBGTile() { var tileID int for x
:= 0; x < constants.ScreenWidth; x++ { tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32 tileX := x+int(g.scrollX))/8%32 tileID = g.getTileID( tileY, uint(tileX, g.getBGTilemapAddr() ) paletteID := g.getBGPaletteID( tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8 ) rgba := g.getBGPalette(uint(paletteID)) base := ((g.ly)*constants.ScreenWidth + uint(x)) * 4 g.imageData[base] = rgba.R g.imageData[base+1] = rgba.G g.imageData[base+2] = rgba.B g.imageData[base+3] = rgba.A } } എܠͷϥΠϯඳը UJMFͷ9:࠲ඪ -
func (g *GPU) buildBGTile() { var tileID int for x
:= 0; x < constants.ScreenWidth; x++ { tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32 tileX := x+int(g.scrollX))/8%32 tileID = g.getTileID( tileY, uint(tileX, g.getBGTilemapAddr() ) paletteID := g.getBGPaletteID( tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8 ) rgba := g.getBGPalette(uint(paletteID)) base := ((g.ly)*constants.ScreenWidth + uint(x)) * 4 g.imageData[base] = rgba.R g.imageData[base+1] = rgba.G g.imageData[base+2] = rgba.B g.imageData[base+3] = rgba.A } } എܠͷϥΠϯඳը λΠϧ൪߸Λ 73".͔Βऔಘ λΠϧ൪߸͔Β ύϨοτ൪߸Λऔಘ -
എܠͷϥΠϯඳը func (g *GPU) buildBGTile() { var tileID int for
x := 0; x < constants.ScreenWidth; x++ { tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32 tileX := x+int(g.scrollX))/8%32 tileID = g.getTileID( tileY, uint(tileX, g.getBGTilemapAddr() ) paletteID := g.getBGPaletteID( tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8 ) rgba := g.getBGPalette(uint(paletteID)) base := ((g.ly)*constants.ScreenWidth + uint(x)) * 4 g.imageData[base] = rgba.R g.imageData[base+1] = rgba.G g.imageData[base+2] = rgba.B g.imageData[base+3] = rgba.A } } όοϑΝʹ3(#" Λ֨ೲ -
func (g *GB) Next() []byte { for { cycles :=
g.cpu.Step() * 4 g.gpu.Step(cycles) …omitted… g.currentCycle += cycles if g.currentCycle >= 70224 { g.win.PollKey() g.currentCycle -= 70224 return g.gpu.GetImageData() } } } ΤϛϡϨʔλͷجຊಈ࡞ -
func (g *GB) Next() []byte { for { cycles :=
g.cpu.Step() * 4 g.gpu.Step(cycles) …omitted… g.currentCycle += cycles if g.currentCycle >= 70224 { g.win.PollKey() g.currentCycle -= 70224 return g.gpu.GetImageData() } } } ΤϛϡϨʔλͷجຊಈ࡞ $16Λ໋ྩ࣮ߦ $16ΫϩοΫप ͳͷͰY -
func (g *GB) Next() []byte { for { cycles :=
g.cpu.Step() * 4 g.gpu.Step(cycles) …omitted… g.currentCycle += cycles if g.currentCycle >= 70224 { g.win.PollKey() g.currentCycle -= 70224 return g.gpu.GetImageData() } } } ΤϛϡϨʔλͷجຊಈ࡞ (16ΛܦաΫϩοΫՔಇ -
ΤϛϡϨʔλͷجຊಈ࡞ func (g *GB) Next() []byte { for { cycles
:= g.cpu.Step() * 4 g.gpu.Step(cycles) …omitted… g.currentCycle += cycles if g.currentCycle >= 70224 { g.win.PollKey() g.currentCycle -= 70224 return g.gpu.GetImageData() } } } ੵࢉΫϩοΫ͕̍ϑϨʔϜ ͑ͨΒը૾σʔλΛฦ͢ -
func (g *GB) Start() { t := time.NewTicker(16 * time.Millisecond)
for { select { case <-t.C: buf := g.Next() g.win.Render(buf) } } } ΤϛϡϨʔλͷجຊಈ࡞ NTपظͰը૾Λੜ -
ΤϛϡϨʔλͷجຊಈ࡞ func (w *Window) Render(buf []byte) { imgData := make([]color.RGBA,
constants.ScreenWidth*constants.ScreenHeight) i := 0 for i*4 < len(buf) { y := constants.ScreenHeight - (i / constants.ScreenWidth) - 1 d := color.RGBA{buf[i*4], buf[i*4+1], buf[i*4+2], buf[i*4+3] imgData[y*constants.ScreenWidth+i%constants.ScreenWidth] = d i++ } w.image.Pix = imgData bg := color.RGBA{R: 0x0F, G: 0x38, B: 0x0F, A: 0xFF} w.win.Clear(bg) spr := pixel.NewSprite(pixel.Picture(w.image), pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight)) spr.Draw(w.win, pixel.IM) w.updateCamera() w.win.Update() } -
t.Run(tt.name, func(t *testing.T) { emu := setup(“hello.gb”) buf := skipFrame(emu,
100) file, err := os.Create(“hello” + ".png") defer file.Close() …omitted… img := image.NewRGBA(image.Rect(0, 0, 160, 144)) set(img, buf) if err := png.Encode(file, img); err != nil { …omitted… } }) ΤϛϡϨʔλͷςετ -
t.Run(tt.name, func(t *testing.T) { emu := setup(“hello.gb”) buf := skipFrame(emu,
100) file, err := os.Create(“hello” + ".png") defer file.Close() …omitted… img := image.NewRGBA(image.Rect(0, 0, 160, 144)) set(img, buf) if err := png.Encode(file, img); err != nil { …omitted… } }) ΤϛϡϨʔλͷςετ ҙͷϑϨʔϜͷը૾Λ औಘͰ͖ΔΑ͏ʹ͓ͯ͘͠ -
t.Run(tt.name, func(t *testing.T) { emu := setup(“hello.gb”) buf := skipFrame(emu,
100) file, err := os.Create(“hello” + ".png") defer file.Close() …omitted… img := image.NewRGBA(image.Rect(0, 0, 160, 144)) set(img, buf) if err := png.Encode(file, img); err != nil { …omitted… } }) ΤϛϡϨʔλͷςετ QOHͱͯ͠อଘ -
const go = new Go(); const res = await fetch("./main.wasm");
const bytes = await res.arrayBuffer(); const { instance } = await WebAssembly.instantiate( bytes, go.importObject ); go.run(instance); 8BTN+4 -
const go = new Go(); const res = await fetch("./main.wasm");
const bytes = await res.arrayBuffer(); const { instance } = await WebAssembly.instantiate( bytes, go.importObject ); go.run(instance); 8BTN+4 XBTN@FYFDKTͰੜ͞ΕΔ JNQPSUΦϒδΣΫτΛ͢ -
func main() { w := js.Global() w.Set("GB", js.FuncOf(newGB)) select {}
} 8BTN(PMBOH HMPCBMʹ(#Λੜ͢ -
func newGB(this js.Value, args []js.Value) interface{} { buf := []byte{}
for i := 0; i < args[0].Get("length").Int(); i++ { buf = append(buf, byte(args[0].Index(i).Int())) } cart, err := cartridge.NewCartridge(buf) …omitted… } 8BTN(PMBOH +4͔ΒΧʔτϦοδͷ σʔλΛड͚औΔ -
func newGB(this js.Value, args []js.Value) interface{} { …omitted… this.Set("next", js.FuncOf(
func(this js.Value, args []js.Value) interface{} { img := emu.Next() return js.TypedArrayOf(img) } )) …omitted… return this } 8BTN(PMBOH -
func newGB(this js.Value, args []js.Value) interface{} { …omitted… this.Set("next", js.FuncOf(
func(this js.Value, args []js.Value) interface{} { img := emu.Next() return js.TypedArrayOf(img) } )) …omitted… return this } 8BTN(PMBOH OFYUͱ͍͏ؔΛ+4ଆʹFYQPSU -
func newGB(this js.Value, args []js.Value) interface{} { …omitted… this.Set("next", js.FuncOf(
func(this js.Value, args []js.Value) interface{} { img := emu.Next() return js.TypedArrayOf(img) } )) …omitted… return this } 8BTN(PMBOH ࣍ͷϑϨʔϜΛ5ZQFE"SSBZͱͯ͠ฦ͢ -
const frame = () => { const img = gb.next();
image.data.set(img); ctx.putImageData(image, 0, 0); requestAnimationFrame(frame); }; frame(); 8BTN+4 -
ύϑΥʔϚϯε HP ϒϥβ ̍'SBNF NT $ISPNF NT 'JSF'PY NT 4BGBSJ
NT MacBook Air (Retina, 13-inch, 2018) 1.6 GHz Intel Core i5 16 GB 2133 MHz LPDDR3 go1.12.12 TobuTobuGirl -
func (cpu *CPU) applyZeroBy(computed byte) { if computed == 0
{ cpu.setFlag(Z) } else { cpu.clearFlag(Z) } } BQQMZ;FSP#Z HP -
BQQMZ;FSP#Z XBU (func $github.com_bokuweb_gopher_boy_pkg_cpu.__CPU_.applyZeroBy (type 0) (result i32) loop ;;
label = @1 block ;; label = @2 block ;; label = @3 block ;; label = @4 block ;; label = @5 block ;; label = @6 block ;; label = @7 block ;; label = @8 global.get 1 br_table 0 (;@8;) 0 (;@8;) 1 (;@7;) 2 (;@6;) 3 (;@5;) 3 (;@5;) 4 (;@4;) 4 (;@4;) 5 (;@3;) 6 (;@2;) end global.get 2 global.get 4 i32.wrap_i64 i32.load offset=16 i32.le_u if ;; label = @8 global.get 2 i32.const 8 i32.sub global.set 2 global.get 2 i64.const 390397952 i64.store i32.const 0 global.set 1 call $runtime.morestack_noctxt if ;; label = @9 i32.const 1 return end end global.get 2 i32.const 16 i32.sub global.set 2 global.get 2 i64.load8_u offset=32 i64.const 56 i64.shl i64.const 56 i64.shr_u i64.eqz i64.extend_i32_u i32.wrap_i64 i32.eqz if ;; label = @8 i32.const 6 global.set 1 br 7 (;@1;) end end global.get 2 global.get 2 i64.load offset=24 i64.store global.get 2 i64.const 4 i32.const 8 i32.sub global.set 2 global.get 2 i64.const 390397955 i64.store i32.const 0 global.set 1 call $github.com_bokuweb_gopher_boy_pkg_cpu.__CPU_.setFlag if ;; label = @7 i32.const 1 return end end end global.get 2 i32.const 16 i32.add global.set 2 global.get 2 i32.load16_u offset=2 global.set 0 global.get 2 i32.load16_u global.set 1 global.get 2 i32.const 8 i32.add global.set 2 i32.const 0 return end global.get 2 global.get 2 i64.load offset=24 i64.store global.get 2 i64.const 4 i64.store offset=8 global.get 2 i32.const 8 i32.sub global.set 2 global.get 2 i64.const 390397960 i64.store i32.const 0 global.set 1 call $github.com_bokuweb_gopher_boy_pkg_cpu.__CPU_.clearFlag if ;; label = @4 i32.const 1 return end end i32.const 4 global.set 1 br 1 (;@1;) end end unreachable)