Close

Programming manual, microcode and Blitter

A project log for Isetta TTL computer

Retro computer built from TTL, with 6502 and Z80 instruction set. Includes video, sound and filesystem. No microprocessor or FPGA.

roelhroelh 11 hours ago0 Comments

PROGRAMMING MANUAL

An Isetta Programming Manual was made, that describes all features that are available to the assembly program.
You can find it in the Hackaday File section.

It describes most of the I/O ports. 
It also describes the video system, bankswitching, ports used for the Blitter, and describes a debugger that I made.

INTRO

One of my plans was, to have some microcode support for writing to the screen. 

Writing to the screen, in Z80 (or 6502) assembly language, can be very tedious. The screen is pixel based. There are several complications:

  - in 640x400 mode, there are two pixels per byte
  - we might have graphics encoded as 2 bits per pixel (4 colors), this must be converted to 4 bits per pixel
  - We might have transparent pixels (for sprites). So sometimes, in a byte, one nibble must be written but the other stays the same.
  - the upper and lower half of the screen are in different memory banks
  - perhaps the 4-bit color pixels must be converted to another 4-bit color code

A Z80 assembly program to do this will be difficult to write, especially because it should be quite fast (to get the screen written in an acceptable time).

MICROCODE FEATURES

To get a good performance, it would be better to write screen handling in microcode. On Isetta, programming in microcode has the following features:

  - A microcode instruction is 24 bits, and is totally different from a Z80 or 6502 instruction.
  - While the main purpose of the microcode is, to implement Z80 or 6502 instructions, it can also be used to do other functions.
  - A 24-bit micro-instruction can be fetched from microcode flash memory at the same time as a data byte is transferred to/from data memory (Harvard style). A micro-instruction always executes in a single cycle (80 nS).
  - The 24-bit instructions are addressed by the instruction register (8 bit), a step counter (4 bit), a page register (4 bit), and the flag F (1 bit).
  - The micro-instructions can read and write flags, and have conditional execution, that lets the F flag choose between two micro-instructions.
  - During execution of a Z80/6502 instruction, when the required steps are done, a micro-instruction will load the instruction register (IR) from an address that is in the program counter (PC++), reset the step counter to 0, and might set the page register to a new value.
  - If, at the end of the 16 steps, the instruction register (IR) is not loaded again, the IR will automatically increment, because the lower 4 bits of the instruction register is also a counter. So a sequence of micro-instructions can be 256 cycles long. If IR is still not loaded, it will go back to the first of these 256 steps. 
  - If the instruction register is loaded, it does not have to be loaded from the PC address. It can also be loaded with a constant (to jump to a fixed next instruction), or can be loaded from a register in memory. It is possible to write simple programs without using the program counter.

MICROCODE DETAILS

What do the micro-instructions do, more in detail ?

  1) Read the next 24-bit micro-instruction (pipelined), from one of two 'tracks' chosen by the F flag.
  2) Determine memory address and memory bank. Memory address can come from:
      - the data pointer (DPH/DPL), or
      - the program counter (PC) with optional auto-increment, or
      - a small fixed value (included in the 24-bit instruction)
     There is an option to set the lowest address bit to 1. This is useful to read the MSB of a word, without having to increment the pointer. Of course, the word must be aligned.
  3) Read a 8 bit data value from memory (except when writing of course), or read a literal value, and put the result on the databus 

  4) One of the following:
     - Do ALU operation with inputs databus and a register (A or T), store result in a register, optional write flags
     - Write 8-bit register A (accumulator) or T (temp) to memory
     - Write to an output location or read from input location
     - jump to another microcode sequence (16 pages of 256 sequences each), by writing the databus value to the IR register. The jump takes an extra cycle before it is effective.
  5) Write register A or T to video output, when that is enabled (one 6-bit pixel or two 4-bit pixels per cycle)
  6) The flag "F" selects from which track the micro-instructions are read. Used to implement
     conditionals in microcode, without using jumps

All these 6 actions are done at the same time in a single cycle, with 12.5 million cycles per second.

The possible ALU operations are:
   one operand: ld inc dec asl rol lsr ror  (ld passes the databus operand unchanged)
   two operand: add sub adc sbc and or xor res   ( res (reset) means: ( X and not Y), used for Z80 RES instruction )
   The ALU functions have several options for the carry input.

