Showing posts with label k1 bus. Show all posts
Showing posts with label k1 bus. Show all posts

2016-04-20

Firmware Download and Access to IDE Board

Two topics in this post:

  • Firmware download
  • Access IDE board, IDE and CF devices

Firmware download

After my explorations into CRC generation, i worked on the firmware download code. This code has to run in RAM, because the eeprom can't be read while it is busy writing a block of data into it's cells.

I tried to keep things simple, and so the program flow looks like this:
write_eeprom.s

1. wait for SIO output to become empty
2. disable interrupts
3. receive magic header bytes
   if they are wrong: bail out
4. copy code from rom into ram
   jump to 6.

in ram:
5. Retry: receive bytes until magic header detected
6. in a loop:
7.    receive 64 bytes of data (last block may be shorter)
      update the crc after each byte
8.    write block of data into eeprom
9.    wait while eeprom busy
10. loop
11. receive and check crc:
    error: print a message, flush input, wait for a key and retry at 5
    ok:    print a message, flush input, wait for a key and reset

Already complicated enough.

ad 1: I wait for the SIO output to become empty, because there may be (and typically are) some bytes left in the output buffer, and as soon as i disable interrupts they will never be sent. This resulted in truncated "last messages".

ad 7: The program receives blocks of 64 bytes, which is the eeprom's block size, writes them into the eeprom and reuses the receive buffer for the next block. It does not keep the bytes around until all bytes are received, so the whole eeprom can be reprogrammed, though i have only 32k of RAM minus approx. 1k for code and buffers available.

The program actively polls the UART and retrieves the bytes as soon as they pop up in the UART queue. Then it immediately updates the CRC, so no extra loop is needed for CRC calculation.

The data is written into the eeprom despite the CRC is not yet checked, which can only be done at the end of the transmission.

ad 9: Waiting for the eeprom takes up to 10 ms (acc. to Atmel docs). During this time i do not poll the UART, for simplicity. And i do not use any flow control. Currently the UART is programmed to 9600 Baud, which means 960 characters per second or 9.6 characters per 10 ms. The UART has an input queue of 16 bytes: The UART is doing my job! :-)

ad 11: Up to now i never had a CRC error.

Overall workflow is now like this:

1. Work on the source, compile & assemble the rom.
2. Launch the new rom in the emulator
   The emulator silently creates a download file from the bare rom file.
3. Connect to the board (most times CoolTerm _is_ connected…)   
4. Select "download firmware" from the menu presented by the board
5. upload a "Textfile" in CoolTerm
6. type A letter to reboot

The upload including writing to the eeprom now takes exactly as long as uploading the rom itself: 1 second per 960 bytes. Currently roughly 20 seconds.

Kio: "See Stager, that's how it works!"

Next i made a modification to the rom, uploaded it and found, that it no longer booted.

Stager: "See Kio, you still need me…" :-(

Access the IDE board

Now that turn-around cycles are much faster and less painful, i started first tests to access the IDE board.

I talked to the i²c eeprom on the board... and it answered! :-)

Then i collected all my knowledge about IDE, which mostly centers around the DivIDE emulation in zxsp, and made up my mind on what to send to and read from the IDE device at first.

The test program looks roughly like this:

1. disable interrupts (in c: you remember the __critical bug?)
2. select the IDE board
3. read status and error register (from master, which is selected after reset)
4. print something
5. wait for ready and !busy in the status register
6. check that the device is not unexpectedly waiting for data
7. finally: issue command "IDENTIFY"
8. wait for data request in the status register, bail out on unexpected state
9. read 256 words into a sector buffer
10. read status and error register
11. print the sector data in hex
12. inspect and print various fields in the sector data

ad 1: Currently i'm back to the sdcc 3.6 nightly build, though it produced the so much slower code than version 3.4.

ad 2: As you may know, if you have followed this blog for the last years B-) boards attached to the K1 bus must be "selected" and from that on any i/o goes to that board. This really is a nice method and i'm really happy with it. I just must make sure that i'm not interrupted while i'm working with a device, as the interrupt (sic!) will probably leave some other board selected…

ad 9: I really read 256 words, not 512 bytes. (Though, in essence off course i do read 512 bytes.) The K1 bus is a 16 bit bus and the Z80 board contains two 8-bit buffers for sending and receiving the high word. Then reading 16 bit values works like this:

Implementation of a function for c:

;  uint16 in_w( uint8 addr ) __z88dk_fastcall;
;
_in_w::
    ld    a,l            ; a = register address
    or    a,k1_rd_data   ; add bits to access the bus
    ld    c,a            
    in    l,(c)       ; read the low byte
    ld    c,k1_rd_hi     ; access the high-byte register
    in    h,(c)          ; read the high byte from the high-byte register
    ret

The function is marked as __z88dk_fastcall, which is really funny. z88dk is the (only?) competitor of sdcc in the field of Z80. __z88dk_fastcall means, that the argument to the function, which must have exactly one argument, is passed in l, hl or hlde, depending on size, and not on the stack. In my opinion this should be the default.

PQI DOM, CF cards and Seagate ST1

For some reason it first didn't work, but then, out of a sudden, i got good looking data from a device. The only thing i did to make it happen was, to scrutinize the circuit diagram for errors. And as soon as i could prove there was no error, reality was modified to match my expectations. Check. ✓

Off course the ascii texts of model name etc. were byte-swapped in the first version. Probably most people fall into this pitfall. The 4-byte values were calculated wrong in one place but correctly in another. After a few iterations the output from the "built-in" PQI DiskOnModule looked like this:

$00: 045A 02EE 0000 0008 0000 0210 0020 0002 EE00 0000 2020 2020 2020 2020 2020 2020
$10: 2020 2020 2020 2020 0002 0002 0004 6462 3031 2E32 3061 5051 4920 4944 4520 4469
$20: 736B 4F6E 4D6F 6475 6C65 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 0001
$30: 0000 0200 0000 0200 0000 0001 02EE 0008 0020 EE00 0002 0100 EE00 0002 0000 0000
$40: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
     ...
$F0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

model name = PQI IDE DiskOnModule                    
serial number =                     
firmware revision = db01.20a
fixed disk
ATA version = 0
LBA supported
Default capacity (sectors) = 192000
default C/H/S = 750/8/32
default capacity (sectors) = 192000
current C/H/S = 750/8/32
current capacity (sectors) = 192000
device supports PIO mode 3 or DMA mode 1 or above

Next reading from the slave, a Seagate ST1 hard disk in the CF card slot, did not work. The ST1 always reported !ready.

I thought there could be a severe problem with the pin assignment of the CF card slot, but after double checking, it was ok. Next i suspected a missing pull-up on the /CS line, which discriminates between master and slave, but these inputs have an internal pull-up.
Then i checked the ST1: with the help of an USB-CF-Card adapter i attached it to my Mac and it spined up. I watched some very cool short videos which i had saved on the drive a few years ago:

Do ya know any of them?

