Do you like asm? 

I am building a z80 emulator!

Arcade games

Consoles

Game Boy

ZX Spectrum

BASIC was crap

  • No procedures
    • GO TO (jmp)
    • GO SUB (call)
  • No memory management
    • PEEK and POKE
  • All vars are global
  • Only FOR loops

Z80 ASM

  • Hand compile BASIC (change BASIC to Assembly)
  • Hand assemble (look up a printed table, POKE away)
  • Without books or internet (basically no English books in Italy in the 80's). Learn by decompiling the ROM

80286 ASM

  • Similar to Z80
  • Hand compile (e.g. Pascal -> Assembly)
  • Automatic assembler! 
  • BBS! Access to people who KNOW assembly!

Registers

  • 7 8bit real registers A,B,C,D,E,H,L
  • 2 16bit addressing registers IX, IY
  • various "strange" registers
    • limited alternate versions of all the above
    • interrupt register I
    • raster register R
    • flag register F
    • stack pointer SP
    • program counter PC

CISC instruction set

  • DJNZ d ("END FOR")
    • decrement B
    • compare 0
    • jump relative "d" if not equal
  • LDIR ("BLIT")
    • copy the byte at address HL at the address DE
    • increment HL
    • increment DE
    • decrement BC
    • repeat until BC = 0

Interrupts

  • Non Maskable (Z80 will always execute)
    • always jump to ROM location 0x0066
  • Maskable (Z80 can be prevented from executing)
    • jump to ROM localtion 0x0042
    • execute instruction on the data bus (typically a RST)
    • look up an address in a memory table and jump
var ram = new byte[65536];

// Load a ROM image
Array.Clear(ram, 0, ram.Length);
Array.Copy(File.ReadAllBytes("48.rom"), ram, 16384);

// Ports is something you supply to emulate I/O ports
var ports = new SamplePorts();

// Set up memory layout
var myZ80 = new Z80(new Memory(ram, 16384), ports);

// Run
while (!myZ80.Halted)
{
    myZ80.Parse();
}

// Show the registers
Console.WriteLine(myZ80.DumpState());

The five stages of denial testing

"Who needs testing?"

"Does this work at all?"

public void Main()
{
    var asm = new byte[]{ 0x21, 0x00, 0x00 }; // LD HL, $0000
    var z80 = new z80(asm);

    z80.Parse();

    z80.DumpState();
}

"Funny..."

[Test]
public void DoesLDHLWork()
{
    var asm = new byte[]{ 0x21, 0x00, 0x00, 0x76 }; // LD HL, $0000 ; HALT
    var testSystem = new TestSystem(asm);

    testSystem.Run();

    Assert.AreEqual(0, testSystem.HL);
}

[Test]
public void DoesLDIXWork()
{
    var asm = new byte[]{ 0xDD, 0x21, 0x00, 0x00, 0x76 }; // LD IX, $0000 ; HALT
    var testSystem = new TestSystem(asm);

    testSystem.Run();

    Assert.AreEqual(0, testSystem.IX);
}

// Moar

"Who tests the tests?"

[Test]
public void Test_LD_HL_nn()
{
    asm.LoadReg16Val(2, 0x1942);
    asm.Halt();

    en.Run();

    Assert.AreEqual(asm.Position, en.PC);
    Assert.AreEqual(0x19, en.H);
    Assert.AreEqual(0x42, en.L);
}


[Test]
public void Test_LD_DE_nn()
{
    asm.LoadReg16Val(1, 0x1942);
    asm.Halt();

    en.Run();

    Assert.AreEqual(asm.Position, en.PC);
    Assert.AreEqual(0x19, en.D);
    Assert.AreEqual(0x42, en.E);
}

"OMG Flags"

[Test]
[TestCase(0, 0x44, 0x11)]
[TestCase(0, 0x44, 0x0F)]
[TestCase(0, 0x44, 0xFF)]
[TestCase(0, 0x44, 0x01)]
[TestCase(0, 0xF4, 0x11)]
// Many, many more test cases
public void Test_ADD_A_r(byte reg, byte val, byte val2)
{
    asm.LoadRegVal(7, val);
    asm.LoadRegVal(reg, val2);
    asm.AddAReg(reg);
    asm.Halt();

    en.Run();

    Assert.AreEqual(asm.Position, en.PC);
    var trueSum = (val + val2);
    var byteSum = trueSum % 256;
    var sbyteSum = (sbyte)byteSum;
    Assert.AreEqual(byteSum, en.A);
    Assert.AreEqual(sbyteSum < 0, en.FlagS, "Flag S contained the wrong value");
    Assert.AreEqual(en.A == 0x00, en.FlagZ, "Flag Z contained the wrong value");
    Assert.AreEqual((0x0F & val2 + 0x0F & val) > 0x0F, en.FlagH, "Flag H contained the wrong value");
    // if both operands are positive and result is negative or if both are negative and result is positive
    var overflow = (val < 0x7F == val2 < 0x7F) && (val < 0x7F == sbyteSum < 0); 
    Assert.AreEqual(overflow, en.FlagP, "Flag P contained the wrong value");
    Assert.AreEqual(trueSum > 0xFF, en.FlagC, "Flag C contained the wrong value");
}

The Project

z80 - a Z80 emulator that works in real time written in C#.

 

  • Z80 Emulator

  • Z80 Assembler backend

  • 2270 tests (98% coverage)

Opcodes

  • 8-bit load group (e.g. LD A, 0x42)
  • 16-bit load group (e.g. POP HL)
  • Exchange, Block Transfer, and Search group (e.g. EX AF, AF')
  • 8-Bit Arithmetic Group (e.g. ADD 0x23)
  • General-Purpose Arithmetic and CPU Control Groups (e.g. NOP, HALT, ...)
  • 16-Bit Arithmetic Group (e.g. ADD HL, 0x2D5F, ...)
  • Rotate and Shift Group (e.g. RLCA, RLA, ...)
  • Bit Set, Reset, and Test Group (BIT, SET, RES)
  • Jump Group (JP nn, JR e, DJNZ e, ...)
  • Call and Return Group (CALL, RET, RST)
  • Undocumented opcodes (CB, DDCB, FDCB, ED)
  • Input and Output Group (IN, OUT, ...)

OTHER FEATURES

  • Address and Data bus
  • R register counts machine cycles (approximately)
  • Interrupts
  • Other pins

Undocumented stuff

  • Undocumented opcodes (DD, FD)
  • Undocumented effects (BIT, Memory Block Instructions, I/O Block Instructions, 16 Bit I/O ports, Block Instructions, Bit Additions, DAA Instruction)

New features

  • An assembler frontend thus having a full z80 assembler
  • A disassembler based on the current CPU emulator code

https://github.com/sklivvz/z80

Thank you

Do you like asm?

By Marco Cecconi

Do you like asm?

I am building a Z80 emulator!

  • 683