Z80 Assembly programming for the MSX and MSX2

The MSX and later the MSX2 were an attermpt to create a 'PC' before the PC!... based around the 8-bit processor, an 'industry standard' compatible computer specification was created, and any manufacturer could realease compatible hardware.

With 2 extension slots, the machine was upgradable, and the end user could be confident that their hardware and software would work whatever MSX machine they had!

The last generation MSX - the Turbo-R was hugely powerful, and it's upgradability means the MSX now has the V9990... combining the Turbo-R and the V9990 (V9K) we effectively have what would have been the MSX3 if it had not been cancelled
Because each MSX has different hardware, we can only generalize, but here's the hardware specification you can expect to see!
Specs:
MSX MSX2 Turbo-R V9990
Cpu 3.5mhz Z80 3.5mhz Z80 7mhz R800 (28mhz effective)
Ram 16k 64k 256k / 512k
Vram 16k 128k 128k 512k (9x MSX2 speed)
Resolution 256x192 256x212 256x212 256x212
Colors 16 16 256 256
Sound chip AY AY AY + PCM

The V9990 can be used with the MSX, or MSX2 as well! - not just the Turbo-R

Although the Turbo-R does not use a Z80, the R800 has effectively perfect compatibility, although it's 'only'  7mhz , it uses memory caching, and in optimum circumstances can perform 4x the speed of a 7mhz Z80




The MSX1 VDP
The MSX1 VDP has 16 KB of memory

The Tile Pattern definitions use 1 byte per line for 1 bit pixel data, and 1 byte per line in the Colormap area to define color, one nibble for Foreground, and one nibble for background, in the format &FB

The Tilemap has 2 modes... normal mode uses 256 definitions, in the area &0000-&07FF...

Alternate mode uses 3 Pattern definitions, one for the first 1/3rd of the screen, another for the 2nd 3rd, and the final for the last 3rd... this allows us to simualte a bitmap screen, as we have enough tiles for each block of the screen to be unique, while only having one byte per tile in the tilemap!
From To Meaning
0000 07FF VRAM: Main Tile Patterns (1/3)
0800 0FFF VRAM: Extra Tile Patterns (2/3)
1000 17FF VRAM: Extra Tile Patterns (3/3)
1800 1AFF VRAM: Tilemap
1B00 1B7F VRAM: Sprite Attributes
1B80 1BAF VRAM: Palette Table
2000 37FF VRAM: Colormap
3800 3FFF VRAM: Sprite Patterns

The color palette is fixed, and there are no brightness or other limitations (unlike the speccy!)
   0       1       2       3       4       5       6       7   
8 9 A B C D E F
Note: Color 0 is transparent.

MSX1 Sprites
the MSX1 is capable of 32 onscreen sprites (max 4 on one line), that are 8x8 or 16x16
Reg Meaning Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
R#1 mode register #1 0 BL IE0 M1 M2 0 SI MAG
R#5 sprite attribute table (LOW) A14 A13 A12 A11 A10 A9 A8 A7
R#6 sprite pattern generator table 0 0 A16 A15 A14 A13 A12 A11
R#8 mode register #2 MS LP TP CB VR 0 SPD BW

SI in R#1 is the Spritesize 0=8x8 1=16x16
MAG in R#1 will double the size of the sprites
SPD in R#8 will DISABLE the sprites - if this is set sprites will not work!
R#5 is the address of the sprite bitmap data
R#6 is the address of the sprite attributes

MSX1 Sprite Attributes
Address Sprite Bits Details
$1B00 0 XXXXXXXX  X=Xpos
$1B01 0 YYYYYYYY  Y=Ypos
$1B02 0 PPPPPPPP  P=Patternnum
$1B03 0 E---CCCC  E=Extended X C=Color
$1B04 1 XXXXXXXX  X=Xpos
$1B05 1 YYYYYYYY  Y=Ypos
$1B06 1 PPPPPPPP  P=Patternnum
$1B07 1 E---CCCC  E=Extended X C=Color
.. .. .. . .
$1BFD 31 XXXXXXXX  X=Xpos
$1BFC 31 YYYYYYYY  Y=Ypos
$1BFE 31 PPPPPPPP  P=Patternnum
$1BFF 31 E---CCCC  E=Extended X C=Color
The position and bitmap data of MSX1 sprites is defined by the attribute table, each sprite has 4 attributes...
Note the sepecial 'E' attribute in byte 4... this allows a sprite to be 'further left' than X=0 for sprites going off the left hand side of the screen