Then i rummaged around for some Compact "Flash" cards and came up with a 256 MB and a 16 MB one (the latter one i actually don't own. Hi Axel, do you miss your 16 MB card? I just found it… :-))

I tested them.

And they worked:

$00: 848A 02B7 0000 000F 0000 0200 0030 0007 A2B0 0000 5830 3130 3220 3230 3033 3130
$10: 3237 3032 3536 3137 0002 0002 0004 5265 7620 332E 3030 4869 7461 6368 6920 5858
$20: 4D32 2E33 2E30 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 0001
$30: 0000 0200 0000 0100 0000 0001 02B7 000F 0030 A2B0 0007 0100 A2B0 0007 0000 0000
     ...
$F0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

model name = Hitachi XXM2.3.0                        
serial number = X0102 20031027025617
firmware revision = Rev 3.00
removable medium
ATA version = 0
LBA supported
Default capacity (sectors) = 500400
default C/H/S = 695/15/48
default capacity (sectors) = 500400
current C/H/S = 695/15/48
current capacity (sectors) = 500400
device supports PIO mode 3 or DMA mode 1 or above

and

$00: 848A 00F4 0000 0004 4000 0200 0020 0000 7A00 0000 3932 3130 3336 3230 3130 3938
$10: 3939 3039 3132 3831 0002 0002 0004 5631 2E30 3220 2020 4C45 5841 5220 4154 4120
$20: 464C 4153 4820 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 0001
$30: 0000 0200 0000 0200 0000 0003 00F4 0004 0020 7A00 0000 0100 7A00 0000 0000 0000
     ...
$70: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
$80: 0000 75D0 0075 A800 75B8 0078 0174 55F6 08F4 B800 FA08 F4F5 F0E6 B5F0 10F4 F5F0
$90: 08B8 00F5 7801 B455 E674 0001 A700 7455 01A7 90C0 5974 01F0 E4F0 90C0 2D8D E6A6
$A0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
     ...
$F0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

model name = LEXAR ATA FLASH                         
serial number = 92103620109899091281
firmware revision = V1.02   
removable medium
ATA version = 0
LBA supported
Default capacity (sectors) = 31232
default C/H/S = 244/4/32
default capacity (sectors) = 31232
current C/H/S = 244/4/32
current capacity (sectors) = 31232
device supports PIO mode 3 or DMA mode 1 or above
device supports Ultra DMA

But the ST1 refused to become ready.

I played around with the master/slave setting and found, that the "jumper" on the PQI module probably enforced master for the device. This had to be taken into account when changing the master/slave jumper on the IDE board. (I first thought it had something to do with power supply, because this is an IDE module and the IDE bus normally provides no power, but i was wrong.)

Finally i jumpered the CF card adapter as master, pulled the master "master" jumper on the PQI module, and the CF card and the PQI module still answered, with roles swapped, and, unbelievable, the Seagate ST1 answered too! So, to make the ST1 work, i need to set the CF card adapter – the "removable" medium – to master and the "fixed" PQI module to slave? I tried with an empty CF card slot and the PQI module still answered - as slave. Is this IDE standard? (Actually i know very little about CF, IDE and so on, the official documents cost money for download or membership. So i get what can be found with the help of aunt Google.)

$00: 848A 12ED 0000 0010 7E00 0200 003F 004A 8530 0000 2020 2020 2020 2020 2020 344D
$10: 4430 3433 4B53 2020 0003 0100 0004 332E 3034 2020 2020 5354 3632 3532 3131 4346
$20: 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 8010
$30: 0000 0B00 0000 0200 0000 0007 12ED 0010 003F 8530 004A 0100 8530 004A 0000 0407
$40: 0003 0078 0078 0078 0078 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
$50: 0000 0000 7069 500C 4000 7069 100C 4000 0007 0000 0000 4040 0000 400D 8080 0000
$60: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
     ...
$90: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
$A0: 814A 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
$B0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
     ...
$F0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

model name = ST625211CF                              
serial number =           4MD043KS  
firmware revision = 3.04    
removable medium
ATA version = 0
command sets supported = $7069 500C
command sets enabled   = $7069 100C
LBA supported
Default capacity (sectors) = 4883760
default C/H/S = 4845/16/63
default capacity (sectors) = 4883760
current C/H/S = 4845/16/63
current capacity (sectors) = 4883760
device supports PIO mode 3 or DMA mode 1 or above
device supports Ultra DMA
max sectors per R/W MULTIPLE = 16
CFA power mode = 33098

Data transfer to and from the IDE disks is not really fast with a Z80 processor, the best what i can get is:

ld      c,k1_wr_data
    ...
    ; send 1 word / 2 bytes in a loop 
    ; or unroll as long as you like    
    ld      e,(hl++)
    ld      a,(hl++)
    out     (k1_wr_hi),a    ; store byte in the high-byte register
    out     (c),e           ; send 1 word / 2 bytes to the device

This takes 25 cc per byte, or, if inc hl can be replaced with inc l only, 21 cc; plus loop and setup overhead. The system has a clock frequency of 6 MHz, which means i can transfer 32 kB (the whole RAM) in 32k * 25 cc = 819200 cc or, at 6 MHz, in 1/7 sec. Ok, the CPU is slow, but the RAM is small as well. :-)

A single sector can be transferred in 512 * 25 cc = 12800 cc or, at 6 MHz, in 1/450 sec. This is also very fine because this means, that i can disable interrupts for a whole sector i/o without losing timer and SIO interrupts, as the timer interrupt is at 100 Hz currently. I could also increase the timer frequency to 300 or 400 Hz to allow a SIO speed of up to 38.4 kBaud, if i desired, but CPU time consumption will go up as well and with 100 Hz it's already around 2% with an idle SIO. And with very intelligent use of INI or OUTI a few cycles for at least one IN or OUT could be saved.

It should be noted that the CF cards can be operated in a byte-wide mode. Then INIR and OTIR should be usable and one byte transferred in 16 cc.

Conclusion

All boards work and now everything left to do is software.

p.s.: Still not yet written to the IDE devices. Surprise ahead? (Shiver…)

2016-04-10

Z80 Microcomputer with SRAM and K1-Bus

After suspending the project for a while, i'm now back to it. This is an update to the current state.

Hardware

There were some errors in the circuit, which i could fix. The V2.0 Eagle file on my website already contains these fixes.

2016-04-08 fixed board
Blue wires: A6 and A7 are used (beside A5) to select the target of an i/o operation. One of these is the access to the i2c bus on the k1 bus, where also my debugging LEDs are attached. When i2c is selected in an i/o operation, then A6 and A7 are used to set the i2c data and clock lines. – But wait, A6/7 are used to select i2c operation AND to select something within the i2c operation? Merde… So i rerouted the i2c lines to use A3 and A4 instead.
Yellow wires: As you can see by a look at the ram/rom address decoder in the last post, ram and rom selection is exchanged. First i fixed this by inserting eeprom and ram into each other's socket, which is possible with the eeprom, but not with the eprom.
For my (e)eproms i use a programmer from Stager Electric, Shenzhen, China. If you ever see something made by Stager Electric: run as fast as you can! It takes ~11 minutes to write a few bytes into an eeprom. The application has an option to "disinterest blanck" and eventually in the next version (which i never saw) it even worked… So it always programs full 32k and, since even this can be done in less than 32768/64*10ms = 5.12 seconds, and it actually takes 11 minutes, which is more than 100 times longer, i presume the eeprom is programmed byte by byte, making sure that it's write endurance of 10000 is in reachable distance... So i wanted to use eproms and fixed the circuit. Programming eproms is even faster as well.
Component side: A minor glitch is on the component side: I have carefully engraved "E" and "EE" for the eprom/eeprom selection pin header into the copper layer, and again, did it wrong: exchanged, as always…

Software

I was playing a little with my c-style compiler to add a Z80 target, and found: the Z80 is really bad suited to implement anything a compiler might try to create. Too few registers which frequently have special features. Deploying the second register set is near impossible. Using index registers is painfully slow. (you already knew that) Local variables on the stack are a pain to access.

Basically you have the choice to generate real machine code, which is not only slow but bloated as well, and some kind of Forth-style virtual code, which is short but even slower.

I finally came to the "fastest possible Forth-style" code model, which i will pursue later: It uses a jump table and opcodes which are 1-byte index into this table; which is faster (and shorter) than using 2-byte addresses in the program as Forth implementations typically do. Drawback: i need the table and the table can contain only ~256/3 entries. So there must be "prefix" opcodes which then are slower.

