-
Announcing kits on the last day of 2024 !
12/31/2024 at 12:44 • 0 commentsBoth prototypes that I have built worked without any HW problem (of course there were SW problems now and then). On the first one, I ran The Great Escape (in its self-running mode) continuous for almost a week. This gives confidence.
SELLING KITS
I could now soon start with selling kits. If you are interested, let me know in the comments or in a private message.
The price of the kit will be around 100 Euro (USD 110) (Shipping costs not included). You will need to have some experience in building electronic kits.
- You buy the bare pcb from me, for 30 Euro. I will make instructions and a detailed list of parts.
- You buy all parts from an electronic parts supplier, like Mouser or Digikey. Cost around 65 Euro, free shipping at several suppliers.
- An enclosure is not yet included. But the pcb is designed to fit in a Hammond RM2055S enclosure.Selling complete Isetta's might come later.
What more do you need
You need several other things, that you might have lying around:
1. A universal programmer to program the three flash chips with microcode. Or the Isetta programmer pcb.
2. A Raspberry Pi (RPi), for transferring files to the 32MB on-board storage of Isetta
3. A 5 volt power supply with USB-B plug
4. A monitor (CRT or LCD) with VGA connection, with cable and 15-pin connector
5. A PS/2 keyboard
6. A PS/2 mouse (not needed for all applications)
7. A micro-SD memory card (might be needed by some applications)
8. A speaker with amplifier, with 3.5mm (stereo) plug. For sound.Programming the microcode
You can use a regular, universal programmer to program the microcode in the three flash chips (PLCC44 case). (I have not done that myself yet, I use the RPi). But, it is very likely that new microcode versions will appear, so you have to use sockets on the pcb to be able to reprogram them. Sockets can be unreliable, however.
You can also use the special Isetta programmer pcb (that I will sell you for 10 Euro). It needs 11 TTL chips, and connects the Isetta to the Raspberry Pi. The Raspberry Pi can now program the microcode flash chips while they are soldered on the board. The programmer pcb also allows the RPi to send files to the on-board storage, and allows low-level testing and single-stepping the Isetta. The low-level testing is VERY useful if you soldered your own Isetta.
The RPi also needs a monitor (HDMI connection), USB keyboard, and power supply.
Note that, as soon as the microcode is programmed and files are transferred, Isetta can work fully stand-alone without needing the RPi or the programmer pcb. In the future, Isetta should be able to fetch files from the micro-SD card, or through its WiFi connection.
If you only need file transfer, you can connect the RPi with 4 wires to Isetta, without needing the programmer pcb.
Future plans
- Make a good description of the available I/O instructions
- Provide more microcode support for writing to the screen
- Implement a nice sound generator
- Support micro-SD card and WiFi
- Have an editor/assembler on Isetta
- Add applications
- Have a nice operating systemI could use help ! But you'll first need the hardware...
What did I do since the last log ?
- Write text to the new 640x400 screen
- Implement the RST #38 interrupt (interrupt mode 1) at each video frame. Can be enabled/disabled with EI, DI instructions.
- Made a mouse driver in microcode. An I/O instruction can now read messages from the mouse.
- Completed several changes that were needed due to HW changes on the new pcb
- Created an output instruction to do bankswitchingEND OF THE YEAR
Best wishes for the new year ! That you and your loved ones all be happy and healthy !
-
New pcb, new video mode
11/17/2024 at 13:07 • 0 commentsA few weeks ago I received the new pcb (ISA2442). Several things have changed with respect to the first pcb, so several software things had to change.
One of the main changes was the 640 x 400 pixel mode. On the first pcb, a byte from memory represents 8 pixels, and the byte tells for each pixel if it has foreground color or background color (these colors being programmable).
On this new pcb, a fetched byte has information for only two pixels (pixels last 40nS). So we can simply have 4 bits (16 colors) per pixel. The color of each of the 256000 pixels can be set independent from the others.
Today I got this new mode to work.
On the picture you see mainly uninitialized memory, that gives a random, noisy pattern of colored pixels.
In the first row I programmed the 8 basic colors, and the second row shows the same colors with low intensity. So the upper two rows show all 16 available colors in this mode.On the last row I made horizontal and vertical stripes, and a few checkerboard-patterns of two colors, that give the
impression of another color (orange, and variants of green and blue).You might ask why this is only 400 lines instead of the standard 480. This is because the processor can not run the user program during the active part of the video frame, because it is busy rendering the screen. By using only 400 lines, the processor has more time to run the user program.
The 320 x 200 pixel mode (used for the ZX Spectrum screen) was essentially unchanged.
-
Second PCB version ordered
10/19/2024 at 08:49 • 1 commentYesterday I finally ordered the new PCB. This PCB is sponsored by the kind people of PCBWay, the well-known high-quality PCB fabrication company from China. During production you can always check in which production state your pcb is, and I watched an impressive series of videos that show all steps in the production process.
This is the KiCad 3D-impression of the new PCB:
With respect to the first PCB, the main changes are:
- fix mistakes (VGA connector)
- add Mouse connector
- add HW for sending info to keyboard (and mouse)
- add memory bank select
- change video pixel/color generation for 640x400 mode
- change WiFi module type
- simple 4-wire connect to Raspberry Pi (for file transfer)
- instead of a SPI header, a micro-SD card connector is mounted
- changed SPI reading, it can now be done in 2 cycles/bit (instead of 5) -
Changed bankswitching system
10/14/2024 at 12:59 • 0 commentsIn this previous log I described the new bankswitching system. But I wanted to add another mode (Linear mode). It would be nice if a program could access the whole 512kB memory without bankswitching. The address would be in a 3-byte register, with 19 significant address bits. A simple processor with 19-bit addresses could then be implemented.
For programming the Z80 or 6502, it is not needed to know all this. You only need this if you implement a bankswitching system or a new processor microcode that accesses the whole memory. Probably it is only for myself.
I came up with the following: (All images can be enlarged by clicking them)
LINEAR MODE
The functions are essentially the same as in the mentioned previous log, but I added the Linear mode. For program code, the upper bits A18 and A17 come from a bank select register, and A16 comes from microcode. For data, upper bits A18, A17 and A16 come from another bank select register. So, the upper three address bits can be different for program and data. Also new is, that this bank select register can be loaded from a memory-resident register in a single cycle.
APPLICATION and KERNEL
It is intended that, for Application and Kernel modes, BS_2 and PC18 are always written with the same value. That makes our table slightly less complex:
This diagram shows all modes. The modes are controlled by signals in the microcode and the bank select registers, as shown.
HARDWARE IMPLEMENTATION
The result is a bit complex because I didn't want to add more chips. Another complication is, that the address must be available quite fast, so there can not be long propagation delays. The following diagrams (there are seven) explain all modes, and also the way it is implemented. The used colors are the same as in the table above.
You find the bankswitching control registers at the top of the diagram. The microcode control signals are at the left side. At the right side, you find the five highest address bits, their value indicated by '0' or a color.
REGISTER MODE
This is the simplest. The (memory resident) registers are in the very first 256 bytes of memory. This mode is selected by CTL_M1 = 1. The CTL_M1 signal switches all upper address outputs OFF, so all upper address bits are zero. The register in question is selected by several bits in the microcode.
The value in the bank select registers is irrelevant.
TABLE / ZPAGE
In this mode only the lower 8 bits of a data or code address are used. The other bits are zero, but A16 and A17 come from microcode. It is used for 6502 Z-page, for an identity table and a shift-right table. If A16=0 and A17=0, this mode can not be used (that is the domain of the memory-resident registers).
This mode is selected by microcode signals only. The value in the bank select registers is irrelevant.
NATIVE
This is the main addressing mode of the first prototype. The upper address bits are 'hardwired' in the microcode. Bit A18 can no longer be used, that should not be a big problem because this will no longer be the main addressing mode.
This mode is selected by microcode signals only. The value in the bank select registers is irrelevant.
APPLICATION MODE
Here, the upper three address bits are freely programmable in the bank select register U50. It is advisable to set BS_2 equal to PC18, otherwise the topmost address bit is different for code and data (also for the following two modes kernel slot1/slot3).
This mode is selected by microcode signals in combination with APPL = 1. Yes, the APPL signal got it's name from this mode.
KERNEL SLOT1
The 64k addressing range is split up in four slots:
- Slot 0 ( 0000 - 3FFF )
- Slot 1 (4000 - 7FFF)
- Slot 2 (8000 - BFFF)
- Slot 3 (C000 - FFFF)
In this kernel-slot1 mode, normally the microcode selects which 64k bank is selected (orange signals). But if the address is in slot 1, the bank number comes from the bank select register (light green). The 16k block within that bank is also selected with that register (dark green).
This mode is selected by microcode signals in combination with APPL = 0 and SLOT3 = 0.
The 74AC151 is enabled because APPL is low. It looks at the value of the A15/A14 bits of the relevant register (DPH or PCH). If this bits indicate the range of Slot 1 (4000 - 7FFF), the multiplexers at the output will change to the banknumber and block number that are stored in the bankswitch register.
KERNEL SLOT3
Here, if the address is in slot 3, the bank number comes from the bank select register. But for the program counter, the bank number is always in microcode.
This mode is selected by microcode signals in combination with APPL = 0 and SLOT3 = 1.
The 74AC151 is enabled because APPL is low. It looks at the value of the A15/A14 bits (only if the DPH register is selected). If this bits indicate the range of Slot 3 (C000 - FFFF), the multiplexers at the output will change to the banknumber and block number that are stored in the bankswitch register. If the PC is in the range of slot 3, this will not be detected.
LINEAR MODE
For program code, the upper bits A18 and A17 (pink) come from the bank select register, and A16 (orange) comes from microcode. For data, upper bits A18, A17 and A16 come from another bank select register. So, the upper three address bits can be different for program and data.
For addressing data, this mode is identical to the APPLICATION mode.
But for addressing code (with the PC), the microcode must set CTL_BK2. That will cause different A16 and A17 bits to be selected by U53. Of course, it is not required that BS_2 is equal to PC18 in this mode.
-
Fixing the LDIR instruction
09/17/2024 at 20:18 • 0 commentsIn the past weeks, I've been working on a new pcb design. That's now almost done.
This weekend I looked at my TODO list, and I found, no surprise, the LDIR instruction.
In one of the previous logs I mentioned that there was an error in the LDIR instruction. It didn't behave correctly when an interrupt occurred.
What is the LDIR instruction ?
In the Z80 processor, this instruction can copy a whole section of memory in just a single instruction. It takes three 16-bit sized values as input:
- A source address in register pair HL
- A destination address in register pair DE
- A byte count in register pair BCLDIR stands for Load, Increment, Repeat.
It will do the following actions:
- read a byte from the address at location HL
- write that byte at location DE
- increment the addresses in HL and DE
- decrement the byte counter in BC
- if the byte counter is not zero, repeat the actions
- if an interrupt occurs during this instruction, it will be handled and the actions will continue after interrupt is handled.LDIR has a 2-byte opcode: ED B0
Optimizing the LDIR
The LDIR was already optimized, with 10 cycles per copied byte, but I had the strong feeling that something better was possible. I didn't try to find the problem in the old version, just started again from scratch.
Isetta has only a single hardware data pointer. But nothing keeps us from using the program counter as a data pointer ! For a normal instruction, that would cost way too many cycles, but for a repeated transfer instruction this will pay off. The LDIR microcode should just save the PC to a temporary location, and copy the HL register pair into the PC. Now the PC can be used to get the bytes out of memory. When all actions are completed, the PC is copied back to HL, and PC is restored from the temporary location. The existing, broken LDIR version also had this feature.
And as a bonus, the PC register can increment in the same cycle where it addresses a byte to be fetched.The destination pointer (register pair D E) is copied to the hardware registers DPH and DPL. The byte, fetched with the address in PC, can now be stored at the address in DPH/DPL. There is a microinstruction that increments DPL, it can also detect a carry, but there is no possibility to increment the DPH register, because it can not be accessed after the change that is described HERE. But we could increment the D register and re-write DPH with the result. This will at least take 3 more cycles (microinstructions).
Then we must decrement the count in the BC registers. So first decrement C (2 cycles) and, when it drops below zero, decrement B (also 2 cycles).
The problems are in the counting, and in the increment of the upper byte (DPH),
But if we unroll the loop, in sections of 8 transfers, we don't have to count each byte. We simply subtract 8 from the LSB of the count, at the beginning of the unrolled loop. But when the count is lower than 8, we jump to a slow old-fashioned loop. This will decrement the MSB of the count when that is needed, and then go back to the unrolled loop. The slow loop will also check for zero to determine if the instruction has completed.
It would be nice if we never had to increment the upper byte (DPH)... What if we never increment the upper byte in the unrolled loop ? Before the unrolled loop starts, we could check if the lower byte (DPL) would overflow in that loop. That would happen if that lower byte is higher than 0xF7 (247), and in that case we do the old-fashioned loop, and when the upper byte has been incremented, we go to the unrolled loop again.
Now, the sections of the unrolled loop have only 3 cycles:
- A <- (PC++)
- (DPH/DPL) <- A
- inc DPLPseudo code of the fast copy loop:
- if LSB of the count < 8, goto slow loop
- if LSB of destination > 247, goto slow loop
- count = count - 8
- copy the DE registers (destination) into DPH/DPL
- destination = destination + 8;
- do the unrolled loop 8 times
- go back to fast copy loopIn this fast loop, there are 8 cycles of overhead, that brings the average number of cycles per byte transfer to 4.
Actually, the slow loop that occasionally must be executed during big transfers will bring the average to around 4.4.Overhead at beginning and end of the instruction (saving PC, put HL in PC, saving A, restoring them) cost 27 cycles.
And here is the microcode
This listing is produced by the Javascript microcode generator. I added some comments.
---- page 0 ED B0 LDIR ---- // start with checking the interrupt. If interrupt, go to addr 32 in page_z80_ix 001600 066224 0132C0 F:[to_ir <- lit32,p_z80_ix] T:[reg_v <- acc_a] 001602 012220 010022 F:[to_dpl <- 0] T:[to_t <- pcl] 001604 013259 013259 reg_tl <- acc_t 001606 01524D 01524D pcl<-pch, to_pch <- reg_l 001608 010022 010022 to_t <- pcl 00160A 013258 013258 reg_th <- acc_t 00160C 01524C 01524C pcl<-pch, to_pch <- reg_h // obtain the value 0x98 (microcode can not use arbitrary immediate values) 00160E 0B0222 0B0222 to_t <- asl(lit8) 001610 0E0222 0E0222 to_t <- or(acc_t, lit8) 001612 0E0246 0E0246 to_t <- or(acc_t, reg_128) // store the opcode (0x98) of the fast loop at temp location reg_vga_acc 001614 01325B 01325B reg_vga_acc <- acc_t 001616 00625B 00625B to_ir <- reg_vga_acc,p_z80_ed // jump to opcode 0x98 001618 0132C0 0132C0 reg_v <- acc_a -- 0/13 + 2 cycles ---- page 0 ED 98 LDIR-LOOP ---- 001300 014249 014249 to_a <- reg_c 001302 3C42A2 3C42A2 to_a <- sub(acc_a, lit8) 001304 610A4B 610A4B to_t <- reg_e,tc_to_f // if F=0, move lit32 (0x20) to IR to go to the slow loop 001306 006224 0D0222 F:[to_ir <- lit32,p_z80_ed] T:[to_t <- add(acc_t, lit8)] 001308 612A4B 612A4B to_dpl <- reg_e,tc_to_f 00130A 01124A 006224 F:[to_dph <- reg_d] T:[to_ir <- lit32,p_z80_ed] 00130C 0132C9 0D0220 F:[reg_c <- acc_a] T:[to_t <- add(acc_t, 0)] 00130E 01324B 01324B reg_e <- acc_t 001310 014008 014008 to_a <- (pc++) 001312 E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 001314 2A2122 2A2122 to_dpl <- inc(dpl) 001316 014008 014008 to_a <- (pc++) 001318 E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 00131A 2A2122 2A2122 to_dpl <- inc(dpl) 00131C 014008 014008 to_a <- (pc++) 00131E E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 001320 2A2122 2A2122 to_dpl <- inc(dpl) 001322 014008 014008 to_a <- (pc++) 001324 E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 001326 2A2122 2A2122 to_dpl <- inc(dpl) 001328 014008 014008 to_a <- (pc++) 00132A E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 00132C 2A2122 2A2122 to_dpl <- inc(dpl) 00132E 014008 014008 to_a <- (pc++) 001330 E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 001332 2A2122 2A2122 to_dpl <- inc(dpl) 001334 014008 014008 to_a <- (pc++) 001336 E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 001338 2A2122 2A2122 to_dpl <- inc(dpl) 00133A 014008 014008 to_a <- (pc++) // if F=1, interrupt not active, go back to the start of the fast loop 00133C 010220 00625B F:[to_t <- 0] T:[to_ir <- reg_vga_acc,p_z80_ed] 00133E E13980 E13980 (dph|dpl) <- acc_a,irq_to_f 001340 010022 010022 to_t <- pcl 001342 01324D 01324D reg_l <- acc_t 001344 010259 010259 to_t <- reg_tl // Before going to interrupt code, sub_0(acc_t,1) will do PC = PC - 2 // So after the interrupt, this LDIR will be re-executed 001346 1C5221 1C5221 pcl<-pch, to_pch <- sub_0(acc_t, 1) 001348 010022 010022 to_t <- pcl 00134A 01324C 01324C reg_h <- acc_t // The dec_dtc will only do a decrement if the previous subtract crossed zero. 00134C DB5258 DB5258 pcl<-pch, to_pch <- dec_dtc(reg_th) 00134E 006246 006246 interrupt, nop 001350 E14AC0 E14AC0 to_a <- reg_v,irq_to_f -- 0/41 + 2 cycles ---- page 0 ED 20 LDIR-SLOW ---- 000400 010249 010249 to_t <- reg_c 000402 0E2248 0E2248 to_dpl <- or(acc_t, reg_b) 000404 1B2122 1B2122 to_dpl <- dec(dpl) 000406 612ACB 612ACB to_dpl <- reg_e,tc_to_f // F=0: count is zero. Restore registers and go to next instruction 000408 010022 01124A F:[to_t <- pcl] T:[to_dph <- reg_d] 00040A 01324D 014088 F:[reg_l <- acc_t] T:[to_a <- (pc++)] 00040C 015259 013180 F:[pcl<-pch, to_pch <- reg_tl] T:[(dph|dpl) <- acc_a] 00040E 010022 2A024B F:[to_t <- pcl] T:[to_t <- inc(reg_e)] 000410 01324C 01324B F:[reg_h <- acc_t] T:[reg_e <- acc_t] // The inc_dtc will only do an increment if the previous increment produced a carry. 000412 015258 CA024A F:[pcl<-pch, to_pch <- reg_th] T:[to_t <- inc_dtc(reg_d)] 000414 046008 01324A F:[next, nop] T:[reg_d <- acc_t] 000416 E14AC0 1B0249 F:[to_a <- reg_v,irq_to_f] T:[to_t <- dec(reg_c)] 000418 013249 013249 reg_c <- acc_t 00041A DB0248 DB0248 to_t <- dec_dtc(reg_b) 00041C E13A48 E13A48 reg_b <- acc_t,irq_to_f // If interrupt not active, jump back to fast loop 00041E 010220 00625B F:[to_t <- 0] T:[to_ir <- reg_vga_acc,p_z80_ed] 000420 010022 010022 to_t <- pcl 000422 01324D 01324D reg_l <- acc_t 000424 010259 010259 to_t <- reg_tl // Before going to interrupt code, sub_0(acc_t,1) will do PC = PC - 2 // So after the interrupt, this LDIR will be re-executed 000426 1C5221 1C5221 pcl<-pch, to_pch <- sub_0(acc_t, 1) 000428 010022 010022 to_t <- pcl 00042A 01324C 01324C reg_h <- acc_t 00042C DB5258 DB5258 pcl<-pch, to_pch <- dec_dtc(reg_th) 00042E 006246 006246 interrupt, nop 000430 E14AC0 E14AC0 to_a <- reg_v,irq_to_f -- 12/25 + 2 cycles
Maximum number of cycles per instruction
The fast copy loop will take 8 * 3 + 8 (overhead) = 24 cycles. It is actually longer, because it also tests the interrupt line, and when that is active, it restores the registers, and jumps to the interrupt code. (and before jumping to the interrupt code, it sets the PC two positions back, so the LDIR will be re-entered after the interrupt has been serviced).
But I said somewhere before, that each instruction has max 16 cycles !
Well, actually, the lower 4 bits of the instruction register are in a counter chip. So when the 16 cycles are executed, and no new instruction was fetched, execution continues in the microcode of the next opcode ! So, a maximum of 256 different cycles can be executed, and after that the same 256 cycles come again, until it jumps out of that sequence somewhere.
Of course, when this system is used, the next opcode should not be defined as a normal instruction.
For the LDIR instruction (opcode 0xB0 in the Z80_ED page), a few initializations will be done in the opcode 0xB0 microcode, but will then jump to the fast loop at address 0x98 in the same page. That is an opcode that is undefined by the Z80, and also the opcodes that follow it (0x99, 0x9A) are undefined. That is where our LDIR fast code lives. It can also do a jump to the slow loop, at 0x20. The slow loop also uses the next opcode, 0x21. Both 0x20 and 0x21 are undefined Z80 opcodes.
LDDR
I could do the same for the LDDR (load-decrement-repeat) instruction, but I did not do that, because it is used less often (only when source and destination range overlap in a certain way). Also, it would mean that PC had to decrement instead of increment. Decrementing the PC is not a hardware function, but it could be done by microcode, at two extra cycles per transfer. So if LDDR got the same treatment, the average time would be 6.4 cycles per transferred byte.
So the LDDR is a standard loop, a byte transfer costs 21 cycles.
-
Progress of new PCB
09/07/2024 at 10:14 • 0 commentsHere is an impression of the new PCB (Image generated by KiCad). It's almost finished.
And here is a small section (of four IC's) :
The TOP layer is RED, first inner layer is groundplane (not shown), second inner layer is ORANGE and the BOTTOM layer is BLUE. You can click on the picture for a better view.
Yes, all routed by hand.
-
On the cover of the hack a day
08/31/2024 at 18:12 • 5 commentsThe Isetta made it to the front page !
The Computer We All Wish We’d Had In The 8-Bit Era by Jenny List.
We take all kind of pills
That give us all kind of thrills
But only one thrill is gonna stay
Thats the thrill that you'll get when you find your project
On the cover of the hack a day
(Hack a Day) Wanna see my project on the cover
(Day) Made a big printout just for my mother (yeah)
(Day) Wanna see my smilin' face
When my project is on Hack a DayThank you, Jenny !
Understand a little Dutch ? We've got you covered. There is a nice cover of the cover....
-
Hardware changes and bankswitching
07/28/2024 at 15:30 • 0 commentsLuckily, the first prototype worked without real problems. The only obvious one being the strange footprint for the VGA connector.
But for my next pcb run, I want to do several changes:
- fix mistakes (VGA connector)
- add Mouse connector
- add HW for sending info to keyboard (and mouse)
- add memory bank select
- change video pixel/color generation
- change WiFi module type
- simple 4-wire connect to Raspberry Pi (file transfer)BANKSWITCHING
The memory bank selection is, I think, the most complicated of this new features. The new system will have the following modes: Register, Table/ZPage, Native, Application, Kernel slot1, and Kernel slot3. You can see that in the following table:
In the current hardware, only the Register mode, Table/ZPage and Native mode are present. The register mode allows access to 64 fixed locations in memory (a kind of zero-page addressing). There is another group of 64 locations that can be switched (for Z80 registerbank switching). Table/ZPage mode is used with an identity (1:1) table to access DPL and PCL, to access the Shift-right table, and for the 6502 to address ZPage values. The Native mode allows addressing the full 512 Kbyte of memory, but it's problem is, that the used 64K bank (yellow fields) is hard programmed in the microcode.
This will now be more flexible. In the new Application mode, one of the 8 selectable 64K banks can be programmed in a bank select register (blue fields). The selected bank will be valid for data as well as program code. Note that the microcode can force that Native mode or Register mode is used, by using the control bits CTL_M1 or CTL_REG2.
An operating system must be able to access all memory, to be able to load programs and move data around. I want to adhere to a common practice in several Z80 systems, where the memory space is divided in slots of 16Kbyte. To make this not too complicated, only 2 of these 16Kbyte slots can be mapped to another 64k memory bank.
So, the Kernel_slot1 mode allows you to map any of the 32 blocks of 16kByte into slot 1 . Eight banks of 64K can be chosen with the blue BS signals, while within such a 64K bank, a 16K block can be chosen with the orange BLK signals. Slot0, 2 and 3 will select the 64K bank that is defined in the microcode instruction (with the yellow BNK signals).
The Kernel_slot3 mode allows you to map another block in slot3. But this has a few restrictions. The mapped-in block can be any 64K bank, but within that bank it is always block3. And no code can be executed from the mapped-in block.
In the table, the bits indicated in RED show which bits are needed to select a certain mode. For the bank select register, there are only 3 types: Application/Native, Kernel_slot1 and Kernel_slot3.
VIDEO CHANGE
In the current video system, in the 640-pixel mode, a byte from memory represents 8 pixels, and the byte tells for each pixel if it has foreground color or background color.
But this system was designed when the processor/memory cycle was still 160nS.
Now that I changed the cycle time to 80 nS some time ago, a fetched byte only needs to provide color for two pixels (pixels last 40nS). So we can simply have 4 bits (16 colors) per pixel ! Now each pixel can have an independent color. This seems much more flexible than the previous system, so I'm gonna change that.
VGA connector footprint issue
It turnes out that all KiCad footprints for these DSUB-15HD connectors are wrong, and that was known already 5 years ago, see this issue report. Swiching to the newest 8.0 version still had the wrong footprint. So I had to edit the footprint myself.
-
Two Spectrum games working
06/27/2024 at 20:39 • 0 commentsAfter a little while, I got the Manic Miner fully working, including sound:
But in the video signal, there are two annoying stripes at the right side of the screen (also going through the score). This is related to a end-of-line flag that is tested there in the microcode that builds the screen. The exact reason is not known yet. [ edit: the reason was found, and the bug removed.]
SOUND
The sound took a little fiddling to get it to work properly. The ZX Spectrum has a very simple sound system, similar to that of the Apple II. It is a single bit of an output port (port 0xFE), that is amplified with a single transistor, and connected to a small speaker. The program has loops that toggle that bit with a certain timing.
The Z80 of the Spectrum runs at 3.5 MHz. Isetta runs at 12.5 MHz. But Isetta spends around 73% of her time rendering the Spectrum screen, so her effective speed is about 3.375 MHz. A Spectrum program, generating a constant tone, would come out as a series of high-frequency 'chirps' during blanking time.
The write to the 0xFE output port is handled by microcode. It now stores the value instead of writing it to the speaker. At every line interrupt, this value is put in a buffer (only during blanking time when the Z80 code runs, that is about 140 lines per frame ). But in every line interrupt (525 times per frame), a value is read from the buffer and written to the sound output. The same value is used four times, and then the buffer pointer is incremented. So the sound is stretched by a factor 4.
It had another problem. The sound bit is normally zero, then it toggles during the tone, and then is zero again. This means that it has a low-frequency component. In the ZX with its tiny speaker this was probably no problem, but in my case I have a set of amplified PC-speakers (with 3.5 mm jack connector) that have a 'good' bass response. So the little 'music' tones were accompanied by loud plop sounds.
The sound system of Isetta has 8-bit samples. I defined a level that was halfway the 0 and 1 of the speaker signal. So normally the signal was at 50%. When port 0xFE was written, the signal would be 0% or 100 %. But the point is, it is difficult to know when the tone stops, so you don't know when to go back to 50% . I did this as follows. At each interrupt, when the 0xEF port was not written, I bring the speaker signal a few steps closer to 50%. So when the tone stops, the signal will go to 50% quite soon. This gave a reasonable sound quality.
KEYBOARD
I already had Z80 code to convert the PS/2 keyboard to ASCII, and to convert ASCII to the codes for the ZX Spectrum keyboard matrix. But for playing a game, I also had to use the PS/2 'key released' messages, to tell the game program exactly when a key was released.
THE GREAT ESCAPE
I tried another program. The Great Escape. This program also has a very good, commented disassembly.
In this program, a prisoner of war has to escape from a prisoners camp. It is totally different from the previous game.
The display is a semi-3D rendering. It is almost a modern game, where a camera follows the hero on the terrain and in the several buildings.
It did not take much time before I saw the first guards walking on the screen. But it was only a few second, then the program crashed. What could it be ? I looked through the source and I found it used an instruction that I had not implemented, because my thought had been, who is ever going to use that ?? RRD Rotate Right Digit
But the game uses it to shift the whole screen four pixels to the left or the right....
So the RRD and RLD are now also in the microcode.
Program worked. But I have no clue how to escape...
-
Trying to get a ZX Spectrum game running
06/14/2024 at 14:44 • 0 commentsTEXT MODE
In the last log I mentioned that there was video output.
A 80-column text mode was created. Each character is in a 8 x 8 pixel matrix. To make it better readable, a few empty scanlines are between the textlines. The VGA pixels are generated by the microcode.
At the moment this is mainly used as a text mode, although it is actually a graphic mode with 8 bytes of 8 pixels each. For each row of a 'character', there is also a color byte. The idea is that every character can have a different color (but since this is a graphic mode, each row of a character could have it's own color). The background color for a character can also change, but that's not fully implemented yet.
The picture shows how the system starts in the current version. It shows a few big colored fields to show some of the colors. At the right side you see some random colors, where you can see that each character can have a different color for each row.
You also see how the file directory shows up. A line that starts with "D" means that this is a directory.
After I got this video mode working, I worked on the PS/2 keyboard input. The keyboard did only give a singe message after startup, always the same, and did not respond to key presses. After searching my keyboard on the internet, I found that some keyboards need a RESET message before they start working. In my case, I had to solder two transistors on the pcb to be able to transmit the reset message to the keyboard. But then it worked ! A Z80 program translates the scancodes to ASCII.
Isetta can now run with its own keyboard, video output and file system, without the need for the RPi !
ZX Spectrum video system
After I got that working, I spend some time making a video mode that behaves as ZX Spectrum graphics .
To test this, I downloaded the source of the game Manic Miner. I could only get this to work if I also found the explanation of Manic Miner source code. Here is the first result:
Obviously, not everything was working correctly yet. There were vertical lines at every character boundary, and apparently the attribute bytes of the lower part of the screen were not correct.
It could be traced (within reasonable time) to a failing LDIR (load-increment-repeat) instruction. This is used in the game to copy big blocks of bytes to the screen, with a single instruction. The LDIR, that is implemented in microcode, was designed to check for an interrupt periodically, and in the case of interrupt set the program counter two steps back, and after the interrupt resume the copy operation. But it didn't work correctly with interrupts.
The LDIR was a highly optimized piece of microcode. At ten cycles per transferred byte, it loaded the byte, stored it at the destination, incremented the HL source address (with carry between low and high byte), do the same for the DE destination address, decrement the BC byte counter, test if BC is zero. And also check the interrupt signal at every transferred byte.
I took the fast way out. I grabbed a RST instruction (0x20) and did put a copy routine there. In the source of the Manic Miner, I replaced every LDIR with a RST 0x20. Followed by a NOP to leave addresses unchanged. Getting the bug out of the LDIR instruction was put on the TODO list. Now I got this:
Already much better !
I could also see on the piano keys that the start-up tune was playing (but of course I have no sound yet). Also, a scrolling message was visible. After that, the program crashed. More debugging to do !