Pattern number can be from 0-255 for 8x8 tile mode....
for 16x16 tile mode it is 0-63...
NOTE... the 8x8 tiles need to be in a different order for this mode... imagine 4 8x8 blocks in a 16x16 sprite... they need to be in the following order:
 1  3
 2  4
There is a special exporter in AkuSprite editor called 'Save RAW MSX1 16x16 Sprite'

The MSX2 VDP
The MSX2 GPU has to be controlled via it's ports via OUT commands.
MSX 2 - V9938 Ports
Port When Read When Written
&98 Read Data Write Data
&99 Control Status
&9A Palette
&9B Indirect register
V9990
Port When Read When Written
&60 Read Data Write Data
&61 Palette
&62 Command
&63 Regsiter Data
&64 Register Select
&65 Status
&66 Interrupt Flag
&67 System
&6F Superimpose

To control the MSX2 GPU, we have to set it's registers, and then send a command to register 46

To Set a register, first send the value to put in the register  to the control port, then send the register number +128 to the control port.

EG lets set Reg 15 to 2...
    ld a,2
    out (&99),a
    ld a,15+128
    out (&99)dd
28 is an instruction meaning 'This is a register number'
Some registers like R#14 need more than 1 byte, just send these after the 14+128 command has been sent - the VDP will be expecting the extra data!

MSX 2 - Ports, Registers and Commands

Commands
Name Command From To Units ByteCode Function
HMMC High Spd Move CPU VRAM bytes %11110000 Fill Bytes from OUTI
YMMM High Spd MoveY VRAM VRAM bytes %11100000 Copy an area ffrom Vram to Vram
only changing Y
HMMM High Spd Move VRAM VRAM bytes %11010000 Copy an area from Vram to Vram fast (blit)
HMMV High Spd Move VDP VRAM bytes %11000000 Flood fill a square with a single byte
LMMC Logical Move CPU VRAM dots %10110000
LMCM Logical Move VRAM CPU dots %10100000
LMMM Logical Move VRAM VRAM dots %10010000 Copy an area of Vram 
with Logical conditions (Transparency)
LMMV Logical Move VDP VRAM dots %10000000
LINE Line VDP VRAM dots %01110000
SRCH Search VDP VRAM dots %01100000
PSET Pset VDP VRAM dots %01010000
POINT Point VDP VRAM dots %01000000
STOP Stop %00000000 Stop processing current task

If you want to know all the details, you should download the v9938 documentation here


Status Registers
Reg Meaning Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
S#0 Status 0 F 5S C 5SN 5SN 5SN 5SN 5SN
S#1 Status 1 FL LPS ID ID ID ID ID FH
S#2 Status 2 TR VR HR BD 1 1 EO CE
S#3 Status 3 X7 X6 X5 X4 X3 X2 X1 X0
S#4 Status 4 1 1 1 1 1 1 1 X8
S#5 Status 5 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0
S#6 Status 6 1 1 1 1 1 1 Y9 Y8
S#7 Status 7 C7 C6 C5 C4 C3 C2 C1 C0
S#8 Status 8 BX7 BX6 BX5 BX4 BX3 BX2 BX1 BX0
S#9 Status 9 1 1 1 1 1 1 1 BX8
             
Note:
The firmware expects status register S#0 to be selected in R#15

Interrupts will fire until S#0 is read... so you must do this if you write your own interrupt handler!
Registers
Reg Meaning Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
R#0 mode register #0 0 DG IE2 IE1 M5 M4 M3 0
R#1 mode register #1 0 BL IE0 M1 M2 0 SI MAG
R#2 pattern name table 0 A16 A15 A14 A13 A12 A11 A10
R#3 colour table (LOW) A13 A12 A11 A10 A9 A8 A7 A6
R#4 pattern generator table 0 0 A16 A15 A14 A13 A12 A11
R#5 sprite attribute table (LOW) A14 A13 A12 A11 A10 A9 A8 A7
R#6 sprite pattern generator table 0 0 A16 A15 A14 A13 A12 A11
R#7 border colour/character colour at text mode TC3 TC2 TC1 TC0 BD3 BD2 BD1 BD0
R#8 mode register #2 MS LP TP CB VR 0 SPD BW
R#9 mode register #3 LN 0 S1 S0 IL E0 *NT DC
R#10 colour table (HIGH) 0 0 0 0 0 A16 A15 A14
R#11 sprite attribute table (HIGH) 0 0 0 0 0 0 A16 A15
R#12 character colour at text blinks T23 T22 T21 T20 BC3 BC2 BC1 BC0
R#13 blinking period ON3 ON2 ON1 ON0 OF3 OF2 OF1  OF0
R#14 VRAM access address (HIGH) 0 0 0 0 0 A16 A15 A14