The jump table looks like this:

vector: macro $NAME
$NAME:: equ $ - vtable 
jp _$NAME 
endm                          

; ------------------------------------

vector RESET  ;( -- ) 
vector SHELL  ;( -- ) 
vector NATIVE ;( -- ) 
vector ABORT  ;( uint -- )   

vector MODs   ;( n n -- n ) 
vector DIVs   ;( n n -- n ) 
vector MODu   ;( n n -- n ) 
vector DIVu   ;( n n -- n ) 
vector MUL    ;( n n -- n )  

vector JP1    ;( n $dest -- )
vector JP0    ;( n $dest -- )
vector JP     ;( $dest -- )  

and so on. You see, each entry is a JP opcode (by virtue of the macro), but "inline" code in the table is sometimes possible as well, e.g. if a variant of an opcode just needs a short mockup of the arguments, it's code can be put directly in the table before the other opcode, where it simply runs into. It's a trade-off of used space and gained speed.

A typical "word" looks like this:

_SUB: ;( n1 n2 -- n )           

pop hl     ; hl=n1 
and a      ; de=n2 
sbc hl,de  ; hl=result
ex de,hl   ; de=result
next                      

where next is a macro:

; fetch next virtual opcode and jump to handler
; 
next: MACRO 
ld h,hi(vtable) 
ld a,(bc) 
inc bc 
ld l,a 
jp hl 
ENDM                 

An alternative is to jump to any implementation of macro next, which is slightly slower (10 cc for the jump) but also shorter (just 3 bytes). If it can be done in a relative jump, then it's even shorter (2 bytes) and even slower as well…

As you can see i use register pair BC for the virtual program counter and DE as result register, which frees HL so that machine coded sub routines can pop the return address into HL, do some work, e.g. pop arguments, and finally return via JP HL, which is not possible if you use HL as result register.

If an opcode implementation does not modify the h register, then it does not need to reload h with the high byte of the vtable address. There are actually some (few) opcodes which can exploit this additional speed boost. :-)

As you can see, the interpreter reads just one byte from the program and jumps into the vtable which contains jumps to the actual implementation of the virtual opcodes. This is faster than reading 2 bytes from the program, the program is shorter, but i need the tables and implementations for all opcodes.

The alternative i'm currently working with – because the Z80 backend of my compiler is not yet completed – is sdcc, the "Small Devices C Compiler", which has a Z80 backend. I can really tell that the generated code is bloated, and sometimes suboptimal, the syntax of the generated code is "unusual" and sometimes the compiler even crashes for me. Especially when i use the "<<" operator.

Here an example of what sdcc generates:

;/Firmware-Sdcc/sio.c:394: if(this->clk_handshake)
8520: DD7EFE   ld  a,-2 (ix)
8523: DD77FB   ld  -5 (ix),a
8526: DD7EFF   ld  a,-1 (ix)
8529: DD77FC   ld  -4 (ix),a
852C: DD6EFB   ld  l,-5 (ix)
852F: DD66FC   ld  h,-4 (ix)
8532: 23       inc hl       
8533: 23       inc hl       
8534: 6E       ld  l,(hl)   
8535: 7D       ld  a,l      
8536: B7       or  a, a     
8537: 2814     jr  Z,00106$ 

The first line (the comment) is the compiled source line. As you can see the compiled code reads a word from (ix-2), which seems to be 'this' ( a valid variable name in C ;-) ) and stores it at (ix-5) which seems to be a scratch cell and immediately reads it back into HL. Then it reads the desired value into l and immediately moves it into a for testing. A wonder of elegance. (note: the scratch value is not used anywhere later, l is used later, but while the value in a is still valid too.) 

Current State of the Project

Current setup
Last and this weekend i refitted all hardware, which is the CPU board, as SIO board and a (not yet tested) IDE board, as can be seen to the left, and hooked it up to a regulated power supply. Current consumption is pretty low, as it's all CMOS: only 50 to 80 mA (depending on how many LEDs are lit) for all three boards, including a 96MB IDE flash rom (hiding between the IDE and the SIO board) and a 2.5GB compact flash size hard drive (sticking out from the rear side so you can't see it as well).
Slowly iterating from one broken software step to the next, regularly erasing and reusing my eproms and finally even testing some steps in the emulator (erm, yes, i have written an emulator for the system too, using my Z80 emulation from zxsp and the SIO and a LCD display emulation from my K1 CPU project) i finally got the first text message from the board. I have attached the SIO port A to a RS232-to-USB converter and use CoolTerm on my Mac to receive the messages. I stepped back to an old version of CoolTerm, as the current versions very quickly use 100% of one CPU core. 
For the SIO software i use a simplified approach: The SIO ports are polled on the system timer interrupt (which is generated by the UART as well) which is currently 100 Hz. The UART has 16 byte fifo queues, so 100 Hz is way enough for 9600 Baud. But i'll probably go up to 200 Hz for 19200 Baud at least. As sending data works, the system interrupt works as well. 
Idle CPU usage for this interrupt is approx. 2% (calculated), and will be ~4% with 200 Hz, if i don't find a better solution. On the photo above you can see that the red LED in front is lit. This LED indicates WAIT state and currently the CPU waits approx. 98% of the time. (Or 96%, as it's also sending some text through some ugly compiled c code…) So i can say, this LED works as well. :-)
Today i have tested writing of data into the eeprom. (actually only detecting whether it's an eeprom or not, but that's quite similar.) This works too. 

Next steps: 
  • Actually write some bytes into the eeprom    ➞ done 2016-04-11
  • test reading of the i2c eprom on the SIO board
  • test writing to the i2c eeprom
  • receive data from the SIO port
  • receive program data from the sio port and write it into eeprom.
  • lock away the Stager Electrics programmer. ;-)
Final question is: what should i do with the board? hm hm…

Stay tuned. 

p.s.: Today i wrote a test message into the eeprom. Off course it did not work right from the start – it crashed because the destination space was too short, and behind that in the eeprom was the sio interrupt handler which was then partly overwritten.
I also tried to overwrite the eeprom with the Stager Electrics programmer, – which could not overwrite it. It took 11 minutes to write, and after that verify failed. I had expected this: Off course the programmer cannot deactivate software data protection in the eeprom. And it can't erase the device as a whole. Luckily i have more than one of these eeproms. And i already have a plan to make them writable again (else they'd be nice ceramic bricks): I can insert them into the ram socket and write a short eprom which does the job…

2014-08-15

68008 SRAM Microcomputer - Circuit Description V1.1

