When studying Z80 programming, we usually learn that is better to use relative jumps (JR) in loops since they save us a byte in code compared to absolute jumps (JP).
Well, that’s certainly the case, but when you are squeezing out cycles from code, one place to look at is the loops you have.
A conditional relative jump wastes 12 cycles (t-states) when the condition is not met, and 7 cycles otherwise:
JR NZ, n -> 12T (unmet condition) / 7T (met condition)
Conversely, a conditional absolute jump always wastes 10 cycles, regardless the condition is met or not*:
JP NZ, nn -> 10T
Since a loop has n-1 iterations where the condition is not met, we can save 2 cycles per iteration just by changing a JR instruction for the corresponding JP one, paying just a one-byte penalty.
If your code has many loops with a big number of iterations each, changing the jump instructions may speed up a little your program.
* A conditional absolute jump always takes 10 T-states to execute because when the condition is not met, the Z80 still reads the next two bytes in the jump instruction in order to point the PC register to the next instruction.
Enfim, consegui colocar as mãos na Multicore 2 e rapidamente fui testá-la. Para tal, escolhi o core do TRS-80, já que o CP500 foi o primeiro computador que usei na vida, no meu primeiro curso de informática, lá pelos idos de 1986.
Pois bem, ao baixar o core do TRS-80 descobri que a MC2 ainda não tem suporte a discos para esse core. Assim, só restou a opção de carregar jogos e programas usando a porta auxiliar da MC2, o que significa ler arquivos .CAS (cassette) usando um gravador com fitas (método raiz) ou usar algum player no PC (método nutella).
Como não dispunha de um gravador funcional à mão nem queria esperar muito para rodar jogos no TRS-80, fui pelo caminho nutella mesmo e comecei a pesquisar utilitários que me pudessem ajudar na tarefa. A boa notícia é que há uma oferta satisfatória de programas e sites que me permitiram, após aprender alguns macetes, a rapidamente conseguir rodar um joguinho para TRS-80 na minha Multicore 2.
Primeiramente, vamos precisar de um player apto a reproduzir os arquivos no formato cassette do TRS-80. Dessa forma, o PC funcionará como gravador, bastando conectar um cabo P2-P2 estéreo na saída de fone de ouvido do computador e na entrada AUX da MC2.
O PlayCAS reproduz arquivos .CAS no formato SYSTEM e BASIC, entre outros. O primeiro formato normalmente é usado para programas em código de máquina e deve ser carregado através do comando SYSTEM (mais detalhes adiante). Arquivos BASIC normalmente são carregados pelo comando CLOAD.
Na figura abaixo, vemos a interface do PlayCAS.
Observem a área destacada em vermelho. Nela tem informações importantes, como a velocidade (highspeed), formato (SYSTEM), nome da fita (‘CATR1S’), duração e status do conteúdo do arquivo (ok!).
Para carregar um arquivo .CAS, basta clicar em Open new e para reproduzi-lo, obviamente, é só clicar em Play. Também é possível salvar o arquivo no formato .WAV, caso se deseje gravar o arquivo em fita K7 e seguir o método raiz.
Carregando .CAS na Multicore 2
Vamos carregar o jogo SEA DRAGON, cujo arquivo .CAS você pode baixar aqui. Abra-o no PlayCAS e conecte o cabo P2 na saída de áudio do PC e na entrada AUX da MC2 (conector vizinho ao slot SD).
Carregue o core do TRS-80 na Multicore. Ao concluir a inicialização do core, aparecerá a tela abaixo. Observe que a MC2 utiliza a ROM do CP-300 da Prológica.
Aperte a tecla S na pergunta “BASIC (S ou N)?”. Na pergunta “Cass?”, o TRS-80 quer saber qual velocidade usar no carregamento de fita, se baixa (200/500 baud) ou alta (1500 baud). Assim, é preciso saber de antemão qual a velocidade do arquivo .CAS que você deseja carregar, o que pode ser obtido abrindo-se o arquivo no PlayCAS. Se o arquivo .CAS for de baixa velocidade, você aperta a tecla B nessa pergunta, ou a tecla A se for de alta.
Para simplificar, sugerimos sempre digitar <enter> na pergunta “Cass?”, que seleciona a alta velocidade na leitura de cassette, e converter qualquer arquivo .CAS de baixa velocidade para 1500 baud (ver item sobre conversão mais adiante).
Depois, basta apertar <enter> na perguntas seguinte. No prompt do BASIC, digite SYSTEM <enter>.
Irá aparecer o prompt:
O computador está aguardando que você insira o nome da fita a ser lida. Basta digitar a primeira letra do nome. No nosso exemplo, digite a letra S seguida de <enter>. O TRS-80, então, passa a escutar a entrada AUX. Ative a reprodução no PlayCAS. Nesse momento devem aparecer dois asteriscos no canto superior direito da tela, um fixo e outro piscando. Esses asteriscos indicam que a leitura está ocorrendo (nessa hora senti saudades do TK90X).
Ao final do processo de carga do arquivo .CAS na memória, o prompt *? é exibido novamente. Basta digitar / <enter> para rodar o programa carregado.
Obtendo arquivos .CAS
Não fiz uma busca extensa de arquivos .CAS na internet, tendo me limitado aos seguintes sites:
- https://www.classic-computers.org.nz/system-80/software_cassette_archive.htm (seleção pequena, mas muito boa, de jogos. Arquivos precisam de conversão de velocidade)
- http://willus.com/trs80 (Lista extensa de jogos, programas diversos, utilitários, compiladores etc. Arquivos em .BAS, .CMD e .DMK precisam ser convertidos para .CAS)
Convertendo a velocidade de arquivos .CAS
Caso o arquivo .CAS esteja em 200/500 baud, é recomendado convertê-lo para 1500 baud (highspeed), pois diminui significantemente o tempo de carga do arquivo. Em caso de dúvida, abra o arquivo no PlayCAS e veja a informação da velocidade na área de status.
Para tal tarefa, vamos utilizar o programa Highlow Cassette Converter, que pode ser baixado aqui. Esse é um programa de linha de comando de fácil utilização. Basta chamá-lo, passando o nome do arquivo .CAS que deseja converter, que ele automaticamente identifica a velocidade do arquivo e converte para a velocidade oposta (de 200/500 baud para highspeed e vice-versa).
Nesse exemplo, considerando que o arquivo original é de baixa velocidade, esse comando gerará um novo arquivo chamado seadrag.hi.cas.
Convertendo arquivos .BAS e .CMD para .CAS
A maioria dos programas para TRS-80 está disponível em formatos .CMD ou .BAS, sendo necessário convertê-los para .CAS. Para isso, vamos lançar mão de um excelente utilitário de linha de comando chamado trld, que pode ser baixado aqui. Ele permite converter arquivos TRS-80 entre diversos formatos.
Supondo que tenhamos um arquivo no formato .CMD, para convertê-lo basta digitar o comando:
>trld CATR1SS2.CMD catr1ss2.cas
Será gerado um arquivo .CAS em highspeed, pronto para ser usado no PlayCAS.
Para arquivos .BAS, o procedimento de conversão é o mesmo. Porém, o arquivo .CAS gerado será do tipo SYSTEM e não BASIC. Portanto, a carga do arquivo deverá ser feita através do comando SYSTEM no TRS-80 (e não pelo comando CLOAD). Uma vez carregado na memória, ao se digitar / <enter>, se retornará para o prompt do BASIC. Aí só precisa digitar RUN <enter> para executar o programa.
Extraindo arquivos de discos virtuais (.DMK/.DSK)
Caso você encontre algum programa disponível somente no formato .DMK ou .DSK, que é um formato de discos virtuais do TRS-80 para PC, é possível extrair arquivos .CMD/.BAS contidos no disco virtual. Dessa forma, será possível convertê-los para cassette e carregá-los na sua MC2.
Vamos utilizar o programa VDisk, do brasileiro Miguel Dutra, para ler e extrair o conteúdo de arquivos .DMK. Baixe o VDisk aqui.
Para ler o conteúdo de um disco virtual do TRS-80, digite o comando:
>vdisk -l disco.dmk
Para extrair um arquivo:
>vdisk -r disco.dmk PROG.CMD
Para mais informações sobre o VDisk, consulte o arquivo de help que acompanha o programa.
AVISO: Se for baixar algum arquivo .DMK do site http://willus.com/trs80, baixe pelo link Debug1, pois os demais links são de arquivos com formato não compatível com o VDisk.
O ideal seria poder utilizar discos virtuais diretamente na Multicore 2 carregados no cartão SD. Enquanto isso não é possível, a opção de carregar arquivos .CAS via entrada AUX é viável e interessante.
Uma alternativa ao uso do PlayCAS a ser testada é utilizar o CASduino. Assim, não precisaria de um PC para carregar os programas.
No mais, bom revival dos jogos e programas do TRS-80 e não esqueçam de ligar uma caixinha de som na Multicore para aproveitar melhor os joguinhos!
* The least significant byte is stored first when referencing a 16-bit number in memory.
* There’s no LD instruction to load a value into a register pair from another pair. When needed, you can use single register load instructions, like LD H, B and LD L, C.
* You can use ADD and ADC instructions to add constants to the accumulator, but there’s no equivalent instruction for the HL pair. Every time you want to add a number to HL, for instance, you should load that number into another pair and then add it to HL:
LD DE, 10EFh LD HL, DE
Another possibility would be to use the accumulator instead and make the addition register by register:
LD A,L ADD A,EFh LD L,A LD A,H ADC A,10h LD H,A
* The instructions INC and DEC don’t affect the carry flag.
* Whereas you can add directly to register pair HL (ADD HL, BC; ADD HL, DE; ADD HL, HL), it’s not possible to subtract directly from it. Thus, there’s no instructions like SUB HL, BC, for instance. As a side effect, all subtraction mnemonics (without carry) are written as SUB, implying that the register A is the only option to subtract from.
* However, you can subtract from HL with the instruction SBC, which takes the carry flag into account.
* Apparently, since both ADC and SBC instructions sets are extended ones (ED XX), there could be SUB HL instructions, unless there were any decoding issues prevent this addition in the Z80 (remember that Z80 took its instruction set from the 8080).
* Although you have instructions to set the carry flag (SCF) or to complement it (CCF), there’s no instruction to reset it. And it’s not needed indeed, since you can issue SCF followed by CCF in order to reset the carry flag. Another possibility is to use a dumb logical operation like AND A or OR A, which always reset the carry flag.
* PUSH and POP instructions only work with register pairs. The accumulator pairs with the F register (flags) making the AF pair.
* If you want to exchange values between two pairs (without overriding other register), you can use the stack:
PUSH BC PUSH DE POP BC POP DE
* Bear in mind that using memory is slower than using registers. Thus, resort to registers as much as you can. The earlier example could use the accumulator, for instance:
LD A, C LD C, E LD E, A LD A, B LD B, D LD D, A
* Within a subroutine (CALL), you can use a small trick to make sure to return to the correct address. Store the SP contents in another memory location upon entering the subroutine, and just before returning, restore the saved value back to SP. Thus, there’s no need to worry about unbalanced PUSHs and POPs inside the subroutine.
* Another useful trick when dealing with subroutines is to change the return address before returning.
POP HL ; removes the original return address LD HL, new_return_address PUSH HL ; inserts the new return address RET or LD HL, new_return_address EX (SP), HL RET
* XOR FF has an equivalent result as the CPL instruction.
* This one sounds weird. Instructions RR and RL mean rotate the specified register’s bits including the carry flag, whereas instructions RRC and RLC do the same thing but exclude the carry flag.
* The instruction RLA performs the same action as RL A, but takes only 4 T-cycles, against 8 T-cycles from RL A, and only affects the carry flag. RRA, RLCA and RRCA work in a similar manner.
* In the Spectrum, when calling a machine code routine from BASIC, like using PRINT USR address, the address value is stored into the BC register pair before executing the machine code. Likewise, when the routine returns control to BASIC (RET instruction), the current BC contents is passed back as the result of the USR expression.
Let’s examine how the load instructions are structured in the Z80.
It is important to say upfront that most of Z80 instruction’s design was taken from the Intel’s 8080 CPU. The Zilog engineers kept binary code compatibility with the 8080, while expanding the design with new and extended instructions.
A generic load instruction takes the following form:
LD operand1, operand2
which means operand1 receives the value indicated by operand2, i.e., operand1 ← operand2.
Register addressing load instructions
The generic form for instructions that move 8-bit data between registers is:
LD r, r'
where r and r’ are 8-bit registers within the currently active register set. Their binary code format is:
76 543 210 01 r r'
Notice that this instruction type has two register fields (register addressing mode), and any one can have one of the following values:
Thus, these are valid op-codes:
01 010 111 -> LD D, A 01 111 010 -> LD A, D 01 001 001 -> LD C, C1
1Same effect as a NOP instruction.
So, we have 49 valid 8-bit register-only load instructions. But, what about the 15 binary combinations left? (did you noticed the missing value 0b110 for the register field?).
Well, it happens that 0b110 is a valid value indeed, but it doesn’t correspond to a CPU internal register. Rather, it stands for the indirect addressing expressed by (HL). You could think of (HL) as a special type of register anyway. Remember this scheme was borrowed from the 8080 CPU.
Then we have 7 more load instructions of the type:
76 543 210
01 r 110 -> LD r, (HL)
01 010 110 -> LD D, (HL)
Likewise, we also have seven store counterparts:
76 543 210
01 110 r -> LD (HL), r 01 110 001 -> LD (HL), C
But, what about the binary code 0b01110110? Is it a valid load instruction? As it wouldn’t make much sense to waste machine cycles only to load a memory location with its own content, this binary code was set to correspond to the HALT instruction.
In summary, for the 64 possible binary codes for the template 0b01 r r’, we have 63 8-bit register move instructions and one outlander HALT instruction.
Immediate addressing load instructions
Now, let’s delve into the LD instructions that use immediate addressing. They are like this:
LD r, n
where r is a 8-bit general purpose register and n is a one-byte operand. This instruction’s two-byte op-code is as follow:
76 543 210 00 r 110 <- n ->
00 100 110 -> LD H, n
<- n ->
And the special case for the value 0b110 still applies:
00 110 110 -> LD (HL), n
In fact, every time you see a register field, remember it can have a value of 0b110 and indirectly access a memory location indicated by the HL register pair (there’s only exception we’ll see later among the undocumented indexed addressing instructions).
Direct and Indirect addressing load instructions
Both types of instructions are structured in a similar way, so let’s talk about them in one topic.
Direct load instructions use HL and A registers for holding data since memory addresses take part of the instruction themselves, whereas indirect ones use BC and DE pairs to hold memory address, loading or storing the accumulator.
The general binary code for those instructions is:
76 5 4 3 210 00 m r t 010
|Bit||Used for||Mode||0||1||Register contains|
|4||register(s) used||indirect||BC||DE||memory address|
|3||type of operation||–||store in memory||load from memory||–|
Thus, these are indirect load instructions:
76 5 4 3 210
00 0 0 0 010 -> LD (BC), A 00 0 0 1 010 -> LD A, (BC) 00 0 1 0 010 -> LD (DE), A 00 0 1 1 010 -> LD A, (DE)
And these are the direct ones:
76 5 4 3 210
00 1 0 0 010 -> LD (nn), HL
00 1 0 1 010 -> LD HL,(nn)
00 1 1 0 010 -> LD (nn), A
00 1 1 1 010 -> LD A, (nn)
Remember all direct instructions have two additional bytes for the memory address.
Indexed load instructions
Up to now, we have been talking about instructions that came from the 8080 CPU. Z80 has introduced some extensions to the instruction set. Indexed addressing load instructions are among them, which use two new registers for indexing memory, IX and IY.
Indexed load instructions general form is:
LD r, (ir + d) LD (ir + d), r
where r is an 8-bit register, ir is a index register (IX or IY) and d is a signed byte displacement (-128 to 127).
All indexed addressing instructions have at least two op-codes. When dealing with the IX register the first op-code is always 0xDD and 0xFD when using the IY register.
The binary format is the following
76 543 210 11 011 101 -> IX op-code 01 r 110 -> LD r, (IX + d) <- d ->
76 543 210 11 111 101 -> IY op-code 01 r 110 -> LD r, (IY + d) <- d ->
where r is a 3-bit register field.
Notice that the second binary code has the same pattern of the instruction LD r, (HL). Which makes perfect sense for the instruction decoding circuitry in the CPU. The first op-code sets the index register to be used while the second one indicates the real operation to be performed, which is a indirect load in this case.
Let’s check the store counterparts LD (ir + d), r
76 543 210 11 011 101 -> IX op-code 01 110 r -> LD (IX + d), r <- d ->
76 543 210 11 111 101 -> IY op-code 01 110 r -> LD (IY + d), r <- d ->
The same pattern applies to the second op-code, i.e., it is the same binary code format used by store instructions LD (HL), r.
Notice that 0xDD76 and 0xFD76 are not valid instructions. You should know why by now…
Indexed load instructions with immediate addressing
In this case, we have instructions that load an indexed memory location with an immediate 8-bit value.
LD (ir + d), n
which binaries code are:
76 543 210 11 011 101 -> IX op-code 00 110 110 -> LD (IX + d), n <- d -> <- n ->
76 543 210 11 111 101 -> IY op-code 00 110 110 -> LD (IY + d), r <- d -> <- n ->
As expected the same pattern for the indexed instruction second op-code still applies.
(Undocumented) Indexed load instructions with register addressing
Here things get a little bit twisted. First, all instructions in this section were not documented by Zilog (I’m still not sure why).
This type of instruction allows to load/save the most/least significant byte of an index register from/to some general purpose 8-bit registers (B, C, D, E, A). The mnemonics for the index register parts are IXH, IXL, IYH and IYL.
Thus, we have generic forms as follow:
LD r, irh LD r, irl LD irh, r LD irl, r
where irh is the most significant byte of an index register and irl is the least significant one. Examples of these instructions are:
LD B, IXL LD IYH, A
Now, let’s see the binaries codes. Remember that all indexed instructions have a two-byte op-code, starting with 0xDD or 0xFD for IX or IY registers, respectively.
76 543 210 11 011 101 -> IX op-code 01 r 100 -> LD r, IXH
76 543 210 11 111 101 -> IY op-code 01 r 101 -> LD r, IYL
Well, r can assume the values we already know of, except that it can’t be 0b110 as a standard register field would. Moreover, the values 0b100 and 0b101 correspond to registers IXH/IYH and IXL/IYL respectively.
Notice that the three least significant bits of the second op-code follow this convention for specifying the most/least significant parts of IX/IY. That is, they form a second register field which, in this case, can only assume two values (0b100 and 0b101). Which makes perfect sense, since we are talking about an instruction that deals with one of the two available index registers.
Being the HL pair the standard memory pointer (as usual in the 8080 CPU), using an indexed instruction in Z80 means that you are going to use IX or IY as the default pointer to memory for that particular instruction (represented by the second op-code).
Thinking of the internal Z80 implementation, decoding the first op-code of an indexed instruction simply means changing the default HL memory pointer to either IX or IY. It also means that the next op-code should be decoded as one of the possible instructions that make use of indirect addressing.
Other implication that we can take away here is that the two most significant bits 0b01 for a standard op-code always means a register addressing load instruction.
The following are the previous instructions’ counterparts:
76 543 210 11 011 101 -> IX op-code 01 100 r -> LD IXH, r
76 543 210 11 111 101 -> IY op-code 01 101 r -> LD IXL, r
16-bit load instructions
Now, let’s see how load instructions that deal with 16-bit values are structured. We have already discussed some instructions of this type on the second part of the section Direct and indirect addressing load instructions (LD HL, (nn) and LD (nn), HL).
The basic form for these instruction is:
LD dd, nn
Where dd is register pair and nn is 16-bit value. Theses instructions load an two-byte value into a register pair.
The binary code format is as follow
76 54 3 210 00 dd 0 001 -> LD dd, nn <- n -> <- n ->
The dd bits make up for a 16-bit register field, as illustrated in the following table:
These op-codes came from the 8080 as usual. Also, notice that LD dd, nn has no counterpart, as LD nn, dd makes no sense at all.
It is also possible to load and store from/into memory using direct addressing:
LD dd, (nn) LD (nn), dd
These instructions were added by Zilog and are among those called extended instructions. Extended instructions have a two-byte op-code having the first one the value 0xED.
76 54 3 210 11 10 1 101 -> 0xED 01 dd 1 011 -> LD dd, (nn)
76 54 3 210 11 10 1 101 -> 0xED 01 dd 0 011 -> LD (nn), dd
Notice that, as we have seen earlier, when using direct or indirect addressing modes, bit 3 indicates the operation type (0 – store, 1 – load).
It is also noteworthy to say that we have here an overleap between extended and standard instructions which deal with the HL pair. That is, LD HL, (nn) have both an standard 8080 op-code (0x2A) and an Z80 extended one (0xED6B). Likewise, LD (nn), HL has two valid op-codes (0x22 and 0xED63).
The remaining 16-bit load instruction is one that moves data from HL to the stack pointer:
LD SP, HL
Its binary code is:
76 543 210 11 111 001 -> LD SP, HL
At last, let’s see the 16-bit indexed load instructions
LD ir, nn LD ir, (nn) LD (nn), ir LD SP, ir
where ir can be IX or IY. The rule about using a standard (not extended) instruction op-code as the second op-code for an indexed instruction keeps sounding true as expected.
76 543 210 11 011 101 -> IX 00 100 001 -> LD IX, nn
<- n -> <- n ->
76 543 210 11 011 101 -> IX 00 101 010 -> LD IX, (nn)
<- n -> <- n ->
76 543 210 11 011 101 -> IX 00 100 010 -> LD (nn), IX
<- n -> <- n ->
76 543 210 11 011 101 -> IX 11 111 001 -> LD SP, IX
I and R registers
The special registers I and R have load instructions to move data from/to the accumulator:
765 43 210 010 00 111 -> LD I, A 010 01 111 -> LD R, A 010 10 111 -> LD A, I 010 11 111 -> LD A, R
where bit 4 defines the load/store operation and bit 3 selects between I and R registers.
Of all LD instructions shown here, these are the only ones that affect the Flag register.
Here are some interesting takeaways from this study:
- There are 161 LD instructions overall in the Z80 instruction set.
- Instructions like LD r,r work as a NOP instruction.
- The first op-code of any indexed instruction just changes the standard memory pointer. The second op-code is the same of a standard indirect addressing instruction that uses the HL pair.
- Because the compatibility with the 8080, the extended load instructions brought up redundancy for the LD HL, (nn) and LD (nn), HL instructions.
- The only LD instructions that affect the flag register are those that move data between the accumulator to/from I or R registers.
Machine code was made simple. Everything it does is move data around and do basic logic/arithmetic operations. Every single CPU instruction can be mapped into those two things. Everything else in computer programming are just abstraction layers added on top of this simple mechanism. Amazing, isn’t it?
So, in order to do what they are supposed to do, instructions need operands. And instructions differ in how they get their operands. In general, instructions get an operand from one or more registers, from memory or from an external device. The way how instructions obtain theirs operands is called addressing mode. There are 10 addressing modes for the Z80: implied, immediate, extended immediate, register addressing, register indirect addressing, extended, modified page zero, relative, indexed and bit addressing.
Implied addressing mode: there is no bytes nor bits in the instruction op-codes to explicitly specify the operands, so they are implicit, as in CPL and LD SP, IY instructions. In general, instructions of this type have one or two op-codes.
Immediate addressing mode: instruction needs an explicit operand which is specified in the instruction itself (second ou third byte), and it’s always one-byte length. Example of this mode are instructions ADD A, N, XOR N and LD IXL, N, where N is a one-byte operand.
Extended immediate addressing mode: it is similar to the immediate mode but takes a two-byte operand. Instructions LD HL, NN and LD IX, NN have this kind of addressing.
Register addressing: in the op-code, there is a three-bit field to indicate one 8-bit register as the operand. RL, R and AND R are examples of this addressing mode, where R is a general purpose register (B, C, D, E, H, L and A).
Register indirect addressing: in this case, the operand itself is not in a register. Instead, a register pair contains the operand’s memory address. That’s why it is called indirect addressing. LD A, (BC) and INC (HL) are examples of this mode. Note the use of parenthesis around registers to denote the indirect addressing.
Extended addressing (or direct addressing): the instruction has a two-byte field containing the operand’s memory address. Instructions like LD A, (NN) and LD (NN), HL illustrate the direct addressing mode, where NN is a 16-bit value representing a memory address.
Modified page zero addressing: this mode is exclusive to a single instruction, RST P, which causes the CPU to jump execution to a specified location P, inside the page zero memory area. Page zero is a memory area comprised by the first 256 bytes of memory (usually ROM). P can assume only the following values: 0x0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30 and 0x38. RST P is normally used to call special ROM subroutines. (For the ZX Spectrum, see which ROM routines are available here).
Relative addressing: it is used in relative jump instructions (conditional and unconditional), in which the memory address to go to is calculated by adding an operand displacement to the PC register. The displacement value ranges from -128 to +127 (signed binary representation), allowing to address a location in memory from -126 to +129 relative to the jump instruction address (this is due to PC getting incremented by two during instruction execution). As an example, instruction JR Z, D, where D is the displacement, uses this kind of addressing mode. The relative addressing mode allows to reduce jump instructions by one byte, compared to absolute jump instructions (JP) where a 16-bit address operand is provided.
Indexed addressing: this mode allows one to use indirect addressing using the index registers IX and IY. Like the relative addressing mode, the instruction has an 8-bit displacement operand, which is added to the index register being used. AND (IX + D) and LD (IY + D), N are examples of the indexed mode, where D is an 8-bit signed displacement and N is an immediate operand.
Bit addressing: this mode is used in conjunction with previous modes to provide testing, setting and resetting any one of the 8-bits in an operand. In the instruction op-code, there’s an 8-bit field allowing to address any of the 8 bits in the operand. 0b000 addresses the least significant bit whereas 0b111 addresses the most significant bit. SET 0, B and SET 7, (HL) are example instructions of bit addressing.
As an exercise, access this page and try to identify more examples of instructions for the above Z80 addressing modes.
So, I started reading about the Z80 architecture these days. In order to better program a CPU, you better understand its inner workings first.
I’ve gotten to know the difference between clock cycle (aka T-cycle) and machine cycle (M-cycle). A T-cycle is just a single square wave provided by the system clock, which has a duration of the inverse of the clock frequency. For, say, a 4MHz clock, one single T-cycle lengths 250 ns.
A M-cycle comprises many T-cycles and can be categorized in these types:
- op-code fetch cycle (also called M1-cycle)
- memory cycle
- I/O cycle
- Bus request cycle
- Interrupt request cycle
- Non-maskable interrupt cycle
- HALT instruction cycle
Each Z80 single instruction has its own lot of M-cycles depending on its type, which may vary from one to six machine cycles. In general, an instruction could range from 4 to 23 T-cycles to be completely executed (see more info here).
Understanding what each machine cycle is and having a comprehension of how many cycles a certain instruction demands to be executed is key to write performant machine code.
Bus request signal is sampled by the Z80 at the last rising edge of any M-cycle (op-code, memory, I/O, etc), which means it can be recognized in the middle of an instruction execution.
Interrupt request signal is sampled only in the last rising edge of the last M-cycle of the executing instruction, which means that the CPU only recognizes an I/O interrupt after the current instruction finishes its execution.
Interesting enough, on every M1-cycle, T3 and T4-cycles execute the refresh memory cycle (for dynamic memory). At the same time, the instruction is decoded by the CPU. I wonder whether Zilog has ever released a version of the Z80 without refresh memory gears, even considering the static memory higher costs in the 80’s.
A note for WAIT states: memory should be able to respond, at least, in a T-cycle (250 ns), whereas I/O devices should do it in 2 T-cycles (500 ns).
[EDIT – 26/05/2017]
Reading about memory contention on the ZX Spectrum, I learnt that in memory refresh cycles (M1’s T3 and T4), the Z80 puts register I contents in the most significant portion of the address bus. This causes the so called ULA “snow effect” to happen when the contents of register I is within the 0x4000 and 0x7FFF (first 16KB of RAM).
The Spectrum Next’s kickstarter campaign has come to an end with a tremendous success as £723,390 has been pledged by 3,113 backers all around the world.
A great community has been building up long before the campaign even started which had culminated in this past month. Now, it is time to keep the community going and to help getting the Next ecosystem up and running with new games, demos and even new software never imagined before for the Spectrum. I personally have some ideas to make that happen and I will share them with you in the upcoming months.
For now I just want to thank the Spectrum Next team and all new friends that came along with the project. As I said last night on facebook, just after the campaign ended:
Rather than having a new machine to play with, we now have new friends to count on and stand by…
From the very first time a saw a computer, I have always wanted to make a game. And probably the same happened to many of you. My first and only game was a Pac-man style one written in BASIC in a TRS-80. I recall myself drawing the maze on a square paper. I was 12, I guess, and never had heard of sprites and the likes back then.
Fast forward to present day, I just ordered some pixel art books online. I didn’t know these books are quite common nowadays. Perhaps it is a trend from the Minecraft mania. I don’t know. Anyway, as I plan to make a game for the Spectrum Next (and even to Atari 2600), I think I could make some use of these books. At least, I could have fun and draw some characters with my son!
Once I have had set up my emulator of choice, I really wanted to try some BASIC programming, like the old times. I recall the INPUT magazine being a great source of information, at least here in Brazil. It covered BASIC programming, the basics of coding games and even some machine code. INPUT was a great collection and helped many people to start programming in early ages. Oh, boy, I used to read each issue from cover to cover, even the sections related to other machines. Thankfully, the entire collection is available here.
Will Stephenson in the Spectrum Next facebook group shared a link to some free programming books from the 80’s by Usborne Publishing, which can be of some interest too.
There is also a new book called ZX Spectrum Games Code Club, which seems to be a good resource for brushing up old BASIC skills. I will probably take a look into it soon.
At last, I also recommend joining the Basic on the ZX Spectrum group on facebook, as a way to make new friends and improve your knowledge by participating in the programming challenges.
As I only use Mac computers nowadays, I went in a search for Spectrum emulators for macOS, mainly to play games and type in some BASIC listings. I first tried Fuse, which is multi-platform and has virtually everything one needs to revive the feeling of using a Spectrum.
However, a few weeks later I came across the Retro Virtual Machine, or RVM for short, by Juan Carlos González Amestoy. RVM has a different spin compared to other emulators as it has a multi-architecture engine, allowing to emulate different machines concurrently. Besides, RVM has some unique features like the Virtual Tape Recorder and Virtual Disk Drives, that provide an immersive experience of loading games from tapes or disks. Oh, and you load tapes and disks by just dragging files onto the virtual devices. The simulation of the tape player and disk driver is nearly perfect. Totally amazing!
Other noteworthy features are analog video simulation, with effects like scanlines, noise and displacement, customizable keyboards, where each machine may have its own keyboard config, and Z80 timing accuracy, that includes bugs like the “snow effect”. And on top of that, RVM is like any other native macOS app, respecting all user interface conventions and providing a natural feeling for Mac users.
The only con I have found so far is that RVM doesn’t have an assembly debugger, preventing it from being used in a developer tool chain. I hope this may change soon, as many people are planning to develop again for the Spectrum, as well as emulation for the new Spectrum Next be included.
In summary, I find RVM really impressive and if you are a Mac user looking for nothing than a great Spectrum emulation with the best user interface out there, go all-in for the Retro Virtual Machine. And if you really enjoy it, make a donation to the developer as an incentive for him to keep improving it.