A7 A6 A5 A4 A3 A2 A1 A0


0 RW A13 A12 A11 A10 A9 A8
R#15 indirect specification of S#n 0 0 0 0 S3 S2 S1 S0
R#16 indirect specification of P#n 0 0 0 0 C3 C2 C1 C0
R#17 indirect specification of R#n AII 0 R5 R4 R3 R2 R1 R0
R#18 screen location adjustment (ADJUST) V3 V2 V1 V0 H3 H2 H1 H0
R#19 scanning line number when the interrupt occurs IL7 IL6 IL5 IL4 IL3 IL2 IL1 IL0
R#20 colour burst signal 1 0 0 0 0 0 0 0 0
R#21 colour burst signal 2 0 0 1 1 1 0 1 1
R#22 colour burst signal 3 0 0 0 0 0 1 0 1
R#23 screen hard scroll DO7 DO6 DO5 DO4 DO3 DO2 DO1 DO0
R#32 SX: X-coordinate to be transferred (LOW) SX7 SX6 SX5 SX4 SX3 SX2 SX1 SX0
R#33 SX: X-coordinate to be transferred (HIGH) 0 0 0 0 0 0 0 SX8
R#34 SY: Y-coordinate to be transferred (LOW) SY7 SY6 SY5 SY4 SY3 SY2 SY1 SY0
R#35 SY: Y-coordinate to be transferred (HIGH) 0 0 0 0 0 0 SY9 SY8
R#36 DX: X-coordinate to be transferred to (LOW) DX7 DX6 DX5 DX4 DX3 DX2 DX1 DX0
R#37 DX: X-coordinate to be transferred to (HIGH) 0 0 0 0 0 0 0 DX8
R#38 DY: Y-coordinate to be transferred to (LOW) DY7 DY6 DY5 DY4 DY3 DY2 DY1 DY0
R#39 DY: Y-coordinate to be transferred to (HIGH) 0 0 0 0 0 0 DY9 DY8
R#40 NX: num. of dots to be transferred in X direction (LOW) NX7 NX6 NX5 NX4 NX3 NX2 NX1 NX0
R#41 NX: num. of dots to be transferred in X direction (HIGH) 0 0 0 0 0 0 0 NX8
R#42 NY: num. of dots to be transferred in Y direction (LOW) NY7 NY6 NY5 NY4 NY3 NY2 NY1 NY0
R#43 NY: num. of dots to be transferred in Y direction (HIGH) 0 0 0 0 0 0 NY9 NY8
R#44 CLR: for transferring data to CPU - - - - C3 C2 C1 C0


- - - - - C1 C0 G5


C7 C6 C5 C4 C3 C2 C1 C0
R#45 ARG: bank switching between VRAM and expanded VRAM 0 - MXD - DIY DIX - -
R#46 CMR: send VDP command CMD CMD CMD CMD MSK MSK MSK MSK

V9990 - Ports, Registers and Commands
Commands
Name Command From To Units ByteCode Function
STOP Stop %00000000 Stop processing current task
LMMC Logical Move CPU VRAM %00010000 Fill Bytes from OUTI
LMMV Logical Move VDP VRAM %00100000 Flood fill a square with a single byte
LMCM Logical Move VRAM CPU %00110000
LMMM Logical Move VRAM VRAM dots %01000000 Copy an area from Vram to Vram fast (blit)
CMMC Color-Develop CPU VRAM %01010000


CMMM Color-Develop VRAM VRAM %01110000
BMXL Linear BMP L-VRAM VRAM %10000000
BMLX Linear BMP VRAM L-VRAM %10010000
BMLL Linear BMP L-VRAM L-VRAM %10100000
LINE Line %10110000
SEARCH Search %11000000
POINT Point %11010000
PSET Pset %11100000
ADVANCE %11110000

If you want to know all the details, you should download the v9938 documentation here


Status 
Reg Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
Port &65 TR VR HR BD 0 MCS EO CE
         
Check CE to see if VDP is busy...
   