While waiting for the Z80 project for co-production, the 68008 board circuit has evolved. A severe bug has been removed (connecting one input of a NAND gate to ground doesn't transform it into an inverter) and a lot of timings have been scrutinized by hand, resulting in some modifications; e.g. only using address lines to select the strobe signal for the K1-bus strobe decoder or swapping /CE and /OE of the EPROM. This will be covered in another article.

This post is about the logic of the circuit. It is very simple and covered in great detail, so i hope it's possible to really understand how the board works. The project web page contains some additional material and eventually reading the 68000 data sheet will provide deeper insight.

This is a simple 68008 CPU board for the K1-bus. It uses a 681000-70 128kB SRAM and one 27C010 128kB EPROM or one 27C512 64kB EPROM or similar. The board does not contain any i/o circuitry except for an unbuffered K1-bus. Even a system timer must be provided this way.

Main Circuit



Main Circuit with CPU, RAM and ROM

The connection of RAM and ROM is very straight forward. Data bus lines and address bus lines are connected 1:1. Only two standard logic chips are used to interface the control signals: one 74HCT00 quad NAND and one 74HCT139 dual 2-to-4 decoder.

CPU control outputs used are /AS (controlling all bus cycles) and /WR (discriminating between read and write cycles). CPU control inputs used are /DTACK (terminate a bus cycle), /VPA (terminate a slow bus cycle) and /IPL1 (interrupt request).

Let's start with the signals provided and received by the 68008 CPU:

CLK: a clock signal of 10 MHz is generated by a DIL clock generator.

FC0 to FC3: These outputs provide information about each bus cycle: Whether it's program or data and whether it's user mode or supervisor and whether it's an interrupt acknowledge cycle. I was thinking of using this to page-in ROM at address 0x000000 after reset but found it did not save me components and would make software more complicated. So these outputs are not used.

/BERR: signals a bus error to the CPU. Not used on this board. There will never be a bus error detected. All bus cycles terminate with /DTACK or /VPA.

/BR and /BG: Used for multi bus master control. Not used on this board. There is only one bus master: the CPU.

/RESET and /HALT: After power-up the CPU must be reset for 0.1 seconds (really!) by pulling these lines low. Both pins are outputs as well: /RESET can be asserted by software and will in return activate /HALT: this will halt the CPU after the current bus cycle, by when /RESET is already released again thus it should not cause problems to the CPU. Or /HALT can be asserted and will reset the CPU. /HALT is asserted if double bus errors are detected (and evtl. other conditions) and can eventually never happen with this board. To be tested when built :-) This signal is directly connected to the K1-bus /RESET line.

/AS: During a bus cycle /AS is activated while the address is valid. Basically /AS going low starts a bus cycle and /AS going high terminates it.

/DS is similar to /AS but in write cycles it is asserted slightly later than /AS. I thought that it was not asserted at all in 6800 cycles, because the signal is not shown in the 6800 timing chart, but it is, as can be seen in the autovector timing chart. This signal is not used.

/WR indicates that this bus cycle is a write cycle. The opposite signal /RD does not exist and is generated by an inverter.

/DTACK: Data Acknowledge is asserted by external circuitry to tell the CPU that it can finish the current bus cycle. It is possible to keep this signal low all the time so that the CPU will run without wait cycles. (Though the timing charts all tell you that this signal has to go up and down.) On this board /DTACK is asserted all the time except if /WAIT on the K1-bus is activated or a slow bus cycle is performed which will be terminated by /VPA.

/VPA is connected to a signal called /SLOW_IO: A bus cycle of the 68008 can be terminated normally in two ways: by /DTACK or by /VPA. /VPA means "valid peripheral address" and is used to interface old (very old!) 6800 peripherals. The 68008 then does an extremely slow bus cycle. I use this to do very slow i/o cycles on the K1-bus. /VPA has a second meaning: If an interrupt acknowledge cycle is terminated with /VPA, then the CPU does not read the interrupt vector from the data bus but generates an 'autovector' internally. This board does not use /VPA for interrupt acknowledge cycles. Instead it uses vectored interrupts and uses /DTACK to terminate interrupt acknowledge cycles.

IPL0/2 and IPL1: Interrupt inputs. The 68000 has 3 interrupt lines, the 68008 has only 2 due to pin shortage in the dip package and connects both IPL0 and IPL2 to one pin. Pulling low any combination of these lines forces an interrupt in the 68008. Pulling low all interrupt lines generates a non-maskable interrupt, pulling low less lines results in an interrupt of level 1 to 6. In the 68008 normal interrupts of level 2 (IPL1) and 5 (IPL0/2) can be generated. This board uses only interrupt IPL1, which is directly connected to the K1-bus interrupt line.

The Control Signals of the RAM and ROM:

/CE: This enables the memory chip. While enabled, it can be read or written. The RAM also has a positive CE2 input, but this is not used. /CE is enabled by a 2-to-4 address decoder. See below.

/OE: If the memory chip is enabled, asserting /OE enables it's output drivers and the currently addressed byte is put on the data bus, where it can be read by the CPU. /OE is connected to the inverted /WR signal of the CPU: so at any time either /OE or /WR is enabled.

/WE: The RAM can also be written: if /WE is asserted while the RAM is enabled, then it will read the byte from the data bus and write it into the currently addressed memory cell. /WE is connected directly to the CPU's /WR output.

/CE and /OE of the EPROM are not connected as expected but swapped: the result is the same – the EPROM puts data on the data bus if both signals are enabled – but memory access after /CE is much slower than after /OE and enabling the chip with /CE would always require one wait state, at least if i use one of my (well stocked) 27C512-250 EPROMs with 250 nano seconds access time from /CE. But the EPROM's access time from /OE is only 100 ns, and therefore i connect /OE to the timing critical output of the 2-to-4 address decoder and /CE to the less timing critical /RD signal and can access the EPROM with no wait cycles.

Glue Logics


Only two standard logic chips are used to interface the control signals: one 74HCT00 quad NAND and one 74HCT139 dual 2-to-4 decoder.

Bringing it all together

The 74HCT00 quad NAND provides one NAND, one inverter and one flip flop, as can be seen to the left.

The first gate generates /DTACK to terminate most bus cycles. /DTACK is permanently low except if /SLOW_IO or /K1_WAIT is low. /K1_WAIT originates from the K1-bus and /SLOW_IO is activated when access to a special address range is decoded.

The 2nd gate is used to invert the /WR signal of the CPU to generate the /RD signal required by the RAM and ROM.

Gate 3 and 4 construct a flip flop. After reset the CPU reads reset vectors from address 0x000000 which can only be provided by reading from ROM. But at runtime we'd like to have RAM at address 0x000000 to allow the running program to modify the vector table. To achieve this, this flip flop is set by the /RESET signal and cleared by the first access to an i/o address which activates signal /CLEAR_INIT. While set, the INIT output is used to temporarily map ROM at address 0x000000.


The two 2-to-4 decoders of the 74HCT139 are used to generate the RAM and ROM /CE signals, to clear the init_FF and to generate the /SLOW_IO signal.

The 1 MB address space of the 68008 CPU is divided into 4 regions: RAM, ROM, fast i/o and slow i/o.

The first decoder generates the RAM and ROM /CE signals. As explained above it actually generates /OE for the EPROM. For this it decodes the highest address lines A19 and A18: A19 must be low for both and A18 discriminates between RAM and ROM. This results in a memory map as follows:

0x000000 .. 0x03ffff  max. 256kB SRAM (actual size: 128kB)
0x040000 .. 0x07ffff  max. 256kB ROM  (actual size: 64kB or 128kB)

As explained above we need ROM at address 0x000000 after reset. Therefore signal INIT from the init_FF is used to pull A0 of the address decoder high, so that memory accesses in the RAM address range will activate the EPROM instead. Technically the resistor and the diode construct an OR gate.

The decoder is strobed with /AS from the CPU so that there are no spikes on the outputs when the address toggles between bus cycles. This is important, so that the RAM does not erroneously write some void data into random cells and that /CLEAR_INIT is not activated too early, e.g. immediately after reset before the CPU even read the first byte of the reset vector.

/CLEAR_INIT is generated when A19 is high and either A18 or INIT is high, and will clear the init_FF. A19 high means this is an i/o address.

The second decoder generates the /SLOW_IO signal, which is connected to the CPU's /VPA address input and to the first NAND gate to suppress /DTACK. If this signal is activated, the CPU will perform a very slow 6800 peripherals bus cycle. The signal is activated when A19 is high and A14 is low:

A19 must be high for any i/o access, because A19 low is used for memory access, either RAM or ROM.

A14 is used to discriminate between fast and slow i/o cycles. If we'd use A18 instead then the first decoder would have done the job. But A14 is used because it is in the low word of the address.

The 68000 uses 32 bit addresses but has a short addressing mode, where a 16 bit address is sign-extended to 32 bits. This saves program space and execution time, as only 2 address bytes must be read from memory.

