I know the spectrum had the IM2 mode, but can I start multiple threads running simultaneously?
In example: can I play a song, while transfering large amounts of data, like in large ldi code blocks?
; start block copy ldi ldi ldi
Retrocomputing Stack Exchange is a question and answer site for vintage-computer hobbyists interested in restoring, preserving, and using the classic computer and gaming systems of yesteryear. It only takes a minute to sign up.Sign up to join this community
Interrupt Mode 2 is not a ZX Spectrum feature, it's a feature of the Zilog Z80 CPU itself.
Per Raffzahn@'s comment, the answer to your question kinda depends on what kind of threading you're talking about.
There are generally two levels of abstraction when it comes to multithreading, and two meanings of the term. First is the hardware level Simultaneous Multi-Threading (SMT) - a feature of the superscalar CPUs that allows multiple instructions to be executed in parallel. Intel calls its implementation of this feature in their CPUs like Pentium 4 HT and Core i7 'Hyper-Threading'. Second is the software level multi-threading - a model of concurrent execution without true parallelism, but that makes it appear to a user that things are executed at the same time by quickly switching back and forth between multiple threads of execution. This is what all multitasking operating systems implement, even on CPUs that do support SMT and have multiple cores, because generally there are way more processes and threads that need to be executed than there are execution units, CPU cores, and CPU sockets in a computer system. On the systems with only scalar CPUs the second variant of multi-threading is the only available option.
Now to go back to Z80 and Spectrum - since Z80 is a scalar CPU, the first kind of multi-threading, the true simultaneous one, with parallel execution of the instructions, is not possible. But the second kind, where threads execute concurrently, but not at the same time in parallel, is definitely possible. You don't necessarily need memory protection for this, all you need for pre-emptive multitasking is interrupt mechanism (the implication being even without interrupt mechanism it should still be possible to implement cooperative multitasking like in Windows 1.x/2.x/3.x and classic macOS). In fact, there is an operating system called SymbOS that supports preemptive multitasking on machines like Amstrad CPC and MSX2 family. Those machines use Z80 CPU and lack memory protection just like ZX Spectrum. The main problem with running SymbOS on original ZX Spectrum is that the OS needs at least 128KiB RAM and currently doesn't support all of Spectrum's hardware. NOTE: the difference between threads and processes is in memory sharing, not scheduling and execution, so for this question I use them somewhat interchangeably.
The specifics of interrupt mechanism, registers, etc., mostly determine not whether it is possible to implement multitasking/multithreading, but how easy or difficult it is. E.g., the register set and memory model of Z80 are limited enough to make C compiler implementation more difficult, and the resulting compiled code to be much less efficient than hand-written assembly, but it has been done, so it's definitely not impossible to do so. In general, you can find plenty examples of programmers achieving many great things on a very limited hardware, it's more a question of persistence and ingenuity than possibility. And a cost/benefit tradeoff.
All of the popular, early 8-bit CPU's support hardware and software interrupts. Therefore, they can all theoretically support preemptive multitasking, which I think is what this question is really asking.
The relevant article from Wikipedia states:
In simple terms: Preemptive multitasking involves the use of an interrupt mechanism which suspends the currently executing process and invokes a scheduler to determine which process should execute next. Therefore, all processes will get some amount of CPU time at any given time.
Of course, more advanced CPU's, such as the i8086/88 and the Motorola 68000, had additional architectural features to help OS developers to implement a practical multitasking environment. The solutions for 8-bit uP (there have been many valiant attempts) tend to be proof-of-concept novelties. The task switching performance and memory constraints usually make it too slow to be practical for real-world application usage.
A notable exception to the above is OS-9, which provides a quite functional Unix-like multitasking OS for the 6809 microprocessor. It can work with only 64KiB, but excels on a system with more memory. OS-9 is the main reason that Tandy Color Computer 3 users frequently expanded these 8-bit systems to 512KiB in the late 1980's.
Not simultaneously, as it only has one CPU, but it has a 50 Hz timer interrupt, so it can do time sharing processing, provided that programs are well behaved, as there is no memory protection and no supervisor mode in the CPU.
As a proof of concept, I've written a small task scheduler and tasks that are executed in a time sharing fashion (with a 20ms quantum)
; (c)2020 Miguel Angel Rodriguez Jodar, for Retrocomputing (Stack Exchange) ; Demonstration of a very simple scheduler and two tasks running on the ZX Spectrum ; Licensed under GPL 3.0 org 0c0e0h Main: halt ;let a whole interrupt happen so we have plenty of time until the next one di ld a,0c0h ld i,a im 2 ;set scheduler interrupt ei ld sp,StackTask1Top ;load task1 jp Task1 ;and execute it org 0c0ffh dw Scheduler Scheduler: push af ;save current process state it its stack push bc push de push hl exx push af push bc push de push hl exx push ix push iy ld hl,0 add hl,sp push hl ;get current stack pointer (just interrupted task) ld hl,(CurrentProc) inc hl pop de ld (hl),e inc hl ld (hl),d ;and store it into process table inc hl ld a,(hl) ;check if end of processes in table or a jr nz,NotEndOfTasks ld hl,TaskTable ;if so, start from the beginning of the table NotEndOfTasks: ld (CurrentProc),hl ;select next process inc hl ld e,(hl) inc hl ld d,(hl) ex de,hl ld sp,hl ;load its stack pointer pop iy ;and jump to it, restoring its state pop ix exx pop hl pop de pop bc pop af exx pop hl pop de pop bc pop af ei ret ;---------------------------------------------------------- ;TASK1: CALCULATE AND PRINT 16-BIT PRIME NUMBERS ;---------------------------------------------------------- Task1: ld ix,DataTask1 ;environment for this task (pointers for PRINT routine) TryNextNumber: ld bc,2 ;BC holds the current divisor to try with TryDivisor: ld hl,(NextCandidate) ld d,b ld e,c KeepSub: or a sbc hl,de jp m,NotDivisible jp z,NotPrime jp KeepSub NotDivisible: inc bc or a ld hl,(NextCandidate) sbc hl,bc sbc hl,bc jp m,ItsPrime jp TryDivisor NotPrime: ld hl,(NextCandidate) inc hl ;HL is not prime, try the next odd number inc hl ld (NextCandidate),hl jp TryNextNumber ItsPrime: ld hl,(NextCandidate) ld (ix+SCRROW),6 ;print number at position 6,14 ld (ix+SCRCOL),14 call CalculateScrPos call PrintNumberHL jp NotPrime NextCandidate: dw 3 ;we begin with 3 to test if prime ;---------------------------------------------------------- ;TASK2: print character set ;---------------------------------------------------------- Task2: ld ix,DataTask2 ld a,' ' AnotherChar: call PrintCharA ;just print the character set, from ASCII 32 inc a jp p,AnotherChar ;to ASCII 127 and start over again ld a,' ' NotResetChar: jp AnotherChar ;---------------------------------------------------------- PrintNumberHL: ld bc,10000 call Div ld a,d add a,'0' call PrintCharA ld bc,1000 call Div ld a,d add a,'0' call PrintCharA ld bc,100 call Div ld a,d add a,'0' call PrintCharA ld bc,10 call Div ld a,d add a,'0' call PrintCharA ld a,l add a,'0' call PrintCharA ret Div: ld d,0 KeepDiv: or a sbc hl,bc jp m,Negative push af inc d pop af ret z jp KeepDiv Negative: add hl,bc ret PrintCharA: push af push bc push de push hl ld l,a ld h,0 ld a,(ix+SCRPOS) or (ix+SCRPOS+1) call z,CalculateScrPos ld e,(ix+SCRPOS) ld d,(ix+SCRPOS+1) push de add hl,hl add hl,hl add hl,hl ex de,hl push hl ld hl,(23606) add hl,de pop de ld b,8 LoopPrintScan: ld a,(hl) ld (de),a inc hl inc d djnz LoopPrintScan pop hl ld a,(ix+SCRCOL) inc a cp (ix+ENDCOL) jp z,NotEndColumn jp nc,EndColumn NotEndColumn: ld (ix+SCRCOL),a inc hl ld (ix+SCRPOS),l ld (ix+SCRPOS+1),h jp EndPrintA EndColumn: ld a,(ix+BGCOL) ld (ix+SCRCOL),a ld a,(ix+SCRROW) inc a cp (ix+ENDROW) jp z,NotEndRow jp nc,EndRow NotEndRow: ld (ix+SCRROW),a jp CalcAndEnd EndRow: ld a,(ix+BGROW) ld (ix+SCRROW),a CalcAndEnd: call CalculateScrPos EndPrintA: pop hl pop de pop bc pop af ret CalculateScrPos: ; HL = (row/8)<<11 + (row%8)<<5 + col push af push hl ld a,(ix+SCRROW) and 0f8h or 40h ld h,a ld a,(ix+SCRROW) and 7 sla a sla a sla a sla a sla a or (ix+SCRCOL) ld l,a ld (ix+SCRPOS),l ld (ix+SCRPOS+1),h pop hl pop af ret ;Table used by the scheduler to get the stack pointer for ;the next task to execute. ;The end of the table is marked by a processor with ID 0 TaskTable: db 1 ;ID dw StackTask2Top-20 ;stack pointer db 2 ;ID dw StackTask2Top-20 ;stack pointer db 0 ;ID (end of table) dw 0 ;null stack CurrentProc: dw TaskTable ;current position within TaskTable ds 64 StackTask1Top: dw 0 ds 64 StackTask2Top: dw Task2 BGROW equ 0 ENDROW equ 1 BGCOL equ 2 ENDCOL equ 3 SCRROW equ 4 SCRCOL equ 5 SCRPOS equ 6 DataTask1: db 0,11,0,31 ;min row, max row, min column, max column. Window of (0,0,31,11) db 0,0 ;current row,col position dw 0 ;screen memory address of such position DataTask2: db 12,22,2,28 ;Text window (12,2,22,28) db 12,2 dw 0 end Main
Yes it is.
Any processor is capable of multithreading if software is written to implement multithreading.
Of course the Spectrum OS doesn't support it. You would have to replace the OS with a multithreading executive of your own. This is not as complex as you might think if you are only attempting to produce a small controller of some sort. You could treat this as a challenge to learn how multithreading works.
Note that multithreading is NOT the same as multitasking. So references to implementing multitasking on the Spectrum will not help you. Similarly, you only need a single processor. I suspect the Spectrum is a good machine to try this on. Although I've never tried it on the Spectrum myself, I've done it on other machines of that era.