Note:
Even if you:re using the VDP, you need to check the MSX status port in your interrupt handler as usual!
Registers
Reg Meaning Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
R#0 Vram Write L L L L L L L L L
R#1 Vram Write H H H H H H H H H
R#2 Vram Write X X X X X X X X X
R#3 Vram Read L L L L L L L L L
R#4 Vram Read H H H H H H H H H
R#5 Vram Read X X X X X X X X X
R#6 Screen Mode D D C C X X B B
R#7 Screen Mode
R#8 Control D S Y S W 0 1 0
R#9 Interrupt
R#10 Interrupt
R#11 Interrupt
R#12 Interrupt
R#13 Palette Control
R#14 Palette Pointer (autoinc) Num Num Num Num Num Num Chn Chn
R#15 Back drop color - - B B B B B B
R#16 Display Adjust V V V V H H H H
R#17 Scroll Control
R#18 Scroll Control
R#19 Scroll Control
R#20 Scroll Control
R#21 Scroll Control
R#22 Scroll Control
R#23 Scroll Control
R#24 Scroll Control
R#25 Sprite Generator Base Address
R#26 LCD Control
R#27 Priority Control
R#28 Cursor Sprite Palette Offset
R#32 SX L L L L L L L L L
R#33 SX H H H H H H H H H
R#34 SY L L L L L L L L L
R#35 SY H H H H H H H H H
R#36 DX L L L L L L L L L
R#37 DX H H H H H H H H H
R#38 DY L L L L L L L L L
R#39 DY H H H H H H H H H
R#40 NX L L L L L L L L L
R#41 NX H H H H H H H H H
R#42 NY L L L L L L L L L
R#43 NY H H H H H H H H H
R#44 Move 0 0 0 0 DIY DIX NEQ NAJ
R#45 Logical Operation 0 0 0 TP L11 L10 L01 L00
R#46 Write Mask L L L L L L L L L
R#47 Write Mask H H H H H H H H H
R#48 Foreground Font Color L L L L L L L L L
R#49 Foreground Font Color H H H H H H H H H
R#50 Background Font Color L L L L L L L L L
R#51 Background Font Color H H H H H H H H H
R#52 Operation Code C C C C - - - -
R#53 Search Command B B B B B B B B
R#54 Search Command - - - - - B B B

AY Sound Chip:
Register Meaning Bit Meaning Details
0 Tone Pitch L - Channel A LLLLLLLL Lower value = Higher pitch
1 Tone Pitch H - Channel A ----HHHH Lower value = Higher pitch
2 Tone Pitch L - Channel B LLLLLLLL Lower value = Higher pitch
3 Tone Pitch H - Channel B ----HHHH Lower value = Higher pitch
4 Tone Pitch L - Channel C LLLLLLLL Lower value = Higher pitch
5 Tone Pitch H - Channel C ----HHHH Lower value = Higher pitch
6 Noise Generator ---NNNNN Higer = Faster noise
7 Mixer  --NNNTTT   N=Noise T=Tone (Channel --CBACBA 1=mute 0=normal)
8 Amplitude - Channel A ---EVVVV E=Envelope (1=Enabled) VVVV=Volume
9 Amplitude - Channel B ---EVVVV E=Envelope (1=Enabled) VVVV=Volume
10 Amplitude - Channel C ---EVVVV E=Envelope (1=Enabled) VVVV=Volume
11 Envelope L (Volume over time)  LLLLLLLL Lower=Faster Envelope
12 Envelope H (Volume over time)  HHHHHHHH Lower=Faster Envelope
13 Envelope Selection ----EEEE Envelope number (See PDF)
For more details, please see the AY sound chip PDF

MSX Banks, Slots and Subslots... Oh my!:
The MSX memory layout is far more advanced and powerful than other 8 bits... unfortunately that tends to just mean that it's more annoying!

Take a look at an MSX! You'll see it has 1 or usually 2 cartridge slots... right?... well actually there are two extra slots! 0 and 3 are 'internal' slots used by the system itself... so we have slots 0-3... simple right... actually no!

A 'Slot' can (but is not allways) be split into 4 subslots... also numbered 0 to 3
EVERYTHING is in a slot, so RAM and ROM are in a slot somewhere... but unfortunately they aren't in the same place on all machines!... Slot 1 and 2 are always the cartridge slots, but the RAM could be in slot 3, or in slot 0-2 (slot 0 subslot 2)

It's all quite annoying!

The MSX memory map is split into 4 16K chunks, and each bank can be 'pointed' to one of the 3 slots... If the slot is expanded, each of these banks can be set to a different subslot... so bank 0 can point to 0-0 (slot 0 subslot 0), and bank 1 can point to 0-2 (slot 0 subslot 2)... while banks 2 and 3 could point to slot 3!

It needs to be understood that each slot has 4 banks for a full 64k - so when bank 0 and 1  points to slot 3-0... they are different 16k chunks... also in this case it is not possible to swap them round... Bank 0 (&0000-&3FFF) of the Z80 memory cannot point to bank 1 (&4000-&7FFF) of the Slot