If we want to use short addressing for i/o, then all address bits from A15 to A31 must be the same. And as A19 is high, they must all be high. (which besides means negative addresses for i/o). This makes A18 unusable for this task. On the other hand we cannot use A14 to discriminate between RAM and ROM as well, because that would break memory into 16 kB chunks. `:-)

Slow i/o is deliberately chosen to be activated when A14 is low:

When the CPU performs an interrupt acknowledge cycle, it puts the acknowledged interrupt level (which is always 2 on this board) on A1 to A3 and pulls all other address bits high. So during an interrupt acknowledge cycle A14 and A19 will be high and /SLOW_IO, and consequently /VPA at the CPU will not be activated and the CPU will do a fast bus cycle and will read a vector number from the data bus. This is explained in more details below.

K1-Bus I/O Circuit


This board uses a K1-bus for all peripherals. This is a hobbyist-grade 16-bit peripherals bus for CMOS devices which can be used unbuffered in small systems, as is done with this CPU board. The core K1-bus uses 16 data lines (8 may be sufficient for most cards), 6 address lines (4 mandatory) and 5 control lines, aka 'strobe lines'. It has one interrupt line, a wait request line and a reset line.

A unique feature of this bus is, that cards are not selected implicitly by the address in an i/o operation but must be selected beforehand instead. The address lines are only used to select registers inside the currently selected card.

Another unique feature of this bus is, that cards have an assigned data line which is used as their address. This data line is selectable with a jumpers on the i/o cards. This data line is used in conjunction with 3 of the strobe signals: /SELECT, /RD_IRPT and /WR_IRPT. The other 2 strobe signals /WR_DATA and /RD_DATA control data transfer to and from the card. This CPU board can only use data lines D0 to D6.

As can be seen in the circuit above, there are 3 more signals used: /RD_I2C accesses an I²C bus on the K1-bus, which is used to attach I²C EEPROMs which are used to detect cards automatically and which can provide driver code. Implementing the I²C bus interface is optional but recommended. /RD_HI and /WR_HI are used to control 2 data registers which pass data from the 8 bit data bus of the 68008 CPU to the high data byte of the 16-bit K1-bus. This is optional.

A 74HCT138 3-to-8 decoder is used to generate all these 8 strobe signals. A strobe signal is enabled when A19 is high, which on this board means an i/o access. A0 to A2 select which strobe signal to activate.

/AS is used to control output enable as well (to strobe the decoder outputs) so that strobe signals are only generated when the address is valid. Otherwise there would be spurious spikes on the strobe lines. The exact timing is tricky, due to wide timing windows in the 68008 timing charts, which i will discuss somewhere else.

There are 4 strobes for read cycles and 4 strobes for write cycles. They are assigned to the decoder so that using the CPU's /WR signal could have been used instead of A2. And originally it was. But the bus cycle timings for the 68000 are so lousy that the risk for spikes on the decoder outputs was too high and i decided to use A2 instead. Now the program must take care to access i/o addresses with bit A2 properly set, or there will be bus collisions when writing to a read address.

The /RD_IRPT strobe is connected to output number 5 to allow vectored interrupts:

The strobe signal decoder is also activated in an interrupt acknowledge cycle, because /AS is activated as in any bus cycle and A19 is high. A1 to A3 encode the acknowledged interrupt level, which is always 2 on this board.

So the address on the bus is %1……111110101, where the blue digits indicate the acknowledged interrupt level. So bits A0 to A2 are %101 which selects output number 5. This instructs all attached cards to put their interrupt state on their assigned data line: '1' if inactive and '0' if active. Additionally, the data bus has pull-up resistors (initially for helping the 68008 with pulling them up for the K1-bus) which will make all unconnected data bits read '1' as well.

So the vector read by the CPU is 0xff – x, where x is the active or potentially a combination of multiple active interrupts. If we never choose D7 for the assigned data line of a card, the vector will always be in range 0x80 to 0xFF, never conflicting with any other predefined vector. The vector table has to be filled with matching interrupt vectors. Wherever more than one bit is low in the vector address the program can decide which vector to store, e.g. always the vector of the interrupt with higher data bit number, thus implementing interrupt priorities.

The CPU's A0 is used for the strobe decoder's A0 to enable word write instructions. The 68008 first writes the high byte to the even address and then the low byte to the odd address. If writing to the right address, this automatically first stores the high byte in the low-to-high data bus latch and then writes both bytes in a 16-bit K1-bus /WR_DATA cycle. Unluckily the same magic does not work for read cycles: the word will be read byte-swapped and must be swapped programmatically, which is a little bit awkward, because the 68000 CPU has no opcode for this.


The low-to-high and the high-to-low data registers

Data is stored in the low-to-high data register when the /WR_HI strobe signal is active, which means, that the CPU writes to an appropriate i/o address, and it put's it's contents on the high byte of the data bus when /WR_DATA is active, while the CPU supplies the low byte.

There is a jumper option on the board to use /WR instead of /WR_DATA to enable the low-to-high latches outputs. This is to solve potential timing problems.

The high-to-low data register reads data from the high byte of the data bus when /RD_DATA is active while the CPU reads the low byte, and it put's it's contents on the low data bus when /RD_HI is active, which means, that the CPU is reading from an appropriate i/o address.

The I²C bus connection is centered around a 74HC367 2+4 bit driver IC. The circuit is described on my K1-bus page. A6 and A7 are chosen for data output to the I²C data and clock line. D7 is used to read the state of the I²C data line. All I²C signals and timings are generated by the CPU under pure software control.

The I²C bus is used to attach I²C EEPROMs which are used to automatically detect cards and which can provide driver code.

In addition, 2 LEDs are connected to the I²C driver for debugging the board. I spent some time to find a place to connect some lights, as this CPU board does not contain any i/o port pins.

A8 to A13 of the CPU are connected to A0 to A5 ot the K1-bus. Also, there are pull-up resistors on A7 to A13. This is because the 68008 CPU uses TTL levels for all signals and has only very poor high-driving capability, while the K1-bus is defined for symmetrical signals, as used by 74HCxx or 74ACxx series ICs.

The reset circuit was already discussed in great detail in January:

After power-up the capacitor is empty and current flows through it and through the base of transistor T1 which opens and pulls the /RESET line low. While /RESET is low T2 is closed. When C1 fills up the current decreases and at some point T1 does no longer drain all current from the /RESET line: the voltage rises and T2 will open and drain the remaining base current of T1 which will rapidly close: /RESET goes high and the system starts.

2014-08-06

Z80 Microcomputer with SRAM and K1-Bus

My 68008 system is waiting for production and this is, because it uses only 1/2 of an Euro board which i prefer to order, because this size is the cheapest. So finally i finished a Z80 board for the K1-bus as well.

The system consists of a CMOS Z80 CPU running at 6 MHz, one 32kB SRAM and one 32kB EPROM or EEPROM. It has no I/O except a K1-bus connector. The K1-Bus allows attachment of 16 bit peripherals. K1-bus card selection is restricted to D0 .. D7.

Memory Map



The Z80 uses separate instructions for I/O and memory access. Therefore the I/O address space is not part of the memory address space.

RAM is mapped to $0000 .. $7FFF and ROM is mapped to $8000 .. $FFFF. This allows modifying the RST vectors at run time.

After reset the ROM is mapped to the whole address space because the CPU starts execution at address $0000 and needs to find code there.

Pin header JP3 allows using an EPROM or an EEPROM. The EEPROM is writable by the Z80.


RAM / ROM select circuit



The RD and WR outputs of the CPU are directly connected to the RAM and ROM's corresponding inputs. Activation of the memory chips is done using their CE input.

RAM is enabled when IORQ is low and A15 is low,
ROM is enabled when IORQ is low and A15 is high.

There is a 'init state FF' which is set by RESET and cleared by any I/O operation. Thus the FF is set after reset, will be cleared by the first I/O instruction and remain cleared throughout the rest of it's life. While it is set it forces A15 high in the RAM/ROM select circuit so that the CPU will always read from ROM.

Reset circuit


The reset circuit was elaborated in great detail in my hardware blog. It is a little bit load dependent but should work up to 5mA pull-up current, which is much more than is to be expected, unless you attach a LED here. ;-)

With the selected value of the timing capacitor the reset pulse will be approx. 2 ms.


I/O


The CPU board does not contain any peripherals, not even a timer interrupt. All Peripherals are expected to be connected at the K1-bus.

The K1-bus provides an I²C bus to attach EEPROMs with driver code and is 16 bit wide. Therefore two data latches are required for buffering data from the Z80's 8 bit bus to the upper half of the K1-bus.
I/O therefore is divided into I²C bus access, high-to-low and low-to-high data latch access and actual bus access.

The K1-bus is specified for symmetrical signals (HC, AC not HCT or TTL) therefore some signals may not have required levels without help.

K1-bus IRPT is directly connected to the IRPT input of the CPU. The interrupt source is determined in software.

K1-bus WAIT is directly connected to the WAIT input of the CPU. Any peripheral board which can issue WAIT fast enough can be used with this CPU board. Since the Z80 is pretty slow this probably means all peripheral boards.

The K1-bus address lines A0 to A5 are directly connected to the corresponding CPU address lines. They are pulled up with 3.3kΩ resistors to help the CPU to pull them up: The CPU has very little driving capabilities and driving high is even less than driving low:
    IOL = 2.0mA     @0.4V
    IOH = -1.6mA    @2.4V (which is too low for HC inputs)
    IOH = -250µA    @4.2V
Eventually 4k7Ω is a better choice. To be tested.

The K1-bus data lines D0 to D7 are directly connected to the CPU as well, which is an allowed design for very small systems. Expect problems with the third card added! They are pulled up with 3.3kΩ resistors to help the CPU to pull them up for the same reason as above. The K1-bus data lines D8 to D15 don't need pull-ups because they are driven by the 74HCT574 which has symmetrical outputs (though TTL inputs) and slightly higher driving capabilities as well.

K1-bus select and strobe generation


Any I/O instruction addresses the K1-bus. This is centered around a 74HCT138 3-to-8 decoder. It is enabled by IORQ=0 && M1=1. It then activates one of 8 strobe lines, depending on A6, A7 and WR. Pin headers JP4 allow to use A6, A7 and A8 instead and IORQ has a "strong" pull-up resistor. The reason for these circuit options are explained below.


The following strobe signals are generated by the 74HCT138:


WR_SEL
Signal on the K1-bus to select a card for subsequent I/O operations. I/O cycles on the K1-bus do not contain the address of the talked-to card, instead a card must be 'selected'.

RD_IRPT
Signal on the K1-bus to read in the interrupt states of all cards to detect which one actually activates the IRPT line.

WR_IRPT
Signal on the K1-bus to mask off interrupts on some or all card. Can be used to implement interrupts with priority levels.


RD_DATA
Read data from a K1-bus card. The lower data byte is read by the CPU directly while the upper data byte (if present) is latched into the high-to-low data latch.

WR_DATA
Write data to a K1-bus card. The lower data byte is supplied by the CPU directly while the upper data byte (if required) is supplied by the low-to-high data latch.

WR_HI
Write one byte of data into the low-to-high data latch for use in the next (probably immediately following) WR_DATA cycle.

RD_HI
Read the upper data byte from the last RD_DATA cycle from the high-to-low data latch.

RD_I2C
Read or write to the I2C bus. Both is done by reading. The I2C data and I2C clock lines are set by A6 and A7, the value from the I2C data line is read on D7. The circuit is taken directly from my K1-bus page and was the only idea i had to use only one IC.

The two LEDs are for debugging. It took me some time to find a place for some debugging lights on a CPU board without any i/o circuitry (except K1-bus) on it. :-)

74HCT138 enable:



A Z80 I/O bus cycle is strobed with IORQ by the CPU, so this is the first signal used to enable the signal decoder. But the CPU issues this signal for interrupt acknowledge cycles as well. This can be distinguished by the M1 signal, which is activated for interrupts but not for I/O cycles, so M1 must be high to detect an I/O cycle. M1 starts 2.5 cycles (that is: long) before IORQ and remains approx. 10ns longer active (M1: 80ns max. and IORQ 70ns max. after T3↑. These are marked with reference number 20 and 52 in the timing chart below) so it should be possible to use M1 directly to mask the IORQ signal.


Next is a timing problem: WR (and RD) and IORQ go up simultaneously: 70ns max. after T3↓ as can be seen in the I/O timing chart below. This may result in spurious pulses at arbitrary outputs of the 74HCT138.
note: T3 in an I/O cycle is one cycle earlier than T3 in an int ack cycle: int ack inserts 2 automatic wait cycles before T3 and I/O only one.


There are some ideas to overcome this:

  • Make the IORQ signal end earlier:
    Problem: edges can be delayed and not be brought forward. So some very clever circuitry would be needed to do this.
  • Make the WR signal longer:
    Adding a delay to the signal would do the job. But it would also delay the starting edge of the signal: One HCT gate adds approx. 10ns, so the front edge would at least move from 60ns to 70ns, which is now after the 65ns of the IORQ signal. Whatever we do with the WR signal, it will always at least add one gate latency to the front edge as well! So this is no easy solution either.
  • Delay both signals to have time for front and end shaping:
    Unfortunately we don't have any time available for delaying the end of the IORQ signal: in an output cycle data is only guaranteed to be stable for 30ns after IORQ goes up (time #35) which is just enough to propagate IORQ through the '138 to the strobe signal (HCT: typ. 19ns, 40ns max, HC: typ. 17ns, 30ns max)
  • Use an address line instead of WR:
    This is what i do in the Z80 reference design on my K1-bus webpage. This is safe in respect to no spikes on the strobe lines but has a small risk of bus collisions, e.g. if a program crashes. Since all lower 8 address lines are currently used up, we'd need A8 as well, which will make block I/O opcodes impossible to use. But this restriction could be limited to K1-bus cards which actually use all 6 address lines if we reassign the address lines, so that the rarely used K1-bus A5 will be driven by Z80 A8. I will use this design as a fallback for safety. This is what pin-header JP4 in the "IO strobe signal decoder" image above is for.
  • Use a "strong" pull-up:
    A method i have already used on my K1 CPU is "signal shaping" with "strong" pull-ups: If you add a low pull-up or pull-down resistor which draws "high" current, it will aid signal flipping into the pull-up's direction and delay signal flipping in the other. Applying a "strong" pull-up to the IORQ signal will slightly delay the starting edge of the pulse and slightly bring forward the ending edge. So what is "strong" here? The CMOS Z80 can only sink 2.0mA @ 0.4V, so a pull-up which supplies just this will be a reasonable choice: This is approx. 2.5kΩ. Disadvantage: The amount of time which can be shifted this way is tiny: some few ns only, but they may just be enough here.

Besides WR (or A8, if nothing else works) A6 and A7 are chosen for strobe selection, because they are in the low address half, making all I/O instructions usable and they are the only unused address lines because A0 to A5 are already used for addressing K1-bus card registers.

The board


This is a view of the component placement on the board. The board will be produced within the next few weeks in one go with the 68008 board because they can be placed on one Euro board (160x100mm). More material can be found on the project's web page on my web site.

2014-01-27

68008 SRAM Microcomputer – K1-Bus Circuit v0.4

The design phase is coming to an end now, if no problems show up during verification phase later, this will be the K1-Bus I/O circuit:

K1-Bus 68008 CPU board with SRAM – K1-Bus I/O Circuit

Short summary of the K1-Bus


The ➧ K1-Bus is a 16 bit peripherals bus designed for simplicity. The basic unique concept of this bus is that the peripheral board accessed is not encoded in the address of each I/O opcode but the board to talk to must be selected. During selection the boards are addressed with a low level on their assigned data line.

Interrupts are signaled on the !K1_IRPT collector line and during interrupt processing the CPU queries the interrupt state of all devices. The CPU can enable and disable interrupts for each device individually by writing a mask work on the data bus. This way all devices may be assigned an individual interrupt priority if the programmer desires.

There are 5 control lines (strobe signals):
  • !SELECT – The CPU writes a mask word on the bus to select a peripheral board for subsequent data transfer.
  • !WR_IRPT – The CPU writes a mask word on the bus to enable or disable interrupts on the attached boards.
  • !RD_IRPT – The CPU reads a mask word with the interrupt state of all attached boards.
  • !WR_DATA – Send data to the selected device.
  • !RD_DATA – Read data from the selected device.

The circuits


The 74HCT138 3-to-8 line decoder generates the strobe signals for the bus when A19 is '1' during a bus cycle when !AS is low. A0, A5 and the !WR signal are used to determine which signal to activate. For the reason to chose A0 see below.

IC 74HC367 with the surrounding resistors and diode implement an i2c interface. Every K1-Bus extension board can provide an i2c EEprom with software driver for the board. Also, the i2c EEprom is used to detect the presence of the board. The funny circuit design is directly taken from my ➧ K1-Bus homepage. It was the simplest circuit i could come up with if no port pins are available to be used for the i2c bus.

The i2c bus is always accessed by reading from the K1-Bus and one of the select lines of the 74HCT138 3-to-8 line decoder is used to address the 74HC367. A6 and A7 are forwarded to the i2c data and clock line while the i2c data line is also read and returned in D7. When the i2c bus is selected, the upper 4 drivers of the 74HC367 are enabled. Then A6 and A7 are forwarded into the lower two drivers. When the upper drivers are disabled the resistors R1 and R2 feed back the output of the lower drivers to their inputs thus making them state keepers. So the low or high state of the i2c data and clock outputs are preserved until the next access is made to the 74HC367. The timing and protocol of the i2c bus must implemented in software. The i2c bus is optional but recommended unless you want to build a one-task computer which does not support to be extended later.

The two LEDs D4 and D5 may be attached here during debugging. As the 68008 CPU board does not have any I/O pins whatsoever, i have spent some time searching for a place to attach some status lights for initial testing. The LEDs should be removed when an I/O board is attached, because the LEDs may disturb operation of the i2c bus.

The two 74HCT574 data latches expand the 8-bit 68008 data bus to 16 bit for the K1-Bus. This is optional and only required if 16 bit extensions are actually attached. A serial card will probably be only 8 bit wide, but an IDE interface will most likely use the full bus width.

When 16 bit data is written to the bus this is done in two stages: First the upper byte is written into the 74HCT574  low-to-high data latch. One output of the 74HCT138 3-to-8 line decoder provides the !WR_HI strobe. Next the low byte is written to the !WR_DATA address as usual. While the CPU provides the low byte the latch outputs are enabled simultaneously and put their data on the upper half of the bus.

Note that only A0 discriminates between !WR_HI and !WR_DATA. This way the program can perform a 16 bit write opcode to the even base address: The high byte will be written first to the even address (A0=0) and the low byte thereafter to the odd address (A0=1). That's really sweet! :-)

When 16 bit data is read from the bus this is also done in two stages: First the low byte is read using !RD_DATA as usual. This will also load the high byte from the K1-Bus into the high-to-low data latch. Then the high byte can be read from the latch. For this the 74HCT138 3-to-8 line decoder provides the !RD_HI strobe.

Reading a 16 bit word is not as convenient as writing. Again !RD_DATA and !RD_HI are chosen to differ only in A0 and the word can be read with one 16 bit read opcode, but the result will be byte-swapped. This cannot be avoided except with some more latches and drivers. So the program has to swap them thereafter and as the 68000 does not provide a byte-swap opcode (though it provides one for word swapping) the best is probably to do an 8-fold rotate left or right on the word data which is a 24 clock cycles time consuming operation.

There are some pull-up resistors and networks related to the K1-Bus:

The !K1_IRPT collector line has a pull-up because it must be driven with open drain outputs.
!K1_RESET line has a pull-up for the same reason.
!K1_WAIT has a pull-up in case no peripheral board is selected. Whenever a board which uses the wait line is selected it must drive the wait line with a tri-state driver which actively pulls high and low. When a board is not selected it must not drive the wait line.

D0..D7 are pulled high with 3.3kΩ resistors. The data bus of the CPU is connected without bus drivers to the K1-Bus. This is possible for very small projects because the K1-Bus is defined to attach CMOS devices which draw only very little current, though switching the level on a line draws some current for the line capacitance which increases with line length and every device attached.

The 68008 CPU outputs TTL levels with very poor high-driving capability: 0.4mA only. The K1-Bus is defined for symmetrical signals as used by 74HC or 74AC types. This is in general no problem, only some timing parameters will shift a little. But as the 68008 can only drive 0.4mA high (and still guaranteeing only 2.4V, so effectively driving even less than 0.4mA) these resistors are there to help the CPU with the '1' bits. 3.3kΩ result in 1.5mA when the outputs are low which is approx. 1/2 of what the CPU can drive low. Peripheral drivers also must provide this additional current for low bits, but 1.5mA should be ok with almost any IC.

A0..A5 (on the K1 Bus) are pulled high for the same reason.

A6 and A7 are pulled high for the same reason because they are attached to the 74HC367 which is not a 74HCT367. I'm too lazy to modify the circuit for a 74HCT367 and i have HCs in stock, HCTs none.

More random notes:

The 74HCT574 high-to-low and low-to-high data latches were chosen to use HCT because when the CPU can operate the bus with TTL levels then the latches can do this as well. No need to read some data lines with HC circuits – keep it consistent.

This CPU board can attach 16 bit K1-Bus extension boards. This means, it can read and write 16 bit data. But it cannot use the upper byte of the data bus for board selection: !SELECT, !RD_IRPT and !WR_IRPT all do not trigger a read or write of the high-to-low / low-to-high data latches. Therefore peripheral cards are limited to D0..D7 for selection.

2014-01-14

New Project: 68008 Microcomputer

Servus,

one of my weird ideas is to build a microcomputer with every CPU i own. Ok, maybe not really *every*, but some of them i memorize with nostalgia. These are:
  • Z80 with SRAM
  • Z80 with DRAM and paged memory
  • 68008, one with SRAM and one with ~ 2MB DRAM SIMMs
  • 68000, let's see
  • 68020, eventually with FPU
  • 68040, with ~ 64MB DRAM PS2-SIMMs probably
All of them will connect to the K1 Peripheral Bus, so that half of the work is already done. ;-) Beyond that, no I/O will be implemented on these boards.

You see, in essence these are two processors. I started with a ZX Spectrum and proceeded with an Atari ST before i entered the world of Apple, Linux and not Windows.

68008 SRAM Microcomputer

Let's start with the 68008. I have both, the DIL and the PLCC variant. I'll use the DIL version for the SRAM board and the PLCC version for the DRAM board, because it can address more memory: 4MB instead of 1 MB only.

The 68008-SRAM  board will the half sized – 79x100mm – pretty tight, but it will fit. The 68k CPUs are a little bit nasty to integrate into a system, because they have quite a lot of requirements, especially the 68008 which implements an asynchronous bus model. But i'll use any trick, cheat and simplification i can find to make it fit. :-)

Basic Requirements

  • 68008 PDIP CPU
  • Eprom 32 .. 256 kByte
  • SRAM 128 or 256 kByte
  • K1-Bus half-sized card

Project Page


The Board

Layout v0.1 2014-01-14
To the left is a first layout of the board with all components required for the current circuit.

The top row ICs are glue logics.
To the left is the K1-bus connector.
The circuits to the left of the CPU connect the CPU to the bus.
The two 74HC573 registers expand the 8 bit bus of the 68008 to the 8 or 16 bit K1-bus.
The 74HC367 hex driver plus some resistors implements the i2c interface.
The 74HC138 decodes the strobe signals for the K1-bus.
In the center of the board is the CPU
and right of it the SRAM and the Eprom.


Requirements and Simplifications

• Memory Map. Memory is divided into 4 sections: RAM, ROM, fast I/O and slow I/O each of which is 256 kByte in size. This limits the size of the RAM and Eprom.

• CPU Clock. This is generated by a 10 MHz clock module. I avoid the hassle of generating the clock signal "by hand" with a quartz and inverters.

• CPU Reset. The 68000 family needs 0.1s low on Reset and on Halt for power-up initialization. This is currently done with some R and C and a Schmitt Trigger inverter.

• CPU DTACK. The 68008 needs an acknowledge for every memory read or write access. By delaying this signal you can add wait cycles. By never asserting this signal you can make the CPU hang for ever. This signal will be handled in the most simple way possible: It is tied to ground and this way always asserted. According to some other 68008 projects, where they do this for the test run on a bread board, this should work. Drawback: No wait cycles possible. I'll need a reasonably fast Eprom and SRAM. This also applies to the fast I/O.

• Bus Error. DTACK is always asserted and BERR is tied to Vcc. There will never be a bus error.

• Bus arbitration. The K1-bus does not support multiple bus masters and so does this board: bus request BR and bus grant BG are not used.

• Interrupt control. The 68008 PDIP has two interrupt input lines which can encode 4 states: no interrupt, 2 normal, prioritized interrupts and a non maskable interrupt. The only source for interrupts on this board is the K1-bus and so i need only one normal interrupt. The K1-bus supports prioritized interrupts by enabling/disabling interrupts directly on the attached extension cards.
Now the nasty thing: Devices must provide an interrupt vector during the interrupt acknowledge cycle. An automatic vector can be requested by asserting VPA and so we need to know when a bus cycle is an interrupt acknowledge cycle. For this we must decode the Function Code outputs FC0, 1 and 2 which are all '1' during an interrupt acknowledge bus cycle.

• K1-bus access. As said above there are two address ranges for the K1-bus: fast and slow.
The idea is to use a standard memory cycle for fast I/O where wait cycles are not supported because DTACK is permanently asserted. This is suitable for very fast peripheral cards and for switching interrupts and i2c on the K1-bus.
For slow devices i want to use 6800 peripheral I/O cycles by asserting VPA (valid peripheral address) in this address range. This will do a bus cycle synchronized with the free-running E output of the CPU (which has a fixed period of 10 CPU clock cycles) with at least 11 and at most 18 CPU clock cycles due to synchronizing; and no wait states because of the fixed alignment to the E signal. Eventually i'll come up with something better here.

• CPU VPA. This input was already discussed in two requirements above: Interrupt control and K1-bus slow access. During an interrupt acknowledge cycle it is pulled low to request an automatic vector (interrupt routine start address) and in slow I/O it is pulled low to request a slow 6800 peripheral bus cycle.

• K1-bus 16-bit I/O. Peripherals on the K1-bus may use 16 bit I/O. The 68008 has only an 8 bit data bus. There are two possibilities: I use it 'as is' and attach only 8-bit extension cards. Or i add 2 latches to store and receive the upper byte during a 16 bit I/O. I have some K1-bus cards which use the 16 bit bus, most namely the IDE board because IDE is 16 bit wide, and so i'll invest in two '573 data latches.

• K1-bus i2c. Peripheral cards on the K1-bus can have i2c EEproms to signal presence of and identify the card and to provide a universal byte-coded drivers. This costs one '367 hex driver IC plus some resistors. This makes it possible to add arbitrary cards to the microcomputer.

• ROM and RAM. The EPROM and the SRAM may be up to 256 kByte in size each. Due to space constraints only one SRAM IC is possible. Memory access cycles of the CPU are without wait states (see DTACK above) and therefore the memory ICs must be fast enough: Scrutinizing the bus cycle timing charts i expect that 150 ns access time will do it; eventually up to 200 ns will work.

• System Timer. There is no system timer on the board. Instead it is expected that one of the K1-bus cards supplies one. This is fairly easy, because my serial cards with one (or more) 88C192 dual UARTs can supply this.

• Serial and Parallel Ports. Any connection to the outer world requires a K1-bus extension card.

2013-02-03

Version 1.0 of i2c driver EEprom specification


Hello,

Version 1.0 of the specification for the driver i2c Eproms on the peripheral cards is finished.

I have also finished translation of the K1-Bus documentation, which was initially written in German.

Here are the links:

K1-Bus documentation: http://k1.spdns.de/.../K1-Bus/
Driver EEprom layout and bytecode: http://k1.spdns.de/.../K1-Bus/i2c-eeprom.pdf

    ... Kio !

2013-01-21

K1 Bus Update

Hello,

after i have worked for some months on my ZX Spectrum emulator, i'm now back for a while to the K1 CPU.

I worked on the draft for the driver i2c eeproms on the peripherial cards. They are going to version 1.0 soon. In the course i've started translating the K1 bus documentation, which was initially written in German. There is quite a lot of text to translate.

If you are interested, here are some links:

K1 bus documentation: http://k1.spdns.de/.../K1-Bus/
Driver eeprom layout and bytecode: http://k1.spdns.de/.../K1-Bus/i2c-eeprom.pdf

2012-07-18

XVGA TFT update

Hello,
i made some progress with the XVGA controller board.

Current State of the Board

First, it becomes more expensive, because it had to buy a minimum of 5 of the FPD-Link transmitter chips (note: i sell the others. Interested? ;-). Then the layout is very tight. See here:
2012-07-18 autorouted board
ICs placement is nearly final, then i'll add some more hand-routed wires, auto-route again and then hand-optimize. This will take a week or two.
I had real problems with the rams and FPD-link controller, they actually just fit between the '245 bus transceivers and the VGA connector. I tested a couple of arangements, but this produced the least vias.
FYI: bottom left is the 16 bit K1 bus, directly above two 74245 bus transceivers, the SMD ICs are RAM and the FPD-Link transmitter. 6 chips next to the right are drivers and drivers with latches, which are used to select between an externally supplied address (for the CPU reading/writing the video RAM) or internal address from the counter cascade, used to address the video RAM for display. Next 'column' of chips are the external address registers/counters, next are the internal address counter cascade, the vertical chips at the right are clock and ATtiny. Bottom right 6 ICs are the control logic. There's an I2C EEPROM sitting on the rear side of the PCB underneath the bus connector.
Though i us a 15-pin VGA SUB-D connector, the signal is not VGA but transmits 4 LVDS signal lanes, each 3 wires: pos. and neg. differental signals and associated GND. In addition one PWM signal is transmitted on pin 15 which will control the LCD backlight brightnes. According to what i found in the net this is a 5V 125kHz PWM signal.

Current Circuit

Here's an update to the circuit as well:
Circuit 2012-07-18 - Data Paths

Circuit 2012-07-18 - Control Logics