Shop OBEX P1 Docs P2 Docs Learn Events
Propeller Z80 CPM hybrid - Page 2 — Parallax Forums

Propeller Z80 CPM hybrid

2456789

Comments

  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-04 18:30
    The mini80 is quite cunning. Note how he uses the Z80 as a counter to step through RAM addresses as he jams the boot code into the RAM. That's neat.

    Yes that is cunning. I think he is using 16 propeller pins to talk to the Z80 (including the clock). I sort of ran out of pins once I had assigned pins for audio, keyboard, VGA, SD card but there would have to be some cunning solutions around. I also wanted to add bank switching as I have an idea to extend CP/M with a whole lot of touchscreen driver routines and move all that code into a separate bank so the bios/bdos doesn't get any larger. Once you need a latch for the bank switching that is one chip and then it seemed the MCP23017 chips had the lowest count. But there could well be another cunning solution. I have another design using one propeller chip and more TTL glue chips and that does have 8 pins to talk to a Z80 so there are some options there.

    One thing very nice about the mini80 is the experiments with running the Z80 on lower volts. That can save a couple of LVC chips. And also the experiments pushing it out to 10Mhz - that ends up being faster than any propeller emulation could possibly run at.

    There are also some experiments that can be done with pasm - for instance at the moment to send a bit from the Z80 to the propeller I send it into a latch and then the propeller reads the latch. But the prop might be fast enough to capture an IORQ. Esp if the propeller was running the clock, you could then slow down the clock. Or, any IORQ could go into the propeller and the prop could output a /WAIT to the Z80 and then slow down the clock.

    Ok, that has got me thinking.

    Pin audit on the prop - along the top, 2 for download, 2 for i2c, 2 for audio, 2 for keyboard, 8 for display.

    Along the bottom - 4 for SD card. But if you think about it, when the propeller is fetching data or writing data to the SD card, the Z80 is not going to be doing much, so you could share some pins there. One pin for /CS on the SD card, if it is low then talk to the SD card, and if high, talk to the Z80. eg the SD clock pin could clock the Z80 too. No need to be clocking the Z80 while card access is happening.

    So in a general sense, this could give you 15 propeller pins to talk to the Z80. That opens up a lot of possibilities, particularly if the Z80 is running at 3V3 as well. One could replace the slow i2c bus MCP23017s with some TTL chips that could use a byte wide data interface. DMA should be possible. Need some 245s to isolate the Z80 from the prop when it is running. Maybe some latches for the address, but perhaps that idea in the mini80 of pushing data into the Z80 could be used. CP/M uses a DMA area from byte 80H to FFH but it could be lower, right down to byte 03H if you wanted (mem location 0,1,2 would be a jump to another ram location).

    I'll ponder this some more. I can feel a new board design emerging...
  • cavelambcavelamb Posts: 720
    edited 2013-08-04 21:41
    I think I almost remember that the Z80 inserts a wait state for ALL I/O access. Also, while I've not gone as far with mine, the CMOS Z80H will (allegedly) run at 20 MHz. Dr. A? Perhaps it would be better to start by delegating pins to the Z80 and add another Prop chip to handle the User stuff after the computer is alive? One other thing.... Does anyone have a more substantial list of 3 volt tolerant support chips? I was just googling around for cmos Z80 and came across this AllDataSheets page: http://category.alldatasheet.com/index.jsp?sSearchword=3.6V%20Z80H The format sux, but there are an awful lot of new faces in the data book! Richard
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-05 00:56
    you know me, two chips is one too many ...

    Ok, the gauntlet has been thrown down.

    Take a propeller chip, 2 pins download, 2 for i2c, 2 for audio, 2 for keyboard, 8 for VGA and 4 for SD card. That leaves 12 pins.

    Put a HC245 (or 244 or any other 8 way buffer chip) on the VGA pins. Use these for the bootstrap and control the Z80 pins like reset, busrq, rd, wr, iorq and mreq. Control this with P11. Bootstrap the Z80 before turning on the display. Once the Z80 is running, this buffer chip is never enabled again.

    Use the mini80 circuit to bootstrap the program in. But one more cunning thing. Instead of the buffer on the /wr line, use a resistor eg 470R. So the Z80 thinks it is reading an instruction, its /rd line is low, /wr is high, override the /wr with a low but the resistor prevents any damage to the Z80. Then put zero (nop) on the data lines and carry on.

    P10 does the clock.

    So far - one 245 chip, one ram and one Z80.

    P0-7 are the Z80 data bus. Two pins left - P8 and P9. How can we capture IORQ with those and still allow addresses to other ports too?

    How about Z80 A7 goes to P8 and IORQ to P9. To talk to the propeller, do either a rd or wr to any port from 128 to 255. There are not enough prop pins to decode if it is a read or a write, but no matter, talk in nibble mode and set one bit in the byte to say if it is a read or a write.

    Still just one 245 chip.

    Any iorq to ports 0-127 still work as normal and you can bring out the whole Z80 bus to a 40 pin header etc.

    Pins - P0-7 = data bus to Z80 data bus, P8= Z80 A7, P9-Z80 iorq, P10 = Z80 clock, p11 = 245 buffer enable to bootstrap, P12-15 = SD card, P16-23 = VGA and also 245 bootstrap chip, P24-25=keyboard, P26-27 - audio, p28-29 = I2C bus, P30-31 - serial port.

    need a weak pullup on (say) D7 if this is the pin that flags if it is a port read or write. If it is a write make this low and the prop knows to read the pins. If a read it will be high due to the pullup and the prop knows to put some data on the pins.

    There has to be a flaw somewhere....
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2013-08-05 03:16
    I have used the "RED" 2 pins and "GREEN" 2 pins as the SD card's 4 pins before by altering the mask that is in the VGA setup ( 0F instead of FF). that gives H,V, B low and B High which was split over the three VGA outputs to give a dark blue background and a White(ish) foreground.

    That wasn't the Romless boot that I was thinking about, but thanks very much for dangling yet another distraction in front of me!

    He has a tristate buffer to disconnect some of the busses at reset, I have a thought that whilst the reset is low then a fair few of the pins go hi-z anyway. I look into that, it could be that that was back in the days of non CMOS chips. The couple that I have are the 10MHz ones that he thinks could be under marked higher speed ones. At least the clocking via the Prop will allow for the max to be found, and suitably backed off from.

    I was just about to print a PCB for the CX80 too.

    Alan
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2013-08-05 05:10
    I have just looked up what happens on a Z80 reset. The Addr and Data lines go Hi-z but the control signals get driven HIGH, as in inactive. I wonder how soon a /busrq could be stuck in?
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-05 07:15
    <pulls out data book>

    busrq - higher priority than NMI and is always recognised at the end of the current machine cycle and data, address and output control lines go to high impedance so other devices can control these busses.

    reset - three clock cycles.

    With a minimal circuit, one may never even use busrq anyway. Load in a bootstrap program and issue a reset and then you have an intelligent program to talk to.

    When I get some time I'll draw this up and see if any flaws become obvious.

    btw that was a nice find by Heater spotting how the mini80 bootloader works. Saves quite a few chips.

    I found a few 20Mhz Z80s on ebay just now and couldn't resist buying some *grin*.
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2013-08-05 07:58
    I was laying out a rats-nest from that schematic, until I noticed there are errors on it. the Z80's Busrq and Reset lines and a couple of pullups are all commoned.

    The theory of stuffing the data buss with NOPs is what I remember of "the other" romless booting.
  • blittledblittled Posts: 681
    edited 2013-08-05 12:39
    Stuffing NOPs on the data buss was also a trick Sir Sinclair used on his ZX81 which was use in the display functions. I am very interested in the direction of this project is going. I may blow the dust off my Z80 chips and jump in :).
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2013-08-05 12:53
    On a user groups for the (blessed) Nascoms, I have been in contact with a fellow sufferer. He has just blown the dust off of his Nascom keyboard (Licon),

    That was about 30 year's worth.
  • cavelambcavelamb Posts: 720
    edited 2013-08-05 15:08
    blittled wrote: »
    Stuffing NOPs on the data buss was also a trick Sir Sinclair used on his ZX81 which was use in the display functions. I am very interested in the direction of this project is going. I may blow the dust off my Z80 chips and jump in :).
    That trick was also used (originally?) by Don Lancaster for video timing on his TV Typewriter.
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2013-08-05 15:45
    On the AVRs they often use a very short sleep (snooze??) to account for any in-determinant timings of interrupts.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-05 16:09
    I was laying out a rats-nest from that schematic, until I noticed there are errors on it. the Z80's Busrq and Reset lines and a couple of pullups are all commoned.

    Yes there are a few errors on that schematic. He has put wire joins where every wire joins a chip (not needed on Eagle) but has left out some joins that are needed. No matter, the gist of the concept is clear. I'm going to see if I can draw up a schematic. I was pondering also adding memory bank switching using two more optional chips. My circuit at the beginning of this thread has the bank switching controlled by the propeller, but Z80 purists would probably prefer the Z80 was in control. The N8VEM has a good system with sixteen 32k banks and the top bank is always selected whenever A15 is high, so CP/M is always in memory.

    And Yikes I just found an error in my schematic on post #1 - A8 goes twice to the memory chip :(


    Attached is a concept schematic. It has three extra chips for bank select, and it should be possible to leave out the 138, the 374 and the 32 if just a 64k system is needed. For such a system, one could not decode any ports and any /IORQ would be captured by the propeller. If also detect /WR, then the propeller can work out if it is a port read or write and either read data or put data on the bus.

    If ports are being decoded then one needs a 138 or similar.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-07 16:53
    I've tweaked that schematic a little.

    There is a minimalist circuit using just one buffer chip (244 or 245) for bootstrapping, and you can detect use /iorq and /wr to detect port reads and stop the clock and transfer data.

    I want to add banked memory and that adds another three chips. Ok, not so minimalist. But... I think I can then add a touchscreen without needing any more chips at all.

    These touchscreens are coming down in price. I saw one on ebay for $14 including shipping, and that gives you an SD socket as well as a 320x240 full color display.

    Previous designs using the touchscreen have used a 16 bit bus and dumped graphics out 16 bits at a time. For a little sacrifice in speed it is possible to use the display in 8 bit mode. The manufacturers call this "8080" mode, and I think it can interface very nicely with a Z80. Data lines go to data lines. /RD and /WR are as expected. And then there are three latch outputs to /CS, RS and Reset. The latch is already there from the bank select.

    The catch - there are not enough propeller pins to run the touch SPI interface, so these have to be shared with the VGA pins. On the other hand, it is unlikely one would use the VGA and the touchscreen at the same time, so this board lets you use one of TV, VGA or touchscreen, depending on which components are installed.

    Next little challenge - I've been using Seeed Studios for PCBs and their price for five boards 5x5cm is $9.90, 10x10cm is $20.90 and 10x15cm is $45.90. So there is a bit of an incentive to get the board down from 10x15 to 10x10cm. Something had to go and the something was the SD socket. But it does not matter so much as there is an SD socket on the touchscreen, and also I brought out a header for those 4 pins and I have some little 5x5cm boards already made with SD sockets.

    For real world interfacing there is the Z80 bus but I think the I2C bus on the propeller is more convenient, so that is also brought out to a header.

    I think the Z80 might end up being quite a useful co-processor for the touchscreen. Possibly better than the Propeller even, as up to 512k of graphics can be stored in the banked memory and dumped out quickly to the display - much quicker than a propeller could read data off an SD card. Good for game sprites.
    764 x 780 - 277K
  • roglohrogloh Posts: 5,151
    edited 2013-08-08 22:57
    Dr_A,

    This is interesting stuff with good ideas for bootstrapping the Z80. I have an old Z80 CPU lying around at home, and I've been meaning to hook it up to a prop at some point to see how they could play together and this thread is giving me some motivation to try it out - not sure about the 3.3V Z80 operation part though, I might want to run it at 5V and use resistors to drop the voltage a bit for not frying the prop I/O. 2.2kohm resistors worked okay for me at 5MHz in other projects for limiting the prop's internal protection diode current.

    After looking at your latest schematic I saw a potential issue with how your banked latch worked. After reset it would contain random data until first written by the Z80. This would not let the correct bank be written until A15 went high (eventually). After a subsequent reset the CPU will start at address 0 and it would not be reading from the right bank until the latch is loaded.

    To get around this issue one alternative bootup method that can be used after reset is to feed opcodes from the prop into the CPU directly when it does its memory reads and let the CPU itself load the boot program into high memory backwards with the stack pointer and PUSH operations. I was thinking you could feed it with something like this pattern of opcodes below out to the data bus. You would need to monitor the !RD signal after reset and just assume the presence of the MREQ signal based on the program being loaded. You would also need to control the prop's data bus pin directions at the appropriate time in the read/write sequence to avoid bus contention - you do need to be careful there with instruction timing. It will take slightly longer than your method due to extra LD,PUSH opcodes required but it will load high banked memory and you could jump into this code too.
    ld hl, xxxx  ;  xxxx is last 2 bytes of boot code 
    push hl
    ld hl, yyyy  ;  yyyy second last 2 bytes of boot code etc
    push hl
    ld hl, zzzz  ; etc - repeat until boot code is fulled loaded by all the push operations
    push hl
    ...
    ...
    jmp nnnn   ;  at end where nnnn is the entry point of the Z80 boot code, or use a RET instead
    
    From memory I think the SP register is zeroed after reset, if not you could always preload it (to wherever in memory you want to end) before the push sequence is started.

    Even though it adds another I/O pin you might want to consider bring a Z80 address line into the prop if you want to use it for I/O. A single 8 bit R/W port is not very flexible to encapsulate multiple audio/video/serial/SD accesses by the Z80. I know you can split it into nibbles etc, but this drops the I/O throughput and adds compexity. A single address effectively line gives 2 read ports and 2 write ports (eg, 1 indirect WO address reg and RO status reg, plus a R/W data port) That combination can be rather versatile and should suffice for this purpose.

    You might be able to save a pin by using the I2C clock signal to clock the Z80 unless you need I2C for other purposes.

    I'm still trying to come up with a scheme that avoids the extra 74HC125. I think at a minimum you would need to observe !IORQ, !RD, A0, !RESET and CLOCK from the prop. This combination could let me do it but I would still need to assume !IORQ low without !RD low is an IO write, and this could have issues during interrupt acknowledgement cycles which drops !IORQ and !M1 but not !RD or !WR. But if no interrupts are used it could be okay. It also needs to share the I2C SCL pin as the Z80 CLOCK but personally I don't have issues with that - however some people could.

    Then if no SRAM banking is used it could mean a minimal 4 chip solution, RAM, Z80, PROP and i2c BOOTROM allowing VGA, UART, SD, PS2 KBD, and stereo AUDIO out. That would be pretty cool if it's possible. One extra chip may be needed for RS232 level conversion, or you could just use a FTDI cable.

    Roger.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-08 23:23
    Wow, some clever thoughts there.

    Yes, I noticed the latch problem too - but <after> I sent the board off to be made.

    I am hoping it does not matter. Assume the latch starts up in any random bank. Whatever random number it does start in should not change. So one should be able to send a program to memory location zero and run it and move data to the high bank and once it is running in the high bank the bank latch can be addressed. It may take two bootstrap programs. That is the theory anyway.

    Others might chime in about the I2C clock but a cursory reading of the datasheet seemed to precude that as toggling the clock means something. I need to take another look at the datasheet. Another issue is that the board needs some sort of real world interface and the I2C bus is a convenient way to talk to D to A and A to D and port expanders and so using it for the clock as well may be a problem. I wish the prop had just a few more pins....

    Keep thinking about different designs. You could be onto something with the 125. Maybe replace with a MCP23008?

    Without banking there certainly is a very minimal chip count. Maybe someone will rustle one up on a breadboard?
    let the CPU itself load the boot program into high memory backwards with the stack pointer and PUSH operations.

    That could be really cunning.


    While I am waiting for the boards in post #44 to come, I am working with the board in post #1.

    Errors so far - needs 10k pullups on Z80 pins, and A8 goes twice to the ram chip. Minimalist version is one propeller, two MCP23017 chips, one LVC 245, one LVC 374, one 32 or gate. Writing a little bootloader that can send data.

    This is serial mode and the schematic in post #44 will be at least 8x faster. But slowly building up a little bootloader code and getting it to run is fun. Next step is getting CP/M running.
    ' **************** Z80 Clock **********************************
    PUB Z80Clock                    ' start the Z80 clock
          cognew(@zToggle, 0) 'Launch new cog
    DAT
    {Toggle P15}
            org 0                   'Begin at Cog RAM addr 0
    zToggle mov dira, Pin           'Set Pin to output
            mov Time, cnt           'Calculate delay time
            add Time, #9            'Set minimum delay here
    :zloop  waitcnt Time, zDelay    'Wait
            xor outa, Pin           'Toggle Pin
            jmp #:zloop             'Loop endlessly
            
    Pin     long |< 15              'Pin number
    zDelay  long 16                 'Clock cycles to delay    - stops working if under 15 and a 4Mhz Z80
    Time    res 1                   'System Counter Workspace
          
    
    ' ************************ I2C routines ***************************
    
    PUB MCP23017_Out    ' uses Z80OUTA, Z80DIRA
    ' chips share the I2c bus with the eeprom. Pin 18 reset high, pin 19 and 20 = NC, A0-1-2 on first chip all low
    ' and on second chip A0 is high. The chips can be run from 3 or 5V (useful for level translation)
    ' gives a propeller another 32 input and output pins. Futurlec sell these chips for $1.20. DIP28 narrow package.
    ' if using sockets and can't source 28 pin sockets, use two 14 pin ones. 
    ' use johnny macs code, the io codes are different for the 23016 and 23017
      mcp23017.start(i2cSCL,i2cSDA,%000)                    ' clock, data, address of chip (2 chips, either 000 or 001)   
      mcp23017.cfg_dira(!(Z80DIRA & %11111111))             ' lsb. ! inverts bits. spin uses 1 for output and 23017 is the reverse so NOT the value
      mcp23017.wr_gpioa(Z80OUTA & %11111111)                ' output low byte 
      mcp23017.cfg_dirb(!(Z80DIRA >> 8))                    ' direction
      mcp23017.wr_gpiob(Z80OUTA >> 8)                       ' second lowest byte
      mcp23017.stop                                         ' stop this chip
      mcp23017.start(i2cSCL,i2cSDA,%001)                    ' clock, data, address of second chip   
      mcp23017.cfg_dira(!(Z80DIRA >> 16))                   ' direction
      mcp23017.wr_gpioa(Z80OUTA >> 16)                      ' output second high byte
      mcp23017.cfg_dirb(!(Z80DIRA >> 24))                   ' direction
      mcp23017.wr_gpiob(Z80OUTA >> 24)                      ' output high byte
      mcp23017.stop
    
    PUB MCP23017_InA ' reads the A port on IC4
      mcp23017.start(i2cSCL,i2cSDA,%000)                    ' clock, data, address of chip (2 chips, either 000 or 001)   
      mcp23017.cfg_dira(%11111111)                          ' 1 = input
      result := mcp23017.rd_gpioa                           ' read the bits in port A
      mcp23017.stop   
    
    VAR
      long Z80OUTA
      long Z80DIRA
    
     ' *********** Z80 I2C interface routines ********************
    ' three variables similar to spin/pasm Z80INA, Z80OUTA and Z80DIRA
    ' the DIRA variable defines which pins are inputs and which are outputs
    ' use the Spin protocol for DIRA; H = output, L = Input (the MCP23017 is the opposite, so ! inverts in the mcp23017_out code)
    
    ' Reset.Busrq.Mreq.Rd.Wr.3 Bank.16 address.8 data = 32 pins
    
    'PUB Z80Start
    '   Z80DIRA := %11000111_00000000_00000000_00000000      ' reset, busrq and bank pins outputs, rest all HiZ   
    '   Z80ChangeBank(%000)                                  ' set to bank zero
    '   Z80BusrqHigh                                         ' set busrq high
    '   Z80ResetLow                                          ' reset low
    '   delay.pause1ms(1)                                    ' short delay
    '   Z80Run                                               ' sets reset high again
                   
    
    PUB Z80ResetLow
       Z80DIRA |= %10000000_00000000_00000000_00000000      ' set the direction of the reset pin to output
       Z80OUTA &= %01111111_11111111_11111111_11111111      ' set the output to Low
       MCP23017_Out
       
    PUB Z80ResetHigh
       Z80DIRA |= %10000000_00000000_00000000_00000000      ' set the direction of the reset pin to output
       Z80OUTA |= %10000000_00000000_00000000_00000000      ' set the output to High
       MCP23017_Out
    
    PUB Z80BusrqLow
       Z80DIRA |= %01000000_00000000_00000000_00000000      ' set the direction of the reset pin to output
       Z80OUTA &= %10111111_11111111_11111111_11111111      ' set the output to Low
       MCP23017_Out
       
    PUB Z80BusrqHigh
       Z80DIRA |= %01000000_00000000_00000000_00000000      ' set the direction of the reset pin to output
       Z80OUTA |= %01000000_00000000_00000000_00000000      ' set the output to High
       MCP23017_Out   
    
    PUB Z80Run                                              ' set pins so Z80 runs
       Z80OUTA := %01000000_00000000_00000000_00000000      ' reset low, busrq high, bank zero
       Z80DIRA := %11000111_00000000_00000000_00000000      ' reset, busrq and bank pins outputs, rest all HiZ
       MCP23017_Out
       delay.pause1ms(1)                                    ' for a proper reset
       Z80OUTA |= %10000000_00000000_00000000_00000000      ' reset pin high, Z80 can now run
       MCP23017_Out
    
    PUB Z80Stop                                             ' set busrq low and wait for Z80 to respond
       Z80BusrqLow                                          ' note clock needs to be running for this to have any effect
       delay.pause1ms(1)                                    ' small delay to wait for Z80 to respond, and could be a lot shorter than this.
       Z80DIRA := %11111111_11111111_11111111_00000000      ' control all the pins except data
       Z80OUTA := %10111000_00000000_00000000_00000000      ' control pins high except busrq which is low. Bank is zero
       MCP23017_Out                                         ' output
    
    PUB Z80ChangeBank(bank)                                 ' uses Z80Bank %000 to %111.    
       Z80OUTA &= %11111000_11111111_11111111_11111111      ' set bank to zero
       Z80OUTA |= (bank << 24)                              ' shift in the bank value and or 
       ' calling routine needs to output
      
    PUB Z80WriteByte(address,data)                          ' address includes the bank so A0-A18, 512k.
       Z80DIRA := %11111111_11111111_11111111_11111111      ' all pins are outputs 
       Z80OUTA &= %11111000_00000000_00000000_00000000      ' clear the bank, address and data bits
       Z80OUTA |= (address << 8)                            ' merge z80outa with the address (including bank)
       Z80OUTA |= data                                      ' merge with data
       Z80OUTA &= %11010111_11111111_11111111_11111111      ' set the write and mreq pins low
       MCP23017_Out                                         ' output
       Z80OUTA |= %00101000_00000000_00000000_00000000      ' set the write and mreq pins high
       MCP23017_Out                                         ' output
       Z80DIRA := %11111111_11111111_11111111_00000000      ' data pins are now inputs (default condition) for next routine
       Z80OUTA &= %11111111_11111111_11111111_00000000      ' clear the data pins too 
    
    PUB Z80ReadByte(address)                                ' address includes the bank so A0-A18 = 512k
       Z80DIRA := %11111111_11111111_11111111_00000000      ' all pins outputs except the data pins  
       Z80OUTA &= %11111000_00000000_00000000_00000000      ' clear the bank, address and data bits
       Z80OUTA |= (address << 8)                            ' merge z80outa with the address (including bank)
       Z80OUTA &= %11001111_11111111_11111111_11111111      ' set the read and mreq pins low
       MCP23017_Out                                         ' output
       result :=  MCP23017_InA                              ' get the byte
       Z80OUTA |= %00111000_00000000_00000000_00000000      ' set the read and mreq pins high
       MCP23017_Out                                         ' output
    
    PUB Z80Dump(n) | i,j
       j := 0
       vt100.hex(0,4)               ' address
       vt100.out(32)
       repeat i from 0 to n
         vt100.hex(z80ReadByte(i),2)
         vt100.out(32)
         j +=1
         if j > 15
           j := 0                                    
           crlf
           vt100.hex(i+1,4) ' address
           vt100.out(32)
       crlf    
       
    
    PUB Bootstrap | i ' send a tiny program capable of receiving a block of data
     ' uses 4 Z80 pins, 2 each way.
     ' P0 = Prop to Z80.
     ' P1 = Prop to Z80. 
     ' P4 = Z80 to Prop. 
     ' P5 = Z80 to Prop.
    ' write this in the N8VEM program and compile using RASM, view with hexedit, copy and paste from hexedit as hex text
    { 
            .Z80
            org 0h          ; need this to start at zero, add other org statements as needed
    
    main:
            nop                     ; first byte ? gets corrupted to zero so put in a NOP
            ld      sp,0FFFFh       ; stack
            call    fetchbyte       ; get low byte of address to store
            ld      l,d             ; store in l
            call    fetchbyte       ; get high byte of address to store
            ld      h,d             ; store in h
            
    mainloop:
            call    fetchbyte       ; get a byte in d
            ld      (hl),d  ; store in memory
            inc     hl              ; increment counter
            jr      mainloop        ; repeat
    
    fetchbyte:                      ; returns byte in d
            ld      d,0             ; byte coming in is stored in d so reset to zero
            ld      b,8             ; load b with 8 to count the bits
    get8bits:                       ; do 8 times
            call    fetchbit        ; waits till data is clocked in, returns e = 0 or 1
            ld      a,d             ; get previous d
            or      e               ; or with the returned bit
            ld      d,a             ; store back to d
            rrc     d               ; rotate right 
            djnz  get8bits  ; do this 8 times
            ret                     ; return
    
    fetchbit:                       ; returns e = 0 or 1
            in      a,(0)           ; ports are not decoded so any port number will work
            bit     1,a             ; bit 0 is the data and bit 1 is the clock, wait till High
            jr      z,fetchbit      ; loop until the clock goes to 1
            and     00000001b       ; mask off other bits
            ld      e,a             ; store answer in e
    clockzero:                      ; wait until the clock goes to zero again
            in      a,(0)           ; read the port         
            bit     1,a             ; test the clock bit, wait till Low
            jr      nz,clockzero; loop
            ret                     ; return
    }
    ' see the dat section below for the compiled program
    
             printstringcr(string("Load Bootstrap"))
             Z80Stop
             repeat i from 0 to 63                          ' just a bit longer than the program in case change a few bytes
                Z80WriteByte(i,byte[@Z80BootProgram][i])
             DIRA |= %00000000_00000000_00000000_00000011   ' bit 0 is data and bit 1 is clock
             OUTA &= %11111111_11111111_11111111_11111100   ' both bits zero
              Z80Run                                         ' run this program on the Z80
             SendByte($40)                                  ' low byte of address to send data to
             SendByte($00)                                  ' high byte of address to send data to
             repeat 1000
               SendByte($AA)                                  ' send out data
             Z80Stop
             Z80Dump(80)
    DAT
       Z80BootProgram
            byte $00,$31,$FF,$FF,$CD,$13,$00,$6A,$CD,$13,$00,$62,$CD,$13,$00,$72 
            byte $23,$18,$F9,$16,$00,$06,$08,$CD,$22,$00,$7A,$B3,$57,$CB,$0A,$10 
            byte $F6,$C9,$DB,$00,$CB,$4F,$28,$FA,$E6,$01,$5F,$DB,$00,$CB,$4F,$20 
            byte $FA,$C9
                                       
    PUB SendByte(n) ' send a byte out serially to the Z80
         repeat 8
           OUTA |= %00000000_00000000_00000000_00000010 | (n & %00000001) ' clock bit high, or with data bit
           repeat 3                                      ' with a 4Mhz Z80 clock, 2 works, 1 fails, use 3 to be safe
           OUTA &= %11111111_11111111_11111111_11111100 ' clock low, data low cleared for next bit
           repeat 3
           n >>= 1             
    
  • cavelambcavelamb Posts: 720
    edited 2013-08-09 21:33
    Digging around in the bottom of the storage room boxes I also turned up a stash of old (spelled obsolete?) chips.
    A few Z80H and 84C00 processors. But no book or datasheets.

    So I went a-googlin'

    <http://www.zilog.com/appnotes_download.php?FromPage=DirectLink&dn=PS0178&ft=Product Specification (Data Sheet) &f=YUhSMGNEb3ZMM2QzZHk1NmFXeHZaeTVqYjIwdlpHOWpjeTk2T0RBdmNITXdNVGM0TG5Ca1pnPT0=>

    aka http://tinyurl.com/mnjj957 Z80 datasheet for the current chips.


    The guaranteed minimum Vcc is listed as 4.5Vdc. (for what that's worth?)

    On the other hand, the z84C0020 is listed as a 20 mhz part!



    .
  • YanomaniYanomani Posts: 1,524
    edited 2013-08-10 00:09
    Hi cavelamb,
    The 84C0020 is really a 20 MHz part.
    As long they are CMOS parts, and both the Z80H and 84C0020 are, for sure, their operating frequencies can be directly scaled down if you lower the operating voltage, within certain limits, naturally.
    I don't know exactly at what extent it can be done, one must carefully test the allowable working range, but simultaneously lowering both the operating voltage and frequency, it will not harm the CPU itself, it only poses a limit to its usefulness as a valid control signal generator such as address lines and !MREQ, !IORQ, !RD, !WR, and the like.
    The 4.5 V lower limit shown in the specs sheet is there in the belief one will expect 'see' TTL level signaling and, of course, correctly interpret data and control inputs like !WAIT, !BUSRQ, !INT and !NMI, all specified in the TTL realm.
    When a design falls out of the specified range, NORMAL operation is not ENSURED, but in no way it is FORBIDEN to try it and check its stability.
    According to my own experience, back in 1998-99, when I tryed to extend the operating range, both upper and lower, for clock frequency and operating voltage, was to tight regulate the power supply and filter out as much noise as attainable, using several combinations of ceramic and tantalum capacitors.
    A refined layout was also done, since the experiments were done using a two layer circuit board and a lot of capacitors in a gridded distribution to obtain <40 mV of total noise, measured with a good osciloscope at Z80's and RAM's power pins.
    And be certain that i've payed a lot of attention to heat development/dissipation when my work was done, specially when pushing the limits to higher voltages and frequencies.
    Another concern, during my work, was with data integrity, so I decided to copy the testing software, at low speed, from a cmos eprom into a fast cmos static cache ram, 20 ns if I do remember correctly.
    A clock circuit, specially crafted according Zilog specs and that only stops in sync with the !M1 fall, to avoid RAM data corruption, was used to start/stop the clock, allowing the muxed selection of another frequency, and that restarts manualy, under the control of two debounced switches, commanding a 74HCT flip/flop.
    !RESET was always applied before checking normal operation thru the tests, without reloading the RAM.
    Also I followed every design tip available in Zilog's application notes, with respect to active/resistive pullups and everything else I could find to read and learn.
    As an upper limit example, using GAL22V10-15 and 74ALS gates to do the glue logic, I could push a Z80A to 8 MHz@5.0Vdc, limited to 10 minutes, without using heatsinks nor frying it.
    As a lower limit, using a Z80H with GAL22V10-15 and now 74HC gates to do the glue logic, It sustained 6MHz@3.8Vdc, now with an almost cold CPU and more than 30 minutes long.
    Since I don't intended to interface it to 3.3V logic, I'd never pushed it to a lower limit than that.
    Both setups used a 74HC374 with shorted Din/Dout's, LE and !OE controlled by !IORQ, !RW and !RD to succesfully test basic I/O operations. In another setup, INIR and OTIR were tested directing I/O operations to upper RAM memory, by tweaking the 22V10 and forcing A15-A08 high during !IORQ windows by the use of pullups and a 3-stated 74HC374.

    So, my conclusion was, there is always room for testing and improvement.

    I hope this will help a bit.

    Yanomani
  • cavelambcavelamb Posts: 720
    edited 2013-08-10 11:42
    That's pretty awesome work, Yanomani.

    I've been wondering how to set up to test the Propeller's response time to react to a chip select signal from the Z80 (and address decoders)
    to use the Prop as a byte-wide peripheral.

    8 data pins, a couple of address pins, a chip select pin, and /RD or /WR?

    Probably impossible... :)
  • Mike GreenMike Green Posts: 23,101
    edited 2013-08-10 12:20
    In assembly, this would be pretty easy that could be tuned up a little:
    idleWait:
              mov temp,ina   ' read everything in
              test temp,csMask   wz   ' check for CS high
         if_z jmp #idleWait   ' wait for bus idle state
    mainLoop:
              mov temp,ina   ' read everything in
              test temp,csMask   wz   ' check for CS low
        if_nz jmp #mainLoop
              test temp,rdMask   wz   ' check for read first
         if_z jmp #doRead
              test temp,wrMask   wz   ' check for write
         if_z jmp #doWrite
              jmp #idleWait   ' ignore if both low or both high
    doRead:
              andn outa,dataMask   ' zero bits to receive data
              or outa,dataByte   ' move data to output register
              or dira,dataMask   ' output on data bus
    testDone:
              mov temp,ina   ' wait for RD to go high
              test temp,rdMask   wz
         if_z jmp #testDone
              andn dira,dataMask   ' make bus high impedance
              test temp,csMask   wz   ' check for CS high too
        if_nz jmp #mainLoop   ' if so, start new
              jmp #idleWait   ' wait for CS to go high
    doWrite:
              and temp,dataMask   ' isolate data bits
              move dataByte,temp   ' save data
              jmp #idleWait   ' wait for CS to go high
    
    This takes about 8 instructions at 200ns each to respond to a read request and another 6 instructions to go back to idle for the next request. That's 1.6us access time and 2.8us cycle time. Write cycles are faster. If you want address decoding, that takes more time. I'm sure this could be done better and faster, but this gives a worst case. I'd probably use hub memory instead of cog memory for the device registers and use the address bits as part of the hub address. That would certainly slow things down further, maybe adding 3 instruction times plus hub access time (up to 1us).
  • cavelambcavelamb Posts: 720
    edited 2013-08-10 16:50
    Mike Green wrote: »
    In assembly, this would be pretty easy that could be tuned up a little:

    This takes about 8 instructions at 200ns each to respond to a read request and another 6 instructions to go back to idle for the next request. That's 1.6us access time and 2.8us cycle time. Write cycles are faster. If you want address decoding, that takes more time. I'm sure this could be done better and faster, but this gives a worst case. I'd probably use hub memory instead of cog memory for the device registers and use the address bits as part of the hub address. That would certainly slow things down further, maybe adding 3 instruction times plus hub access time (up to 1us).

    Golly Mike! Talk about impossible! :)

    Well, I was thinking mode of a waitpne type thing for /CS, rather than polling.
    (Might that reduce the /cs response time by several instructions?)

    But looks like polling is unexpectedly mighty quick.


    For video memory - how to address a particular location?

    Write a control word to indicate what you are doing (write video address)?
    If writing to set up a video address, write two more bytes to make up the address, then
    another byte to go in that location (or set attributes at that location)?

    define control words for -

    set video address for X number of writes? (then write that many bytes and the Prop autoincrements)
    set attribute(s)? color, blink, invert,?
    clear screen, clearEoL, etc
    read from?
    insert character (move everything to the right)
    read from keyboard (buffer?)
    read from serial port
    write to serial port
    read mouse
    write audio?
    what else?

    That might get pretty messy pretty quick.
    But it would be so much faster than serial.
    ESPECIALLY for graphics oriented touch screens where there is so much to write.


    (edit)

    PS: maybe just one address pin?
    Like the 8255 - o=data, 1 = control word
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-10 17:32
    I wonder if there is a solution that combines the clock with testing for a response? If the propeller is running the Z80's clock, there are all sorts of clever things that can be done. Take Mike's code and

    In pseudo code..
    loop:
    clock high
    test temp,mask
    if z jmp readwrite
    clock low
    jmp loop


    and then the routine readwrite can take as long as it likes, as the clock has stopped.

    There should be lots of time in the pasm code for this - quick experiments with a 4Mhz Z80 and it starts to fail with anything less than about 14 nops between clock cycles. I have a feeling based on experiments some years back that the 55ns ram chip needed 2 or 3 nops to be reliable. So the propeller should be more than fast enough to test a pin each clock cycle.
  • jmgjmg Posts: 15,144
    edited 2013-08-10 18:09
    You could also add a SPLD to do some simple state-engine handshakes, and handle the clock without having SW loops impact all clock cycles. Prop can still generate the raw MHz clock, for easy scaling and tests, but pauses are managed in PLD.
    SW delays then only need delay those where the Z80 and Prop know they are in direct dialog.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-10 18:19
    Good point.

    Thinking out loud, from the Z80's perspective, collecting a series of bytes will involve, at the least, 4 instructions? a port read/write, then read/write to ram, then increment the ram counter, then do a loop (djnz).

    But using the direct memory stuffing routine, DMA access might be a lot quicker. Block read write in CP/M is done in 128 byte blocks and the memory for this is between address 128 and 255. I wonder if there is a way of doing this in one Z80 cycle - eg the Z80 has some code at around address 120 which is a loop with an OUT or IN instruction. This tells the propeller to take over. The propeller starts putting nops in until it gets to address 128 (maybe there is an instruction at 127 as a marker) and then stuffs the data in, or reads the block. From the Z80s' point of view this would be one instruction per direct memory access rather than 4 bytes.
  • YanomaniYanomani Posts: 1,524
    edited 2013-08-10 18:36
    Hi cavelamb!
    Thank you!
    Doing things like that was my bread and butter, most of the time, but in the that specific case, was adding knowledge to my savings and a lot of enjoyment!

    Hi Dr_Acula!

    I think I've missed something about the following code operation:
    Dr_Acula wrote: »

    In pseudo code..
    loop:
    clock high
    test temp,mask
    if z jmp readwrite
    clock low
    jmp loop


    and then the routine readwrite can take as long as it likes, as the clock has stopped.

    There should be lots of time in the pasm code for this - quick experiments with a 4Mhz Z80 and it starts to fail with anything less than about 14 nops between clock cycles. I have a feeling based on experiments some years back that the 55ns ram chip needed 2 or 3 nops to be reliable. So the propeller should be more than fast enough to test a pin each clock cycle.

    When you are commenting about the number of 'nops' you used to fill the loop of flipping Z80's clock signal, you stated that a minimum of 14 was needed.
    Looking at the code snippet shown above, if one demands something nearly shaped as a square wave, it does not means that it would be better to balance the amount of nops for each high and low level branch?
    I'm thinking this way because 14 nops@80MHz will last 700nS and adding the pin level fliping and decision making instructions certainly will give a <1MHz clock frequency.

    Since I have almost no experience in Propeller programming, have I missed something that otherwise was obvious?

    Yanomani
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2013-08-10 18:55
    You are right - it does seem a bit slow. Maybe it is something wrong with my board layout? The code I was testing that with also was doing some data transfer and maybe that is the problem. I need to do some more experiments!

    One thing that is nice with the propeller is the way you can test things out really slowly with Spin first, and then move things into Pasm later. Makes debugging a lot easier, especially when you can completely stop the clock and test all the pins with a logic probe.
  • YanomaniYanomani Posts: 1,524
    edited 2013-08-10 19:47
    Dr_Acula
    I think it was not an error. As a true doc, you can call this a 'perfection addicted old man, full of memories' maniac behavior! :tongue:

    Since I've moved to Rio de Janeiro, i left my whole databook and application notes collection behind me, about 150kg.
    Fortunately my grey matter, and all the memories it carries, persists inhabiting my head.
    Googling a bit (after blowing the dust) with the following text, between quotes:

    "z80 cpu clock buffer circuitry"

    leads to an old friend of mine, from ancient Mostek, at:

    http://maben.homeip.net/static/S100/zilog/z80/Mostek MK3880 aka Zilog Z80 Central Processing Unit.PDF

    Don't be affraid, it lasts almost a century to download, but worths each bit.

    Take a look at figure 9.0-6@pp. 68,

    That was the circuit i EVER used on my NMOS Z80 & Z80A boards. And I had not a single prototype that failed performing. At least no one blaming the clock! :cool:

    I'm unsure if it is/was needed on the CMOS versions, must Google and study a little before stating with any confidence.

    Hope it helps a bit

    Yanomani

    P.S. the 74S04 seems a bit 'old style logic'. If my memories are just not failling, you can substitute for a 74ALS04 for testing purposes. Maybe one of the actual tiny gates could also do the job, more testing to be done...
  • Mike GreenMike Green Posts: 23,101
    edited 2013-08-10 20:32
    I think WAITPxx would be a little faster:
    idleWait:
              waitpeq csMask,csMask   ' wait for CS high
    mainLoop:
              waitpne csMask,csMask   ' wait for CS low
              mov temp,ina   ' read everything in
              test temp,rdMask   wz   ' check for read first
         if_z jmp #doRead
              test temp,wrMask   wz   ' check for write
         if_z jmp #doWrite
              jmp #idleWait   ' ignore if both low or both high
    doRead:
              andn outa,dataMask   ' zero bits to receive data
              or outa,dataByte   ' move data to output register
              or dira,dataMask   ' output on data bus
              waitpeq rdMask,rdMask   ' wait for RD to go high
              andn dira,dataMask   ' make bus high impedance
              jmp #idleWait   ' wait for CS to go high
    doWrite:
              and temp,dataMask   ' isolate data bits
              move dataByte,temp   ' save data
              jmp #idleWait   ' wait for CS to go high
    
    This takes about 2.2us for a read cycle and 1.9us for a write cycle. I figure roughly 2us more to add the use of address bits to access an array of locations and to use hub memory instead of cog memory.
  • YanomaniYanomani Posts: 1,524
    edited 2013-08-10 21:09
    Dr_Acula

    To avoid further amending my last post, this is a following to its contents.

    About my suggestion to use an actual tiny gate inverter to substitute the 74F04....
    After searching and comparing their specs, bearing in mind the fact that an old style design like the Z80A does have specific needs in its clock tree input pin, as shown in Mostek's literature, i've found Fairchild's NC7SZ04M5 TinyLogic UHS Inverter an almost perfect replacement for the F04.

    I hope you feel confortable soldering a male Drosophila Melanogaster sized SMT. Despite the fact it exhibits only five legs, maybe it proves to be an interesting mutation!:smile:
  • cavelambcavelamb Posts: 720
    edited 2013-08-10 21:42
    Dr_Acula wrote: »
    Good point.

    Thinking out loud, from the Z80's perspective, collecting a series of bytes will involve, at the least, 4 instructions? a port read/write, then read/write to ram, then increment the ram counter, then do a loop (djnz).

    But using the direct memory stuffing routine, DMA access might be a lot quicker. Block read write in CP/M is done in 128 byte blocks and the memory for this is between address 128 and 255. I wonder if there is a way of doing this in one Z80 cycle - eg the Z80 has some code at around address 120 which is a loop with an OUT or IN instruction. This tells the propeller to take over. The propeller starts putting nops in until it gets to address 128 (maybe there is an instruction at 127 as a marker) and then stuffs the data in, or reads the block. From the Z80s' point of view this would be one instruction per direct memory access rather than 4 bytes.

    I dunno, Doc.
    The Prop would then be busy poking nops instead of shoveling in data.
    True DMA on a Z80 is usually done between mem/IO cycles, isn't it?

    Like jmg said, just stop the clock.
    The Z80 is a static device.
    As long as there is no dynamic memory in the circuit that needs refresh cycles, just stop the clock...

    Kinda like: (it HAS been a long time since I did Z80 coding)
    WriteBlock: ; BC has byte count,
    ; HL the source address
    Mov a, (hl)
    out PropData, a ; at which time the clock is stopped until the prop has read and stashed that byte,
    DJNZ WriteBlock ; back to real time clock
  • YanomaniYanomani Posts: 1,524
    edited 2013-08-10 21:47
    @Mike Green & Dr_Acula


    Since there are lots of peripheral circuits that can be controlled by the Propeller itself and sparing a few pins could be of great value, i'm wondering if, at the cost of a bit longer loop, one could not create an indexed file of registers, whose pointer was settle in a previous Z80 commanded I/O write operation into the Propeller.
    There are plenty of examples of using such strategy to avoid connecting address lines to peripheral circuits.
    The data lines are a must, even in cases one don't need the full eight set.
    Address lines could proof an expensive luxury.
    Even address auto increment schemas, for exchanging information in blocks, are feasible.
    An interlocked protocol can be tightly coded, avoiding register file corruption by the loose of sync between the two processors during chained write operations,

    Any thoughts?

    Yanomani
Sign In or Register to comment.