The possible registers to write to, are:
   A (Accumulator)
   T (Temporary)
   DPL data pointer low
   DPH data pointer high
   PCH program counter high (At same time, PCL is loaded with old PCH value)
   BNK memory bank register

The possible flags to write to, are:
   F   selects from which track the micro-instructions are read
   C   Carry
   N   Negative (also called S, sign)
   TC  Temporary carry. Not visible to Z80/6502 instructions. Written at every instruction except ld, xor.
   

Instructions asl, rol are implemented by connecting both adder inputs to the databus (done by the logic units).
Instructions lsr, ror are implemented with a 256-byte table in RAM, filled at Isetta-reset.

Note that for operations with memory data, most RISC processors need two cycles because it does either a memory access or an ALU function. For such situations, Isetta takes only one cycle (memory read and ALU function in one cycle), and can be considered being a 25 MIPS processor (however, with 8 bit data size).

MICROCODE ADVANTAGES

When the program should run very fast, using microcode on Isetta helps a lot, because:

- The program counter can be used as second data pointer, eliminating many datapointer load instructions
- The T register can be used as second accumulator 
- In a Z80/6502 program, only the hardware registers A and PCH/PCL are transferred from one instruction to another. But in microcode, all hardware registers A, T, PCH/PCL, DPH, DPL are available to the program.
- A Z80/6502 needs, for every instruction, a cycle to fetch the opcode from memory. These cycles are eliminated when programming in microcode. (a real Z80 takes even two cycles to fetch the opcode).
- By putting a selector value in the instruction register, CASE statements can be very fast
- In many cases, conditional jumps are eliminated due to the two-track microcode, so they take no time

- In many cases, Z80/6502 instructions take cycles to write the flags correctly, even when these flags are not used. In microcode, you only have to write flags if that is needed for your program.

MICROCODE DISADVANTAGES

- The microcode is in parallel flash memory, and a programming device is needed to change it
- The microcode must check the interrupt-request signal regularly. This interrupt occurs every 400 cycles. It
  must always be serviced, otherwise the functions for screen output, keyboard and mouse input will not work correctly (But checking the interrupt request can be done in zero cycles).
- Not very memory efficient. A micro-instruction, on its two 'tracks', takes 6 bytes (2 x 24 bits). And several micro-instructions might be needed to do the work of a single Z80 or 6502 instruction.

 
INTRODUCING THE BLITTER

After glorifying the microcode, it won't be a surprise that I wrote screen functions in it.

The Blitter (Wikipedia) is a microcoded unit that is specialized in writing to rectangular sections of the Hires screen.
To use this, you first setup the parameters, using special OUTPUT ports. After that, you start the blitter by
writing the action code to the P_BLITCTRL output port. 

Most commands require three kinds of parameters:  (most parameters are 16 bits)
 - memory source base address, coordinates, line-length
 - screen coordinates
 - rectangle size
A few commands use FILL1 and FILL2 that both specify a color.

The Blitter supports the following actions (not all of them fully working yet):

D_BMCOPY  ; COPY source to screen
D_BMSKIP  ; Copy source to screen, except where source has color 0 (transparent)
D_BMSAVE  ; Save from screen to source memory address
D_BMTEXT  ; TEXT write a character from a font map to the screen. Font map is 1bpp.
D_BMFILL  ; Fill a screen rectangle, alternating colors FILL1 and FILL2 (in most cases they are the same)
D_BMFXOR  ; Fill a screen rectangle using XOR operator, alternating colors FILL1 and FILL2
  
Most of these use the format of two 16-color pixels per byte (4bpp).
The COPY and SKIP will possibly also support four 4-color pixels per byte (2bpp).

The blitter is fully described in the Isetta Hardware Manual, that you can find it in the Hackaday File section.

MOUSE

I also have more code for the mouse now...  I have a mouse pointer on the screen, that I can move with my mouse (Taking for granted nowadays, but it will not work without hardware or code that implements it). 
Before the mouse pointer graphic is placed, a D_BMSAVE is used to save the background. Then, the mouse pointer is copied to screen using D_BMSKIP (but transparent is not yet implemented).
When the mouse is moved, the background is restored with a D_BMCOPY action, and then the mouse pointer is placed at the new position.

OK.... you can conclude that I didn't spend a lot of my free time watching TV.

Discussions