[FAQ]
[Frequently Asked Questions]
[Resources]
[Emulators]
[Where Is...?]
[File Formats]
[Technical Information]
[Pinouts]
[Acknowledgements]
| SPECCY TECHNICAL INFORMATION |
|---|
This page last updated on 5 June 1998
In addition to reading this, if you are interested in lots of technical information about various aspects of the Spectrum and how to emulate it, you are strongly advised to read the documentation accompanying Gerton Lunter's Z80, in particular the techinfo.doc file. Many other emulators also include useful information in their documentation.
[Z80 CPU]
[CB ops]
[DD/FD ops]
[ED ops]
[R reg]
[Undocumented flags]
[IFF1/2]
[Channels & Streams]
[Hardware]
[Interface 1]
[Port E7]
[Port EF]
[Joysticks]
[The 128K Spectrums]
[Memory]
[Keypad]
[Sound chip]
[Disk drive]
| ZILOG Z80 CPU |
|---|
I had hoped to add some information from several threads on the newsgroup (Z80 emulation related facts, BIT and the flags and Z80 interrupts) but just never got the time, if someone wants to compile the relevant information together so I can just paste it in, I'd be most grateful ;)
This section is based on the text contributed by Gerton Lunter, author of the Z80 Spectrum emulator. Marat Fayzullin made some changes which didn't change the content and Ian Collier has contributed some extra information (some of which conflicts with Gerton's).
Most Z80 opcodes are one byte long, not counting a possible byte or word operand. The four opcodes CB, DD, ED and FD are shift opcodes: they change the meaning of the opcode following them.
| CB opcodes |
|---|
There are 248 different CB opcodes. The block CB 30 to CB 37 is missing from the official list. These instructions, usually denoted by the mnemonic SLL, Shift Left Logical, shift left the operand and make bit 0 always one. These instructions are quite commonly used. For example, Bounder and Enduro Racer use them.
| DD and FD opcodes |
|---|
The DD and FD opcodes precede instructions using the IX and IY registers. If you look at the instructions carefully, you see how they work:
2A nn LD HL,(nn) DD 2A nn LD IX,(nn) 7E LD A,(HL) DD 7E d LD A,(IX+d)
A DD opcode simply changes the meaning of HL in the next instruction. If a memory byte is addressed indirectly via HL, as in the second example, a displacement byte is added. Otherwise the instruction simply acts on IX instead of HL (A notational awkwardness, that will only bother assembler and disassembler writers: JP (HL) is not indirect; it should have been denoted by JP HL). If a DD opcode precedes an instruction that doesn't use the HL register pair at all, the instruction is executed as usual. However, if the instruction uses the H or L register, it will now use the high or low halves of the IX register! Example:
44 LD B,H FD 44 LD B,IYh
These types of 'undocumented' instructions are used in very many programs. By the way, many DD or FD opcodes after each other will effectively be NOPs, doing nothing except repeatedly setting the flag "treat HL as IX" (or IY) and taking up 4 T states (But try to let MONS disassemble such a block.).
From Joseph S. Myers:
[DD and FD have no effect on ED instructions, or on EX DE,HL. If an
instruction references both (HL), and one of H and L, then (HL) becomes
(IX+d) or (IY+d), but H or L stays as it is. For example, DD6601 is
LD H,(IX+01).
DDCB and FDCB prefix instructions in which the next byte is a displacement byte, and the byte after that is the instruction byte. If the CB instruction references (HL), the effect is as expected; otherwise, the instruction seems to act on (IX+d) or (IY+d), and, if the instruction is such as to change the value acted on (any instruction other than BIT), the new value is loaded into the register the CB instruction would act on. Thus:
CB07 is RLC A; DDCB0107 is effectively RLC (IX+01) followed by LD A,(IX+01).]
| ED opcodes |
|---|
There are a number of unofficial ED instructions, but none of them are very
useful. The ED opcodes in the range 00-3F and 80-FF (except for the block
instructions of course) do nothing at all but taking up 8 T states and
incrementing the R register by 2. Most of the unlisted opcodes in the range
40-7F do have an effect, however. The complete list:
(* = not official, i.e. not listed in Zilog documentation)
(++ = documented in Spectrum manual)
ED40 IN B,(C) ED60 IN H,(C) ED41 OUT (C),B ED61 OUT (C),H ED42 SBC HL,BC ED62 SBC HL,HL ED43 LD (nn),BC ED63 * LD (nn),HL ++ ED44 NEG ED64 * NEG ED45 RETN ED65 * RET ED46 IM 0 ED66 * IM 0 ED47 LD I,A ED67 RRD ED48 IN C,(C) ED68 IN L,(C) ED49 OUT (C),C ED69 OUT (C),L ED4A ADC HL,BC ED6A ADC HL,HL ED4B LD BC,(nn) ED6B * LD HL,(nn) ++ ED4C * NEG ED6C * NEG ED4D RETI ED6D * RET ED4E * IM 0/1 ED6E * IM 0/1 ED4F LD R,A ED6F RLD ED50 IN D,(C) ED70 * IN (C) ++ as IN F,(C) ED51 OUT (C),D ED71 * OUT (C),0 ED52 SBC HL,DE ED72 SBC HL,SP ED53 LD (nn),DE ED73 LD (nn),SP ED54 * NEG ED74 * NEG ED55 * RET ED75 * RET ED56 IM 1 ED76 * IM 1 ED57 LD A,I ED77 * NOP ED58 IN E,(C) ED78 IN A,(C) ED59 OUT (C),E ED79 OUT (C),A ED5A ADC HL,DE ED7A ADC HL,SP ED5B LD DE,(nn) ED7B LD SP,(nn) ED5C * NEG ED7C * NEG ED5D * RET ED7D * RET ED5E IM 2 ED7E * IM 2 ED5F LD A,R ED7F * NOP
The ED70 instruction reads from port (C), just like the other instructions, but throws away the result. It does change the flags in the same way as the other IN instructions, however. The ED70 instruction is documented in at least two other publications beside the original Spectrum manual - one of which is an SGS Z80 data booklet.
The ED71 instruction OUTs a byte zero to port (C). In fact the instructions INI and OUTI do almost exactly the same as the proposed action for ED70 and ED71 as well as incrementing the registers.
The IM 0/1 instruction puts the processor in either IM 0 or 1 (undetermined at this time). The SGS booklet says that there are two flip-flops which determine the interrupt mode and are set by the IM instructions. The possible values are:
00 IM 0 01 not used 10 IM 1 11 IM 2
...so it is entirely possible that the IM 0/1 instruction sets these registers to 01. What the Z80 does when this happens is anyone's guess.
| The R Register |
|---|
This is not really an undocumented feature, although I have never seen any thorough description of it anywhere. The R register is a counter that is updated every instruction, where DD, FD, ED and CB are to be regarded as separate instructions. So shifted instruction will increase R by two. There's an interesting exception: doubly-shifted opcodes, the DDCB and FDCB ones, increase R by two too. LDI increases R by two, LDIR increases it by 2 times BC, as does LDDR etcetera. The sequence LD R,A/LD A,R increases A by two, except for the highest bit: this bit of the R register is never changed. This is because in the old days everyone used 16 Kbit chips. Inside the chip the bits where grouped in a 128x128 matrix, needing a 7 bit refresh cycle. Therefore ZiLOG decided to count only the lowest 7 bits. You can easily check that the R register is really crucial to memory refresh. Assemble this program:
ORG 32768 DI LD B,0 L1: XOR A LD R,A DEC HL LD A,H OR L JR NZ,L1 DJNZ L1 EI RET
It will take about three minutes to run. Look at the upper 32K of memory, for instance the UDG graphics. It will have faded. Only the first few bytes of each 256 byte block will still contain zeros, because they were refreshed during the execution of the loop. The ULA took care of the refreshing of the lower 16K (This example won't work on the emulator, of course!).
From Alvin Albrecht:
[The effect of interrupts upon the R register is as follows:
| Undocumented Flags |
|---|
This undocumented "feature" of Z80 has its effect on programs like Sabre Wulf, Ghosts'n Goblins and Speedlock. Bits 3 and 5 of the F register are not used. They can contain information, as you can readily figure out by PUSHing AF onto the stack and then POPping some it into another pair of registers. Furthermore, sometimes their values change. I found the following empirical rule:
The values of bits 7, 5 and 3 follow the values of the corresponding bits of the last 8 bit result of an instruction that changed the usual flags.
For instance, after an ADD A,B those bits will be identical to the bits of the A register (Bit 7 of F is the sign flag, and fits the rule exactly). An exception is the CP x instruction (x=register, (HL) or direct argument). In this case the bits are copied from the argument.
If the instruction is one that operates on a 16 bit word, the 8 bits of the rule are the highest 8 bits of the 16 bit result - that was to be expected since the S flag is extracted from bit 15.
Ghosts'n Goblins use the undocumented flag due to a programming error. The rhino in Sabre Wulf walks backward or keeps running in little circles in a corner, if the (in this case undocumented) behaviour of the sign flag in the BIT instruction isn't right. I quote:
AD86 DD CB 06 7E BIT 7,(IX+6) AD8A F2 8F AD JP P,#AD8F
An amazing piece of code! Speedlock does so many weird things that all must be exactly right for it to run. Finally, the '128 ROM uses the AF register to hold the return address of a subroutine for a while.
| Interrupt flip-flops IFF1 and IFF2 |
|---|
There seems to be a little confusion about these. These flip flops are simultaneously set or reset by the EI and DI instructions. IFF1 determines whether interrupts are allowed, but its value cannot be read. The value of IFF2 is copied to the P/V flag by LD A,I and LD A,R. When an NMI occurs, IFF1 is reset, thereby disallowing further [maskable] interrupts, but IFF2 is left unchanged. This enables the NMI service routine to check whether the interrupted program had enabled or disabled maskable interrupts. So, Spectrum snapshot software can only read IFF2, but most emulators will emulate both, and then the one that matters most is IFF1.
| CHANNELS & STREAMS |
|---|
[This section from Sean A. Irvine]
The Spectrum has a surprisingly modern system of input and output when the age of the Spectrum is considered. However, what is more surprising is the fact that the Spectrum manual barely scratches the surface of what is possible.
I/O on the Spectrum is based on channels and streams. Since the standard Spectrum has only a limited range of I/O devices is makes sense that different commands are available for each I/O device. For example, PRINT is used to send output to the screen; whereas LPRINT is used to send output to the printer.
The extra devices catered for by Interface 1 (microdrives, RS232, and networking) reduced the practicality of continuing to invent new commands, although this was provided for in the case of the microdrives.
Streams and channels intuitively correspond to the software and hardware parts of I/O respectively.  On the Spectrum streams are numbered from 0 through 15; and their basic operations are reading and writing data.
The BASIC statement INPUT #s; <input-list> will read data from stream number s, 0 <= s <= 15, into the variables specified in the input-list. Conversely, the BASIC statement PRINT #s; <print-list> will write data to stream s, 0 <= s <= 15. In general both INPUT # and PRINT # can be used in the same way as their ordinary counterparts INPUT and PRINT. In particular all the normal complexity of a PRINT statement can be used equally well in a PRINT # statement. In each case the data sent to the stream is exactly the same as the data which would be sent to the screen by the PRINT statement. The INPUT # statement is slightly more complicated in that is can both read and write data, as in INPUT "What is your name? "; A$. In fact each stream really has two components, an input stream and an output stream. Data written to the stream by either PRINT # or INPUT # goes to the output stream while input comes from the input stream.
It is even possible to change streams part way through a PRINT statement, as in PRINT #3; "hello"; #6; "there". Such practice, however, should be avoided as a style matter unless there is compelling reason to do so.
How do you know which stream numbers are associated with which channel? Before a stream is used it must be OPENed. Opening a stream serves two purposes. It associates the given stream with a particular piece of hardware (the channel), and actually signals the relevant device that it is going to be used. Stream are opened in BASIC using the syntax OPEN #s, c where s is the stream number being opened and c is a string specifying the channel to associate the stream with. Following this command any data sent to stream s will go to the specified channel. It is possible to open several streams to the same device; but each stream can only be associated with a single channel.
The statement CLOSE #s, c is used to end the association of stream s with channel c. It also informs that associated hardware that it is no longer required by stream s.
The unexpanded Spectrum supports four channels: "K" the keyboard channel, "S" the screen channel, "P" the printer channel, and "R" an internal channel used by the Spectrum to send data to the edit buffer. In practice the only channel that has both input and output of these is the keyboard channel. When the Spectrum is first powered up the following streams are opened automatically: 0 -- "K", 1 -- "K", 2 -- "S", 3 -- "P". Thus, the command LPRINT is really an alternative to writing PRINT #3. The "R" channel cannot be opened from BASIC. It is possible to redefine the standard channels, thus OPEN #2, "P" will cause output normally sent to the screen to be redirected to the printer.
For example, OPEN #5, "K" associates stream 5 with the keyboard, and thereafter INPUT #5; A$ would behave in an identical manner to INPUT A$.
The most important advantage of using streams is in the writing of device independent programs. Say that you wish to give the user the option of having all output go to either the screen or to the printer. Without using streams it is necessary then to have separate output statements for each device, as in
IF (output = printer) THEN LPRINT "Hello" ELSE PRINT "Hello"
but using streams we can just open a particular stream (say 4) to the desired output device and thereafter use only one output statement
PRINT #4; "Hello"
Obviously this will result is a much shorter program, particularly, if there are many output statements in the program. Further, it is an easy matter to add even further output devices if they become an option later in the programs development.
BASIC also allows LIST and INKEY$ to be used to streams. LIST #s will send a copy of the program to stream s; e.g. normally LIST #3 is the same thing as LLIST. On the standard Spectrum INKEY$ can only be used with the keyboard channel.
Technical section for machine code programmers.
Knowing about the actually layout of the stream records in memory is useful if you want to add your own hardware devices to the Spectrum, or if you which to make you own specialized streams. The information that defines each channel is stored in the channel information area starting at CHANS and ending at PROG - 2. Each channel record has the following format:
two-byte address of the output routine two-byte address of the input routine one-byte channel code letter
where the input and output routines are address of machine code sub- routines. The output routine must accept Spectrum character codes passed to it in the A register. The input routine must return data in the form of Spectrum character codes, and signal that data is available by setting the carry flag. If no data is available then this is indicated be resetting both the carry and zero flags. Stubs should be provided if a channel does not support either input or output (e.g. the stub may simply call RST 8 with an error code).
With Interface 1 attached an extended format is used. The above fields are followed by
two-byte address 8K error routine 40 two-byte address 8K error routine 40 one-byte length of channel descriptor
Data about which streams are associated with which channels is in a 38-byte area of memory starting at STRMS. The table is a series of 16-bit offsets to the channel record vectored from CHANS. A value of one indicates the channel record starting at CHANS, and so on. This accounts for 32 bytes of the 38. The remaining 6 bytes are for three hidden streams (253, 254, 255) used internally by BASIC. A zero entry in the table indicates a stream not open.
It is possible to redirect existing channels to your own I/O routines. This can be used among other things to cause LPRINT to use your own printer driver rather than the one provided in the ROM. It allows you to perform I/O for your own hardware devices, or for you to write your own handlers from PRINT and INPUT.
It is easiest to modify the existing "P" channel record. The "K" channel is not a good option for modification because its values are restored every time a INPUT statement is executed. It is possible to create new channel records elsewhere in memory, e.g. by changing the value of CURCHL. Another difficulty is that without Interface 1, OPEN will only work with K, S, and P and so it is necessary to provide some other way of opening your own channels.
To make space for a new record a call should be made to the ROM routine at 5717. This will allocate the requested space and alter any system variables affected by the change. The amount of space required is passed in BC, and the address of the first location to be allocated is passed in Hl. For example, LD BC, 100; LD HL, 23700; CALL 5717 will allocate 100 bytes starting at 23700. A new channel descriptor should start at one less than PROG.
| HARDWARE |
|---|
At the hardware level, the Spectrum is a very simple machine. There's the 16K ROM which occupies the lowest part of the address space, and 48K of RAM which fills up the rest. An ULA which reads the lowest 6912 bytes of RAM to display the screen, and contains the logic for just one I/O port completes the machine, from a software point of view at least. Every even I/O address will address the ULA, but to avoid problems with other I/O devices only port FE should be used. If this port is written to, bits have the following meaning:
Bit 7 6 5 4 3 2 1 0 +-------------------------------+ | | | | E | M | Border | +-------------------------------+
The lowest three bits specify the border colour; a zero in bit 3 activates the MIC output, and a one in bit 4 activates the EAR output (which sounds the internal speaker). The real Spectrum also activates he MIC when the ear is written to. The upper three bits are unused.
If port FE is read from, the highest eight address lines are important too. A zero on one of these lines selects a particular half-row of five keys:
IN: Reads keys (bit 0 to bit 4 inclusive) #FEFE SHIFT, Z, X, C, V #EFFE 0, 9, 8, 7, 6 #FDFE A, S, D, F, G #DFFE P, O, I, U, Y #FBFE Q, W, E, R, T #BFFE ENTER, L, K, J, H #F7FE 1, 2, 3, 4, 5 #7FFE SPACE, SYM SHFT, M, N,
A zero in one of the five lowest bits means that the corresponding key is pressed. If more than one address line is made low, the result is the logical AND of all single inputs, so a zero in a bit means that at least one of the appropriate keys is pressed. For example, only if each of the five lowest bits of the result from reading from port 00FE (for instance by XOR A/IN A,(FE)) is one, no key is pressed. A final remark about the keyboard. It is connected in a matrix-like fashion, with 8 rows of 5 columns, as is obvious from the above remarks. Any two keys pressed simultaneously can be uniquely decoded by reading from the IN ports. However, if more than two keys are pressed decoding may not be uniquely possible. For instance, if you press Caps shift, B and V, the Spectrum will think also the Space key is pressed, and react by giving the "Break into Program" report. Without this matrix behaviour Zynaps, for instance, won't pause when you press 5,6,7,8 and 0 simultaneously.
Bit 6 (not 5!) (value 64) of IN-port FE is the ear input bit. When the line is silent, its value is zero, except in the early Model 2 of the Spectrum, where it was one. When there is a signal, this bit toggles. The Spectrum loading software is not sensitive to the polarity of this bit (which it definitely should not be, not only because of this model difference, but also because you cannot be sure the tape recorder doesn't change the polarity of the signal recorded!). Some old programs rely on the fact that bit 6 (not 5!) is always one (for instance Spinads).
From Pera Putnik:
[In this letter I will try correct some incomplete and partially incorrect
explanations about port 254 of the ZX Spectrum, about MIC and EAR sockets, and
give what I hope is the correct explanation about the differences between
Issue 2 and 3 Spectrums.
All this started when I tested some games on different Spectrum emulators: I found that the games Abu Simbel Profanation and Rasputin wouldn't start play from the control selection menu.
When I downloaded x128 v0.8s, I was very impressed with the many new features and the quality of this emulator, so I decided to help the author. I tried to find the reason why Abu Simbel Profanation wasn't working with x128: I did some tests/experiments, and I found the reason; I sent an e-mail to James McKay, but never received an answer. This was around October 1997.
Fortunately in December 1997, I found a Spectrum board with old ULA chip, and could determine the exact difference between Issue 2 and 3.
This program is intended for authors of Spectrum emulators:
10 OUT 254,BIN 11101111 20 PRINT IN 254 30 OUT 254,BIN 11111111 40 PRINT IN 254 50 GOTO 10
For a correct test do not press any key while running, and of course have no EAR input.
The only correctly responding emulator which I found is zx32 by Vaggelis Kapartzianis: in this test this emulator acts exactly like an Issue 2, and also zx32 was the only emulator which correctly started Abu Simbel Profanation and Rasputin. (I tested x128 v0.8, Z80 v3.05, Warajevo v1.5, UKV, JPP, ZX-Jam, WSpecEm, SP2 and VGASpec.)
From Joe Mackay:
[The above test on ZXAM produces results
identical to an Issue 2 Spectrum]
From the above there is a visible difference between Issue 2 and 3 Spectrums.
Well, now an explanation of port 254, only part of which is for tape storage and sound output: the ULA chip uses the same pin for 3 functions: MIC output, EAR input and internal speaker drive. That's why OUT 254 or sound generation affects bit 6 as read by IN 254.
In techinfo.doc by Gerton Lunter (which is the reference for most other documents) there are some errors:
What is missing from techinfo.doc and from the hardware section of the Spectrum FAQ is the fact that not only the input level at EAR affects bit 6 by IN 254: bits 3 and 4 from the previous OUT 254 also have an effect, and it is here that there is a little difference between Issue 2 and 3, and it isn't something like the inversion of the EAR input. The difference between Issue 2 and 3 is:
Therefore, I think the situation is:
Value output to bit: 4 3 | Iss 2 Iss 3 Iss 2 V Iss 3 V 1 1 | 1 1 3.79 3.70 1 0 | 1 1 3.66 3.56 0 1 | 1 0 0.73 0.66 0 0 | 0 0 0.39 0.34
Iss 2 is value of bit 6 read by IN 254 after the appropriate OUT from an Issue 2, and Iss 3 is same for an Issue 3. Iss 2 V and Iss 3 V are voltage levels on pin 28 of the ULA chip after the OUT, with no input signal on the EAR socket. (Pin 28 is the common pin for MIC, EAR and the internal speaker).
I tested the level at pin 28 at which input bit 6 changes from 0 to 1 or reverse. This is exactly 0.70 Volts on both Issue 2 and Issue 3, with no inverting or hysteresis; this means that bit 6 is 1 if the voltage on pin 28 is over 0.70 V, and otherwise it is 0, on both Issues. At the hardware level, the only difference between Issue 2 and 3 is that there are slightly higher voltages from Issue 2 machines. As can be seen from the table, the input combination '0 1' gives output voltages that are very close to the crucial 0.7 V.
However, for faster programs the situation isn't so simple, as there is some delay when output bit 4 changes from 1 to 0. To illustrate this, here are 2 short assembler routines:
ORG 45000 LD A,#18 OR #F8 OUT (254),A LD A,#08& OR #E8 OUT (254),A TIMING LD B,7 ;crucial value DL LD IX,0 DJNZ DL IN A,(254) ;query state
In this case IN A,(254), or output of this value sometimes gives 255 and sometimes 191. If you make the constant in the TIMING line smaller then result will be always 255, if delay is longer then result will be always 191. Of course, the effect occurs only for Issue 3 machines.
The situation is again slightly different for a longer duration of high output level on port 254:
ORG 50000 HALT ;synchronize with interrupts LD A,#18 OUT (254),A HALT ;wait 20ms LD A,#08 OUT (254),A LD B,107 ;crucial value LD IX,0 DJNZ DL IN A,(254)
As you can see, after a longer high level duration, the delay is also much longer. The delay varies from approximately 180 T states (about 50 microsec) to 2800 T states (about 800 microsec), depending from duration of high level on port 254. The explanation for this delay are the capacitors connected between pin 28 of the ULA and the EAR and MIC connectors. There is no delay when bit 4 changes from 0 to 1.
In the ROM 'Beeper' subroutine, at address #03CF there is the instruction OR 8, which is before OUT (#FE),A, and this routine is activated every time you type a character in Basic; Well, this is the reason why IN 254 on an Issue 2 normally gives 255, and 191 on an Issue 3.
In my opinion, the reason why Abu Simbel Profanation doesn't work in Z80 (and other emulators) is that that authors didn't know that OUT 254 affects bit 6 of IN 254, which is very visible when you start the simple Basic program above on these.]
Bits 5 (not 6!) and 7 are always one. The ULA with the lower 16K of RAM, and the processor with the upper 32K RAM and 16K ROM are working independently of each other. The data and address buses of the Z80 and the ULA are connected by small resistors; normally, these do effectively decouple the buses. However, if the Z80 wants to read or write the lower 16K, the ULA halts the processor if it is busy reading, and after it's finished lets the processor access lower memory through the resistors. A very fast, cheap and neat design indeed!
From Rui Ribeiro:
[If you read from a port that activates both the keyboard and a joystick port
(e.g. Kempston), the joystick takes priority. Every emulator except SpecEm,
WSpecEm and xzx
get this wrong - see Street Hawk and Command4.]
From James McKay:
[x128 has had this right since V0...... I
can't remember, but V0.8 does.]
If you run a program in the lower 16K of RAM, or read or write in that memory, the processor is halted sometimes. This part of memory is therefore somewhat slower than the upper 32K block. This is also the reason that you cannot write a sound- or save-routine in lower memory; the timing won't be exact, and the music will sound harsh. Also, INning from port FE will halt the processor, because the ULA has to supply the result. Therefore, INning from port FE is a tiny bit slower on average than INning from other ports; whilst normally an IN A,(nn) instruction would take 11 T states, it takes 12.15 T states on average if nn=FE. See below for more exact information.
If the processor reads from a non-existing IN port, for instance FF, the ULA won't stop, but nothing will put anything on the data bus. Therefore, you'll read a mixture of FF's (idle bus), and screen and ATTR data bytes (the latter being very scarce, by the way). This will only happen when the ULA is reading the screen memory, about 60% of the 1/50th second time slice in which a frame is generated. The other 40% the ULA is building the border or generating a vertical retrace. This behaviour is actually used in some programs, for instance, in Arkanoid.
Finally, there is an interesting bug in the ULA which also has to do with this split bus. After each instruction fetch cycle of the processor, the processor puts the I-R register "pair" (not the 8 bit internal Instruction Register, but the Interrupt and R registers) on the address bus. The lowest 7 bits, the R register, are used for memory refresh. However, the ULA gets confused if I is in the range 64-127, because it thinks the processor wants to read from lower 16K ram very, very often. The ULA can't cope with this read-frequency, and regularly misses a screen byte. Instead of the actual byte, the byte previously read is used to build up the video signal. The screen seems to be filled with 'snow'; however, the Spectrum won't crash, and program will continue to run normally. There's one program I know of that uses this to generate a nice effect: Vectron (which has very nice music too, by the way).
The processor has three interrupt modes, selected by the instructions IM 0, IM 1 and IM 2. In mode 1, the processor simply executes an RST #38 instruction if an interrupt is requested. This is the mode the Spectrum is normally in.
The other mode that is commonly used is IM 2. If an interrupt is requested, the processor first builds a 16 bit address by combining the I register (as the high byte) with whatever the interrupting device places on the data bus. The subroutine at this address is then called. Rodnay Zaks in his book "Programming the Z80" states that only even bytes are allowed as low index byte, but that isn't true. The normal Spectrum contains no hardware to place a byte on the bus, and the bus will therefore always read FF (because the ULA also doesn't read the screen if it generates an interrupt), so the resulting index address is 256*I+255. However, some not-so-neat hardware devices put things on the data bus when they shouldn't, so later programs didn't assume the low index byte was FF. These programs contain a 257 byte table of equal bytes starting at 256*I, and the interrupt routine is placed at an address that is a multiple of 257. A useful but not so much used trick is to make the table contain FF's (or use the ROM for this) and put a byte 18 hex, the opcode for JR, at FFFF. The first byte of the ROM is a DI, F3 hex, so the JR will jump to FFF4, where a long JP to the actual interrupt routine is put.
In interrupt mode 0, the processor executes the instruction that the interrupting device places on the data bus. On a standard Spectrum this will be the byte FF, coincidentally (...) the opcode for RST #38. But for the same reasons as above, this is not really reliable.
The 50 Hz interrupt is synchronized with the video signal generation by the ULA; both the interrupt and the video signal are generated by it. Many programs use the interrupt to synchronize with the frame cycle. Some use it to generate fantastic effects, such as full-screen characters, full-screen horizon (Aquaplane) or pixel colour (Uridium for instance). Very many modern programs use the fact that the screen is "written" (or "fired") to the CRT in a finite time to do as much time-consuming screen calculations as possible without causing character flickering: although the ULA has started displaying the screen for this frame already, the electron beam will for a moment not "pass" this or that part of the screen so it's safe to change something there. So the exact time in the 1/50 second time-slice at which the screen is updated is very important. Each line takes exactly 224 T states.
After an interrupt occurs, 64 line times pass before the byte 16384 is displayed. At least the last 48 of these are actual border-lines. I could not determine whether my monitor didn't display the others or whether it was in vertical retrace, but luckily that's not really important.
From Ian Collier:
[This is probably not quite correct. I have some exact figures, but so
far I have failed to bring them into the lab in order to program them
into xz80. The 48K constants in there at the moment are wrong, but the +3
ones are correct. On the +3 there are exactly 14368 (well, give or take 1)
cycles between the interrupt and byte 16384 being displayed, which turns out
to be 63 lines and 4 cycles. On the 48K it's 14339 cycles. I think!]
Then the 192 screen+border lines are displayed, followed by about 56 border lines again. 56.5 border lines would make up exactly 70000 T states, 1/50th of 3500000. However, I noticed that the frequency of the 50 Hz interrupt (measured in 1/T states!) changes very slightly when my Spectrum gets hot (I think it has something to do with the relative change of the frequencies of the two crystals in the Spectrum), so the time between interrupts will probably not be exactly 70000 T states.
From Malyrules:
[The changing speed in 128 k depends NOT of "the relative frequency of the
two crystals...". One is ULA crystal, one is colour crystal. They are 100%
independent. The reason is still unknown, but Omega said, that placing
cooler onto ula fixes problem.]
From Ian Collier:
[This can't be true. A line contains exactly 224 cycles, and the screen
contains exactly 312 lines. If the screen didn't contain a whole number
of lines then the TV would not be able to display it correctly. This means
that there are exactly 69888 cycles in a frame. Unfortunately I have never
determined whether this means that the 50Hz clock is too quick or the 3.5MHz
clock is too slow (or neither, which would prove me wrong - note, though,
that I have written a program which depends on the correct number of cycles
per frame).]
Anyway, whether the final border block is of fixed or variable length doesn't concern us either, the timings of the start and end of the screen, which are the timings of real interest, are fixed.
Now for the timings of each line itself. I define a screen line to start with 256 screen pixels, then border, then horizontal retrace, and then border again. All this takes 224 T states. Every half T state a pixel is written to the CRT, so if the ULA is reading bytes it does so each 4 T states (and then it reads two: a screen and an ATTR byte). The border is 48 pixels wide at each side. A video screen line is therefore timed as follows: 128 T states of screen, 24 T states of right border, 48 T states of horizontal retrace and 24 T states of left border.
When an interrupt occurs, the running instruction has to be completed first. So the start of the interrupt is fixed relatively to the start of the frame up to the length of the last instruction in T states. If the processor was executing a HALT (which, according to the Z80 books I read, is effectively many NOPs), the interrupt routine starts at most 3 T states away from the start of the frame. Of course the processor also needs some T states to store the program counter on the stack, read the interrupt vector and jump to the routine. In interrupt modes 0, 0/1 and 1 the total time taken to get to address 56 is 13 cycles. In interrupt mode 2 the time taken to get to the interrupt routine is 19 cycles. Interpreted as follows:
5 cycles: a read of the data bus (in IM 1 this is thrown away) 6 cycles: a read of the 2-byte interrupt vector IM 2 only) 8 cycles: to push the current PC and set the new one.
When an NMI occurs, the total time taken to get to address 102 is 15 cycles. This is the same as for an ordinary interrupt but with two extra cycles (because it must also put IFF2 flipflop to the P flag, and reset IFF1).
Now when to OUT to the border to change it at the place you want? First of all, you cannot change the border within a "byte", an 8-pixel chunk. If we forget about the screen for a moment, if you OUT to port FE after 14326 to 14329 T states (including the OUT) from the start of the IM 2 interrupt routine, the border will change at exactly the position of byte 16384 of the screen. The other positions can be computed by remembering that 8 pixels take 4 T states, and a line takes 224 T states. You would think that OUTing after 14322 to 14325 T states, the border would change at 8 pixels left of the upper left corner of the screen. This is right for 14322, 14323 and 14324 T states, but if you wait 14325 T states the ULA happens to be reading byte 16384 (or 22528, or both) and will halt the processor for a while, thereby making you miss the 8 pixels. This exception happens again after 224 T states, and again after 448, an so forth. These 192 exceptions left of the actual screen rectangle are the only ones; similar things don't happen at the right edge because the ULA don't need to read things there - it has just finished! As noted above, reading or writing in low ram (or OUTing to the ULA) causes the ULA to halt the processor. When and how much? The processor is halted each time you want to access the ULA or low memory and the ULA is busy reading. Of the 312.5 'lines' the ULA generates, only 192 contain actual screen pixels, and the ULA will only read bytes during 128 of the 224 T states of each screen line. But if it does, the processor is halted for exactly 4 T states.
From Pedro Gimeno:
[In the 48K Spectrum the time taken by the TV electron beam to go
from a point to the one exactly below that (i.e. the time for a
scanline) is exactly 224 cycles. The time used by a single pixel is 1/2 clock
cycle, so the time used by a 256-pixel wide screen line is 128 cycles, and the
remaining 96 are for screen border and horizontal retrace.
The Speccy generates exactly 312 lines of video (not 312.5), which means there are 69888 cycles between the start of each frame (moment in which an interrupt is generated), not 70000. The 312 lines are divided as follows: 64 are for vertical retrace and border, 192 for screen data and border, and the remaining 56 for border until the next frame/interrupt.
So 64 scanlines (14336 cycles) after the generation of an interrupt, the electron beam reaches the top left corner of the actual screen (not border). The border and the screen are both byte-granular. This means that changes may be applied only in a 8-pixel chunk, so it's not possible to make horizontal colour changes with pixel precision in either the border or the screen.
The ULA is the holder of the contents of the border colour value, so to update the border everything is OK. But when the thing to be updated is the video screen itself, the ULA needs to read the video memory to get the TV updated whilst the CPU needs to access memory to execute instructions. The electron beam can't be interrupted, so the ULA is given a higher priority to access the contended memory. When it needs to read from video memory, and the CPU needs to access the memory bank containing video memory or I/O port 254, then the CPU is halted. The exact number of cycles by which the CPU is halted depends on the current cycle relative to the generation of an interrupt. Here's how:
Cycle # Delay ------- ----- 14335 6 (until 14341) 14336 5 ( " " ) 14337 4 ( " " ) 14338 3 ( " " ) 14339 2 ( " " ) 14340 1 ( " " ) 14341 No delay 14342 No delay 14343 6 (until 14349) 14344 5 ( " " ) 14345 4 ( " " ) 14346 3 ( " " ) 14347 2 ( " " ) 14348 1 ( " " ) 14349 No delay 14350 No delay
etc., until the cycle #14463 (always relative to the start of the interrupt), in which the electron beam reaches the border again for 96 more cycles. And at cycle #14559 the same situation repeats. This is valid for all 192 lines of screen data. While the ULA is updating the border the delay does not happen at any time.
When counting cycles several things must be taken into account. One is the interrupt setup time; another one is the precise moment within an instruction in which the R/W or I/O operation is performed (see the table below). And one more thing: the fact that an interrupt can't happen in the middle of a instruction (and a HALT counts as many NOPs), so some cycles may be lost while waiting for the current instruction to end. That's an additional difficulty e.g. for byte-precision colour changes.
Now all that remains is to know exactly in which point(s) within an instruction is the R/W or I/O operation acting, to know where to apply the delay. That depends on each instruction. For those one-byte ops which do not perform memory or I/O access, the only affected point is the opcode fetch which happens at the first cycle of the instruction, and the address to test for contention is the current value of the program counter PC.
For example, for a NOP (4 cycles), only the first cycle will be affected and only if PC lies within the contended memory range. So if it's executed in contended memory at cycle #14334, no delay will happen and the next instruction will (try to) be executed at cycle #14338, but if the NOP is executed at cycle #14335, it will be delayed for 6 cycles thus taking 6+4=10 cycles so the next instruction will (try to) be executed at cycle #14345. This case will be annotated in the table below as pc:4, meaning that if PC lies within contended memory then the first cycle will be subject to delay and the remaining three will be free of delays.
The "try to" in the above paragraph is because, unless the NOP is at PC=32767, the next instruction will be subject to another delay when its opcode is fetched (the first cycle in an opcode fetch is always subject to delays) since the cycle number relative to the start of the frame is also delayed.
So an entry like 'hl+1:3' means that if HL+1 is in range 16384-32767 and the current cycle number is subject to delays, then the delay corresponding to the current cycle must be inserted before the number of T-states that figure after the colon.
Things get a bit more difficult with more-than-one-byte-long instructions. Here's the sample pseudocode to apply delays to an instruction with an entry in the table which reads 'pc:4,hl:3' (e.g. LD (HL),A):
If 16384<=PC<=32767 then <Insert the delay corresponding to the current cycle, relative to the start of the frame> (according to the above table). (end if) Delay for 4 cycles (time after 'pc:'). If 16384<=HL<=32767 then <Insert the delay corresponding to the current cycle...> (end if) Store A into (HL) Delay for 3 cycles (time taken to store A)
Example 1: if PC = 25000 and HL = 26000 and the instruction at address 25000 is LD (HL),A and we're in cycle #14335:
Next opcode will be read at cycle #14352 (and 5 cycles will be inserted then for sure because PC=25001).
Example 2: same but PC=40000 (not contended):
If an entry in the table has something like 'io:5', it means that if the I/O port is even (bit 0 = 0, like port FEh) then it counts exactly like an address lying in contended memory.
The values for the registers listed in the table below are relative to the starting value of the register when the instruction is about to be executed.
In the table below:
For conditional instructions, entries in {} mean that they have only to be applied if the condition is met. If the instruction is not conditional (e.g. CALL nn) the entries in {} should be ignored.
The CB/ED/DD/FD prefixes count always as pc:4. It will not be counted in each instruction: I found it convenient to count them as separate instructions. Also, in places where HL appears we assume that it may be replaced by IX or IY (same for H and L alone) when valid. Timings for instructions with an operand of the form (IX/IY+n) have not been thoroughly tested.
Some formats of the instructions may be illegal. For example, the instruction LD r,(ss) is not valid for r=B and ss=DE. Just ignore the illegal combinations.
In some read-modify-write operations (like INC (HL)), the write operation is always the last one. That may be important to know the exact point in which video is updated, for example. In such instructions that point is annotated for clarity as "(write)" after the address.
Instruction Breakdown ----------- --------- NOP; pc:4 CB prefix; ED prefix; DD prefix; FD prefix; LD r,r'; alo A,r; sro r; BIT b,r; SET b,r; RES b,r; INC/DEC r; EXX; EX AF,AF'; EX DE,HL; DAA; CPL; NEG; IM 0/1/2; CCF; SCF; DI; EI; RLA; RRA; RLCA; RRCA; JP (HL) LD A,I; pc:5 LD A,R; LD I,A; LD R,A INC/DEC dd; pc:6 LD SP,HL ADD HL,dd; pc:11 ADC HL,dd; SBC HL,dd LD r,n; pc:4,pc+1:3 alo A,n LD r,(ss); pc:4,ss:3 LD (ss),r alo A,(HL) pc:4,hl:3 BIT b,(HL) pc:4,hl:4 LD dd,nn; pc:4,pc+1:3,pc+2:3 JP nn; JP cc,nn LD (HL),n pc:4,pc+1:3,hl:3 LD A,(nn); pc:4,pc+1:3,pc+2:3,nn:3 LD (nn),A LD dd,(nn); pc:4,pc+1:3,pc+2:3,nn:3,nn+1:3 LD (nn),dd INC/DEC (HL); pc:4,hl:4,hl(write):3 SET b,(HL); RES b,(HL); sro (HL) POP dd; pc:4,sp:3,sp+1:3 RET; RETI; RETN RET cc pc:5,{sp:3,sp+1:3} PUSH dd; pc:5,sp-1:3,sp-2:3 RST n CALL nn; pc:4,pc+1:3,pc+2:3,{pc+2:1, CALL cc,nn sp-1:3,sp-2:3} JR n; pc:4,pc+1:3,{pc+1:1,pc+1:1,pc+1:1, JR cc,n pc+1:1,pc+1:1} DJNZ n pc:5,pc+1:3,{pc+1:1,pc+1:1,pc+1:1, pc+1:1,pc+1:1} RLD; pc:4,hl:7,hl(write):3 RRD IN A,(n); pc:4,pc+1:4,io:3 OUT (n),A IN r,(C); pc:5,io:3 OUT (C),r EX (SP),HL pc:4,sp:3,sp+1:4,sp(write):3,sp+1(write):5 LDI/LDIR; pc:4,hl:3,de:3,de:1,de:1,{de:1,de:1,de:1, LDD/LDDR de:1,de:1} CPI/CPIR; pc:4,hl:3,hl:1,hl:1,hl:1,hl:1,hl:1,{hl:1, CPD/CPDR hl:1,hl:1,hl:1,hl:1} INI/INIR; pc:6,io:3,hl:3,{hl:1,hl:1,hl:1,hl:1,hl:1} IND/INDR Note: The next instruction is not very clear because of its complexity - help on confirmation would be appreciated: OUTI/OTIR; if last time or non-repeated version: OUTD/OTDR pc:5,hl:4,io:3 if not last time (for repeated versions): pc:5,hl:4,io:1,pc+1:1,pc+1:1,pc+1:1,pc+1:1, pc+1:1,pc+1:1,pc:1
If anyone out there can further this information (especially about the effect of (IX+n), (IY+n) and the differences for the 128K/+2), please contact Pedro.]
| INTERFACE 1 |
|---|
The Interface I is quite complicated. It uses three different I/O ports, and contains logic to page and unpage an 8K ROM if new commands are used. The ROM is paged if the processor executes the instruction at ROM address 0008 or 1708 hexadecimal, the error and close# routines. It is inactivated when the Z80 executes the RET at address 0700.
| Port E7 |
|---|
I/O port E7 is used to send or receive data to and from the microdrive. Accessing this port will halt the Z80 until the Interface I has collected 8 bits from the microdrive head; therefore, it the microdrive motor isn't running, or there is no formatted cartridge in the microdrive, the Spectrum hangs. This is the famous 'IN 0 crash'.
| Port EF |
|---|
Bit 7 6 5 4 3 2 1 0 +---------------------------------------+ READ| | | |busy| dtr |gap| sync|write| | | | | | | | |prot.| |---+---+----+----+-----+---+-----+-----| WRITE| | |wait| cts|erase|r/w|comms|comms| | | | | | | | clk | data| +---------------------------------------+
Bits DTR and CTS are used by the RS232 interface. The WAIT bit is used by the Network to synchronise, GAP, SYNC, WR_PROT, ERASE, R/_W, COMMS CLK and COMMS DATA are used by the microdrive system. If the microdrive is not being used, the COMMS DATA output selects the function of bit 0 of out-port F7:
Bit 7 6 5 4 3 2 1 0 +------------------------------------------+ READ|txdata| | | | | | | net | | | | | | | | | input | |------+---+---+---+---+---+---+-----------| WRITE| | | | | | | |net output/| | | | | | | | | rxdata | +------------------------------------------+
TXDATA and RXDATA are the input and output of the RS232 port. COMMS DATA determines whether bit 0 of F7 is output for the RS232 or the network.
| JOYSTICKS |
|---|
[From Alvin Albrecht:] The ports assigned to joysticks are:
#7F Fuller Box (FxxxRLDU, active low) #1F Kempston (000FUDLR, active high) #EFFE Sinclair1 (000LRDUF, active low, corresponds to keys '6' to '0') #F7FE Sinclair2 (000FUDRL, active low, corresponds to keys '1' to '5')
TS2068 (joysticks attached to R14 of sound chip):
LD A,7 OUT (#F5),A ;set R7 IN A,(#F6) AND #BF ;clear bit 6 to read from i/o port a - R14 OUT (#F6),A LD A,14 OUT (#F5),A ;set R14 LD A,3 ;(3=both joysticks, 2=left only, 1=right only) IN A,(#F6) ;(FxxxRLDU, active low)
This method ensures other sound chip functions aren't messed with.
| THE 128K SPECTRUMS |
|---|
| Memory |
|---|
When memory is being paged, interrupts should be disabled and the stack should be in an area which is not going to change. If normal interrupt code is to run, then the system variable at 5B5Ch (23388) must be kept updated with the last value sent to port 7FFDh. It is not possible to read this port.
On the 128 and +2, memory is entirely controlled by port 7FFDh. The byte to output will be interpreted thus:
Bits 0-2: RAM page (0-7) to map into memory at 0C000h Bit 3: Select normal (0) or shadow (1) screen. The normal screen is in bank 5 and normally appears at 4000h; the shadow screen is in bank 7 and can be switched in at 0C000h Bit 4: ROM select. ROM 0 is the 128k editor and menu system; ROM 1 contains 48k BASIC. Bit 5: If set, memory paging will be disabled and further output to this port will be ignored until the computer is reset.
The memory map of these computers is:
FFFFh +--------+--------+--------+--------+--------+--------+--------+--------+ | Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 | | | |(also at| | |(also at| | | | | | 8000h) | | | 4000h) | | | | | | | | | screen | | screen | C000h +--------+--------+--------+--------+--------+--------+--------+--------+ | Bank 2 | Any one of these pages may be switched in. | | | | | | 8000h +--------+ | Bank 5 | | | | | | screen | 4000h +--------+--------+ | ROM 0 | ROM 1 | Either ROM may be switched in. | | | | | | | | | 0000h +--------+--------+
Memory banks 4-7 are contended, i.e. the processor shares them with the ULA. This reduces the speed of memory access in these banks.
[From Erik Kunze:]
The basic principle of paging on the +2A and +3 is the same. However, the +2A and +3 have four ROMs rather than two, and certain extra memory configurations.
Port 7FFDh behaves in the same way as before, except that bit 4 is now the low bit of the ROM selection. The extra features are controlled by port 1FFDh. This port is also write-only, and its last value should be saved at 5B67h (23399).
Port 1FFDh responds thus:
Bit 0: Paging mode. 0=normal, 1=special Bit 1: In normal mode, ignored. Bit 2: In normal mode, high bit of ROM selection. The four ROMs are: ROM 0: 128k editor, menu system and self-test program ROM 1: 128k syntax checker ROM 2: +3DOS ROM 3: 48 BASIC Bit 3: Disk motor; 1=on, 0=off Bit 4: Printer port strobe.

On the 128 and +2, Banks 1,3,4,6 and most of 7 are used for the silicon disc; the rest of 7 contains editor scratchpads. On the +2A and +3, Banks 1,3,4 and 6 are used for the disc cache and RAMdisc, while Bank 7 contains editor scratchpads and +3DOS workspace.
From Pedro Gimeno
and Jose Luis Sanchez:
[The basic principle behind the contended memory timings is the same on the
+2A/+3 and the 48K machine, but the specifics are different:
This means that the top-left pixel of the screen is displayed 14364 T-states after the 50 Hz interrupt occurs, as opposed to 13336. The T-states (relative to the interrupt) at which delays occur are given in the following table:
Cycle # Delay ------- ----- 14361 1 14362 No delay 14363 7 14364 6 14365 5 14366 4 14367 3 14368 2 14369 1 14370 No delay 14371 7 14372 6
and so on, until cycle 14490, when the display of the first scanline on the screen has been completed, and no more delays are inserted until 14589 (=14361+228) when the cycle repeats.
This information here is not complete; some things which have not been tested so far:
| Keypad |
|---|
The 128K machine's keypad extra editing facilities are also available via the normal keyboard:
FUNCTION KEYS ----------------------------------------- Beginning of next word [E] [S] J Beginning of previous word [E] I Up ten lines [E] P Down ten lines [S] I Start of line [E] [S] 2 End of line [E] M First line [E] N Last line [E] T Screen [E] [S] 8 Delete this character [E] [S] K Delete word left [E] E Delete word right [E] W Delete to start of line [E] K Delete to end of line [E] J [E] = Extended Mode [S] = Symbol Shift
| Sound Chip |
|---|
The AY-3-8912 sound chip is a widely used one, to be found in the MSX, Vectrex, Amstrad CPC range, etc. It is controlled by two I/O ports:
OUT (0FFFDh) - Select a register 0-14 IN (0FFFDh) - Read the value of the selected register OUT (0BFFDH) - Write to the selected register
There's a guide to how to use the registers at this site - this is for the CPC, so the I/O commands used will be different, and on the Spectrum register 14 is used for the serial ports rather than the keyboard as on the CPC. There is also an excellent load of info on the AY chip in the techinfo.doc file accompanying the Z80 emulator.
The Fuller Box also used the Speccy 128's AY sound chip:
[From Alvin Albrecht:] The ports assigned to the Fuller Box are:
#3F AY register #5F AY data #7F joystick (byte read=FxxxRLDU, active low)
The Timex TS2068 is one of the Spectrum clones and also had an AY chip:
[From Alvin Albrecht:] TS2068 AY ports are:
#F5 AY register #F6 AY data
For a more complete list of TS2068 ports, see the TS2068 ROMs file (to be found on NVG).
Typically, the AY chip is written to inside 128K games using:
LD BC,#FFFD 01 FD FF OUT (C),D ED 51 LD B,#BF 06 BF OUT (C),E ED 59
To convert to a ts2068 poke a few values as follows:
LD BC,#FFF5 01 F5* FF OUT (C),D ED 51 LD C,#F6 0E* F6* OUT (C),E ED 59
If you've got a Fuller box, you can do the same mod, replacing F5 with 3F and F6 with 5F.
| Disk Drive |
|---|
[From Damien Guard:]
Another useful bit of information is that it is possible to add an Amiga 3.5" drive to a Spectrum +3 by Simply wiring the pins by name and providing it with some external power source (or in my case using an Amiga PSU with increased amp's to power the entire +3 and external drive in one go). There is a public domain formatter around to format it to 720k - this is quite useful as 3" disks are now a rarity and were never that reliable.
The added advantage is that PC users equipped with a copy of Sybex's shareware package 22Disk can read these files into a PC too.


BY EDWIN JAHIEL
THE WEDDING PLANNER * (2001) Directed by Adam Shankman. Screenplay, Pamela Falk, Michael Ellis. Photography, Julio Macat. Music, Mervyn Warren. Cast: Jennifer Lopez (Mary Fiore), Matthew McConaughey (Steve Edison) Bridgette Wilson-Sampras (Fran Donolly), Justin Chambers (Massimo), Judy Greer (Penny), Alex Rocco (Salvatore Fiore), Joanna Gleason (Mrs. Donolly), Charles Kimbrough (Mr. Donolly), et al . A Sony release. 101 minutes. PG-13
"Veni, vidi, vici" said Julius Caesar, "I came, I saw, I conquered." Re The Wedding Planner, I came, I saw, I was monumentally bored.after he first reel. In the latter, Mary (Lopez) is the powerhouse top handler in a chic firm of wedding consultants in San Francisco. The early sequences are amusing. She runs a wedding with the modern, technological efficiently of a three-star general. Self-assured and fully aware of her value to the firm, she is gunning for a partnership. The scenes are fast-moving and fast-talking, like a throwback to classic screwball comedies such as Twentieth Century, Bringing Up Baby or His Girl Friday.
But her private life, while minutely organized, is empty. Then, while planning the highly profitable wedding of heiress Fran, in a forced development a street accident causes Meeting Cute with pediatrician Steve (McConaughey).
An unlikely quid pro quo is reinforced by shovelfuls of artificiality in plot, cardboard characters, relationships, antagonisms, side-events ... Another thread has Mary's father Salvatore (Alex Rocco), surprise her with the arrival from Italy of Massimo (Justin Chambers) a childhood playmate. As absurdities escalate, Massimo wants to marry Mary.  His Italian as well as his Italian-accented English are atrociously false, as are daddy Salvatore's.
Shots, scenes and sequences pile up in a gauche, unintegrated script. The movie is like a cut-up cadaver that they are trying to make whole with stitches. There is no forward movement, no impetus, no suspense, no guessing in all this flab. You can see everything coming from Day Two as the "developments" increase to Himalayan heights of dumbness.
In the finale, the Lopez character and the pediatrician will be the new couple. Predictable. But it makes no sense that she blames herself for having been a control freak. She was simply doing her job. All this is nap-inducing with the force of five Tylenol-Plus.
There's an embarrassment of choices in attributing film guilt to the makers. A first-time director whose trade is choreography; a novice writer and her inexperienced collaborator; a regiment of producers, executive producers and co-producers; a plethora of companies ( the credits read: A Sony Pictures release of a Columbia Pictures and Interme presentation of a Tapestry Films/Dee Gee Entertainment/IMF production in association with Prufrock Pictures.) Too many cooks?
The screening of this feature was preceded by three unpromising previews. The Wedding Planner confirmed that after the mostly awful summer 2000 movies, and after their subsequent replacement by some good films year-end items (the usual pre-Oscar race), bad movies are back to reinforce the dismal norms of Hollywood.
By coincidence, a couple of hours after seeing The Wedding Planner, I caught on cable Gregory Nava's 1997 biopic "Selena," with Jennifer Lopez in the title role. It is about the Tejano singer who was murdered in 1995, at age 23, just as she was becoming a household name. It is just a good movie --but it feels even better when compared to The Wedding Planner.
A trivial note: Fran is played by the former Bridgette Wilson who married tennis player Sampras and now goes under the hyphenated name Wildon-Sampras.
A non-trivial note: for her role Lopez was paid 9 million dollars.
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


purchases / com.revenuecat.purchases / Entitlement
Entitlement
class Entitlement : Parcelable
An entitlement represents features or content that a user is "entitled" to.
Entitlements are unlocked by having an active subscription or making a one-time purchase.
Many different products can unlock. Most subscription apps only have one entitlement,
unlocking all premium features. However, if you had two tiers of content such as premium and
premium_plus, you would have 2 entitlements. A common and simple setup example is one entitlement
with identifier pro, one offering monthly, with one product.
See this link for more info
Types
Constructors
Properties
Functions
Companion Object Functions


This was my first attempt at fair isle knitting and it really was not as bad as I thought it was going to be. I had some difficulty at the beginning figuring out the two colour rib (you can’t just keep your purling colour to the front!) but other than that it was pretty straightforward.
I used the Katie’s Kep pattern - designed by Wilma Malcolmson for this year’s Shetland Wool Week festival (due to be held in September 2020 but cancelled due to COVID-19, of course). The pattern can be downloaded for free here. The pattern is easy to follow if you are a beginner, and there is even a Katie’s Kep knit-along Facebook group if you get stuck (I only discovered the group after I finished knitting my hat!)
I used Jamieson & Smith 2ply jumper weight yarn, as per the pattern instructions (yarn purchased from the Shetland Woolbrokers website).
I love how the hat turned out and think the colours look great!


Former IT Business Analyst looking to make a career switch to web development.
Send me an email
Etsy clone to buy and sell mugs.
Built using: Ruby on Rails, Backbone, HTML, CSS, and Bootstrap
View details »
Built using: Javascript


|
Marian
v1.6.0
A C++ GPU-specific Neural Machine Translation
|
#include <graph_group_multinode.h>
Public Member Functions | |
| virtual void | setScheduler (Ptr< Scheduler > scheduler) override |
| MultiNodeGraphGroup (Ptr< Config > options) | |
| virtual | ~MultiNodeGraphGroup () |
| void | update (Ptr< data::Batch > batch) override |
| void | load () override |
| void | save (bool final=false) override |
| void | save (Ptr< ExpressionGraph > graph, bool final=false) |
| Ptr< data::BatchStats > | collectStats () |
| virtual void | finalize () override |
| Public Member Functions inherited from marian::GraphGroup | |
| GraphGroup (Ptr< Config > options) | |
| virtual Ptr< data::BatchStats > | collectStats (Ptr< ExpressionGraph > graph, Ptr< models::ModelBase > model, size_t multiplier=1) |
Protected Member Functions | |
| Tensor | newTensor (int size, Ptr< Backend > backend) |
| virtual void | init (Ptr< data::Batch > batch) |
| void | setupMPI () |
| void | setupClients (Ptr< data::Batch > batch) |
| void | runBatchThroughClientGraphs (Ptr< data::Batch > batch) |
| void | calculateNodeSizes () |
| void | initClientCpuBuffers () |
| void | initClientCommOverlapVars () |
| void | initClientCommOverlapGpuTensors () |
| void | setupServerShards () |
| void | calculateShardSizes () |
| void | initShardGpuTensors () |
| virtual void | launchServerThread () |
| void | shutDownServerThread () |
| void | launchCommOverlapThreads () |
| void | shutDownCommOverlapThreads () |
| virtual void | synchronizeWithServerShards (Tensor newGrads, Tensor oldParams, int gpu, size_t batchWords=0) |
| void | execute (Ptr< data::Batch > batch) |
| virtual void | signalFinishedToServerShards () |
| void | loadDeviceConfig (std::vector< size_t > deviceConfig) |
Protected Attributes | |
| bool | initialized_ {false} |
| std::vector< Ptr< TensorAllocator > > | allocators_ |
| ThreadPool * | clientThreadPool_ |
| std::vector< Ptr< models::ModelBase > > | clientBuilders_ |
| std::vector< Ptr< ExpressionGraph > > | clientGraphs_ |
| std::vector< size_t > | devices_ |
| std::mutex | mutexClientInit_ |
| std::mutex | schedulerMutex_ |
| size_t | batchIter_ = 0 |
| std::thread * | serverShardThread_ |
| std::vector< float > | serverShardBufferCPU_ |
| std::vector< Tensor > | shardParams_ |
| std::vector< Tensor > | shardGrads_ |
| std::vector< Ptr< OptimizerBase > > | shardOptimizers_ |
| std::vector< std::mutex > | shardMutex_ |
| std::vector< int > | numberClientsOfNodes_ |
| std::vector< size_t > | nodeSizes_ |
| std::vector< size_t > | shardSizes_ |
| std::vector< std::vector< float > > | clientCommBuffersCPU_ |
| int | mpi_my_rank_ {0} |
| int | mpi_comm_world_size_ {1} |
| bool | clientCommOverlap |
| std::vector< std::thread * > | clientCommThreads_ |
| bool | stopClientCommThreads_ {false} |
| std::vector< Tensor > | clientSummedGradsGPU |
| std::vector< size_t > | clientSummedWordCounts_ |
| std::vector< size_t > | clientCommittedWordCounts_ |
| std::vector< Ptr< OptimizerBase > > | clientLocalOptimizers_ |
| std::vector< Tensor > | clientCommOverlapBuffersGPU_ |
| std::vector< bool > | clientCommOverlapBuffersFilled_ |
| std::vector< std::mutex > | mutexClientCommOverlapBuffersFilled_ |
| std::vector< std::condition_variable > | cvClientCommOverlapBuffersFilled_ |
| size_t | tau_ {1} |
| std::vector< std::mutex > | optDelayMutex_ |
| std::vector< size_t > | delay_count |
| std::vector< int > | totalBatchWords |
| std::vector< Tensor > | accGradients |
| std::vector< Tensor > | accGradientBuffer |
| Protected Attributes inherited from marian::GraphGroup | |
| Ptr< Config > | options_ |
| Ptr< OptimizerBase > | opt_ |
| Ptr< Scheduler > | scheduler_ |
| bool | finalized_ {false} |
| bool | scaleLearningRate_ |
| float | avgBatchWords_ |
Static Protected Attributes | |
| static const int | MPI_TAG_GRAD_PUSH_MSG_ {0} |
| static const int | MPI_TAG_GRAD_PUSH_ {5} |
| static const int | MPI_TAG_PARAM_PUSH_ {10} |
| static const unsigned int | MSG_INFO_SIZE_ {0} |
| static const unsigned int | MSG_INFO_CLIENT_ {1} |
| static const unsigned int | MSG_INFO_BATCHWORDS_ {2} |
| static const unsigned int | MSG_INFO_STATUS_ {3} |
| static const unsigned int | STATUS_NODE_TRAINING_ {0} |
| static const unsigned int | STATUS_NODE_FINISHED_ {1} |
Multi-node graph group for asynchronous training over multiple machines each with one or multiple GPUs
|
inline |
(Constructor) Call super class and initialize client graphs and builders.
|
inlinevirtual |
(Destructor) Shut down server shard thread and (if comm. overlap enabled) communication overlap threads.
|
protected |
Calculate the size of each node in the MPI world (cluster). Account for the edge case where the last node has fewer parameters because the model size is not perfectly divisible by the number of nodes.
|
protected |
Calculate the size of each shard on this node. Account for the edge case where the last shard has fewer parameters because the node size is not perfectly divisibly by the number of shards.
|
inline |
Collect statistics from first client's graph.
|
protected |
Execute given batch on this node, pushing/pulling the resulting gradients/parameters to/from the server shards or – if comm. overlap enabled – to/from the communication buffers, summing gradients locally if the communication thread is busy
| batch | Batch on which to perform forward and backward passes. |
|
protectedvirtual |
Setup training environment and launch server thread and (if enabled) client communication overlap threads.. Includes setting up MPI, node and shard sizes, clients, server shards and communication overlap stuff.
Setup training environment and launch server thread and (if enabled) client communication overlap threads. Includes setting up MPI, node and shard sizes, clients, server shards and communication overlap stuff.
|
protected |
Initialize GPU tensors required for overlapping client computations and communication. Includes secondary buffers for params/grads, buffers for locally summing gradients, and local optimizers to apply received gradients to client parameters.
|
protected |
Initialize variables required for overlapping client computations and communication. Includes summed and committed word counts, buffer flags, mutexes and condition variables.
|
protected |
Initialize a CPU buffer for each client on this node for storing gradients or parameters. Required for sending GPU data through MPI to other nodes (GPU -> CPU -> MPI network).
|
protected |
Initialize the GPU tensors for storing the parameters and gradients of each server shard.
|
protected |
Launch independent threads which continually synchronize their client's gradients/parameters whenever the respective communication buffers are full.
|
protectedvirtual |
Launch independent thread which continually receives gradients assigned to this shard from any client, runs the shard optimizer and sends back the updated parameters.
|
inlineoverridevirtual |
Load models from disk if file exists and setting is not disabled
Implements marian::GraphGroup.
|
inlineprotected |
Load the GPU configuration of this node (i.e. which GPUs to use) and the number of GPUs on the other nodes.
|
protected |
LocalOptimizers related variables Allocate new tensor on given GPU and store allocator.
Allocate new tensor on given GPU and store allocator.
|
protected |
Initialize the graphs (models) of all clients on this node with the given batch.
|
inlineoverridevirtual |
Save model of first client's graph to disk
Implements marian::GraphGroup.
|
inline |
Save model of given graph to disk.
|
overridevirtual |
Set given scheduler to register training observers on the shard optimizers.
Implements marian::GraphGroup.
|
protected |
Setup clients that will compute gradients and communicate them with the server shards. There is one client per GPU.
|
protected |
Setup MPI world size and rank of this node.
|
protected |
Setup server shards that will receive gradients from clients, apply them to their part of the global parameters, and send them back to the same clients. There is one server shard per GPU. (Each GPU acts both as a client and as a server shard.)
|
protected |
Safely shut down the launched communication overlap threads
|
protected |
Safely shut down the launched server shard thread.
|
protectedvirtual |
Notify server shards that this node has finished training.
|
protectedvirtual |
Send new gradients to the server shards and receive the updated (global) parameters.
| newGrads | Gradients to send |
| oldParams | Parameters to replace |
| gpu | GPU/client performing synchronize (to access appropriate buffers etc.) |
| batchWords | Number of batch words to pass to server shard optimizers |
|
inlineoverridevirtual |
Update any client model with given batch if batch is assigned to this node.
Implements marian::GraphGroup.
|
protected |
Memory allocators for tensors (GPUs).
|
protected |
Batch number counter used for evenly distributing mini-batches across nodes.
|
protected |
Graph builders for clients (which run forward and backward passes).
|
protected |
CPU buffer for sending gradients and receiving parameters via MPI.
|
protected |
Word counts of clients submitted to overlapping/communication thread for current mini-batch.
|
protected |
Whether client computations should continue while gradients and parameters are being exchanged with server shards.
|
protected |
Flags to indicate whether a client's communication buffer is filled, in which case the communication thread will exchange the data with server shards.
|
protected |
GPU buffers used by clients to copy gradients to for use by the communication threads.
|
protected |
Threads for client communication overlap. They send gradients and receive gradients to/from the server shards when the communication buffer is filled.
|
protected |
Graphs of clients.
|
protected |
Optimizers used locally by clients to apply gradients from the communication thread to their graph's parameters.
|
protected |
GPU buffer (tensor) to sum up gradients computed by the clients. Used to enable clients to proceed with computations while waiting for communication channel to become available.
|
protected |
Summed word counts of clients. Used for overlap purposes.
|
protected |
Thread pool to enable clients to run concurrently.
|
protected |
Condition variable to notify communication threads that their buffers have been filled.
|
protected |
Devices (GPUs) on this node.
|
protected |
Whether graph group has been properly initialized with a first batch.
|
protected |
Number of nodes in MPI world (cluster).
|
protected |
MPI rank of this node.
|
staticprotected |
Flag to indicate that an MPI message contains gradient (client -> server).
|
staticprotected |
Flag to indicate that an MPI message contains message info before sending the gradient (client -> server).
|
staticprotected |
Flag to indicate that an MPI message contains parameters (server -> client).
|
staticprotected |
Message info indices: 0 = size; 1 = originating client; 2 = number of batch words; 3 = status of node
|
protected |
Mutex to enable communication thread to wait for its buffers to be filled.
|
protected |
Mutex to ensure clients are uniquely assigned to graphs and builders.
|
protected |
Number of parameters allocated (sharded) to nodes.
|
protected |
Number of clients on nodes in MPI world (cluster).
|
protected |
Mutex to avoid race conditions in scheduler.
|
protected |
Main server CPU buffer to enable MPI sending and receiving (GPU -> CPU -> Network -> CPU -> GPU).
|
protected |
Main server thread that continually receives gradients from a client, copies them to the shards on this node, runs the shard optimizers and then returns the result to that client.
|
protected |
GPU buffer to store gradients received by this node's server thread, so that they can be applied to the shard parameters.
|
protected |
Mutex to enforce critical sections in server shards.
|
protected |
Server shard optimizers used to update global parameters with gradients received from clients.
|
protected |
Parts of the global parameters that are assigned to server shards on this node.
|
protected |
Number of parameters allocated to shards on THIS node.
|
staticprotected |
Status of node: 0 = training; 1 = finished. Used to indicate to other nodes whether this node is still training.
|
protected |
Flags indicating whether the client overlapping communication threads should stop.
|
protected |
Variables for optimizer delay


--- layout: post authors: ["Uwe Hilgert"] title: "Workshop at iPlant" date: 2015-03-17 time: "10:45:00" tags: ["iPlant"] ---
The iPlant Software Carpentry Workshop in February at the University of Arizona in Tucson was an awesome realization of iPlant's and BIO5's collaborative nature. Bringing together iPlant, BIO5, the UA and Software Carpentry, this workshop served a large group of students and staff from a wide variety of backgrounds and a wide array of interests. 53 participants registered within 36 hours of publicizing the workshop. Participant demographics were as follows:
| Affiliation | |
|---|---|
| UA | 49 |
| ASU | 3 |
| NAU | 1 |
| Level | |
| Students | 11 |
| Graduate Students | 25 |
| Research Associates & Postdocs | 10 |
| Faculty | 3 |
| System Administrators | 2 |
| Research Staff | 2 |
| Discipline | |
| Bio Sciences | 24 |
| MIS & Computer Sciences | 21 |
| Statistics & Biostatistics | 4 |
| Engineering & Physics | 4 |
In Arizona, iPlant and AEGIS currently are working with ASU and NAU to support these institutions in offering respective workshops. The fact that demand by far exceeded workshop seats clearly shows that there is a strong need for this kind of training. Therefore, proposed next steps include increasing the number of instructors that are not only able to teach Software and Data Carpentry workshops, but that will use iPlant CI to do so.
iPlant developer Jonathan Strootman, and Plant Sciences postdoc Naupaka Zimmerman, instructed the workshop. They were assisted by 7 helpers, including myself and:
We are especially fortunate to have, with Jonathan and Naupaka, 2 Software Carpentry certified instructors on campus and that these two had been teaching already four workshops together in the past. What that means is that, unlike many other SWC workshops where instructors may co-teach that have not seen each other before, Jonathan and Naupaka had had (and used) the chance to build a coherent curriculum that took attendees all the way from Excel to publishable analyses and diagrams, using desktop GUIs, command lines, the Internet, and the cloud. Specifically, the curriculum entailed:
The workshop was a tour de force that, with students and postdocs, enabled exactly the right group of users to move their science to the next level.


Accessibility Guidelines
accessibility
Designing and developing your application to reach AA+ standards is good for all users, not just disabled users. Thinking about less-than-perfect contexts in which a user may try to access your application will make things more clear and direct throughout. This page will get you on the right track for ensuring everyone can have the best experience possible accessing and interacting with your app.
Accessible Design Overview
Step one to an easily accessible application is designing with accessibility in mind from the start. This includes foundational details like:
HTML Markup
One of the most direct ways to make your application accessible is to write proper, semantic code. Screen readers and other accessibility tools depend on markup tags to convey what kind of information or interaction the user has encountered.
In addition to simply using the proper markup, a few other attributes on these tags will make your application even better, and are highly encouraged:
alt
description within images, unless they are purely decorative.summary
to tables.id
on form inputs with afor
attribute on the respective label to link them together.Keyboard Accessibility
Not all users have the ability to use a mouse. To allow them to still use your application, all actions that are activated with a mouse click should also be fully accessible when only using a keyboard. Forms should keep a logical tab order, and the submit button should activate with the Return key.
Another important thing to keep in mind is including visual queues on your interface for what is currently focused or selected.
Voice Accessibility
Blind or sight-impaired users will be listening to (rather than looking at) your application. When semantic markup is insufficient, WAI-ARIA attributes should be used to provide clarity.
Additional Resources
This guide is a high-level overview of accessibility best practices. More information about accessibile web practices can be found at W3C's Web Accessibility Initiative.


Introduction
Computer modules
Clock circuit: Timer
List of ordered components
Arithmetic Logic Unit (ALU)
4-bit full adder design
Planned
improvements
components
Registers
Random access memory modules (RAM)
Program counter
Output register
CPU control register