So suppose we have the following set up:


Subslots
Slot 0 0-0 0-1 0-2 0-3
Slot 1 1 Unexpanded slot
Slot 2 2-0 2-1 2-2 2-3
Slot 3 3-0 3-1 3-2 3-3

ROM  
RAM  
Cartridge  
Empty

Z80 Banks
The Z80 address range is split into 4 banks of 16k
We may see a systems setup at boot in the following... .Remember Slot 3-0 will have 4 different memory banks, but they can only be mapped into the matching positions of the Z80 address range.

*** We can only map a Z80 bank to the MATCHING slot bank!

Slot Selection Register
Which Slot the Z80 will see in each bank is defined by the slot selection register at port &A8... it's 8 bits define all 4 banks slot number... each 2 bits define the slot number for an area of the address range

Port &A8 Bits 7 6 5 4 3 2 1 0
Bank number     3 (&C000-&FFFF)     2 (&8000-&BFFF)     1 (&4000-&7FFF)     0 (&0000-&3FFF)  

Sub-Slot Selection
Not all slots are expanded.... but it's easy to tell if they are, on boot addresses &FCC1-&FCC4 will be configured to record if slots 0-3 are expanded... the top bit (bit 7) will be 1 if they are

If a slot IS expanded... there will be a memory mapped register at memory address &FFFF

the format is the same as the slot selection register

Slot 0 &FFFF Bits 7 6 5 4 3 2 1 0
Subslot number     3 (&C000-&FFFF)     2 (&8000-&BFFF)     1 (&4000-&7FFF)     0 (&0000-&3FFF)  

Memory Mappers
As mentioned, we can only map a bank of ram in a slot to the matching bank in the Z80 range... wouldn't it be nice if we could map a bank of ram to ANY bank of the Z80 range?

Well actually we can!... in theory!
A slot CAN have something called a 'Memory Mapper'... what's this? well it allows exactly what I just mentioned... any one of the 16k banks in the slot can be mapped to the that slots position...  we still need to map in the slot (and subslot if required) but that slot can expose any bank of memory... this also allows us to address more than 64k... we can have up to 512k!

We select a slots memory mapping using 4 ports...  note we can only write to these - we cannot rely on reading from them
Port   Z80 Address range Default value
&FC &0000-&3FFF 3
&FD &4000-&7FFF 2
&FE &8000-&BFFF 1
&FF &C000-&FFFF 0
A system may have more than one Memory mapper...one internal, and one in an upgrade... but they will all set at the same time with these ports... we will still need to page in the slot-subslot using the usual method to get access to the memory.

So Memory mappers are more flexible, and allow more memory... what's not to like?.... well almost NO MSXes have them... even with the MSX2+, most MSXes do not have them... so unless your game is Turbo-R only, you're not going to be able to use them!

Cartridge Mappers
Cartridges also have 'mappers'... though they work differently - we just WRITE the bank number to a special address in the ROM, and the ROM will switch it's bank for us!

There are various mappers, but we'll look at "Konami with SCC" (Konami5) , it provides 4 rom areas that can be reconfigured - note they do not cover the whole Z80 range - as the bottom bank is assumed to be ROM, and the top bank is assumed to be RAM

Area   Z80 Address Range   Write Address to change bank
1 &4000-&5FFF &5000
2 &6000-&7FFF &7000
3 &8000-&9FFF &9000
4 &A000-&BFFF &A000

Minimum MSX configurations
So what can we expect from our MSXes memory wise, well here's what you need to assume you'll have!
System     MSX1   MSX2   MSX2+   Turbo-R  
Memory 16k 64k 64k 256K
Disk System?   NO NO (sometimes) NO (usually - not always) YES
Mapper? NO NO NO (sometimes) YES
Soooo.... Effectively on the MSX1 we're pretty much going to have to rely on using Cartrige ROM, and we won't be able to use a Memory Mapper unless we're using the Turbo-R as the minimum requirement.


MSX and MSX2 Programming Tutorials:
Lesson P5 - Bitmap graphics on the TI-83 and MSX
Lesson P7 - Keyreading on the MSX, Enterprise and TI-83
Lesson P10 - Tilemap graphics on the MSX1
Lesson P11 - Tilemap graphics on the MSX2

General Z80 Assembly Tutorials:
B. Beginner series - Learn the basics
A. Advanced series - In more detail
M. Multiplatform series - programming methods that work on all systems




Visit www.ChibiAkumas.com to get my games and their source code! | Support me on patreon