Kragelj ZX Spectrum User Manual

ZX Spectrum Next
Assembler Developer Guide
Tomaˇz Kragelj
ZX Spectrum Next Assembler Developer Guide
Tomaˇz Kragelj
15 September 2021
REVISIONS 2021-09-15 2021-07-16
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.
©
2021 Tomaˇz Kragelj
©
2005 Jan Wilmans
©
1997, 1998, 2001, 2003, 2005 Sean Young
Contents
i
Chapter 1
Introduction
1.1 Where to get this document
ZX Spectrum Next Assembler Developer Guide is available as coil bound printed book on
https://bit.ly/zx-next-assembler-dev-guide
You can also download it as PDF document from GitHub where you can also find its source LATEX form so you can edit it to your preference
https://github.com/tomaz/zx-next-dev-guide
1.2 Companion Source Code
GitHub repository also includes companion source code. Sample projects were created in a cross-platform environment on Windows so instructions here are written with these in mind. But consider them merely as a suggestion; you should be able to use your preferred editor or tools.
1
CHAPTER 1. INTRODUCTION
Visual Studio Code (https://code.visualstudio.com/)
My code editor of choice! I use it with the following plugins:
DeZog plugin (https://github.com/maziac/DeZog)
Essential plugin; features list is too large to even attempt to enumerate here but essentially turns VS Code into a fully-fledged debugging environment.
Z80 Macro-Assembler (https://github.com/mborik/z80-macroasm-vscode)
Another must-have plugin for the Z80 assembly developer; syntax highlighting, code formatting and code completion, renaming etc.
Z80 Instruction Set (https://github.com/maziac/z80-instruction-set)
Adds mouse hover action above any Z80N instruction for quick info.
Z80 Assembly meter (https://github.com/theNestruo/z80-asm-meter-vscode)
Shows the sum of clock cycles and machine code bytes for all instructions in the current selection.
sjasmplus 1.18.2 (https://github.com/z00m128/sjasmplus)
Source code includes sjasmplus specific directives for creating nex files at the top and bottom of main.asm files; if you use a different compiler, you may need to tweak or comment them out.
VS Code projects are set up to expect binaries in a specific folder. You will need to download and copy so that sjasmplus.exe is located in Tools/sjasmplus.
CSpect 2.13.0 (http://cspect.org)
Similar to sjasmplus, CSpect binaries are expected in a specific folder. To install, download and copy so that CSpect.exe is located in Tools/CSpect folder.
CSpect Next Image (http://www.zxspectrumnext.online/#sd)
You will also need to download the ZX Spectrum Next image file and copy it to the folder where CSpect.exe is located. I use a 2GB image, hence VS Code project file is configured for that. If you use a different image, make sure to update .vscode/tasks.json file.
DeZog CSpect plugin (https://github.com/maziac/DeZogPlugin)
DeZog requires this plugin to be installed to work with CSpect. To install, download and copy to the same folder where CSpect.exe is located. Make sure the plugin version matches the DeZog version!
Note: you need to have CSpect launched before you can run the samples. I created couple tasks1for it: open VS Code command palette (Ctrl+Shift+P shortcut on my installation) and select Tasks: Run Task option, then select Launch CSpect from list. This is only needed once. Afterwards, use Run > Start Debugging from the main menu to compile and launch the program.
Note: default DeZog port of 11000 doesn’t work on my computer, so I changed it to 13000. This needs to be managed in 2 places: .vscode/launch.json and on the plugin side. Companion code repository already includes the setup needed, including DeZogPlugin.dll.config file, so it should work out of the box.
Note: sample projects are ready for ZEsarUX as well, select the option from debugging panel in VS Code.
1
Workspace tasks seem to not be supported in some later VS Code versions. If this is the case for you, copy them to user tasks (shared between projects): open .vscode/tasks.json file from any of the sample projects, scroll down a little and copy Launch CSpect and Launch ZEsarUX tasks to user tasks. You can do this all from within VS Code. To open the user tasks file, open the command palette and start typing open user tasks, then select the option from the drop-down menu.
2
CHAPTER 1. INTRODUCTION
1.3 Background, Contact & Feedback
My first computer was ZX Spectrum 48K. Initially, it was only used to play games, but my creative mind soon set me on the path of building simple games of my own in BASIC. While too young to master assembler at that point, the idea stayed with me. ZX Spectrum Next revived my wish to learn Z80 and return to writing games for the platform.
My original intent was to have coil bound list of all ZX Next instructions so I can quickly compare. However, after finding Z80 Undocumented online, it felt like a perfect starting point. And with additional information included, it also encouraged me to extend the mere instructions list with the Next specific chapters. So in a way, this book represents my notes as I was learning those topics. That being said, I did my best to present information as a reference to keep the book relevant.
English is not my native tongue. And our mind is not the best tool to correct our own work either. Since I can’t afford a professional proofreader, mistakes are a matter of fact I’m afraid. If you spot something or want to contribute, feel free to open an issue on GitHub. Pull requests are also welcome! If you want to contribute, but are unsure of what, check the accompanying readme file on GitHub for ideas. If you want to discuss in advance, or for anything else, you can find me on email tkragelj@gmail.com or Twitter @tomsbarks.
That being said, I hope you’ll enjoy reading this document as much as I did writing it!
Sincerely, Tomaˇz
3
CHAPTER 1. INTRODUCTION
1.4 Z80 Undocumented
As the saying “standing on the shoulders of giants” goes, this book is also based on pre-existing work from Jan and Sean. While my work is ZX Spectrum Next developer-oriented, their original project was more focused on hardware perspective, for Z80 emulator developers.
If interested, you can find it at http://www.myquest.nl/z80undocumented/.
Jan
http://www.myquest.nl/z80undocumented/
Email jw@dds.nl Twitter @janwilmans
Interested in emulation for a long time, but a few years after Sean started writing this document, I have also started writing my own MSX emulator in 2003 and I’ve used this document quite a lot. Now (2005) the Z80 emulation is nearing perfection, I decided to add what extra I have learned and comments various people have sent to Sean, to this document.
I have restyled the document (although very little) to fit my personal needs and I have checked a lot of things that were already in here.
Sean
http://www.msxnet.org/
Ever since I first started working on an MSX emulator, I’ve been very interested in getting the emulation absolutely correct - including the undocumented features. Not just to make sure that all games work, but also to make sure that if a program crashes, it crashes exactly the same way if running on an emulator as on the real thing. Only then is perfection achieved.
I set about collecting information. I found pieces of information on the Internet, but not everything there is to know. So I tried to fill in the gaps, the results of which I put on my website. Various people have helped since then; this is the result of all those efforts and to my knowledge, this document is the most complete.
4
CHAPTER 1. INTRODUCTION
1.5 ChangeLog
15 September 2021 Corrections and updates based on community comments - with special
thanks to Peter Ped Helcmanovsky. Restructured and updated many ZX Next chapters: added sample code to ports, completely restructured memory map and paging, added new palette chapter including 9-bit palette handling, updated ULA with shadow screen info and added Next extended keyboard description. Other than that couple of cosmetic changes: redesigned title, copyright pages etc. Also, many behind the scenes improvements like splitting previous huge single LATEX file into multiple per-chapter/section. This is not only more manageable but can also compile much faster.
16 July 2021 Added ZX Spectrum Next information and instructions and restructured text
for better maintainability and readability.
18 September 2005 Corrected a textual typo in the R register and memory refresh section,
thanks to David Aubespin. Corrected the contradiction in the DAA section saying the NF flag was both affected and unchanged :) thanks to Dan Meir. Added an error in official documentation about the way Interrupt Mode 2 works, thanks to Aaldert Dekker.
15 June 2005 Corrected improper notation of JP x,nn mnemonics in opcode list, thanks to
Laurens Holst. Corrected a mistake in the INI, INIR, IND, INDR section and documented a mistake in official Z80 documentation concerning Interrupt Mode 2, thanks to Boris Donko. Thanks to Aaldert Dekker for his ideas, for verifying many assumptions and for writing instruction exercisers for various instruction groups.
18 May 2005 Added an alphabetical list of instructions for easy reference and corrected
an error in the 16-bit arithmetic section, SBC HL,nn sets the NF flag just like other subtraction instructions, thanks to Fredrik Olssen for pointing that out.
4 April 2005 I (Jan jw@dds.nl) will be maintaining this document from this version on. I
restyled the document to fix the page numbering issues, corrected an error in the I/O Block Instructions section, added graphics for the RLD and RRD instructions and corrected the spelling in several places.
20 November 2003 Again, thanks to Ramsoft, added PV flag to OUTI, INI and friends.
Minor fix to DAA tables, other minor fixes.
13 November 2003 Thanks to Ramsoft, add the correct tables for the DAA instruction (section
??). Minor corrections & typos, thanks to Jim Battle, David Sutherland and most of all
Fred Limouzin.
September 2001 Previous documents I had written were in plain text and Microsoft Word,
which I now find very embarrassing, so I decided to combine them all and use LATEX. Apart from a full re-write, the only changed information is “Power on defaults” (section ??) and the algorithm for the CF and HF flags for OTIR and friends (section ??).
5
CHAPTER 1. INTRODUCTION
This page intentionally left empty
6
Chapter 2
Zilog Z80
7
OVERVIEW
2.1 Overview
2.1.1 History of the Z80
In 1969 Intel was approached by a Japanese company called Busicom to produce chips for Busicom’s electronic desktop calculator. Intel suggested that the calculator should be built around a single-chip generalized computing engine and thus was born the first microprocessor
- the 4004. Although it was based on ideas from a much larger mainframe and mini-computers the 4004 was cut down to fit onto a 16-pin chip, the largest that was available at the time, so that its data bus and address bus were each only 4-bits wide.
Intel went on to improve the design and produced the 4040 (an improved 4-bit design) the 8008 (the first 8-bit microprocessor) and then in 1974 the 8080. This last one turned out to be a very useful and popular design and was used in the first home computer, the Altair 8800, and CP/M.
In 1975 Federico Faggin who had worked at Intel on the 4004 and its successors left the company and joined forces with Masatoshi Shima to form Zilog. At their new company, Faggin and Shima designed a microprocessor that was compatible with Intel’s 8080 (it ran all 78 instructions of the 8080 in almost the same way that Intel’s chip did)1but had many more abilities (an extra 120 instructions, many more registers, simplified connection to hardware). Thus was born the mighty Z80, and thus was the empire forged!
The original Z80 was first released in July 1976, coincidentally Jan was born in the very same month. Since then newer versions have appeared with much of the same architecture but running at higher speeds. The original Z80 ran with a clock rate of 2.5MHz, the Z80A runs at 4MHz, the Z80B at 6MHz and the Z80H at 8Mhz.
Many companies produced machines based around Zilog’s improved chip during the 1970s and 80’s and because the chip could run 8080 code without needing any changes to the code the perfect choice of the operating system was CP/M.
Also, Zilog has created a Z280, an enhanced version of the Zilog Z80 with a 16-bit architecture, introduced in July 1987. It added an MMU to expand addressing to 16Mb, features for multitasking, a 256-byte cache, and a huge number of new opcodes (giving a total of over 2000!). Its internal clock runs at 2 or 4 times the external clock (e.g. a 16MHz CPU with a 4MHz bus.
The Z380 CPU incorporates advanced architectural while maintaining Z80/Z180 object code compatibility. The Z380 CPU is an enhanced version of the Z80 CPU. The Z80 instruction set has been retained, adding a full complement of 16-bit arithmetic and logical operations, multiply and divide, a complete set of register-to-register loads and exchanges, plus 32-bit load and exchange, and 32-bit arithmetic operations for address calculations.
The addressing modes of the Z80 have been enhanced with Stack pointer relative loads and stores, 16-bit and 24-bit indexed offsets and more flexible indirect register addressing. All of the addressing modes allow access to the entire 32-bit addressing space.
1
Thanks to Jim Battle (frustum@pacbell.net): the 8080 always puts the parity in the PF flag; VF does not exist and the timing is different. Possibly there are other differences.
8
CHAPTER 2. ZILOG Z80
2.1.2 Registers
The following accessible registers exist in the Z80.
A F } Accumulator and Flags
BC DE HL IX IY PC SP
I R
AF’ BC’ DE’ HL’
, .
General purpose registers
- *
Index registers
, .
Special purpose registers
- , /
/
.
Alternate general purpose registers
/
/
-
2.1.3 Flags
The conventional way of denoting the flags is with one letter, “C” for the carry flag for example. It could be confused with the C register, so I’ve chosen to use the “CF” notation for flags (except “P” which uses “PV” notation due to having dual-purpose, either as parity or overflow). And for YF and XF the same notation is used in MAME2.
bit 7 6 5 4 3 2 1 0
flag SF ZF YF HF XF PF NF CF
SF Set if the 2-complement value is negative; simply a copy of the most significant bit.
ZF Set if the result is zero.
YF A copy of bit 5 of the result.
HF The half-carry of an addition/subtraction (from bit 3 to 4). Needed for BCD correction
with DAA.
XF A copy of bit 3 of the result.
PV This flag can either be the parity of the result (PF), or the 2-complement signed overflow
(VF): set if 2-complement value doesn’t fit in the register.
NF Shows whether the last operation was an addition (0) or a subtraction (1). This information
is needed for DAA.
3
CF The carry flag, set if there was a carry after the most significant bit.
2
http://www.mame.net/
3
Wouldn’t it be better to have separate instructions for DAA after addition and subtraction, like the 80x86
has instead of sacrificing a bit in the flag register?
9
OVERVIEW
2.1.4 Pin Descriptions [?]
This section might be relevant even if you don’t do anything with hardware; it might give you insight into how the Z80 operates. Besides, it took me hours to draw this.
1 2 3 4 5 6 7 8 9
10
Z80 CPU
11 12 13 14 15 16 17 18 19 20

40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21
A
10
A
9
A
8
A
7
A
6
A
5
A
4
A
3
A
2
A
1
A
0
GND RFSH
M1 RESET BUSREQ WAIT BUSACK WR RD
A
A
A
A
A
CLK
D D D D
5V D D D D
INT
NMI HALT MREQ IORQ
11
12
13
14
15
4
3
5
6
2
7
0
1
A
A0Address bus (output, active high, 3-state). This bus is used for accessing the memory
15
and for I/O ports. During the refresh cycle the IR register is put on this bus.
BUSACK Bus Acknowledge (output, active low). Bus Acknowledge indicates to the requesting
device that the CPU address bus, data bus, and control signals MREQ, IORQ, RD and WR have been entered into their high-impedance states. The external device now control these lines.
BUSREQ Bus Request (input, active low). Bus Request has a higher priority than NMI and is
always recognised at the end of the current machine cycle. BUSREQ forces the CPU address bus, data bus and control signals MREQ, IORQ, RD and WR to go to a high-impedance state so that other devices can control these lines. BUSREQ is normally wired-OR and requires an external pullup for these applications. Extended BUSREQ periods due to extensive DMA operations can prevent the CPU from refreshing dynamic RAMs.
D
D0Data Bus (input/output, active low, 3-state). Used for data exchanges with memory,
7
I/O and interrupts.
HALT Halt State (output, active low). Indicates that the CPU has executed a HALT instruction
and is waiting for either a maskable or nonmaskable interrupt (with the mask enabled) before operation can resume. While halted, the CPU stops increasing the PC so the instruction is re-executed, to maintain memory refresh.
INT Interrupt Request (input, active low). Interrupt Request is generated by I/O devices. The
CPU honours a request at the end of the current instruction if IFF1 is set. INT is normally wired-OR and requires an external pullup for these applications.
IORQ Input/Output Request (output, active low, 3-state). Indicates that the address bus holds
a valid I/O address for an I/O read or write operation. IORQ is also generated concurrently
10
CHAPTER 2. ZILOG Z80
with M1 during an interrupt acknowledge cycle to indicate that an interrupt response vector can be placed on the databus.
M1 Machine Cycle One (output, active low). M1, together with MREQ, indicates that the current
machine cycle is the opcode fetch cycle of an instruction execution. M1, together with IORQ, indicates an interrupt acknowledge cycle.
MREQ Memory Request (output, active low, 3-state). Indicates that the address holds a valid
address for a memory read or write cycle operations.
NMI Non-Maskable Interrupt (input, negative edge-triggered). NMI has a higher priority than
INT. NMI is always recognised at the end of an instruction, independent of the status of the
interrupt flip-flops and automatically forces the CPU to restart at location$0066.
RD Read (output, active low, 3-state). Indicates that the CPU wants to read data from memory
or an I/O device. The addressed I/O device or memory should use this signal to place data onto the data bus.
RESET Reset (input, active low). Initializes the CPU as follows: it resets the interrupt flip-flops,
clears the PC and IR registers, and set the interrupt mode to 0. During reset time, the address bus and data bus go to a high-impedance state, and all control output signals go to the inactive state. Note that RESET must be active for a minimum of three full clock cycles before the reset operation is complete. Note that Matt found that SP and AF are set to
$
FFFF.
RFSH Refresh (output, active low). RFSH, together with MREQ, indicates that the IR registers
are on the address bus (note that only the lower 7 bits are useful) and can be used for the refresh of dynamic memories.
WAIT Wait (input, active low). Indicates to the CPU that the addressed memory or I/O device
are not ready for data transfer. The CPU continues to enter a wait state as long as this signal is active. Note that during this period memory is not refreshed.
WR Write (output, active low, 3-state). Indicates that the CPU wants to write data to memory
or an I/O device. The addressed I/O device or memory should use this signal to store the data on the data bus.
2.1.5 Power on Defaults
Matt4has done some excellent research on this. He found that AF and SP are always set to
$
FFFF after a reset, and all other registers are undefined (different depending on how long the CPU has been powered off, different for different Z80 chips). Of course, the PC should be set to 0 after a reset, and so should the IFF1 and IFF2 flags (otherwise strange things could happen). Also since the Z80 is 8080 compatible, the interrupt mode is probably 0.
Probably the best way to simulate this in an emulator is to set PC, IFF1, IFF2, IM to 0 and set all other registers to$FFFF.
4
redflame@xmission.com
11
UNDOCUMENTED OPCODES
2.2 Undocumented Opcodes
There are quite a few undocumented opcodes/instructions. This section should describe every possible opcode so you know what will be executed, whatever the value of the opcode is.
The following prefixes exist: CB, ED, DD, FD, DDCB and FDCB. Prefixes change the way the following opcodes are interpreted. All instructions without a prefix (not a value of one the above) are single-byte opcodes (without the operand, that is), which are documented in the official documentation.
2.2.1 CB Prefix [?]
An opcode with a CB prefix is a rotate, shift or bit test/set/reset instruction. A few instructions are missing from the official list, for example SLL (Shift Logical Left). It works like SLA, for one exception: it sets bit 0 (SLA resets it).
CB30 SLL B CB31 SLL C CB32 SLL D CB33 SLL E CB34 SLL H CB35 SLL L CB36 SLL (HL) CB37 SLL A
2.2.2 DD Prefix [?]
In general, the instruction following the DD prefix is executed as is, but if the HL register is supposed to be used the IX register is used instead. Here are the rules:
Any usage of HL is treated as an access to IX (except EX DE,HL and EXX and the ED prefixed instructions that use HL).
Any access to (HL) is changed to (IX+d), where “d” is a signed displacement byte placed after the main opcode - except JP (HL), which isn’t indirect anyway. The mnemonic should be JP HL.
Any access to H is treated as an access to IXh(the high byte of IX) except if (IX+d) is used as well.
Any access to L is treated as an access to IXl(the low byte of IX) except if (IX+d) is used as well.
A DD prefix before a CB selects a completely different instruction set, see section ??.
12
CHAPTER 2. ZILOG Z80
Some examples:
Without DD prefix With DD prefix
LD H, (HL) LD H, (IX+d) LD H, A LD IXH, A LD L, H LD IXL, IXH JP (HL) JP (IX) LD DE, 0 LD DE, 0 LD HL, 0 LD IX, 0
2.2.3 FD Prefix [?]
This prefix has the same effect as the DD prefix, though IY is used instead of IX. Note LD IXL, IYH is not possible: only IX or IY is accessed in one instruction, never both.
2.2.4 ED Prefix [?]
There are a number of undocumented EDxx instructions, of which most are duplicates of documented instructions. Any instruction not listed here has no effect (same as 2 NOPs). indicates undocumented instruction:
ED40 IN B, (C) ED41 OUT (C), B ED42 SBC HL, BC ED43 LD (nn), BC ED44 NEG ED45 RETN ED46 IM 0 ED47 LD I, A ED48 IN C, (C) ED49 OUT (C), C ED4A ADC HL, BC ED4B LD BC, (nn) ED4C NEG
**
ED4D RETI ED4E IM 0
**
ED4F LD R, A
ED50 IN D, (C) ED51 OUT (C), D ED52 SBC HL, DE ED53 LD (nn), DE ED54 NEG ED55 RETN
**
**
ED56 IM 1 ED57 LD A, I ED58 IN E, (C) ED59 OUT (C), E ED5A ADC HL, DE ED5B LD DE, (nn) ED5C NEG ED5D RETN
**
**
ED5E IM 2 ED5F LD A, R
**
13
UNDOCUMENTED OPCODES
ED60 IN H, (C) ED61 OUT (C), H ED62 SBC HL, HL ED63 LD (nn), HL ED64 NEG ED65 RETN ED66 IM 0
**
**
**
ED67 RRD ED68 IN L, (C) ED69 OUT (C), L ED6A ADC HL, HL ED6B LD HL, (nn) ED6C NEG ED6D RETN ED6E IM 0
**
**
**
ED6F RLD
ED70 IN (C) / IN F, (C) ED71 OUT (C), 0
**
ED72 SBC HL, SP ED73 LD (nn), SP ED74 NEG ED75 RETN ED76 IM 1 ED77 NOP
**
**
**
**
ED78 IN A, (C) ED79 OUT (C), A ED7A ADC HL, SP ED7B LD SP, (nn) ED7C NEG ED7D RETN ED7E IM 2 ED7F NOP
**
**
**
**
**
The ED70 instruction reads from I/O port C, but does not store the result. It just affects the flags like the other IN x,(C) instructions. ED71 simply outs the value 0 to I/O port C.
The ED63 is a duplicate of the 22 opcode (LD (nn),HL) and similarly ED6B is a duplicate of the 2A opcode (LD HL,(nn)). Of course the timings are different. These instructions are listed in the official documentation.
According to Gerton Lunter5:
The instructions ED 4E and ED 6E are IM 0 equivalents: when FF was put on the bus (physically) at interrupt time, the Spectrum continued to execute normally, whereas when an EF (RST$28) was put on the bus it crashed, just as it does in that case when the Z80 is in the official interrupt mode 0. In IM 1 the Z80 just executes a RST$38 (opcode FF) no matter what is on the bus.
All the RETI/RETN instructions are the same, all like the RETN instruction. So they all, including
RETI, copy IFF2 to IFF1. See section ?? for more information on RETI and RETN and IM x.
2.2.5 DDCB Prefix
The undocumented DDCB instructions store the result (if any) of the operation in one of the seven all-purpose registers. Which one depends on the lower 3 bits of the last byte of the opcode (not operand, so not the offset).
000 B 001 C 010 D 011 E
100 H 101 L 110 (none: documented opcode) 111 A
5
gerton@math.rug.nl
14
CHAPTER 2. ZILOG Z80
The documented DDCB0106 is RLC (IX+$01). So, clear the lower three bits (DDCB0100) and something is done to register B. The result of the RLC (which is stored in (IX+$01)) is now also stored in register B. Effectively, it does the following:
LD B, (IX+$01) RLC B LD (IX+$01), B
So you get double value for money. The result is stored in B and (IX+$01). The most common notation is: RLC (IX+$01), B
I’ve once seen this notation:
RLC (IX+$01) LD B, (IX+$01)
That’s not correct: B contains the rotated value, even if (IX+$01) points to ROM. The DDCB SET and RES instructions do the same thing as the shift/rotate instructions:
DDCB10C0 SET 0, (IX+$10), B DDCB10C1 SET 0, (IX+$10), C DDCB10C2 SET 0, (IX+$10), D DDCB10C3 SET 0, (IX+$10), E DDCB10C4 SET 0, (IX+$10), H DDCB10C5 SET 0, (IX+$10), L DDCB10C6 SET 0, (IX+$10) - documented instruction DDCB10C7 SET 0, (IX+$10), A
So for example with the last instruction, the value of (IX+$10) with bit 0 set is also stored in register A.
The DDCB BIT instructions do not store any value; they merely test a bit. That’s why the undocumented DDCB BIT instructions are no different from the official ones:
DDCB d 78 BIT 7, (IX+d) DDCB d 79 BIT 7, (IX+d) DDCB d 7A BIT 7, (IX+d) DDCB d 7B BIT 7, (IX+d) DDCB d 7C BIT 7, (IX+d) DDCB d 7D BIT 7, (IX+d) DDCB d 7E BIT 7, (IX+d) - documented instruction DDCB d 7F BIT 7, (IX+d)
15
UNDOCUMENTED EFFECTS
2.2.6 FDCB Prefixes
Same as for the DDCB prefix, though IY is used instead of IX.
2.2.7 Combinations of Prefixes
This part may be of some interest to emulator coders. Here we define what happens if strange sequences of prefixes appear in the instruction cycle of the Z80.
If CB or ED is encountered, that byte plus the next make up an instruction. FD or DD should be seen as prefix setting a flag which says “use IX or IY instead of HL”, and not an instruction. In a large sequence of DD and FD bytes, it is the last one that counts. Also any other byte (or instruction) resets this flag.
FD DD 00 21 00 10 NOP NOP NOP LD HL,$1000
2.3 Undocumented Effects
2.3.1 BIT Instructions
BIT n,r behaves much like AND r,2nwith the result thrown away, and CF flag unaffected. Compare BIT 7,A with AND$80: flag YF and XF are reset, SF is set if bit 7 was actually set; ZF is set if the result was 0 (bit was reset), and PV is effectively set if ZF is set (the result of the AND leaves either no bits set (PV set - parity even) or one bit set (PV reset - parity odd). So the rules for the flags are:
SF flag Set if n = 7 and tested bit is set.
ZF flag Set if the tested bit is reset.
YF flag Set if n = 5 and tested bit is set.
HF flag Always set.
XF flag Set if n = 3 and tested bit is set.
PV flag Set just like ZF flag.
NF flag Always reset.
CF flag Unchanged.
This is where things start to get strange. With the BIT n,(IX+d) instructions, the flags behave just like the BIT n,r instruction, except for YF and XF. These are not copied from the result but from something completely different, namely bit 5 and 3 of the high byte of IX+d (so IX plus the displacement).
16
CHAPTER 2. ZILOG Z80
Things get more bizarre with the BIT n,(HL) instruction. Again, except for YF and XF, the flags are the same. YF and XF are copied from some sort of internal register. This register is related to 16-bit additions. Most instructions do not change this register. Unfortunately, I haven’t tested all instructions yet, but here is the list so far:
ADD HL, xx Use high byte of HL, ie. H before the addition. LD r, (IX+d) Use high byte of the resulting address IX+d. JR d Use high byte target address of the jump. LD r, r’ Doesn’t change this register.
Any help here would be most appreciated!
2.3.2 Memory Block Instructions [?]
The LDI/LDIR/LDD/LDDR instructions affect the flags in a strange way. At every iteration, a byte is copied. Take that byte and add the value of register A to it. Call that value n. Now, the flags are:
YF flag A copy of bit 1 of n.
HF flag Always reset.
XF flag A copy of bit 3 of n.
PV flag Set if BC not 0.
SF, ZF, CF flags These flags are unchanged.
And now for CPI/CPIR/CPD/CPDR. These instructions compare a series of bytes in memory to register A. Effectively, it can be said they perform CP (HL) at every iteration. The result of that comparison sets the HF flag, which is important for the next step. Take the value of register A, subtract the value of the memory address, and finally subtract the value of HF flag, which is set or reset by the hypothetical CP (HL). So, n=A-(HL)-HF.
SF, ZF, HF flags Set by the hypothetical CP (HL).
YF flag A copy of bit 1 of n.
XF flag A copy of bit 3 of n.
PV flag Set if BC is not 0.
NF flag Always set.
CF flag Unchanged.
17
UNDOCUMENTED EFFECTS
2.3.3 I/O Block Instructions
These are the most bizarre instructions, as far as the flags are concerned. Ramsoft found all of the flags. The “out” instructions behave differently than the “in” instructions, which doesn’t make the CPU very symmetrical.
First of all, all instructions affect the following flags:
SF, ZF, YF, XF flags Affected by decreasing register B, as in DEC B.
NF flag A copy of bit 7 of the value read from or written to an I/O port.
And now the for OUTI/OTIR/OUTD/OTDR instructions. Take the state of the L after the increment or decrement of HL; add the value written to the I/O port; call that k for now. If k¡255, then the CF and HF flags are set. The PV flag is set like the parity of k bitwise and’ed with 7, bitwise xor’ed with B.
HF and CF Both set if ((HL) + L
PV The parity of ((((HL) + L)
INI/INIR/IND/INDR use the C register instead of the L register. There is a catch though, because not the value of C is used, but C + 1 if it’s INI/INIR or C - 1 if it’s IND/INDR. So, first of all INI/INIR:
HF and CF Both set if ((HL) + ((C + 1)
PF The parity of (((HL) + ((C + 1)
And last IND/INDR:
HF and CF Both set if ((HL) + ((C - 1)
PF The parity of (((HL) + ((C - 1)
^
¡
7)
255)
Y
^
^
B)
^
255))
^
255))
255)
^
255)
^
7)
7)
Y
¡
255)
Y
255)
Y
B)
B)
2.3.4 16 Bit I/O ports
Officially the Z80 has an 8-bit I/O port address space. When using the I/O ports, the 16 address lines are used. And in fact, the high 8 bits do have some value, so you can use 65536 ports after all. IN r, (C), OUT (C), r, and the block I/O instructions actually place the entire BC register on the address bus. Similarly IN A, (n) and OUT (n), A put A256 + n on the address bus.
The INI, INIR, IND and INDR instructions use BC before decrementing B, and the OUTI, OTIR, OUTD and OTDR instructions use BC after decrementing.
18
CHAPTER 2. ZILOG Z80
2.3.5 Block Instructions
The repeated block instructions simply decrement the PC by two so the instruction is simply re-executed. So interrupts can occur during block instructions. So, LDIR is simply LDI + if BC is not 0, decrement PC by 2.
2.3.6 16 Bit Additions
The 16-bit additions are a bit more complicated than the 8-bit ones. Since the Z80 is an 8-bit CPU, 16-bit additions are done in two stages: first, the lower bytes are added, then the two higher bytes. The SF, YF, HF, XF flags are affected by the second (high) 8-bit addition. ZF is set if the whole 16-bit result is 0.
2.3.7 DAA Instruction
This instruction is useful when you’re using BCD values. After addition or subtraction, DAA corrects the value back to BCD again. Note that it uses the CF flag, so it cannot be used after INC and DEC.
Stefano Donati from Ramsoft6has found the tables which describe the DAA operation. The input is the A register and the CF, NF, HF flags. The result is as follows:
Depending on the NF flag, the “diff ”
from this table must be added (NF is
reset) or subtracted (NF is set) to A: CF flag is affected: NF flag is affected:
high low
CF nibble HF nibble diff
0 0-9 0 0-9 00 0 0-9 1 0-9 06 0 0-8 * A-F 06 0 A-F 0 0-9 60 1 * 0 0-9 60 1 * 1 0-9 66 1 * * A-F 66 0 9-F * A-F 66 0 A-F 1 0-9 66
high low
CF nibble nibble CF’
0 0-9 0-9 0 0 0-8 A-F 0 0 9-F A-F 1 0 A-F 0-9 1 1 * * 1
low
NF HF nibble HF’
0 * 0-9 0 0 * A-F 1 1 0 * 0 1 1 6-F 0 1 1 0-5 1
SF, YF, XF are copies of bit 7, 5, 3 of the result respectively; ZF is set according to the result and NF is always unchanged.
6
http://www.ramsoft.bbk.org/
19
INTERRUPTS
2.4 Interrupts
There are two types of interrupts, maskable and non-maskable. The maskable type is ignored if IFF1 is reset. Non-maskable interrupts (NMI) will are always accepted, and they have a higher priority, so if both are requested at the same time, the NMI will be accepted first.
For the interrupts, the following things are important: interrupt Mode (set with the IM 0, IM 1, IM 2 instructions), the interrupt flip-flops (IFF1 and IFF2), and the I register. When a maskable interrupt is accepted, the external device can put a value on the data bus.
Both types of interrupts increase the R register by one when accepted.
2.4.1 Non-Maskable Interrupts (NMI)
When an NMI is accepted, IFF1 is reset. At the end of the routine, IFF1 must be restored (so the running program is not affected). That’s why IFF2 is there; to keep a copy of IFF1.
An NMI is accepted when the NMI pin on the Z80 is made low (edge-triggered). The Z80 responds to the change of the line from +5 to 0 - so the interrupt line doesn’t have a state, it’s just a pulse. When this happens, a call is done to address$0066 and IFF1 is reset so the routine isn’t bothered by maskable interrupts. The routine should end with an RETN (RETurn from Nmi) which is just a usual RET but also copies IFF2 to IFF1, so the IFFs are the same as before the interrupt.
You can check whether interrupts were disabled or not during an NMI by using the LD A,I or LD A,R instruction. These instructions copy IFF2 to the PV flag.
Accepting an NMI costs 11 t-states.
2.4.2 Maskable Interrupts (INT)
If the INT line is low and IFF1 is set, a maskable interrupt is accepted - whether or not the last interrupt routine has finished. That’s why you should not enable interrupts during such a routine, and make sure that the device that generated it has put the INT line up again before ending the routine. So unlike NMI interrupts, the interrupt line has a state; it’s not a pulse.
When an interrupt is accepted, both IFF1 and IFF2 are cleared, preventing another interrupt from occurring which would end up as an infinite loop (and overflowing the stack). What happens next depends on the Interrupt Mode.
A device can place a value on the data bus when the interrupt is accepted. Some computer systems do not utilize this feature, and this value ends up being$FF.
Interrupt Mode 0 This is the 8080 compatibility mode. The instruction on the bus is
executed (usually an RST instruction, but it can be anything). I register is not used. Assuming it’s a RST instruction, accepting this takes 13 t-states.
Interrupt Mode 1 This is the 8080 compatibility mode. The instruction on the bus is
20
CHAPTER 2. ZILOG Z80
executed (usually an RST instruction, but it can be anything). I register is not used. Assuming it’s a RST instruction, accepting this takes 13 t-states.
Interrupt Mode 2 A call is made to the address read from memory. What address is read
from is calculated as follows:pI register
q
256
p
value on busq. Zilog’s user manual
states (very convincingly) that the least significant bit of the address is always 0, so they calculate the address that is read from as:pI register
q
256
p
value on bus^$FEq. I
have tested this and it’s not correct. Of course, a word (two bytes) is read, making the address where the call is made to. In this way, you can have a vector table for interrupts. Accepting this interrupt type costs 19 t-states.
At the end of a maskable interrupt, the interrupts should be enabled again. You can assume that was the state of the IFFs because otherwise the interrupt wasn’t accepted. So, an interrupt routine always ends with an EI and a RET (RETI according to the official documentation, more about that later):
1 InterruptRoutine: 2 ... 3 EI 4 RETI or RET
Note a fact about EI: a maskable interrupt isn’t accepted directly after it, so the next opportunity for an interrupt is after the RETI. This is very useful; if the INT line is still low, an interrupt is accepted again. If this happens a lot and the interrupt is generated before the RETI, the stack could overflow (since the routine would be called again and again). But this property of EI prevents this.
DI is not necessary at the start of the interrupt routine: the interrupt flip-flops are cleared when accepting the interrupt.
You can use RET instead of RETI, depending on the hardware setup. RETI is only useful if you have something like a Z80 PIO to support daisy-chaining: queuing interrupts. The PIO can detect that the routine has ended by the opcode of RETI, and let another device generate an interrupt. That is why I called all the undocumented EDxx RET instructions RETN: All of them operate alike, the only difference of RETI is its specific opcode which the Z80 PIO recognises.
2.4.3 Things Affecting the Interrupt Flip-Flops
All the IFF related things are:
Accept NMI IFF1 IFF2
CPU reset 0 0
DI 0 0
EI 1 1
Accept INT 0 0
Accept NMI 0 -
RETI/N IFF2 - All the EDxx RETI/N instructions
LD A,I / LD A,R - - Copies IFF2 into PV flag
21
INTERRUPTS
If you’re working with a Z80 system without NMIs (like the MSX), you can forget all about the two separate IFFs; since an NMI isn’t ever generated, the two will always be the same.
Some documentation says that when an NMI is accepted, IFF1 is first copied into IFF2 before IFF1 is cleared. If this is true, the state of IFF2 is lost after a nested NMI, which is undesirable. Have tested this in the following way: make sure the Z80 is in EI mode, generate an NMI. In the NMI routine, wait for another NMI before executing RETN. In the second NMI IFF2 was still set, so IFF1 is not copied to IFF2 when accepting an NMI.
Another interesting fact: I was trying to figure out whether the undocumented ED RET instructions were RETN or RETI. I tested this by putting the machine in EI mode, wait for an NMI and end with one of the ED RET instructions. Then execute a HALT instruction. If IFF1 was not restored, the machine would hang but this did not happen with any of the instructions, including the documented RETI!
Since every interrupt routine must end with EI followed by RETI officially, It does not matter that RETI copies IFF2 into IFF1; both are set anyway.
2.4.4 HALT Instruction
The HALT instruction halts the Z80; it does not increase the PC so that the instruction is re-executed until a maskable or non-maskable interrupt is accepted. Only then does the Z80 increase the PC again and continues with the next instruction. During the HALT state, the HALT line is set. The PC is increased before the interrupt routine is called.
2.4.5 Where interrupts are accepted
During the execution of instructions, interrupts won’t be accepted. Only between instructions. This is also true for prefixed instructions.
Directly after an EI or DI instruction, interrupts aren’t accepted. They’re accepted again after the instruction after the EI (RET in the following example). So for example, look at this MSX2 routine that reads a scanline from the keyboard:
1 LD C, A
2 DI
3 IN A, (
4 AND
5 ADD A, C
6 OUT (
7 EI
8 IN A, (
9 RET
$
$
0F0
$
0AA), A
$
0AA)
0A9)
You can assume that there never is an interrupt after the EI, before the IN A,($0A9) - which would be a problem because the MSX interrupt routine reads the keyboard too.
22
CHAPTER 2. ZILOG Z80
Using this feature of EI, it is possible to check whether it is true that interrupts are never accepted during instructions:
1 DI 2 make sure interrupt is active 3 EI 4 insert instruction to test 5 InterruptRoutine: 6 store PC where interrupt was accepted 7 RET
And yes, for all instructions, including the prefixed ones, interrupts are never accepted during an instruction. Only after the tested instruction. Remember that block instructions simply re-execute themselves (by decreasing the PC with 2) so an interrupt is accepted after each iteration.
Another predictable test: at the “insert instruction to test” insert a large sequence of EI instructions. Of course, during the execution of the EI instructions, no interrupts are accepted.
But now for the interesting stuff. ED or CB make up instructions, so interrupts are accepted after them. But DD and FD are prefixes, which only slightly affects the next opcode. If you test a large sequence of DDs or FDs, the same happens as with the EI instruction: no interrupts are accepted during the execution of these sequences.
This makes sense if you think of DD and FD as a prefix that sets the “use IX instead of HL” or “use IY instead of HL” flag. If an interrupt was accepted after DD or FD, this flag information would be lost, and:
DD 21 00 00 LD IX, 0
could be interpreted as a simple LD HL,0 if the interrupt was after the last DD. Which never happens, so the implementation is correct. Although I haven’t tested this, as I imagine the same holds for NMI interrupts.
Also see section ?? for details on handling interrupts on ZX Spectrum Next.
23
TIMING AND R REGISTER
2.5 Timing and R register
2.5.1 R register and memory refresh
During every first machine cycle (beginning of instruction or part of it - prefixes have their own M1 two), the memory refresh cycle is issued. The whole IR register is put on the address bus, and the RFSH pin is lowered. It’s unclear whether the Z80 increases the R register before or after putting IR on the bus.
The R register is increased at every first machine cycle (M1). Bit 7 of the register is never changed by this; only the lower 7 bits are included in the addition. So bit 7 stays the same, but it can be changed using the LD R,A instruction.
Instructions without a prefix increase R by one. Instructions with an ED, CB, DD, FD prefix, increase R by two, and so do the DDCBxxxx and FDCBxxxx instructions (weird enough). Just a stray DD or FD increases the R by one. LD A,R and LD R,A access the R register after it is increased by the instruction itself.
Remember that block instructions simply decrement the PC with two, so the instructions are re-executed. So LDIR increases R by BC2 (note that in the case of BC = 0, R is increased by
$
100002, effectively 0).
Accepting a maskable or non-maskable interrupt increases the R by one.
After a hardware reset, or after power on, the R register is reset to 0.
That should cover all there is to say about the R register. It is often used in programs for a random value, which is good but of course not truly random.
24
CHAPTER 2. ZILOG Z80
2.6 Errors in Official Documentation
Some official Zilog documentation contains errors. Not every documentation has all of these mistakes, so your milage may vary, but these are just things to look out for.
The flag affection summary table shows that LDI/LDIR/LDD/LDDR instructions leave the SF and ZF in an undefined state. This is not correct; the SF and ZF flags are unaffected.
Similarly, the same table shows that CPI/CPIR/CPD/CPDR leave the SF and HF flags in an undefined state. Not true, they are affected as defined elsewhere in the documentation.
Also, the table says about INI/OUTD/etc “Z=0 if B latter should be Z=1.
The INI/INIR/IND/INDR/OUTI/OUTD/OTIR/OTDR instructions do affect the CF flag (some official documentation says they leave it unaffected, important!) and the NF flag isn’t always set but may also be reset (see ?? for exact operation).
When an NMI is accepted, the IFF1 isn’t copied to IFF2. Only IFF1 is reset.
¡
0 otherwise Z=0”; of course the
In the 8-bit Load Group, the last two bits of the second byte of the LD r,(IX + d) opcode should be 10 and not 01.
In the 16-bit Arithmetic Group, bit 6 of the second byte of the ADD IX,pp opcode should be 0, not 1.
IN x,(C) resets the HF flag, it never sets it. Some documentation states it is set according to the result of the operation; this is impossible since no arithmetic is done in this instruction.
Note: In zilog’s own z80cpu um.pdf document, there are a lot of errors, some are very confusing, so I’ll mention the ones I have found here:
Page 21, figure 2 says “the Alternative Register Set contains 2 B’ registers”; this should of course be B’ and C’.
Page 26, figure 16 shows very convincingly that “the least significant bit of the address to read for Interrupt Mode 2 is always 0”. I have tested this and it is not correct, it can also be 1, in my test case the bus contained$FF and the address that was read did not end in$FE but was$FF.
25
Chapter 3
ZX Spectrum Next
With modern I/O ports, increased CPU speeds, more memory, better graphics, hardware sprites and tiles, to mention just the most obvious, ZX Spectrum Next is an exciting platform for the retro programmer.
27
PORTS
3.1 Ports
3.1.1 Mapped Spectrum Ports
RW Addr Mask Description
RW$103B %0001 0000 0011 1011 Sets and reads the I2C SCL line
RW$113B %0001 0001 0011 1011 Sets and reads the I2C SDA line
RW$123B %0001 0010 0011 1011 Enables layer 2 and controls paging of layer 2 screen
into lower memory (see ??)
RW$133B %0001 0011 0011 1011 Sends byte to serial port. Read tells if data is
available in RX buffer
RW$143B %0001 0100 0011 1011 Reads data from serial port, write sets the baud rate
RW$153B %0001 0101 0011 1011 Configuration of UART interfaces
-W$1FFD %0001 ---- ---- --0- Controls ROM paging and special paging options
from the +2a/+3 (see ??)
RW$243B %0010 0100 0011 1011 Selects active port for TBBlue/Next feature
configuration
RW$253B %0010 0101 0011 1011 Reads and/or writes the selected TBBlue control
register
RW$303B %0011 0000 0011 1011 Sets active sprite-attribute index and pattern-slot
index, reads sprite status (see ??)
-W$7FFD %01-- ---- ---- --0- Selects active RAM, ROM, and displayed screen (see
??)
-W$BFFD %10-- ---- ---- --0- Writes to the selected register of the selected sound
chip (see ??)
-W$DFFD %1101 1111 1111 1101 Provides additional bank select bits for extended
memory (see ??)
R-$FADF %---- ---0 --0- ---- Reads buttons on Kempston Mouse
R-$FBDF %---- -0-1 --0- ---- X coordinate of Kempston Mouse, 0-255
R-$FFDF %---- -1-1 --0- ---- Y coordinate of Kempston Mouse, 0-192
-W$FFFD %11-- ---- ---- --0- Controls stereo channels and selects active sound chip
and sound chip channel (see ??)
28
CHAPTER 3. ZX SPECTRUM NEXT
RW Addr Mask Description
RW$xx0B %---- ---- 0000 1011 Controls Z8410 DMA chip via MB02 standard
R-$xx1F %---- ---- 0001 1111 Reads movement of joysticks using Kempston
interface
RW$xx37 %---- ---- ---- ---- Kempston interface second joystick variant and
controls joystick I/O
-W$xx57 %---- ---- 0101 0111 Uploads sprite positions, visibility, colour type and
effect flags (see ??)
-W$xx5B %---- ---- 0101 1011 Used to upload the pattern of the selected sprite (see
??)
RW$xx6B %---- ---- 0110 1011 Controls zxnDMA chip
-W$xxDF %---- ---- --01 1111 Output to SpecDrum DAC
RW$xxFE %xxxx xxxx ---- ---0 Reading with particular high bytes returns keyboard
status (see ??), write changes border colour and base Spectrum audio settings (see ??)
RW$xxFF %---- ---- ---- ---- Controls Timex Sinclair video modes and colours in
hi-res mode. Readable when Peripheral 3 Register
$
08 bit 2 is set (see ??)
29
PORTS
3.1.2 Next/TBBlue Feature Control Registers
Specific features of the Next are controlled via these register numbers, accessed via TBBlue
Register Select$243B
1
and TBBlue Register Access
RW Port Description
R-$0 Identifies TBBlue board type. Should always be 10 on Next R-$1 Identifies core (FPGA image) version RW$2 Identifies type of last reset. Can be written to force reset RW$3 Identifies timing and machine type
-W$4 In config mode, allows RAM to be mapped to ROM area RW$5 Sets joystick mode, video frequency and Scandoubler RW$6 Enables CPU Speed key, DivMMC, Multiface, Mouse and AY audio RW$7 Sets CPU Speed, reads actual speed RW$8 ABC/ACB Stereo, Internal Speaker, SpecDrum, Timex Video Modes, Turbo
Sound Next, RAM contention and (un)lock 128k paging (see ??)
RW$9 Sets scanlines, AY mono output, sprite-id lockstep, resets DivMMC mapram and
disables HDMI audio (see ??)
RW$0A Mouse buttons and DPI config R-$0E Identifies core (FPGA image) version (sub minor number) RW$10 Used within the Anti-brick system RW$11 Sets video output timing variant RW$12 Sets the bank number where Layer 2 video memory begins (see ??) RW$13 Sets the bank number where the Layer 2 shadow screen begins RW$14 Sets the transparent colour for Layer 2, ULA and LoRes pixel data RW$15 Enables/disables sprites and Lores Layer, and chooses priority of sprites and Layer
2 (see ??)
RW$16 Sets X pixel offset used for drawing Layer 2 graphics on the screen (see ??) RW$17 Sets Y offset used when drawing Layer 2 graphics on the screen (see ??) RW$18 Sets and reads clip-window for Layer 2 (see ??) RW$19 Sets and reads clip-window for Sprites (see ??) RW$1A Sets and reads clip-window for ULA/LoRes layer RW$1B Sets and reads clip-window for Tilemap (see ??) RW$1C Controls (resets) the clip-window registers indices (see ??) R-$1E Holds the MSB of the raster line currently being drawn R-$1F Holds the eight LSBs of the raster line currently being drawn
$
253B
2
, or via the NEXTREG instruction.
1
https://wiki.specnext.dev/TBBlue Register Select
2
https://wiki.specnext.dev/TBBlue Register Access
30
CHAPTER 3. ZX SPECTRUM NEXT
RW Port Description
RW$22 Controls the timing of raster interrupts and the ULA frame interrupt
RW$23 Holds the eight LSBs of the line on which a raster interrupt should occur
RW$26 Pixel X offset (0-255) to use when drawing ULA Layer
RW$27 Pixel Y offset (0-191) to use when drawing ULA Layer
RW$28 PS/2 Keymap address MSB, read (pending) first byte of palette colour
-W$29 PS/2 Keymap address LSB
-W$2A High data to PS/2 Keymap (MSB of data in bit 0)
-W$2B Low eight LSBs of PS/2 Keymap data
RW$2C DAC B mirror, read current I2S left MSB
RW$2D SpecDrum port 0xDF / DAC A+D mirror, read current I2S LSB
RW$2E DAC C mirror, read current I2S right MSB
RW$2F Sets the pixel offset (two high bits) used for drawing Tilemap graphics on the
screen (see ??)
RW$30 Sets the pixel offset (eight low bits) used for drawing Tilemap graphics on the
screen (see ??)
RW$31 Sets the pixel offset used for drawing Tilemap graphics on the screen (see ??)
RW$32 Pixel X offset (0-255) to use when drawing LoRes Layer
RW$33 Pixel Y offset (0-191) to use when drawing LoRes Layer
RW$34 Selects sprite index 0-127 to be affected by writes to other Sprite ports (and
mirrors) (see ??)
-W$35 Writes directly into byte 1 of Sprite Attribute Upload
-W$36 Writes directly into byte 2 of Sprite Attribute Upload
-W$37 Writes directly into byte 3 of Sprite Attribute Upload
-W$38 Writes directly into byte 4 of Sprite Attribute Upload
-W$39 Writes directly into byte 5 of Sprite Attribute Upload
$
xx57 (see ??)
$
xx57 (see ??)
$
xx57 (see ??)
$
xx57 (see ??)
$
xx57 (see ??)
RW$40 Chooses a palette element (index) to manipulate with (see ??)
RW$41 Use to set/read 8-bit colours of the ULANext palette (see ??)
RW$42 Specifies mask to extract ink colour from attribute cell value in ULANext mode
RW$43 Enables or disables Enhanced ULA interpretation of attribute values and toggles
active palette (see ??)
RW$44 Sets 9-bit (2-byte) colours of the Enhanced ULA palette, or to read second byte
of colour (see ??)
31
RW Port Description
RW$4A 8-bit colour to be used when all layers contain transparent pixel (see ??) RW$4B Index of transparent colour in sprite palette (see ??) RW$4C Index of transparent colour in Tilemap palette (see ??) RW$50 Selects the 8k-bank stored in 8k-slot 0 (see ??) RW$51 Selects the 8k-bank stored in 8k-slot 1 (see ??) RW$52 Selects the 8k-bank stored in 8k-slot 2 (see ??) RW$53 Selects the 8k-bank stored in 8k-slot 3 (see ??) RW$54 Selects the 8k-bank stored in 8k-slot 4 (see ??) RW$55 Selects the 8k-bank stored in 8k-slot 5 (see ??) RW$56 Selects the 8k-bank stored in 8k-slot 6 (see ??) RW$57 Selects the 8k-bank stored in 8k-slot 7 (see ??)
-W$60 Used to upload code to the Copper RW$61 Holds low byte of Copper control bits RW$62 Holds high byte of Copper control flags
-W$63 Used to upload code to the Copper RW$64 Offset numbering of raster lines in copper/interrupt/active register RW$68 Disable ULA, controls ULA mixing/blending, enable ULA+ (see ??) RW$69 Layer2, ULA shadow, Timex$FF port RW$6A LoRes Radastan mode RW$6B Controls Tilemap mode (see ??) RW$6C Default tile attribute for 8-bit only maps (see ??) RW$6E Base address of the 40x32 or 80x32 tile map (see ??) RW$6F Base address of the tiles’ graphics (see ??) RW$70 Layer 2 resolution, palette offset (see ??) RW$71 Sets pixel offset for drawing Layer 2 graphics on the screen (see ??)
-W$75 Same as Attribute 0 Register
-W$76 Same as Attribute 1 Register
-W$77 Same as Attribute 2 Register
-W$78 Same as Attribute 3 Register
-W$79 Same as Attribute 4 Register
$
35 plus increments
$
36 plus increments
$
37 plus increments
$
38 plus increments
$
39 plus increments
$
34 (see ??)
$
34 (see ??)
$
34 (see ??)
$
34 (see ??)
$
34 (see ??)
PORTS
32
CHAPTER 3. ZX SPECTRUM NEXT
RW Port Description
RW$7F 8-bit storage for user
RW$80 Expansion bus enable/config
RW$81 Expansion bus controls
RW$82 Enabling internal ports decoding bits 0-7 register
RW$83 Enabling internal ports decoding bits 8-15 register
RW$84 Enabling internal ports decoding bits 16-23 register
RW$85 Enabling internal ports decoding bits 24-31 register
RW$86 When expansion bus is enabled: internal ports decoding mask bits 0-7
RW$87 When expansion bus is enabled: internal ports decoding mask bits 8-15
RW$88 When expansion bus is enabled: internal ports decoding mask bits 16-23
RW$89 When expansion bus is enabled: internal ports decoding mask bits 24-31
RW$8A Monitoring internal I/O or adding external keyboard
RW$8C Enable alternate ROM or lock 48k ROM
RW$8E Control classic Spectrum memory mapping
RW$90-93 Enables GPIO pins output
RW$98-9B GPIO pins mapped to Next Register
RW$A0 Enable Pi peripherals: UART, Pi hats, I2C, SPI
RW$A2 Pi I2S controls
RW$A3 Pi I2S clock divide in master mode
RW$A8 ESP WiFi GPIO output
RW$A9 ESP WiFi GPIO read/write
R-$B0 Read Next keyboard compound keys separately (see ??)
R-$B1 Read Next keyboard compound keys separately (see ??)
RW$B2 DivMMC trap configuration
RW$B4 DivMMC trap configuration
-W$FF Turns debug LEDs on and off on TBBlue implementations that have them
33
PORTS
3.1.3 Accessing Registers
Writing to Spectrum Ports
When writing to one of the lower 256 ports, OUT (n),A instruction is used. For example to write the value of 43 to peripheral device mapped to port$15:
1 LD A, 43 ; we want to write 43
2 OUT (
To write using full 16-bit address, OUT (C),r instruction is used instead. Example of writing a byte to serial port using UART TX
1 LD A, 42 ; we want to write 42
2 LD BC,
3 OUT (C), A
The difference between the two speed-wise is tangible: first example requires only 18 t-states (7+11) while second 29 (7+10+12).
$
15), A ; writes value of A to port$15
$
133B:
$
133B ; we want to write to port$133B
Reading from Spectrum Ports
Reading also uses the same approach as on original Spectrums - for the lower 256 ports IN A,(n) is used. For example reading a byte from port$15:
1 LD A, 0 ; perhaps not strictly required, but good idea
2 IN A, (
$
15) ; read byte from port$15 to A
Note how the accumulator A is cleared before accessing the port. With IN A,(n), the 16-bit address is composed from A forming high byte and n low byte.
Let’s see how we can use this for reading from 16-bit ports - we have two options: we can either use IN A,(n) or IN r,(C). Example of both, reading a byte from serial port:
1 LD BC,
2 IN A, (C) ; read byte to A
$
143B ; read$143B port
1 LD A, 2 IN A, (
$
14 ; high byte
$
3B) ; read byte to A
Both have the same result. The difference speed-wise is 22 t-states (10+12) vs 18 (7+11). Not by a lot, but it may add up if used frequently. However, the intent of the first code is clearer as the port address is provided in full instead of being split between two instructions.
This example nicely demonstrates a common dilemma when programming: frequently we can have readable but not as optimal code, or vice versa. But I also thought this was worth pointing out to avoid possible confusion in case you will encounter different ways in someone else’s code.
34
CHAPTER 3. ZX SPECTRUM NEXT
Writing to Next registers
Writing values to Next/TBBlue registers occurs through TBBlue Register Select
$
243B and
TBBlue Register Access$253B ports. It’s composed from 2 steps: first we select the register
via write to port$243B, then write the value through port$253B. For example writing value of 5 to port$16:
1 LD A, 2 LD BC, 3 OUT (C), A
4 5 LD A, 5 ; write 5 6 LD BC, 7 OUT (C), A
$
16 ; register$16
$
243B ; port$243B
$
253B ; to port$254B
1 LD A, 2 LD BC, 3 OUT (C), A
4 5 LD A, 5 ; write 5 6 INC B ; to port 7 OUT (C), A
$
16 ; register$16
$
243B ; port$243B
$
253B
Quite involving, isn’t it? Speed-wise, first example requires 58 t-states ((7+10+12)2) and second 6 t-states less: 52 ((7+10+12)+(7+4+12)).
The second code relies on the fact that the only difference between two port addresses is the high byte ($24 vs$25). So given we already assigned$243B to BC, we can simply increment B to get$253B. Again, the intent of the first example is clearer. And again, I thought it was worth pointing out in case you will encounter both approaches and wonder...
However, we can do better. Much better, in fact, using Next NEXTREG instruction, which allows direct writes to given registers. So above examples could simply be changed to either:
1 LD A, 5 ; write 5 2 NEXTREG
$
16, A ; to reg$16
1 NEXTREG
$
16, 5 ; write 5 to reg$16
The first example requires 24 t-states (7+17) while second 20. So less than half of that of traditional approach. In fact, using NEXTREG is the preferred method of writing to Next registers!
Reading from Next Registers
Reading values from Next/TBBlue registers also occurs through$243B and$253B ports. Similar to write, read is also composed from 2 steps: first select the register with port$243B, then read the value from port$253B. For example reading a byte from port$B0:
1 LD A, 2 LD BC, 3 OUT (C), A ; set port
4 5 LD BC, 6 IN A, (C) ; read to A
$
16 ; register$16
$
243B ; port$243B
$
253B ; port$253B
1 LD A, 2 LD BC, 3 OUT (C), A ; set port
4 5 INC B ; port 6 IN A, (C) ; read to A
$
16 ; register$16
$
243B ; port$243B
$
253B
The difference is small: 51 t-states ((7+10+12)+(10+12)) vs 45 ((7+10+12)+(4+12)).
Unfortunately, we don’t have faster means of reading Next registers directly as we do for writing; there is no NEXTREG alternative for reads.
35
MEMORY MAP AND PAGING
3.2 Memory Map and Paging
ZX Spectrum Next comes with 1024K (expanded version with 2048K) of memory. But it can’t see it all at once.
3.2.1 Banks and Slots
Due to its 16-bit address bus, Next can only address 216= 65.536 bytes or 64K of memory at a time. To get access to all available memory, it’s divided into smaller chunks called “banks”.
Next supports two interchangeable memory management models. One is inherited from the original Spectrum 128K, +2, +3 series and Pentagon clones and uses 16K banks. The other is unique to Next and uses 8K banks. Hence, addressable 64K is also divided into 16K or 8K “slots” into which banks are swapped in and out3.
Banks are selected by their number - first bank is 0, second 1 and so on. If you ever worked with arrays, banks and their numbers work the same as array data and indexes. Both 16K and 8K banks start with number 0 at the same address. So if 16K bank n is selected, then the two corresponding 8K bank numbers would be n2 and n21.
After startup, addressable 64K space is mapped like this:
Slots Banks
Address
Description
16K 8K 16K 8K
$
0000-$1FFF 0 0 ROM ROM ROM, R/W redirect by L2, IRQ, NMI
$
2000-$3FFF 1 ROM ROM, R/W redirect by Layer 2
$
4000-$5FFF 1 2 5 10 Normal/shadow ULA screen, Tilemap
$
6000-$7FFF 3 11 ULA extended attribute/graphics, Tilemap
$
8000-$9FFF 2 4 2 4 Free RAM
$
A000-$BFFF 5 5 Free RAM
$
C000-$DFFF 3 6 0 0 Free RAM
$
E000-$FFFF 7 1 Free RAM
3.2.2 Default Bank Traits
First few addressable banks have certain uses and traits:
Banks
16K 8K
Description
0 0-1 Standard RAM, maybe used by EsxDOS. Initially mapped to$C000-$FFFF 1 2-3 Standard RAM, contended on 128, may be used by EsxDOS, RAM disk on
NextZXOS
3
You may also see the term “page” used instead of “bank” (in fact, that’s why the process of swapping banks into slots is usually called “paging”). I also noticed sometimes 64K addressable memory is referred to as “bank”. In this book, I will keep naming consistent to avoid confusion.
36
CHAPTER 3. ZX SPECTRUM NEXT
Banks
Description
16K 8K
2 4-5 Standard RAM. Initially mapped to$8000-$BFFF 3 6-7 Standard RAM, contended on 128, may be used by EsxDOS, RAM disk on
NextZXOS
4 8-9 Standard RAM, contended on +2/+3, RAM disk on NextZXOS 5 10-11 ULA Screen, contended except on Pentagon, cannot be used by NextBASIC
commands. Initially mapped to$4000-$7FFF
6 12-13 Standard RAM, contended on +2/+3, RAM disk on NextZXOS 7 14-15 ULA Shadow Screen, contended except on Pentagon, NextZXOS Workspace,
cannot be used by NextBASIC commands
8 16-17 Next RAM, Default Layer 2, NextZXOS screen and extra data, cannot be
used by NextBASIC commands
9-10 18-21 Next RAM, Rest of default Layer 2 11-13 22-27 Next RAM, Default Layer 2 Shadow Screen
3.2.3 Memory Map
As hinted before, not all available memory is addressable by programs. The first 256K is always reserved for ROMs and firmware. Hence bank 0 starts at absolute address$40000:
16K bank 8K bank Size Absolute Address Description
- - 64K
- - 16K
- - 16K
- - 16K
- - 16K
$
000000-$00FFFF ZX Spectrum ROM
$
010000-$013FFF EsxDOS ROM
$
014000-$017FFF Multiface ROM
$
018000-$01BFFF Multiface Extra ROM
$
01C000-$01FFFF Multiface RAM
- - 128K$020000-$03FFFF DivMMC RAM 0-7 0-15 128K$040000-$05FFFF Standard 128K RAM
Unexpanded Next
Expanded Next
8-15 16-31 128K$060000-$07FFFF Extra RAM 16-47 32-95 512K$080000-$0FFFFF 1st Extra IC RAM 48-79 96-159 512K$080000-$0FFFFF 1st Extra IC RAM 80-111 160-223 512K$080000-$0FFFFF 2st Extra IC RAM
So when swapping in, for example:
16K bank 20 to slot 3 and writing 10 bytes to memory$C000 (start of 16K slot 3), we’re effectively writing to absolute memory$90000-$90009 ($40000 + 20
16384)
8K bank 30 to slot 5 and writing 10 bytes to memory$A000 (start of 8K slot 5), we’re effectively writing to absolute memory$7C000-$7C009 ($40000 + 30
8192)
.
37
MEMORY MAP AND PAGING
3.2.4 Legacy Paging Modes
As mentioned, Next inherits the memory management models from the Spectrum 128K/+2/+3 models and Pentagon clones. It’s unlikely you will use these modes for Next programs, as Next own model is much simpler to use. They are still briefly described here though in case you will encounter them in older programs. All legacy models use 16K slots and banks.
128K Mode
Slot 0 1 2 3
Start End
Allows selecting:
$
0000
$
3FFF
$
4000
$
7FFF
$
8000
$
BFFF
$
C000
$
FFFF
Ò Ò
ROM 0-1 BANK 0-7 on 128K
BANK 0-127 on Next
16K ROM to be visible in the bottom 16K slot (0) from 2 possible banks
16K RAM to be visible in the top 16K slot (3) from 8 possible banks (128 banks on Next)
Registers involved:
Memory Paging Control$7FFD bit 4 selects ROM bank for slot 0
Memory Paging Control$7FFD bits 2-0 select one of 8 RAM banks for slot 3
Next Memory Bank Select$DFFD bits 3-0 are added as MSB to 2-0 from
$
7FFD to form
128 banks for slot 3 (Next specific)
If you are using the standard interrupt handler or OS routines, then any time you write to
Memory Paging Control$7FFD you should also store the value at
$
5B5C.
+3 Normal Mode
Slot 0 1 2 3
Start End
$
0000
$
3FFF
$
4000
$
7FFF
$
8000
$
BFFF
$
C000
$
FFFF
Ò Ò
ROM 0-3 BANK 0-7 on 128K
BANK 0-127 on Next
Allows selecting:
16K ROM to be visible in the bottom 16K slot (0) from 4 possible banks
16K RAM to be visible in the top 16K slot (3) from 8 possible banks (128 banks on Next)
Registers involved:
38
CHAPTER 3. ZX SPECTRUM NEXT
Plus 3 Memory Paging Control$1FFD bit 2 as LSB for selecting ROM bank for slot 0
Memory Paging Control$7FFD bit 4 forms MSB for selecting ROM bank for slot 0
Memory Paging Control$7FFD bits 2-0 select one of 8 RAM banks for slot 3
Next Memory Bank Select$DFFD bits 3-0 are added as MSB to 2-0 from
$
7FFD to form
128 banks for slot 3 (Next specific)
If you are using the standard interrupt handler or OS routines, then any time you write to Plus
3 Memory Paging Control$1FFD you should also store the same value at
time your write to Memory Paging Control
$
7FFD you should also store the value at
$
5B67 and every
$
5B5C.
+3 All-RAM Mode
Slot 0 1 2 3
Start End
$
0000
$
3FFF
$
4000
$
7FFF
$
8000
$
BFFF
$
C000
$
FFFF
Ò Ò Ò Ò
00 = BANK 0 BANK 1 BANK 2 BANK 3 01 = BANK 4 BANK 5 BANK 6 BANK 7 10 = BANK 4 BANK 5 BANK 6 BANK 3 11 = BANK 4 BANK 7 BANK 6 BANK 3
Ó
Lo bit = bit 1 from$1DDF
Hi bit = bit 2 from$1DDF
Also called “Special Mode” or “CP/M Mode”. Allows selecting all 4 slots from limited selection of banks as shown in the table above.
Registers involved:
Plus 3 Memory Paging Control$1FFD bit 0 enables All-RAM (if 1) or normal mode (0)
Plus 3 Memory Paging Control$1FFD bits 2-1 select memory configuration
If you are using the standard interrupt handler or OS routines, then any time you write to Plus
3 Memory Paging Control$1FFD you should also store the same value at
$
5B67.
Pentagon 512K/1024K Mode
Next also supports paging implementation from Pentagon spectrums. It’s unlikely you will ever use it on Next, so just mentioning for completness sake. You can find more information on Next Dev Wiki4or internet if interested.
4
https://wiki.specnext.dev/Next Memory Bank Select
39
MEMORY MAP AND PAGING
3.2.5 Next MMU Paging Mode
Next MMU based paging mode is much more flexible in that it allows mapping 8K banks into any 8K slot of memory available to the CPU. It’s also the simplest to use - a single instruction assigning bank number to desired MMU slot register.
In this mode, 64K memory accessible to the CPU is divided into 8 slots called MMU0 through MMU7, as shown in the diagram below. Physical memory is thus divided into 96 (or 224 on expanded Next) 8K banks. This is the only mode that allows paging in all memory from 2048K extended Next.
16K Slot 0 1 2 3 8K Slot 0 1 2 3 4 5 6 7
Start End
$
0000$2000$4000$6000$8000
$
1FFF$3FFF$5FFF$7FFF$9FFF
$
A000$C000$E000
$
BFFF$DFFF$FFFF
Ò Ò Ò Ò Ò Ò Ò Ò
BANK BANK BANK BANK BANK BANK BANK BANK
0-255 0-255 0-255 0-255 0-255 0-255 0-255 0-255
Bank selection is set via Next registers:
Memory Management Slot 0 bank$50
Memory Management Slot 1 bank$51
Memory Management Slot 2 bank$52
Memory Management Slot 3 bank$53
Memory Management Slot 4 bank$54
Memory Management Slot 5 bank$55
Memory Management Slot 6 bank$56
Memory Management Slot 7 bank$57
While not absolutely required, it’s good practice to store original slot values and then restore before exiting program or returning from subroutines.
Example of writing 10 bytes (00 01 02 03 04 05 06 07 08 09) to 8K bank 30 swapped in to slot 5. As mentioned before, this will effectively write to absolute memory$7C000-$7C009:
1 NEXTREG
2 3 LD DE, 4 LD A, 0 ; starting data to write 5 LD B, 10 ; number of bytes to write 6 next: 7 LD (DE), A ; write next byte 8 INC A ; increment source byte 9 INC DE ; increment destination location
10 DJNZ next
$
55, 30 ; swap bank 30 to slot 5
$
A000 ; slot 5 starts at$A000
40
CHAPTER 3. ZX SPECTRUM NEXT
Note: Memory Management Slot 0 bank
$
50 and Memory Management Slot 1 bank$51
have extra “functionality”: ROM can be automatically paged in if otherwise nonexistent 8K page$FF is set. Low or high 8K ROM bank is automatically determined based on which 8K slot is used. This may be useful if temporarily paging RAM into the bottom 16K region and then wanting to restore back to ROM.
3.2.6 Interaction Between Paging Modes
As mentioned, legacy and Next paging modes are interchangeable. Changing banks in one will be reflected in the other. The most recent change always has priority. Again, keep in mind that legacy modes use 16K banks, therefore single bank change will affect 2 8K banks.
Paging Out ROM
ROM is usually mapped to the bottom 16K slot, addresses$0000-$3FFF. This area can only be remapped using +3 All-RAM or Next MMU-based mode. Beware though that some programs may expect to find ROM routines at fixed addresses between$0000 and$3FFF. And if default interrupt mode (IM 1) is set, Z80 will jump PC to$0038 expecting to find interrupt handler there.
ULA
ULA always reads content from 16K bank 5. This is mapped to 16K slot 1 by default, addresses
$
4000-$7FFF. ULA will always use bank 5, regardless of which bank is mapped to slot 1, or
which slot bank 5 is mapped to (or if it is mapped into any slot at all).
You can redirect ULA to read from 16K bank 7 instead (the “shadow” screen), using bit 3 of
Memory Paging Control$7FFD. However, you still need to map bank 7 into one of the slots
if you want to read or write to it (that’s 8K banks 14 and 15 if using MMU for paging). Read more in ULA chapter, section ??.
Layer 2 Paging
The bottom 16K slot can be set for write-only access for Layer 2. This can be handy as this slot is typically mapped to ROM and thus useless to write to. There are also other Layer 2 related combinations available, read more in Layer 2 chapter, section ??
41
MEMORY MAP AND PAGING
3.2.7 Paging Mode Registers
+3 Memory Paging Control$1FFD
Bit Effect
7-3 Unused, use 0
2 In normal mode high bit of ROM selection. With low bit from bit 4 of$7FFD:
00 ROM0 = 128K editor and menu system 01 ROM1 = 128K syntax checker 10 ROM2 = +3DOS 11 ROM3 = 48K BASIC
In special mode: high bit of memory configuration number
1 In special mode: low bit of memory configuration number 0 Paging mode: 0 = normal, 1 = special
Memory Paging Control$7FFD
Bit Effect 7-6 Extra two bits for 16K RAM bank if in Pentagon 512K/1024K mode (see Next
Memory Bank Select$DFFD)
5 1 locks pages; cannot be unlocked until next reset on regular ZX128) 4 128K: ROM select (0 = 128K editor, 1 = 48K BASIC)
+2/+3: low bit of ROM select (see +3 Memory Paging Control
$
1FFD above)
3 ULA layer shadow screen toggle (0 = bank 5, 1 = bank 7)
2-0 Bank number for slot 4 ($C000)
Next Memory Bank Select$DFFD
Bit Effect
7 1 to set Pentagon 512K/1024K mode
3-0 Most significant bits of the 16K RAM bank selected in Memory Paging Control
$
7FFD
Memory Management Slot 0-7$50-$57
Bit Effect 7-0 Selects 8K bank stored in corresponding 8K slot
42
CHAPTER 3. ZX SPECTRUM NEXT
Memory Mapping Register$8E
Bit Effect
7 Access to bit 0 of Next Memory Bank Select
6-4 Access to bits 2-0 of Memory Paging Control
$
DFFD
$
7FFD
3 Read will always return 1
Write 1 to change RAM bank, 0 for no change to MMU6,7,$7FFD and$DFFD
2 0 for normal paging mode, 1 for special all-RAM mode 1 Access to bit 2 of +3 Memory Paging Control 0 If bit 2 = 0 (normal mode): bit 4 of Memory Paging Control
If bit 2 = 1 (special mode): bit 1 of +3 Memory Paging Control
Acts as a shortcut for reading and writing +3 Memory Paging Control
Paging Control$7FFD and Next Memory Bank Select$DFFD all at once. Mainly to simplify
$
1FFD
$
7FFD
$
1FFD
$
1FFD, Memory
classic Spectrum memory mapping. Though, as mentioned, Next specific programs should prefer MMU based memory mapping.
43
PALETTE
3.3 Palette
Next greatly enhances ZX Spectrum video capabilities by offering several new ways to draw graphics on a screen. We’ll see how to program each in later chapters, but let’s check common behaviour first - colour management.
3.3.1 Palette Selection
To draw a pixel on a screen, we need to set its colour as data in memory. There are different approaches to how this data is defined. Next shares implementation to other 8-bit computers of the era - all possible colours are stored together in a palette, as an array of RGB values, and each pixel is simply an index into this array. This approach requires less memory and allows creating efficient effects such as fade to/from black, transitions from day to night, water animations etc.
Contrary to most computers of the era that only had predefined palettes, Next allows changing all colours. Furthermore, each layer has not one but two palettes, each of which can be changed independently. Of course, only one of two can be active at any given time for each mode. The other can be initialized with alternate colours and can be quickly activated to achieve colour animation effects. Active palette is set with Enhanced ULA Control Register Layer 2 and Sprites and Tilemap Control Register
$
6B for Tilemap.
$
43 for ULA,
3.3.2 Palette Editing
Data for each pixel for most layers and modes is 1 byte long, meaning each palette can have up to 256 colours.
All palettes are initialized with default colours, so they are usable out of the box. But it’s also possible to change individual colours. Regardless of the palette, the procedure to read or write colours is:
1. Enhanced ULA Control Register
2. Palette Index Register
3. Palette Value Register
$
40 selects colour index that will be read or written
$
41 or Enhanced ULA Palette Extension$44 reads or writes
data for selected colour
When writing colours, we can chose to automatically increment colour indexes after each write. Bit 7 of Enhanced ULA Control Register for both write registers ($41 and$44). Colour RGB values can either be 8-bit RRRGGGBB, or 9-bit RRRGGGBBB values. Use Palette Value Register
Extension$44 for 9-bit.
Note: Enhanced ULA Control Register selects the active palette for display (out of two available - only for ULA, Layer 2 and Sprites) and selects palette for editing (for all layers, including Tilemap). Therefore care needs to be taken when updating colour entries to avoid accidentally changing the active palette for display
$
43 selects palette which colours you want to edit
$
43 is used for that purpose. This works the same
$
41 for 8-bit and Enhanced ULA Palette
$
43 has two roles when working with palettes - it
44
CHAPTER 3. ZX SPECTRUM NEXT
at the same time. Depending on our program, we may first need to read the value and then only change bits affecting the palette for editing to ensure the rest of the data remains unaffected.
3.3.3 8 Bit Colours
8-bit colours are stored as RRRGGGBB values with 3 bits per red and green and 2 bits per blue component. Each colour is therefore stored as a single byte. Palette Value Register used to read or write the value.
Here’s a reusable subroutine for copying B number of colours stored as a contiguous block in memory addressed by HL register, starting at the currently selected colour index:
1 Copy8BitPalette: 2 LD A, (HL) ; Load RRRGGGBB into A 3 INC HL ; Increment to next colour entry 4 NEXTREG 5 DJNZ Copy8BitPalette ; Repeat until B=0
$
41, A ; Send colour data to Next HW
$
41 is
To use the subroutine, we’d do something like:
1 NEXTREG 2 NEXTREG 3 LD HL, palette ; Address to copy RRRGGGBB values from 4 LD B, 255 ; Copy 255 colours 5 CALL Copy8BitPalette
$
43, %00010000 ; Auto increment, L2 first palette for read/write
$
40, 0 ; Start copying into index 0
3.3.4 9 Bit Colours
With 9 bits per colour, each RGB component uses full 3 bits, thus greatly increasing the available colour gamut. However, each colour needs 2 bytes in memory instead of 1. To read or write we use Enhanced ULA Palette Extension to$41 except that each colour requires two writes: first one stores RRRGGGBB part and second least significant bit of blue component. Subroutine for copying 9-bit colours:
1 Copy9BitPalette: 2 LD A, (HL) ; Load RRRGGGBB into A 3 INC HL ; Increment to next byte 4 NEXTREG 5 LD A, (HL) ; Load LSB of B into A 6 INC HL ; Increment to next colour entry 7 NEXTREG 8 DJNZ Copy9BitPalette ; Repeat until B=0
$
44, A ; Send colour data to Next HW
$
44, A ; Send colour data to Next HW and increment index
$
44 register instead of
$
41. It works similarly
Note: subroutine requires that colours are stored in 2 bytes with first containing RRRGGGBB part and second least significant bit of blue. Which is how typically drawing programs store a 9-bit palette anyways. The calling subroutine is exactly the same as for the 8-bit colours above.
45
3.3.5 Palette Registers
Palette Index Register$40
Bit Effect 7-0 Reads or writes palette colour index to be manipulated
PALETTE
Writing an index 0-255 associates it with colour set through Palette Value Register or Enhanced ULA Palette Extension
Control Register$43. Write also resets value of Enhanced ULA Palette Extension$44 so
$
44 of currently selected pallette in Enhanced ULA
$
41
next write will occur for first colour of the palette.
While Tilemap, Layer 2 and Sprites palettes use all 256 distinct colours (with some caveats, as described in specific chapters), ULA modes work like this:
Classic ULA
Index Colours
0-7 Ink
8-15 Bright ink 16-23 Paper 24-31 Bright paper
Border is taken from paper colours.
ULA+
Index Colours 0-64 Ink
Paper and border are taken from Transparency Colour Fallback Register$4A.
ULANext normal mode
Index Colours
0-127 Ink (only a subset)
128-255 Paper (only a subset)
Border is taken from paper colours. The number of active indices depends on the number of attribute bits assigned to ink and paper out of the attribute byte by Enhanced ULA
Ink Colour Mask$42.
ULANext full-ink mode
Index Colours 0-255 Ink
Paper and border are taken from Transparency Colour Fallback Register$4A.
46
CHAPTER 3. ZX SPECTRUM NEXT
Palette Value Register$41
Bit Effect
7-0 Reads or writes 8-bit colour data
Format is:
7 6 5 4 3 2 1 0
R2R1R0G2G1G0B2B
1
Red Green Blue
Least significant bit of blue is set to OR between B2and B1.
Writing the value will automatically increment index in Palette Index Register increment is enabled in Enhanced ULA Control Register
$
43. Read doesn’t auto-increment
$
40, if auto-
index.
Enhanced ULA Ink Colour Mask$42
Bit Effect
7-0 The number for last ink colour entry in the palette. Only used when ULANext mode
is enabled (see Enhanced ULA Control Register
$
43). Only the following values are
allowed, harware behavior is unpredictable for other values:
1 Ink and paper only use 1 colour each on indices 0 and 128 respectively 3 Ink and paper use 4 colours each, on indices 0-3 and 128-131 7 Ink and paper use 8 colours each, on indices 0-7 and 128-135 15 Ink and paper use 16 colours each, on indices 0-15 and 128-143 31 Ink and paper use 32 colours each, on indices 0-31 and 128-159 63 Ink and paper use 64 colours each, on indices 0-63 and 128-191 127 Ink and paper use 128 colours each, on indices 0-127 and 128-255 255 Enables full-ink colour mode where all indices are ink. In this mode paper and
border are taken from Transparency Colour Fallback Register
$
4A
47
Default value is 7 for core 3.0 and later, 15 for older cores.
Enhanced ULA Control Register$43
Bit Effect
7 1 to disable palette index auto-increment, 0 to enable
6-4 Selects palette for read or write
000 ULA first palette 100 ULA second palette 001 Layer 2 first palette 101 Layer 2 second palette 010 Sprites first palette 110 Sprites second palette 011 Tilemap first palette 111 Tilemap second palette
3 Selects active Sprites palette (0 = first palette, 1 = second palette) 2 Selects active Layer 2 palette (0 = first palette, 1 = second palette) 1 Selects active ULA palette (0 = first palette, 1 = second palette) 0 Enables ULANext mode if 1 (0 after reset)
PALETTE
Write will also reset the index of Enhanced ULA Palette Extension
$
44 so next write there
will be considered as first byte of first colour.
Enhanced ULA Palette Extension$44
Bit Effect 7-0 Reads or writes 9-bit colour definition
Two consequtive writes are needed:
First write:
7 6 5 4 3 2 1 0
R2R1R0G2G1G0B2B
Red Green Blue
1
Second write:
7 6 5 4 3 2 1 0
P
r
L2 Reserved, set to 0 B
- B
0
Bit 7 of the second write must be 0 except for Layer 2 palettes where it specifies colour priority. If set to 1, then the colour will always be on top, above all other layers, regardless of priority set with Sprite and Layers System Register
$
15. So if you need exactly the same colour with
priority and non-priority, you will need to set the same data twice, to different indexes, once with priority bit 1 and then with 0.
After second write palette colour index in Palette Index Register if auto-increment is enabled in Enhanced ULA Control Register
$
40 is automatically increment,
$
43.
Note: reading will always return the second byte of the colour (least significant bit of blue) and will not auto-increment index. You can read RRRGGGBB part with Palette Value Register
$
41.
48
CHAPTER 3. ZX SPECTRUM NEXT
Transparency Colour Fallback Register$4A
Bit Effect
7-0 8-bit colour to be used when all layers contain transparent pixel. Format is RRRGGGBB
This colour is also used for paper and border when ULANext full-ink mode is enabled - see
Enhanced ULA Ink Colour Mask$42.
49
ULA LAYER
3.4 ULA Layer
Original ZX Spectrum didn’t have a dedicated graphics chip. To keep the price as low as possible, screen rendering was performed by ULA (“Uncommitted Logic Array”) chip.
ZX Spectrum Next inherits ULA mode. The resolution of the screen in this mode is 256192 pixels. If we translate this to 88 pixels characters, it gives us 32 character columns in 24 character rows.
ULA always reads from 16K bank 5 which is assigned to the second 16K slot at addresses$4000-
$
7FFF by default. Similar to the memory configuration of other contemporary computers, pixel
memory is separate from attributes/colour memory. If using default memory configuration:
ROM RAM
16K 16K 16K 16K
Pixels Attributes (free)
$
4000-$57FF$5800-$5AFF$5B00-$7FFF
3.4.1 Pixel Memory
Each screen pixel is represented by a single bit, meaning 1 byte holds 8 screen pixels. So, for each line of 256 pixels, 32 bytes are needed. However, for sake of efficiency, the original Spectrum optimized screen memory layout for speed but made it inconvenient for programming.
Pixel memory is not linear but is instead divided to fill character rows line by line. The first 32 bytes of memory represent the first line of the first character row, followed by 32 bytes representing the first line of the second character row and so on until the first line of 8 character rows is filled. Then next 32 bytes of screen memory represent the second line of the first character row, again followed by the second line of the second character row, until all 8 character rows are covered:
Addr. Ln. Ch. Addr. Ln. Ch. Addr. Ln. Ch.
$
4000 0 0/0
$
4020 8 1/0
$
4040 16 2/0
$
4060 24 3/0
$
4080 32 4/0
$
40A0 40 5/0
$
40C0 48 6/0
$
40E0 56 7/0
$
4100 1 0/1
$
4120 9 1/1
$
4140 17 2/1
$
4160 25 3/1
$
4180 32 4/1
$
41A0 41 5/1
$
41C0 49 6/1
$
41E0 57 7/1
$
4200 2 0/2
$
4220 10 1/2
$
4240 18 2/2
$
4260 26 3/2
$
4280 33 4/2
$
42A0 42 5/2
$
42C0 50 6/2
$
42E0 58 7/2
...
Ln. Screen line (0-191) Ch. Character <row>/<line> (0-23/0-7)
But this is not the end of the peculiarities of Spectrum ULA mode. If you attempt to fill the screen memory byte by byte, you’ll realize the top third of the screen fills in first, then middle third and lastly bottom third. The reason is, ULA mode divides the screen into 3 banks. Each bank covers 8 character rows, so 8832 or 2048 bytes:
50
CHAPTER 3. ZX SPECTRUM NEXT
Memory Range Screen Lines Char. Rows
$
4000 -$47FF 0 - 63 0 - 8
$
4800 -$4FFF 64 - 127 9 - 16
$
5000 -$57FF 128 - 191 17 - 23
In fact, to calculate the address of memory for any given (x,y) coordinate, we’d need to prepare a 16-bit value like this:
High Byte Low Byte
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 Y
Y
7
Y
6
Y1Y0Y5Y4Y3X7X6X5X4X
2
3
0 1 0 Y X
As you can see, X is straightforward; we simply need to take the upper 5 bits and fill them into the lower 5 bits of a 16-bit register pair. Y coordinate requires all 8 bits written into bits 12-5 of 16-bit register pair. However, notice how individual bits are scrambled. It makes incrementing address for next character row simple operation of INC H (assuming HL stores the address of the previous row), which is likely one of the reasons for such implementation. But imagine for a second how complex a Z80 program would need to be to handle all of this. Sure, nothing couple shifts and masking operations couldn’t handle but still, lots of wasted CPU cycles. However, on ZX Spectrum Next we have 3 new instructions that take care of all of the complexity for us:
PIXELAD calculates the address of a pixel with coordinates from DE register pair where D is Y and E is X coordinate and stores the memory location address into HL register pair for ready consumption
PIXELDN takes the address of a pixel in HL and updates it to point to the same X coordinate but one screen line down
SETAE takes X coordinate from E register and prepares mask in register A for reading or writing to ULA screen
Furthermore; each instruction only uses 8 t-states, which is far less than the corresponding Z80 assembly program would require. Somewhat naive program for drawing vertical line write from the pixel at coordinate (16,32) to (16,50):
1 LD DE, 2 PIXELAD ; HL=address of pixel (E,D) 3 loop: 4 SETAE ; A=pixel mask 5 OR (HL) ; we’ll write the pixel 6 LD (HL), A ; actually write the pixel
7 8 INC D ; Y=Y+1
9 LD A, D ; copy new Y coordinate to A 10 CP 51 ; are we at 51 already? 11 RET NC ; yes, return
12 13 PIXELDN ; no, update HL to next line 14 JR loop ; continue with next pixel
51
$
1020 ; Y=16, X=32
ULA LAYER
Note: because we’re updating our Y coordinate in D register within the loop, we could also use PIXELAD instead of PIXELDN in line 13. Both instructions require 8 T states for execution, so
there’s no difference performance-wise.
If we instead wanted to check if the pixel at the given coordinate is set or not, we would use AND (HL) instead of OR (HL). For example:
1 LD DE, 2 PIXELAD ; HL=address of pixel (E,D) 3 SETAE ; A=pixel mask 4 AND (HL) ; we’ll read the pixel 5 RET Z ; exit if pixel is not set
$
1020 ; Y=16, X=32
3.4.2 Attributes Memory
Now that we know how to draw individual pixels, it’s time to handle colour. Memory wise, it’s stored immediately after pixel RAM, at memory locations$5800 -$5AFF. Each byte represents colour and attributes for 88 pixel block on the screen. Byte contents are as follows:
7 6 5 4 3 2 1 0
F B P2P1P0I F B Paper Ink
Bit 7: 1 to enable flashing, 0 to disables it
Bit 6: 1 to enable bright colours, 0 for normal colours
Bits 5-3: paper colour 0-7
I
2
I
1
0
Bits 2-0: ink colour 0-7
Colour value 0-7 corresponds to:
Value Binary Colour Bright
0 000 Black Black 1 001 Blue Bright blue 2 010 Red Bright red 3 011 Magenta Bright magenta 4 100 Green Bright green 5 101 Cyan Bright cyan 6 110 Yellow Bright yellow 7 111 Gray White
Spectrum only requires 768 bytes to configure colour and attributes for the whole screen. And memory is contiguous so it’s simple to manage. However, it comes at expense of restricting to only 2 colours per character block - the reason for the (in)famous colour clash.
Note: on Next, default ULA colours can be changed, see Palette chapter ?? for details.
52
CHAPTER 3. ZX SPECTRUM NEXT
3.4.3 Border
Next inherits Spectrum border colour handling through ULA Control Port
$
xxFE. The bottom
3 bits are used to specify one of 8 possible colours (see table on the previous page for full list). Example:
1 LD A, 1 ; Select blue colour
2 OUT (
$
FE), A ; Set border colour from A
Note: border colour is set the same way regardless of graphics mode used. However, some Layer 2 modes and Tileset may partially or fully cover the border, effectively making it invisible to the user.
3.4.4 Shadow Screen
As mentioned, ULA uses 16K bank 5 by default to determine what to show on the screen. However, it’s possible to change this to bank 7 instead by using bit 3 of Memory Paging
Control$7FFD. Bank 7 mode is called the “shadow” screen. It gives us two separate memory
spaces for rendering ULA data and means for quickly swapping between them. It allows always drawing into inactive bank and only swapping it in when ready thus help eliminating flicker.
Note: Memory Paging Control used by ULA, but it doesn’t map the bank into any of the memory slots. This needs to be done by one of the paging modes as described in the Memory Map and Paging chapter, section ??. Using MMU, we could do something like:
$
7FFD only controls which of the two possible banks is being
1 LD HL,
2
3 NEXTREG
4 LD A, %00000000 ; paper=black, ink=black
5 LD (HL), A ; write data to screen (immediately visible)
6
7 NEXTREG
8 LD A, %00000101 ; paper=black, ink=cyan
9 LD (HL), A ; write to 16K bank 7 (not visible)
10 11 LD BC, 12 LD A, %00001000 ; activate shadow layer 13 OUT (C), A ; top left char now has black background
14 15 LD A, %00000000 ; deactivate shadow layer 16 OUT (C), A ; top left char now has cyan background
$
5800 ; we’ll be swapping colours
$
52, 10 ; swap first half of 16K bank 5 to 8K slot 2
$
52, 14 ; swap first half of 16K bank 7 to 8K slot 2
$
7FFD ; prepare port for changing layers
Remember: 16K bank 7 corresponds to 8K banks 14 and 15. And because pixel and attributes combined fit within single 8K, only single bank needs to be swapped in.
53
ULA LAYER
3.4.5 Enhanced ULA Modes
ZX Spectrum Next also supports several enhanced ULA modes like Timex Sinclair Double Buffering, Timex Sinclair Hi-Res and Hi-Colour, etc. However, with the presence of Layer 2 and Tilemap modes, it’s unlikely these will be used when programming new software on Next. Therefore they are not described here. If interested, read more on:
https://wiki.specnext.dev/Video Modes
3.4.6 ULA Registers
ULA Control Port$xxFE
Bit Effect
7-5 Reserved, use 0
4 EAR output (connected to internal speaker) 3 MIC output (saving to tape via audio jack)
2-0 Border colour
Note: when reading this port with certain high byte values will read keyboard status. See section ?? for details.
Memory Paging Control$7FFD
See description under Memory Map and Paging chapter, section ??.
Palette Index Register$40
Palette Value Register$41
Enhanced ULA Ink Colour Mask$42
Enhanced ULA Control Register$43
Enhanced ULA Palette Extension$44
Transparency Colour Fallback Register$4A
See description under Palette chapter, section ??.
54
CHAPTER 3. ZX SPECTRUM NEXT
This page intentionally left empty
55
LAYER 2
3.5 Layer 2
As we saw in the previous section, drawing with ULA graphics is much simplified on Next. But it can’t eliminate the colour clash. Well, not with ULA mode at least. However, Next brings a couple of brand new graphic modes to the table, hidden behind a somewhat casual name “Layer 2”. But don’t let its name deceive you; Layer 2 raises Next graphics capabilities to a whole new level!
Layer 2 may appear behind or above the ULA layer. It supports different resolutions with every pixel coloured independently and memory organized sequentially, line by line, pixel by pixel. Consequently, Layer 2 requires more memory compared to ULA; each mode needs multiple 16K banks. But of course, Next has far more memory than the original Speccy ever did!
Resolution Colours BPP Memory Organization
256192 256 8 48K, 3 horizontal banks of 64 lines 320256 256 8 80K, 5 vertical banks of 64 columns 640256 16 4 80K, 5 vertical banks of 128 columns
5
5
3.5.1 Initialization
Drawing on Layer 2 is much simpler than using ULA mode. But in contrast with ULA, which is always “on”, Layer 2 needs to be explicitly enabled. This is done by setting bit 1 of Layer
2 Access Port$123B.
By default, Layer 2 will use 256192 with 256 colours, supported across all Next core versions. You can select another resolution with Layer 2 Control Register modes also require setting up clip window correctly with Clip Window Layer 2 Register
$
70. 320
256 and 640256
$
18.
3.5.2 Paging
After Layer 2 is enabled, we can start writing into memory banks. As mentioned above, Layer 2 requires 3-5 contiguous 16K banks. While Next initializes default configuration during boot, it’s nonetheless a good idea to set it up manually to ensure our code will work across all devices.
Layer 2 Ram Page Register$12 selects the bank number where Layer 2 video memory begins.
Note it’s a good idea to store the original bank values so we can restore them afterwards.
All supported modes can be used for paging, as described in section ??, by swapping in bank numbers to 16K slot at$C000. However, the simplest and most versatile is MMU mode; MMU6 and MMU7 registers correspond to 2 8K slots starting at$C000.
5
Core 3.0.6+ only
56
CHAPTER 3. ZX SPECTRUM NEXT
3.5.3 Drawing
In general, drawing pixels requires the programmer to:
Determine and select bank to write to
Calculate address of the pixel within the bank
Write byte with colour data
All Layer 2 modes use the same approach when drawing pixels. Each pixel uses one byte (except 640320 where each byte contains data for 2 pixels). The value is simply an index into the palette entries list. Similar to other layers, Layer 2 also has two palettes, of which only one can be active at any given time. Enhanced ULA Control Register
$
43 is used to select active
palette. See Palette chapter ?? for details on how to program palettes.
See specific modes in the following pages for examples of writing pixel data.
3.5.4 Effects
Sprite and Layers System Register$15 can be used to change Layer 2 priority, effectively
moving Layer 2 above or below other layers - see Tilemap chapter, section ?? for details.
We can even be more specific and only prioritize specific colours, so only pixels using those colours will appear on top while other pixels below other layers. This way we can achieve a simple depth effect. Per-pixel priority is available when writing a custom palette with Enhanced
ULA Palette Extension$44 (9-bit colours). See description under Palette chapter, section ??
for details on how to program palette.
We can also use both Layer 2 palettes to achieve simple effects. For example, certain colours can be marked with the priority flag on one palette but not on the other. When swapping palettes, pixels drawn with these colours would appear on top or below other layers. Another simple effect using both palettes could be colour animation, though it can’t be very smooth with only two states.
Global Transparency Register$14 can be used to alter the transparent colour of Layer 2.
This same register also affects ULA, LoRes and 1-bit (“text mode”) tilemap.
Scrolling effects can be achieved by writing pixel offsets to Layer 2 X Offset Register
Layer 2 X Offset MSB Register$71 and Layer 2 Y Offset Register$17.
$
16,
57
3.5.5 256192 256 Colour Mode
LAYER 2
3 horizontal banks:
8BPP:
0 . . . 255
0 16K BANK 0 8K BANK 0
.
.
.
0. .. 31
8K BANK 1 63 32 . .. 63 64 16K BANK 1 8K BANK 2
.
.
.
64 . .. 95
8K BANK 3
127 96 . .. 127 128 16K BANK 2 8K BANK 4
.
.
.
128 . .. 159
8K BANK 5
7 6 5 4 3 2 1 0
I7I6I5I
4
I3I
I
2
1
Colour index
Banking Setup:
15 14 13 12-8 7-0
Y X
16K Y
8K Y
50
40
X
X
I
0
191 160 . .. 191
This mode is the closest to ULA, resolution wise, so is perhaps the simplest to grasp. It’s also supported across all Next core versions. Pixels are laid out from left to right and top to bottom. Each pixel uses one byte that represents an 8-bit index into the palette. 3 16K banks are needed to cover the whole screen, each holding data for 64 lines. Or, if using 8K, 6 banks, 32 lines each. Combined, colour data requires 48K of memory.
Each (x,y) coordinate pair requires 16-bits. If the upper byte is used for Y and lower for the X coordinate, together they will form exact memory location offset from the top of the first bank. But to account for bank swapping; for 16K banks, the most significant 2 bits of Y correspond to bank number and for 8K banks, top 3 bits. The rest of Y + X is memory location within the bank.
Example of filling the screen with a vertical rainbow:
1 START_16K_BANK EQU 9 2 START_8K_BANK EQU START_16K_BANK*2
3 4 ; Enable Layer 2 5 LD BC, 6 LD A, 2 7 OUT (C), A
8 9 ; Setup starting Layer2 16K bank
10 NEXTREG
11 12 LD D, 0 ; D=Y, start at top of the screen
13 14 nextY: 15 ; Calculate bank number and swap it in 16 LD A, D ; Copy current Y to A 17 AND %11100000 ; 32100000 (3MSB = bank number) 18 RLCA ; 21000003
$
123B
$
12, START_16K_BANK
58
CHAPTER 3. ZX SPECTRUM NEXT
19 RLCA ; 10000032 20 RLCA ; 00000321 21 ADD A, START_8K_BANK ; A=bank number to swap in 22 NEXTREG
23 24 ; Convert DE (yx) to screen memory location starting at 25 PUSH DE ; (DE) will be changed to bank offset 26 LD A, D ; Copy current Y to A 27 AND %00011111 ; Discard bank number 28 OR 29 LD D, A ; D=high byte for
30 31 ; Loop X through 0..255; we don’t have to deal with bank swapping 32 ; here because it only occurs when changing Y 33 LD E, 0 34 nextX: 35 LD A, E ; A=current X 36 LD (DE), A ; Use X as colour index 37 INC E ; Increment to next X 38 JR NZ, nextX ; Repeat until E rolls over
39 40 ; Continue with next line or exit 41 POP DE ; Restore DE to coordinates 42 INC D ; Increment to next Y 43 LD A, D ; A=current Y 44 CP 192 ; Did we just complete last line? 45 JP C, nextY ; No, continue with next linee
$
56, A ; Swap bank
$
C0 ; Screen starts at$C000
$
C000 screen memory
$
C000
Worth noting: MMU page 6 (next register$56) covers memory$C000 -$DFFF. As we swap different 8K banks there, we’re effectively changing 8K banks that are readable and writable at those memory addresses. That’s why we OR$C0 in line 24; we need to convert zero based address to$C000 based. See section ?? for details on MMU paging mode.
We don’t have to handle bank swapping on every iteration; once per 32 rows would do for this example. But the code is more versatile this way and could be easily converted into a reusable pixel setting routine.
59
3.5.6 320256 256 Colour Mode
LAYER 2
5 vertical banks:
8BPP:
0 319
0
7 6 5 4 3 2 1 0
I7I6I5I
4
I3I
I
2
1
I
0
Colour index
Banking Setup:
16 15 14 13 12-8 7-0
X
8K BANK 9
8
16K X
8K X
X
70
50
40
Y
Y
Y
255
16K BANK 0
8K BANK 0
16K BANK 1
8K BANK 1
8K BANK 2
16K BANK 2
8K BANK 3
8K BANK 4
16K BANK 3
8K BANK 5
8K BANK 6
16K BANK 4
8K BANK 7
8K BANK 8
16K bank contains 64 columns 8K bank contains 32 columns
320256 mode is only available on Next core 3.0.6 or later. Pixels are laid out from top to bottom and left to right. Each pixel uses one byte that represents an 8-bit index into the palette. To cover the whole screen, 5 16K banks of 64 columns or 10 8K banks of 32 columns are needed. Together colour data requires 80K of memory.
In contrast with 256192, this mode allows drawing to the whole screen, including border. In fact, you can think of it as the regular 256192 mode with additional 32 pixel border around (32 + 256 + 32 = 320 and 32 + 192 + 32 = 256).
Addressing is more complicated though. As we need 9 bits for X and 8 for Y, we can’t address all screen pixels with single 16-bit register pair. But we can use 16-bit register pair to address all pixels within each bank. From this perspective, the setup is similar to 256192 mode, except that X and Y are reversed: if the upper byte is used for X and lower for Y, then most significant 2 bits of 16-bit register pair represent lower 2 bits of 16K bank number. And for 8K banks, the most significant 3 bits correspond to the lower 3 bits of 8K bank number. In either case, the most significant bit of the bank number arrives from the 9th bit of the X coordinate (X8in the table above). The rest of the X + Y is memory location within the bank.
To use this mode, we must explicitly select it with Layer 2 Control Register also not forget to set clip window correctly with Clip Window Layer 2 Register
Window Control Register$1C, as demonstrated in example below:
1 START_16K_BANK EQU 9 2 START_8K_BANK EQU START_16K_BANK*2
3 4 RESOLUTION_X EQU 320 5 RESOLUTION_Y EQU 256
6 7 BANK_8K_SIZE EQU 8192 8 NUM_BANKS EQU RESOLUTION_X * RESOLUTION_Y / BANK_8K_SIZE 9 BANK_X EQU BANK_8K_SIZE / RESOLUTION_Y
$
70. We must
$
18 and Clip/
60
CHAPTER 3. ZX SPECTRUM NEXT
10 11 ; Enable Layer 2 12 LD BC, 13 LD A, 2 14 OUT (C), A
15 16 ; Setup starting Layer2 16K bank 17 NEXTREG 18 NEXTREG
19 20 ; Setup window clip for 320x256 resolution 21 NEXTREG 22 NEXTREG 23 NEXTREG 24 NEXTREG 25 NEXTREG
26 27 LD B, START_8K_BANK ; Bank number 28 LD H, 0 ; Colour index 29 nextBank: 30 ; Swap to next bank, exit once all 5 are done 31 LD A, B ; Copy current bank number to A 32 NEXTREG
33 34 ; Fill in current bank 35 LD DE, 36 nextY: 37 ; Fill in 256 pixels of current line 38 LD A, H ; Copy colour index to A 39 LD (DE), A ; Write colour index into memory 40 INC E ; Increment Y 41 JR NZ, nextY ; Continue with next Y until we wrap to next X
42 43 ; Prepare for next line until bank is full 44 INC H ; Increment colour 45 INC D ; Increment X 46 LD A, D ; Copy X to A 47 AND %00111111 ; Clear 48 CP BANK_X ; Did we reach next bank? 49 JP NZ, nextY ; No, continue with next Y
50 51 ; Prepare for next bank 52 INC B ; Increment to next bank 53 LD A, B ; Copy bank to A 54 CP START_8K_BANK+NUM_BANKS; Did we fill last bank? 55 JP NZ, nextBank ; No, proceed with next bank
$
123B
$
12, START_16K_BANK
$
70, %00010000 ; 320x256 256 colour mode
$
1C, 1 ; Reset Layer 2 clip window reg index
$
18, 0 ; X1; X2 next line
$
18, RESOLUTION_X / 2 - 1
$
18, 0 ; Y1; Y2 next line
$
18, RESOLUTION_Y - 1
$
56, A ; Switch to bank
$
C000 ; Prepare starting address
$
C0 to get pure X coordinate
61
3.5.7 640256 16 Colour Mode
LAYER 2
5 vertical banks:
4BPP:
0 639
0
7 6 5 4 3 2 1 0
I3I2I1I
0
I3I
I
2
1
I
0
Colour 1 Colour 2
Banking Setup:
X8
8K BANK 9
16 15 14 13 12-8 7-0
2 X
16K X
8K X
2 Y
70
50
2 Y
40
2 Y
255
16K BANK 0
8K BANK 0
16K BANK 1
8K BANK 1
8K BANK 2
16K BANK 2
8K BANK 3
8K BANK 4
16K BANK 3
8K BANK 5
8K BANK 6
16K BANK 4
8K BANK 7
8K BANK 8
16K bank contains 128 columns 8K bank contains 64 columns
640256 mode is very similar to 320256, except that each byte represents 2 colours instead of 1. It’s also available on Next core 3.0.6 or later only. Pixels are laid out from top to bottom and left to right. Each pixel takes 4 bits, so each byte contains data for 2 pixels. To cover the whole screen, 5 16K banks of 128 columns or 10 8K banks of 64 columns are needed. Together colour data requires 80K of memory. Similar to 320256, this mode also covers the whole screen, including the border.
Addressing wise, this mode is the same as 230256. Using 16-bit register pair we can’t address all pixels on the screen, but we can address all pixels within each bank. Again, assuming upper byte of 16-bit register pair is used for X and lower for Y and using 9th bit of X coordinate (bit
X8in the table above) as the most significant bit of bank number, then most significant 2 bits
of 16-bit register pair represent lower 2 bits of 16K bank number. And for 8K banks, the most significant 3 bits correspond to the lower 3 bits of 8K bank number. The rest of the X + Y is memory location within the bank. Don’t forget: each colour byte represents 2 screen pixels, so the memory X coordinate (as described above) needs to be multiplied by 2 to convert to screen X coordinate.
To use this mode, we must explicitly select it with Layer 2 Control Register also not forget to set clip window correctly with Clip Window Layer 2 Register
Window Control Register$1C, as demonstrated in example below:
1 START_16K_BANK EQU 9 2 START_8K_BANK EQU START_16K_BANK*2
3 4 RESOLUTION_X EQU 640 5 RESOLUTION_Y EQU 256
6 7 BANK_8K_SIZE EQU 8192 8 NUM_BANKS EQU RESOLUTION_X * RESOLUTION_Y / BANK_8K_SIZE / 2 9 BANK_X EQU BANK_8K_SIZE / RESOLUTION_Y
10
$
70. We must
$
18 and Clip/
62
CHAPTER 3. ZX SPECTRUM NEXT
11 ; Enable Layer 2 12 LD BC, 13 LD A, 2 14 OUT (C), A
15 16 ; Setup starting Layer2 16K bank 17 NEXTREG 18 NEXTREG
19 20 NEXTREG 21 NEXTREG 22 NEXTREG 23 NEXTREG 24 NEXTREG
25 26 LD B, START_8K_BANK ; Bank number 27 LD H, 0 ; Colour index for 2 pixels 28 nextBank: 29 ; Swap to next bank, exit once all 5 are done 30 LD A, B ; Copy current bank number to A 31 NEXTREG
32 33 ; Fill in current bank 34 LD DE, 35 nextY: 36 ; Fill in 256 pixels of current line 37 LD A, H ; Copy colour indexes for 2 pixels to A 38 LD (DE), A ; Write colour indexes into memory 39 INC E ; Increment Y 40 JR NZ, nextY ; Continue with next Y until we wrap to next X
41 42 ; Prepare for next line until bank is full 43 INC H ; Increment colour index for both colours 44 INC D ; Increment X 45 LD A, D ; Copy X to A 46 AND %00111111 ; Clear 47 CP BANK_X ; Did we reach next bank? 48 JP NZ, nextY ; No, continue with next Y
49 50 ; Prepare for next bank 51 INC B ; Increment to next bank 52 LD A, B ; Copy bank to A 53 CP START_8K_BANK+NUM_BANKS; Did we fill last bank? 54 JP NZ, nextBank ; No, proceed with next bank
$
123B
$
12, START_16K_BANK
$
70, %00100000 ; 640x256 16 colour mode
$
1C, 1 ; Reset Layer 2 clip window reg index
$
18, 0
$
18, RESOLUTION_X / 4 - 1
$
18, 0
$
18, RESOLUTION_Y - 1
$
56, A ; Switch to bank
$
C000 ; Prepare starting address
$
C0 to get pure X coordinate
63
3.5.8 Layer 2 Registers
Layer 2 Access Port$123B
Bit Effect
7-6 Video RAM bank select
00 First 16K of layer 2 in the bottom 16K 01 Second 16K of layer 2 in the bottom 16K 10 Third 16K of layer 2 in the bottom 16K 11 First 48K of layer 2 in the bottom 48K (core 3.0+)
5 Reserved, use 0 4 0 (see below) 3 Use Shadow Layer 2 for paging
0 Map Layer 2 RAM Page Register 1 Map Layer 2 RAM Shadow Page
2 Enable Layer 2 read-only paging 1 Layer 2 visible, see Layer 2 RAM Page Register
Since core 3.0 this bit has mirror in Display Control 1 Register
0 Enable Layer 2 write-only paging
$
12
$
13
LAYER 2
$
12
$
69
Since core 3.0.7, write with bit 4 set was also added:
Bit Effect
7-5 Reserved, use 0
4 1 3 Reserved, use 0
2-0 16K bank relative offset (+0..+7) applied to Layer 2 memory mapping
Layer 2 Ram Page Register$12
Bit Effect
7 Reserved, must be 0
6-0 Starting 16K bank of Layer 2
Default 256192 mode requires 3 16K banks while new, 320256 and 640256 modes require 5 16K banks. Banks need to be contiguous in memory, so here we only specify the first one. Valid bank numbers are therefore 0 - 45 (109 for 2MB RAM models) for standard mode and 0
- 43 (107 for 2MB RAM models) for new modes.
Note: this register uses 16K bank numbers. If you’re using 8K banks, you have to multiply this value by 2. For example, 16K bank 9 corresponds to 8K banks 18 and 19.
64
CHAPTER 3. ZX SPECTRUM NEXT
Layer 2 X Offset Register$16
Bit Effect
7-0 Writes or reads X pixel offset used for drawing Layer 2 graphics on the screen.
This can be used for creating scrolling effects. For 320256 and 640256 modes, 9 bits are required; use Layer 2 X Offset MSB Register
$
71 to set it up.
Layer 2 Y Offset Register$17
Bit Effect
7-0 Writes or reads Y pixel offset used for drawing Layer 2 graphics on the screen.
Valid range is:
256192: 191
320256: 255
640256: 255
Clip Window Layer 2 Register$18
Bit Effect
7-0 Reads and writes clip-window coordinates for Layer 2
4 coordinates need to be set: X1, X2, Y1 and Y2. Which coordinate gets set, is determined by index. As each write to this register will also increment index, the usual flow is to reset the index to 0 in Clip Window Control Register
$
1C, then write all 4 coordinates in succession.
Positions are inclusive. Furthermore, X positions are doubled for 320256 mode, quadrupled for 640256. Therefore, to view the whole of Layer 2, the values are:
256192 320256 640256
0 X1 position 0 0 0 1 X2 position 255 159 159 2 Y1 position 0 0 0 3 Y2 position 191 255 255
65
Clip Window Control Register$1C
Write:
Bit Effect
7-4 Reserved, must be 0
3 1 to reset Tilemap clip-window register index 2 1 to reset ULA/LoRes clip-window register index 1 1 to reset Sprite clip-window register index 0 1 to reset Layer 2 clip-window register index
Read:
Bit Effect
7-6 Current Tilemap clip-window register index 5-4 Current ULA/LoRes clip-window register index 3-2 Current Sprite clip-window register index 1-0 Current Layer 2 clip-window register index
LAYER 2
Palette Index Register$40
Palette Value Register$41
Enhanced ULA Control Register$43
Enhanced ULA Palette Extension$44
See description under Palette chapter, section ??.
Layer 2 Control Register$70
Bit Effect
7-6 Reserved, must be 0 5-4 Layer 2 resolution (0 after soft reset)
00 256192, 8BPP 01 320256, 8BPP 10 640256, 4BPP
3-0 Palette offset (0 after soft reset)
Layer 2 X Offset MSB Register$71
Bit Effect
7-1 Reserved, must be 0
0 MSB for X pixel offset
This is only used for 320256 and 640256 modes. Together with Layer 2 X Offset Register
$
16 full 319 pixels offsets are available. For 640
256 only 2 pixel offsets are possible.
66
CHAPTER 3. ZX SPECTRUM NEXT
This page intentionally left empty
67
TILEMAP
3.6 Tilemap
Tilemap is fast and effective way of displaying 8x8 pixel blocks on the screen. There are two possible resolutions available: 40x32 or 80x32 tiles. Tilemap layer overlaps ULA by 32 pixels on each side. Or in other words, similar to 320x256 and 640x256 modes of Layer 2, tilemap also covers the whole of the screen, including the border.
Tilemap is defined by 2 data structures: tile definitions and tilemap data itself.
3.6.1 Tile Definitions
Tiles are 8x8 pixels with each pixel representing an index of the colour from the currently selected tilemap palette.
Each pixel occupies 4-bits, meaning tiles can use 16 colours. However, as we’ll see in the next section, it’s possible to specify a 4-bit palette offset for each tile which allows us to reach all 256 colours from the palette.
A maximum of 256 tile definitions are possible, but this can be extended to 512 if needed using
Tilemap Control Register$6B.
All tiles definitions are specified in a contiguous memory block. The offset of tile definitions memory address relative to the start of bank 5 needs to be specified with Tile Definitions Base
Address Register$6F.
3.6.2 Tilemap Data
Tilemap data specifies the tile definition index for each of the 40x32 or 80x32 tiles. Each tile takes 2 bytes:
High Byte Low Byte
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
X Mirror
Palette Offset
Y Mirror
Rotate
ULA Mode
Tile Index
Palette Offset 4-bit palette offset for this tile. This allows shifting colours to other 16-colour
“banks” thus allowing us to reach the whole 256 colours from the palette.
X Mirror If 1, this tile will be mirrored in X direction.
Y Mirror If 1, this tile will be mirrored in Y direction.
Rotate If 1, this tile will be rotated 90oclockwise.
ULA Mode If 1, this tile will be rendered on top, if 0 below ULA display. However in
512 tile mode, this is the 8th bit of tile index.
68
CHAPTER 3. ZX SPECTRUM NEXT
Tile Index 8-bit tile index within the tile definitions.
However, it’s possible to eliminate attributes byte by setting bit 5 in Tilemap Control Register
$
6B. This only leaves an 8-bit tile index. Tileset then only occupies half the memory. But we
lose the option to specify attributes for each tile separately. Instead attributes for all tiles are taken from Default Tilemap Attribute Register
$
6C.
The offset of the tilemap data memory address relative to the start of bank 5 needs to be specified with Tilemap Base Address Register
$
6E.
3.6.3 Memory Organization
The Tilemap layer is closely tied with ULA. Memory wise, it always exists in 16K slot 5. By default, this page is loaded into 16K slot 1$4000-$7FFF (examples here will assume this configuration, if you load into a different slot, you will have to adjust addresses accordingly).
If both ULA and tilemap are used, memory should be arranged to avoid overlap. Given ULA pixel and attributes memory occupied memory addresses$4000-$5AFF, this leaves$5B00-$7FFF for tilemap. If we also take into account various system variables that reside on top of ULA attributes,$6000 should be used for starting address. This leaves us:
40x32 80x32
Bytes per tile 1 2 1 2 Bytes per tileset 1280 2560 2560 5120 Max Tile Definitions 215 175 175 95
We as programmers need to tell hardware where in the memory tilemap and tile definitions are stored. Tilemap Base Address Register
$
6F are used for that.
$
6E and Tile Definitions Base Address Register
Both addresses are provided as most significant byte of the offset into memory slot 5 (which starts at$4000). This means we can only store data at multiples of 256 bytes. For example, if data is stored at$6000, the MSB offset value would be$20 ($6000 -$4000 =$2000).
Generic formula to calculate MSB of the offset is: (Address -$4000) >> 8.
3.6.4 Combining ULA and Tilemap
ULA and Tilemap can be combined in two ways:
Standard mode: uses bit 0 from tile’s attribute byte to determine if a tile is above or below ULA. If tilemap uses 2 bytes per tile, we can specify the priority for each tile separately, otherwise we specify it for all tiles. Transparent pixels are taken into account - if the top layer is transparent, the bottom one is visible through.
Stencil mode: only used if both, ULA and tileset are enabled. The final pixel is transparent if both, ULA and tilemap pixels are transparent. Otherwise final pixel is AND of both colour bits. This mode allows one layer to act as a cut-out for the other.
69
TILEMAP
3.6.5 Examples
Using tilemaps is very simple. The most challenging part in my experience was finding a drawing program that would export to required formats in full. The best results I have achieved were with Remy’s Sprite, Tile and Palette editor website6. Even then, I had to manually tweak binary files to achieve desired results (single byte per tile).
Regardless of the editor, we need 3 pieces of data: palette, tile definitions and tileset itself. In this example, they are included as binary files:
1 tilemap: 2 INCBIN "tiles.map" 3 tilemapLength: EQU
4 5 tiles: 6 INCBIN "tiles.spr" 7 tilesLength: EQU
8
9 palette: 10 INCBIN "tiles.pal" 11 paletteLength: EQU
$
- tilemap
$
- tiles
$
-palette
With all data in place, we can start setting up tilemap:
1 START_OF_BANK_5 EQU
2 START_OF_TILEMAP EQU
3 START_OF_TILES EQU
4
5 OFFSET_OF_MAP EQU (START_OF_TILEMAP - START_OF_BANK_5) >> 8
6 OFFSET_OF_TILES EQU (START_OF_TILES - START_OF_BANK_5) >> 8
7
8 ; Enable tilemap mode
9 NEXTREG 10 NEXTREG
11 12 ; Tell hardware where to find tiles 13 NEXTREG 14 NEXTREG
$
6B, %10100001 ; 40x32, 8-bit entries
$
6C, %00000000 ; palette offset, visuals
$
6E, OFFSET_OF_MAP ; MSB of tilemap in bank 5
$
6F, OFFSET_OF_TILES ; MSB of tilemap definitions
$
4000
$
6000 ; Just after ULA attributes and system vars
$
6600 ; Just after 40x32 tilemap
Above code uses couple neat preprocessing tricks to automatically calculate MSB for tilemap and tile definitions offsets. The rest is simply setting up desired behaviour using Next registers.
6
https://zx.remysharp.com/sprites/
70
CHAPTER 3. ZX SPECTRUM NEXT
The only remaining piece is to actually copy all the data to expected memory locations:
1 ; Setup tilemap palette 2 NEXTREG
3 4 ; Copy palette 5 LD HL, palette ; Address of palette data in memory 6 LD B, 16 ; Copy 16 colours 7 CALL Copy8BitPalette ; Call routine for copying
8
9 ; Copy tile definitions to expected memory 10 LD HL, tiles ; Address of tiles in memory 11 LD BC, tilesLength ; Number of bytes to copy 12 CALL CopyTileDefinitions ; Copy all tiles data
13 14 ; Copy tilemap to expected memory 15 LD HL, tilemap ; Addreess of tilemap in memory 16 CALL CopyTileMap40x32 ; Copy 40x32 tilemaps
$
43, %00110000 ; Auto increment, select first tilemap palette
We already know Copy8BitPalette routine from Layer 2 chapter, the other two are straightforward LDIR loops:
1 CopyTileDefinitions:
2 LD DE, START_OF_TILES
3 LDIR
4 RET
5
6 CopyTileMap40x32:
7 LD BC, 40*32 ; This variant always loads 40x32
8 JR copyTileMap
9 10 CopyTileMap80x32: 11 LD BC, 80*32 ; This variant always loads 80x32
12 13 CopyTileMap: 14 LD DE, START_OF_TILEMAP 15 LDIR 16 RET
71
TILEMAP
3.6.6 Tilemap Registers
Sprite and Layers System Register$15
Bit Effect
7 1 to enable lo-res layer, 0 disable it 6 1 to flip sprite rendering priority, i.e. sprite 0 is on top (0 after reset) 5 1 to change clipping to “over border” mode (doubling X-axis coordinates of clip window,
0 after reset)
4-2 Layers priority and mixing
000 S L U (Sprites are at top, Layer 2 under, Enhanced ULA at bottom) 001 L S U 010 S U L 011 L U S 100 U S L 101 U L S 110 Core 3.1.1+: (U|T)S(T|U)(B+L) blending layer and Layer 2 combined
Older cores: S(U+L) colours from ULA and L2 added per R/G/B channel
111 Core 3.1.1+: (U|T)S(T|U)(B+L-5) blending layer and Layer 2 combined
Older cores: S(U+L-5) similar as 110, but per R/G/B channel (U+L-5) 110 and 111 modes: colours are clamped to [0,7]
1 1 to enable sprites over border (0 after reset) 0 1 to enable sprite visibility (0 after reset)
Clip Window Tilemap Register$1B
Bit Effect
7-0 Reads and writes clip-window coordinates for Tilemap
4 coordinates need to be set: X1, X2, Y1 and Y2. Tilemap will only be visible within these coordinates. X coordinates are internally doubled for 40x32 or quadrupled for 80x32 mode. Positions are inclusive. Default values are 0, 159, 0, 255. Origin (0,0) is located 32 pixels to the top-left of ULA top-left coordinate.
Which coordinate gets set, is determined by index. As each write to this register will also increment index, the usual flow is to reset the index to 0 in Clip Window Control Register
$
1C, then write all 4 coordinates in succession.
Clip Window Control Register$1C
See description under Layer 2 chapter, section ??.
72
CHAPTER 3. ZX SPECTRUM NEXT
Tilemap Offset X MSB Register$2F
Bit Effect
7-2 Reserved, use 0 1-0 Most significant bit(s) of X offset
In 40x32 mode, meaningful range is 0-319, for 80x32 0-639. Low 8-bits are stored in Tilemap
Offset X LSB Register$30.
Tilemap Offset X LSB Register$30
Bit Effect
7-0 X offset for drawing tilemap in pixels
Tilemap X offset in pixels. Meaningful range is 0-319 for 40x32 and 0-639 for 80x32 mode. To write values larger than 255, Tilemap Offset X MSB Register
$
2F is used to store MSB.
Tilemap Offset Y Register$31
Bit Effect
7-0 Y offset for drawing tilemap in pixels
Y offset is 0-255.
Palette Index Register$40
Palette Value Register$41
Enhanced ULA Control Register$43
Enhanced ULA Palette Extension$44
See description under Palette chapter, section ??.
Tilemap Transparency Index Register$4C
Bit Effect
7-5 Reserved, must be 0 4-0 Index of transparent colour into tilemap palette
The pixel index from tile definitions is compared before palette offset is applied to the upper 4 bits, so there’s always one index between 0 and 15 that works as transparent colour.
73
ULA Control Register$68
Bit Effect
7 1 to disable ULA output (0 after soft reset)
6-5 (Core 3.1.1+) Blending in SLU modes 6 & 7
00 ULA as blend colour 01 No blending 10 ULA/tilemap as blend colour 11 Tilemap as blend colour
4 (Core 3.1.4+) Cancel entries in 8x5 matrix for extended keys 3 1 to enable ULA+ (0 after soft reset) 2 1 to enable ULA half pixel scroll (0 after soft reset) 1 Reserved, set to 0 0 1 to enable stencil mode when both the ULA and tilemap are enabled.
TILEMAP
See Sprite and Layers System Register
$
15 for different priorities and mixing of ULA, Layer
2 and Sprites.
Tilemap Control Register$6B
Bit Effect
7 1 to enable tilemap, 0 disable tilemap 6 1 for 80x32, 0 40x32 mode 5 1 to eliminate attribute byte in tilemap 4 1 for second, 0 for first tilemap palette 3 1 to activate “text mode”
1
2 Reserved, set to 0 1 1 to activate 512, 0 for 256 tile mode 0 1 to force tilemap on top of ULA
1
In the text mode, tiles are defined as 1-bit B&W bitmaps, same as original Spectrum UDGs. Each tile only requires 8 bytes. In this mode, the tilemap attribute byte is also interpreted differently: bit 0 is still ULA over Tilemap (or 9th bit of tile data index) but the top 7 bits are extended palette offset (the least significant bit is the value of the pixel itself). In this mode, transparency is checked against Global Transparency Register
$
14 colour, not against the
four-bit tilemap colour index.
74
CHAPTER 3. ZX SPECTRUM NEXT
Default Tilemap Attribute Register$6C
If single byte tilemap mode is selected (bit 5 of Tilemap Control Register defines attributes for all tiles.
Bit Effect
7-4 Palette offset
3 1 to mirror tiles in X direction 2 1 to mirror tiles in Y direction 1 1 rotate tiles 90oclockwise 0 In 512 tile mode, bit 8 of tile index
1 for ULA over tilemap, 0 for tilemap over ULA
Tilemap Base Address Register$6E
Bit Effect
7-6 Ignored, set to 0 5-0 Most significant byte of tilemap data offset in bank 5
Tile Definitions Base Address Register$6F
$
6B set), this register
Bit Effect
7-6 Ignored, set to 0 5-0 Most significant byte of tile definitions offset in bank 5
75
SPRITES
3.7 Sprites
One of the frequently used “my computer is better” arguments from owners and developers of contemporary systems such as Commodore 64 was hardware supported sprites. To be fair, they had a point - poor old Speccy had none. But Next finally rectifies this with a sprite system that far supersedes even later 16-bit era machines such as Amiga. And as we’ll see, it’s really simple to program too!
Some of the capabilities of Next sprites:
128 simultaneous sprites
16x16 pixels per sprite
Magnification of 2x, 4x or 8x horizontally and vertically
Mirroring and rotation
Sprite grouping to form larger objects
512 colours from 2 256 colour palettes
Per sprite palette
Built-in sprite editor
So lots of reasons to get excited! Let’s dig in!
3.7.1 Editing
Before describing how sprites hardware works, it would be beneficial to know how to draw them. As mentioned, Next comes with a built-in sprite editor. To use it, change to desired folder, then enter .spredit <filename> in BASIC or command line. The editor is quite capable and can even be used with a mouse if you have one attached to your Next (or in the emulator). Alternatively, if you’re developing cross-platform, you can download UDGeed-Next7or use Remy’s Sprite, Tile and Palette editor8. They all share very similar feature sets, so try them out and decide for yourself.
3.7.2 Patterns
Next sprites have a fixed size of 16x16 pixels. Their display surface is 320x256, overlapping the ULA by 32 pixels on each side. Or in other words, to draw the sprite fully on-screen, we need to position it to (32,32) coordinate. And the last coordinate where the sprite is fully visible at the bottom-right edge is (271,207). This allows sprites to be animated in and out of the visible area. Sprites can be made visible or invisible when over the border as well as rendered on top or below Layer 2 and ULA, all specified by Sprite and Layers System Register possible to further restrict sprite visibility within provided clip window using Clip Window
Sprites Register$19.
$
15. It’s also
7
http://zxbasic.uk/files/UDGeedNext-current.rar
8
https://zx.remysharp.com/sprites/
76
CHAPTER 3. ZX SPECTRUM NEXT
Sprite patterns (or pixel data) are stored in Next FPGA internal 16K memory. As mentioned, sprites are always 16x16 pixels but can be 8-bit or 4-bit.
8-bit sprites use full 8-bits to specify colour, so each pixel can be of any of 256 colours from the sprite palette of which one acts as transparent. Hence each sprite occupies 256 bytes of memory and 64 sprites can be stored.
4-bit sprites use only 4-bits for colour, so each pixel can only choose from 16 colours, one of which is reserved for transparency. However this allows us to store 2 colours per byte, so these sprites take half the memory of 8-bit ones: 128 bytes each, meaning 128 sprites can be stored in available memory.
3.7.3 Palette
Each sprite can specify its own palette offset. This allows sprites to share image data but use different colours. 4 bits are used for palette offset, therefore the final colour index within the current sprite palette (as defined by Enhanced ULA Control Register using the following formula:
$
43) is determined
8-bit sprites
7 6 5 4 3 2 1 0
P3P2P1P
+ S7S6S5S4S3S2S1S = C7C6C5C4C3C2C1C
0 0 0 0
0
0
0
If default palette offset and default palette are used, sprite colour index can be interpretted as
4-bit sprites
7 6 5 4 3 2 1 0
P3P2P1P
0 0 0 0
0
+ 0 0 0 0 S3S2S1S = C7C6C5C4C3C2C1C
Palette offset can be thought of as if selecting one of 16 different 16-colour palettes.
RGB332 colour.
Pnis palette offset bit, Snsprite colour index bit and Cnfinal colour index.
Transparent colour is defined with Sprites Transparency Index Register
$
0
0
4B.
77
SPRITES
3.7.4 Combined Sprites
Anchor Sprites
These are “normal” 16x16 pixel sprites, as described in previous sections. They act as standalone sprites.
The reason they are called “anchors” is because multiple sprites can be grouped together to form larger sprites. In such case “anchor” acts as a parent and all its “relative” sprites are tied to it. In order to combine sprites, anchor needs to be defined first, immediately followed by all its relative sprites. The group ends with the next anchor sprite which can either be another standalone sprite, or an anchor for another sprite group. For example, if sprite 5 is setup as an anchor, its relative sprites must be followed at 6, 7, 8... until another sprite that’s setup as “anchor”.
There are 2 types of relative sprites: composite and unified sprites.
Composite Relative Sprites
Composite sprites inherit certain attributes from their anchor.
Inherited attributes:
Visibility
X
Y
Palette offset
Pattern number
4 or 8-bit pattern
NOT inherited:
Rotation
X & Y mirroring
X & Y scaling
Relative sprites only have 8-bits for X and Y coordinates (ninth bits are used for other purposes). But as the name suggests, these coordinates are relative to their parent anchor sprite so they are usually positioned close by. When the anchor sprite is moved to a different position on the screen, all its relatives are also moved by the same amount.
Visibility of relative sprites is determined as AND between anchor visibility and relative sprite visibility. This way individual relative sprites can be made invisible independently from their anchor, but if the anchor is invisible, then all its relative sprites will also be invisible.
Relative sprites inherit 4 or 8-bit setup from their anchor. They can’t use a different type but can use a different palette offset than its anchor.
It’s also possible to tie relative sprite’s pattern number to act as an offset on top of its anchor’s pattern number and thus easily animate the whole sprite group simply by changing the anchor’s pattern number.
78
CHAPTER 3. ZX SPECTRUM NEXT
Unified Relative Sprites
Unified relative sprites are an extension of the composite type. Everything described above applies here as well.
The main difference is the hardware will automatically adjust relative sprites X, Y, rotation, mirroring and scaling attributes according to changes in anchor. So relatives will rotate, mirror and scale around the anchor as if it was a single larger sprite.
3.7.5 Attributes
Attributes are 4 or 5 bytes that define where and how the sprite is drawn. The data can be set either by selecting sprite index with Sprite Status/Slot Select sending bytes to Sprite Attribute Upload
$
xx57 (which automatically increments sprite index
after all data for single sprite is transferred) or by calling individual direct access Next registers
$
35-$39 or their auto-increment variants$75-$79. See registers section for a description of
individual bytes:
$
303B and then continuously
Byte 0: Sprite port-mirror Attribute 0 Register
Byte 1: Sprite port-mirror Attribute 1 Register
Byte 2: Sprite port-mirror Attribute 1 Register
Byte 3: Sprite port-mirror Attribute 1 Register
Byte 4: Sprite port-mirror Attribute 1 Register
$
35
$
36
$
37
$
38
$
39
3.7.6 Examples
Reading about sprites may seem complicated, but in practice, it’s quite simple. The following pages include sample code for working with sprites. To preserve space, only partial code demonstrating relevant parts is included. You can find full source code on GitHub https: //github.com/tomaz/zx-next-dev-guide.
79
SPRITES
Loading Patterns into FPGA Memory
Before we can use sprites, we need to load their data into FPGA memory. This example introduces a generic routine that uses DMA9to copy from given memory to FPGA. Don’t worry if it seems like magic - it’s implemented as a reusable routine, just copy it to your project. Routine requires 3 parameters:
HL Source address of sprites to copy from
BC Number of bytes to copy
A Starting sprite number to copy to
1 LoadSprites: 2 LD BC, 3 OUT (C), A ; Load index of first sprite 4 LD (.dmaSource), HL ; Copy sprite sheet address from HL 5 LD (.dmaLength), BC ; Copy length in bytes from BC 6 LD HL, .dmaCode ; Setup source for OTIR 7 LD B, .dmaCodeLength ; Setup length for OTIR 8 LD C,
9 OTIR ; Invoke DMA code 10 RET 11 .dmaCode: 12 DB %10000011 ; Disable DMA 13 DB %01111101 ; WR0 transfer mode, A->B, write adress + block length 14 .dmaSource: 15 DW 0 ; WR0 port A, source address 16 .dmaLength: 17 DW 0 ; WR0 block length in bytes 18 DB %01010100 ; WR1 read A, increment, to memory, bitmaks 19 DB %00000010 ; WR1 cycle port A length 20 DB %01101000 ; WR2 write B, port B address fixed, B is IO 21 DB %00000010 ; WR2 cycle length B 22 DB %10101101 ; WR4 continuous mode, write destination address 23 DW 24 DB %10000010 ; WR5 restart on end of block 25 DB %11001111 ; WR6 load 26 DB %10000111 ; WR6 enable DMA 27 .dmaCodeLength: EQU
$
303B ; Prepare port for sprite index
$
6B ; Setup DMA port
$
5B ; Sprite image port$xx5B
$
-.dmaCode
Perhaps worth noting: routine uses a technique called “self-modifying code”. As the name suggests, this means that the program modifies itself in RAM. In this case it modifies 2 addresses “marked” by .dmaSource and .dmaLength labels. But it’s also possible to modify opcodes (in this case NOPs are frequently used as placeholders). Either way, careful planning is required to avoid writing over undesired parts.
And secondly, note the use of a dot in front of some labels. Many assemblers allow this notation for local labels, only “visible” to code between 2 normal labels (without dot prefix).
9
https://wiki.specnext.dev/DMA
80
CHAPTER 3. ZX SPECTRUM NEXT
Loading Sprites
Using loadSprites routine is very simple. This example assumes you’ve edited sprites with one of the editors and saved them as sprites.spr file in the same folder as the assembler code:
1 LD HL, sprites ; Sprites data source 2 LD BC, 16*16*5 ; Copy 5 sprites, each 16x16 pixels 3 LD A, 0 ; Start with first sprite 4 CALL LoadSprites ; Load sprites to FPGA
5 6 sprites: 7 INCBIN "sprites.spr" ; Sprite sheets file
Enabling Sprites
After sprites are loaded into FPGA memory, we need to enable them:
1 NEXTREG
$
15, %01000001 ; Sprite 0 on top, SLU, sprites visible
Displaying a Sprite
Sprites are now loaded into FPGA memory, they are enabled, so we can start displaying them. This example displays the same sprite pattern twice, as two separate sprites:
1 NEXTREG 2 NEXTREG 3 NEXTREG 4 NEXTREG 5 NEXTREG
6 7 NEXTREG 8 NEXTREG
9 NEXTREG 10 NEXTREG 11 NEXTREG
$
34, 0 ; First sprite
$
35, 100 ; X=100
$
36, 80 ; Y=80
$
37, %00000000 ; Palette offset, no mirror, no rotation
$
38, %10000000 ; Visible, no byte 4, pattern 0
$
34, 1 ; Second sprite
$
35, 86 ; X=86
$
36, 80 ; Y=80
$
37, %00000000 ; Palette offset, no mirror, no rotation
$
38, %10000000 ; Visible, no byte 4, pattern 0
81
SPRITES
Displaying Combined Sprites
Even handling combined sprites is much simpler in practice than in theory! This example combines 4 sprites into a single one using unified relative sprites. Note use of “inc” register$79 which auto-increments sprite index for next sprite:
1 NEXTREG 2 NEXTREG 3 NEXTREG 4 NEXTREG 5 NEXTREG 6 NEXTREG
7 8 NEXTREG
9 NEXTREG 10 NEXTREG 11 NEXTREG 12 NEXTREG
13 14 NEXTREG 15 NEXTREG 16 NEXTREG 17 NEXTREG 18 NEXTREG
19 20 NEXTREG 21 NEXTREG 22 NEXTREG 23 NEXTREG 24 NEXTREG
$
34, 2 ; Select third sprite
$
35, 150 ; X=150
$
36, 80 ; Y=80
$
37, %00000000 ; Palette offset, no mirror, no rotation
$
38, %11000001 ; Visible, use byte 4, pattern 1
$
79, %00100000 ; Anchor with unified relatives, no scaling
$
35, 16 ; X=AnchorX+16
$
36, 0 ; Y=AnchorY+0
$
37, %00000000 ; Palette offset, no mirror, no rotation
$
38, %11000010 ; Visible, use byte 4, pattern 2
$
79, %01000000 ; Relative sprite
$
35, 0 ; X=AnchorX+0
$
36, 16 ; Y=AnchorY+16
$
37, %00000000 ; Palette offset, no mirror, no rotation
$
38, %11000011 ; Visible, use byte 4, pattern 3
$
79, %01000000 ; Relative sprite
$
35, 16 ; X=AnchorX+16
$
36, 16 ; Y=AnchorY+16
$
37, %00000000 ; Palette offset, no mirror, no rotation
$
38, %11000100 ; Visible, use byte 4, pattern 4
$
79, %01000000 ; Relative sprite
Because we use combined sprite, we only need to update the anchor to change all its relatives. And because we set it up as unified relative sprites, even rotation, mirroring and scaling is inherited as if it was a single sprite!
1 NEXTREG
2 NEXTREG
3 NEXTREG
4 NEXTREG
5 NEXTREG
6 NEXTREG
$
34, 1 ; Select second sprite
$
35, 200 ; X=200
$
36, 100 ; Y=100
$
37, %00001010 ; Palette offset, mirror X, rotate
$
38, %11000001 ; Visible, use byte 4, pattern 1
$
39, %00101010 ; Anchor with unified relatives, scale X$Y
82
CHAPTER 3. ZX SPECTRUM NEXT
3.7.7 Sprite Registers
Sprite Status/Slot Select$303B
Write: sets active sprite attribute and pattern slot index used by Sprite Attribute Upload
$
xx57 and Sprite Pattern Upload$xx5B.
Bit Effect
7 Set to 1 to offset reads and writes by 128 bytes
6-0 0-63 for pattern slots and 0-127 for attribute slots
Read: returns sprite status information
Bit Effect
7-2 Reserved
1 1 if sprite renderer was not able to render all sprites; read will reset to 0 0 1 when collision between any 2 sprites occurred; read will reset to 0
Sprite Attribute Upload$xx57
Uploads the attributes for the currently selected sprite slot. Attributes require 4 or 5 bytes. After all bytes are sent, the sprite index slot automatically increments. See the following Next registers that directly set the value for specific bytes:
Byte 0: Sprite port-mirror Attribute 0 Register
Byte 1: Sprite port-mirror Attribute 1 Register
Byte 2: Sprite port-mirror Attribute 1 Register
Byte 3: Sprite port-mirror Attribute 1 Register
Byte 4: Sprite port-mirror Attribute 1 Register
$
35
$
36
$
37
$
38
$
39
Sprite Pattern Upload$xx5B
Uploads sprite pattern data. 256 bytes are needed for each sprite. For 8-bit sprites, each pattern slot contains a single sprite. For 4-bit sprites, it contains 2 128 byte sprites. After 256 bytes are sent, the target pattern slot is auto-incremented.
Bit Effect
7-0 Next byte of pattern data for current sprite
83
SPRITES
Peripheral 4 Register$09
Bit Effect
7 1 to enable AY2 “mono” output (A+B+C is sent to both R and L channels, makes it
a bit louder than stereo mode)
6 1 to enable AY1 “mono” output, 0 default 5 1 to enable AY0 “mono” output (0 after hard reset) 4 1 to lockstep Sprite port-mirror Index Register
$
303B
$
34 and Sprite Status/Slot Select
3 1 to reset mapram bit in DivMMC 2 1 to silence HDMI audio (0 after hard reset) (since core 3.0.5)
1-0 Scanlines weight (0 after hard reset)
Core 3.1.1+ Older cores
00 Scanlines off Scalines off 01 Scanlines 50% Scanlines 75% 10 Scanlines 50% Scanlines 25% 11 Scanlines 25% Scanlines 12.5%
Sprite and Layers System Register$15
See description under Tilemap chapter, section ??.
Clip Window Sprites Register$19
Bit Effect
7-0 Reads or writes clip-window coordinates for Sprites
4 coordinates need to be set: X1, X2, Y1 and Y2. Sprites will only be visible within these coordinates. Positions are inclusive. Default values are 0, 255, 0, 191. Origin (0,0) is located 32 pixels to the top-left of ULA top-left coordinate.
Which coordinate gets set, is determined by index. As each write to this register will also increment index, the usual flow is to reset the index to 0 with Clip Window Control Register
$
1C, then write all 4 coordinates in succession.
When “over border” mode is enabled (bit 1 of Sprite and Layers System Register
$
15), X
coordinates are doubled internally.
Clip Window Control Register$1C
See description under Layer 2 chapter, section ??.
Sprite Port-Mirror Index Register$34
If sprite id lockstep in Peripheral 4 Register effect as writing to Sprite Status/Slot Select
$
09 is enabled, write to this registers has same
$
303B.
84
CHAPTER 3. ZX SPECTRUM NEXT
Bit Effect
7 Set to 1 to offset reads and writes by 128 bytes
6-0 0-63 for pattern slots and 0-127 for attribute slots
Sprite port-mirror Attribute 0 Register$35
Bit Effect
7-0 Low 8 bits of X position
Sprite port-mirror Attribute 1 Register$36
Bit Effect
7-0 Low 8 bits of Y position
Sprite port-mirror Attribute 2 Register$37
Bit Effect
7-4 Palette offset
3 1 to enable X mirroring, 0 to disable 2 1 to enable Y mirroring, 0 to disable 1 1 to rotate sprite 90oclockwise, 0 to disable 0 Anchor sprite: most significant bit of X coordinate
Relative sprite: 1 to add anchor palette offset, 0 to use independent palette offset
Sprite port-mirror Attribute 3 Register$38
Bit Effect
7 1 to make sprite visible, 0 to hide it 6 1 to enable optional byte 4, 0 to disable it
5-0 Pattern index 0-63 (7th, MSB for 4-bit sprites is configured with byte 4)
85
SPRITES
Sprite port-mirror Attribute 4 Register$39
For anchor sprites:
Bit Effect
7-6 H+N6 where H is 4/8-bit data selector and N6 is sub-pattern selector for 4-bit sprites
00 Anchor sprite, 8-bit 10 Anchor sprite, 4-bit using bytes 0-127 of pattern slot 11 Anchor sprite, 4-bit using bytes 128-255 of pattern slot
5 0 if this anchor’s relative sprites are composite, 1 for unified sprite
4-3 X axis scale factor
00 1x 01 2x 10 4x 11 8x
2-1 Y axis scale factor, see above
0 Most significant bit of Y coordinate
For composite relative sprites:
Bit Effect
7-6 01 needs to be used for relative sprites
5 4-bit mode: N6, 1 to use bytes 0-127, 0 to use bytes 128-255 of pattern slot
8-bit mode: not used, set to 0 4-3 X axis scale factor, see below 2-1 Y axis scale factor, see below
0 1 to enable relative pattern offset, 0 to use independent pattern index
For unified relative sprites
Bit Effect
7-6 01 needs to be used for relative sprites
5 4-bit mode: N6, 1 to use bytes 0-127, 0 to use bytes 128-255 of pattern slot
8-bit mode: not used, set to 0 4-1 Set to 0; scaling is defined by anchor sprite
0 1 to enable relative pattern offset, 0 to use independent pattern index
86
CHAPTER 3. ZX SPECTRUM NEXT
Palette Index Register$40
Palette Value Register$41
Enhanced ULA Control Register$43
Enhanced ULA Palette Extension$44
See description under Palette chapter, section ??.
Sprites Transparency Index Register$4B
Bit Effect
7-0 Sets index of transparent colour inside sprites palette.
For 4-bit sprites, low 4 bits of this register are used.
Sprite Port-Mirror Attribute N (With Inc) Register$75-$79
This set of registers work the same as their non-inc counterpart in$35-$39; writes byte 0-4 of Sprite attributes for currently selected sprite, except$7X variants also increment Sprite Port-
Mirror Index Register$34 after write. When batch updating multiple sprites, typically the
first sprite is selected explicitly, then$3X registers are used until the last write, which occurs through$7X register. This way we’ll also increment the sprite index for the next iteration.
87
SOUND
3.8 Sound
Next inherits the same 3 AY-3-8912 chips setup as used in 128K Spectrums. This allows us to reuse many of the pre-existing applications and routines to play sound effects and music.
3.8.1 AY Chip Registers
AY chip has 3 sound channels, called A, B and C. Combined with 3 chips, this allows us to produce 9 channel music. Programming wise, each of the 3 chips needs to be selected first via Turbo Sound Next Control through Peripheral 3 Register
AY chip is controlled by 14 internal registers. To program them, we first need to select the register with Turbo Sound Next Control
Register Write$BFFD.
$
FFFD register. Afterwards, we can set various parameters
$
08 and Peripheral 4 Register$09.
$
FFFD and then write the value with Sound Chip
3.8.2 Editing and Players
Several applications can produce sounds or music compatible with the AY chip. For sounds, Shiru’s AYFX Player10can be used. This program also includes a Z80 native player that can directly load and play sound effects. Alternatively, Remy’s AY audio generator website11can produce exactly the same results and is fully compatible with AYFX Player.
A different way of playing sounds is to convert the WAV file into 1, 2 or 4-bit per sample sound with the ChibiWave application. Sounds take a bit more memory this way but are much easier to create. You can find the application, as well as tutorial and playback source code on Chibi Akumas website12. While there, definitely check other tutorials too - they’re all high quality and available as both, written posts and YouTube videos.
For creating music there are also several options. NextDAW13is native composer that runs on ZX Spectrum Next itself. Or if you prefer cross-platform, Arkos Tracker14or Vortex Tracker should do the job. All include “drivers”; Z80 code you can include in your program that can load and play created music.
10
https://shiru.untergrund.net/software.shtml#old
11
https://zx.remysharp.com/audio/
12
https://www.chibiakumas.com/z80/platform4.php#LessonP35
13
https://nextdaw.biasillo.com/
14
https://www.julien-nevo.com/arkostracker/
15
https://bulba.untergrund.net/vortex e.htm
15
88
CHAPTER 3. ZX SPECTRUM NEXT
3.8.3 Examples
Before we can start playing sounds, we need to enable the sound hardware. While this is usually enabled by default, it’s nonetheless a good idea to ensure our program will always run under the same conditions.
1 ; Setup Turbo Sound chip 2 LD BC, 3 LD A, %11111101 ; Enable left+right audio, select AY1 4 OUT (C), A
5 6 ; Setup mapping of chip channels to stereo channels 7 NEXTREG 8 NEXTREG
Programming AY consists of writing various values to its registers. As mentioned, this is a two­step process: first select register number, then write the value. Multiple writes are required for each tone to set period, volume etc. To make it simpler, I created a subroutine. It takes 2 parameters: A for register number (0-13) and D with value to write.
$
FFFD ; Turbo Sound Next Control Register
$
08, %00010010 ; Use ABC, enable internal speaker$turbosound
$
09, %11100000 ; Enable mono for AY1-3
1 WriteDToAYReg: 2 ; Select desired register 3 LD BC, 4 OUT (C), A
5 6 ; Write given value 7 LD A, D 8 LD BC, 9 OUT (C), A
10 11 RET
$
FFFD
$
BFFD
Companion code on GitHub includes expanded code as well as a simple player that plays multiple tones in sequence. For the purposes of this book, I used Remy’s AY audio generator website to load one of the example effects, then manually copied raw values into the source code. Laborious process to say the least - this is not how effects should be handled in real life. But I wanted to learn and demonstrate how to program AY chip, not how to use ready-made drivers to play effects or music. Furthermore, my “player” blocks the main loop; ideally, sound effects and music would play on the interrupt handler. This could be a nice homework for the reader - example in section ?? should give you an idea of how to achieve this - happy coding!
89
3.8.4 Sound Registers
Turbo Sound Next Control$FFFD
When bit 7 is 1:
Bit Effect
7 1 6 1 to enable left audio
5 1 to enable right audio 4-2 Must be 1 1-0 Selects active chip:
00 Unused 01 AY3 10 AY2 11 AY1
When bit 7 is 0:
SOUND
Bit Effect
7 0 6-0 Selects given AY register number for read or write from active sound chip
Sound Chip Register Write$BFFD
Bit Effect
7-0 Writes given value to currently selected register:
0 - Channel A tone, low byte
7 6 5 4 3 2 1 0
A tone
2 - Channel B tone, low byte
7 6 5 4 3 2 1 0
B tone
4 - Channel C tone, low byte
1 - Channel A tone, high 4-bits
7 6 5 4 3 2 1 0
0 0 0 0 A tone high
3 - Channel B tone, high 4-bits
7 6 5 4 3 2 1 0
0 0 0 0 B tone high
5 - Channel C tone, high 4-bits
7 6 5 4 3 2 1 0
C tone
7 6 5 4 3 2 1 0
0 0 0 0 C tone high
90
CHAPTER 3. ZX SPECTRUM NEXT
6 - Noise period
7 6 5 4 3 2 1 0
0 0 0 Noise Period
8 - Channel A volume/envelope
7 6 5 4 3 2 1 0
0 0 0 0 A Volume
10 - Channel C volume/envelope
7 6 5 4 3 2 1 0
0 0 0 0 C Volume
11 - Envelope period fine
7 6 5 4 3 2 1 0
Envelope bits 7-0
13 - Envelope shape
7 - Flags
7 6 5 4 3 2 1 0
0 0 C B A C B A
0 0 Noise Tone
9 - Channel B volume/envelope
7 6 5 4 3 2 1 0
0 0 0 0 B Volume
Note: Registers 8-10 work as volume control if bit 4 is 0, otherwise envelop generator is used (see registers 11-13). In this case bits 3-0 are ignored.
12 - Envelope period coarse
7 6 5 4 3 2 1 0
Envelope bits 15-8
7 6 5 4 3 2 1 0
0 0 0 0 C AtAlH
H “Hold”
1 envelope generator performs 1 cycle then holds the end value 0 cycles continuously
Al“Alternate”
If “hold” set
1 the value held is initial value 0 the value held is the final value
If “hold” not set
1 envelope generator alters direction after each cycle 0 resets after each cycle
At“Attack”
1 the generator counts up 0 the generator counts down
C “Continue”
1 “hold” is followed 0 the envelope generator performs one cycle then drops volume to 0 and
stays there, overriding “hold”
91
Peripheral 2 Register$06
Bit Effect
7 1 to enable CPU speed mode key ”F8”, 0 to disable (1 after soft reset)
6 Core 3.1.2+: Divert BEEP-only to internal speaker (0 after hard reset)
Pre core 3.1.2: DMA mode, 0 zxnDMA, 1 Z80 DMA (0 after hard reset)
5 Core 2.0+: 1 to enable ”F3” key (50/60 Hz switch) (1 after soft reset)
Pre core 2.0: ”Enable Lightpen”
4 1 to enable DivMMC automap and DivMMC NMI by DRIVE button (0 after hard
reset)
3 1 to enable multiface NMI by M1 button (0 after hard reset)
2 1 to set primary device to mouse in PS/2 mode, 0 to set to keyboard 1-0 Audio chip mode:
00 YM 01 AY 10 Disabled 11 Core 3.0+: Hold all AY in reset
SOUND
Peripheral 3 Register$08
Bit Effect
7 1 unlock / 0 lock port$7FFD paging
6 1 to disable RAM and I/O port contention (0 after soft reset)
5 AY stereo mode (0 = ABC, 1 = ACB) (0 after hard reset)
4 Enable internal speaker (1 after hard reset)
3 Enable 8-bit DACs (A,B,C,D) (0 after hard reset)
2 Enable port$FF Timex video mode read (0 after hard reset)
1 Enable Turbosound (currently selected AY is frozen when disabled) (0 after hard reset)
0 Implement Issue 2 keyboard (port$FE reads as early ZX boards) (0 after hard reset)
Peripheral 4 Register$09
See description under Sprite chapter, section ??.
92
CHAPTER 3. ZX SPECTRUM NEXT
This page intentionally left empty
93
KEYBOARD
3.9 Keyboard
Next inherits ZX Spectrum keyboard handling, so all legacy programs will work out of the box. Additionally, it allows reading the status of extended keys.
3.9.1 Legacy Keyboard Status
ZX Spectrum uses 85 matrix for reading keyboard status. This means 40 distinct keys can be represented. The keyboard is read from ULA Control Port There are 8 possible bytes, each will return the status of 5 associated keys. If a key is pressed, the corresponding bit is set to 0 and vice versa.
Example for checking if P or I is pressed:
$
xxFE with particular high bytes.
1 LD BC, 2 IN A, (C) ; A holds values in bits... 43210 3 checkP: 4 BIT 0, A ; test bit 0 of A (P key) 5 JR NZ checkI ; if bit0=1, P not pressed 6 ... ; P is pressed 7 checkI: 8 BIT 2, A ; test bit 2 of A (I key)
9 JR NZ continue ; if bit2=1, I not pressed 10 ... ; I is pressed 11 continue:
$
DFFE ; We want to read keys..... YUIOP
As mentioned in Ports chapter, section ??, we can slightly improve performance if we replace first two lines with:
1 LD A,
2 IN (
$
$
FE)
DF
Reading the port in first example requires 22 t-states (10+12) vs. 18 (7+11). The difference is small, but it can add up as typically keyboard is read multiple times per frame.
The first program is more understandable at a glance - the port address is given as a whole 16­bit value, as usually provided in the documentation. The second program splits it into 2 8-bit values, so intent may not be immediately apparent. Of course, one learns the patterns with experience, but it nonetheless demonstrates the compromise between readability and speed.
3.9.2 Next Extended Keys
Next uses larger 87 matrix for keyboard, with 10 additional keys. By default, hardware is translating keys from extra two columns into the existing 85 set. But you can turn this off with bit 4 of ULA Control Register
Keys 0 Register$B0 and Extended Keys 1 Register$B1.
$
68. Extra keys can be read separately via Extended
94
CHAPTER 3. ZX SPECTRUM NEXT
3.9.3 Keyboard Registers
ULA Control Port$xxFE
Returns keyboard status when read with certain high byte values:
xx
$
7F B N M Symb Space
$
BF H J K L Enter
$
DF Y U I O P
$
EF 6 7 8 9 0
$
F7 5 4 3 2 1
$
FB T R E W Q
$
FD G F D S A
$
FE V C X Z Caps
4 3 2 1 0
Bits are reversed: if a key is pressed, the corresponding bit is 0, if a key is not pressed, bit is 1.
Note: when written to, ULA Control Port
$
xxFE is used to set border colour and audio devices.
See description under ULA chapter, section ?? for details.
ULA Control Register$68
See description under Tilemap chapter, section ??.
Extended Keys Registers 0 ($B0) & 1 ($B1)
Bit Effect
$
B0
$
B1
7 0 if key pressed, 1 otherwise ; Delete 6 0 if key pressed, 1 otherwise " Edit 5 0 if key pressed, 1 otherwise , Break 4 0 if key pressed, 1 otherwise . Inv Video 3 0 if key pressed, 1 otherwise Up True Video 2 0 if key pressed, 1 otherwise Down Graph 1 0 if key pressed, 1 otherwise Left Caps Lock 0 0 if key pressed, 1 otherwise Right Extend
Available since core 3.1.5
95
Loading...