sci4me wrote:
My main question is how would I go about implementing some sort of hardware communication system? Like I said, I could EASILY create a system in the emulator but as far as the hardware (real hardware not virtual) goes, I'm not sure at all...
Garth's summary is useful, but not sure if it's helpful to you.
The 6502 family (among others) uses Memory Mapped I/O. That is, is treats I/O just like it does memory. If you want to determine the values of something (like switches), you read a byte. If you want to change the values (like LEDs), you write a byte.
In contrast, the Intel family of processor historically have specialized IN and OUT instructions, and control pins on the CPU to drive I/O devices.
So, on a 6502, if you wanted to read the value of a serial port, you'd just do something like LDA $1234, where $1234 is the address of the serial port. On an Intel device, you would do something like IN $12, which would read device mapped to $12.
The chips that Garth was referencing are the devices that turn those "memory" references in to real work. For assorted reasons, you don't want to just hang I/O things (like switches and LEDs) straight on the address and data bus, so there are other chips that make that much easier and simpler.
As far as implementing I/O in a simulator, it's quite simple. You have code in your low level memory read and write routines and then handle the I/O. You can do this simply, on my simulator, I have a byte mapped for reading the keyboard and one for displaying a character on the terminal. When the simulator does a STA $C001, my memory write routine checks for the special address and simply calls the internal "display character" routine, that handles the screen address, scrolling, etc.
Normally, a memory write is simply setting an array value: memory[address] = value. When the special address is encountered, it calls the appropriate routine. A downside of my technique is that from a clock time perspective, writes to the character output byte take "longer". The CPU increments the correct number of cycles, but actual, wall clock time, is much longer than setting an array value, it does a lot more actual work in the simulator.
This is fine for my purposes, I don't have strict timing, but you can see that if you did, you'd have to compensate for that somehow.
I also have Disk I/O in the same way. Put the start address in two special bytes of memory, set the disk block number in two other bytes (65536 total blocks), and then store a 1 or a 2 in to the control byte, and, magically a 512 byte block of memory is written to disk, or read from it. Super simple.
Here's the fragment from my Forth:
Code:
0270 c000 CHAR_READY = $C000
0271 c001 CHAR_IN = $C001
0272 c002 CHAR_OUT = $C002
0273 030f
0274 030f ad 00 c0 GETKEY LDA CHAR_READY
0275 0312 f0 fb BEQ GETKEY
0276 0314 ad 01 c0 LDA CHAR_IN
0277 0317 60 RTS
0278 0318
0279 0318 8d 02 c0 OUTCHAR STA CHAR_OUT
0280 031b 60 RTS
The CHAR_READY byte is set to 1 if there is an unread key, then CHAR_IN is called to read the key.
Here's the Forth using the Disk I/O
Code:
3403 1715 ;
3404 1715 ; Disk Control addresses
3405 1715 ;
3406 c003 DSK_SEC_LO = $c003 ; Low byte of disk sector
3407 c004 DSK_SEC_HI = $c004 ; High byte
3408 c005 DSK_SEC_COUNT = $c005 ; Number of sectors to operate on
3409 c006 DSK_ADDR_LO = $c006 ; Low byte of memory address
3410 c007 DSK_ADDR_HI = $c007 ; High byte
3411 c008 DSK_RESULT = $c008 ; Operation result code
3412 c009 DSK_MODE = $c009 ; Operation mode: 1=READ, 2 = WRITE
3413 c00a DSK_CTL = $c00a ; Write non-zero to start operation
3451 1763 ;: R/W ( addr sec f )
3452 1763 ; 0= IF 2 ELSE 1 THEN R> ( Push read flag on to return stack )
3453 1763 ; DSK_SEC_LO ! ( store sector, no need to range check -- 0-65535 )
3454 1763 ; DSK_ADDR_LO ! ( store buffer )
3455 1763 ; R> DSK_MODE C! ( store r/w flag )
3456 1763 ; 1 DSK_SEC_COUNT C! ( 1 sector )
3457 1763 ; 1 DSK_CTL C! ( perform transfer )
3458 1763 ; DSK_RESULT C@ 1- 8 ?ERROR;
This was nice because my simulator does all the heavy lifting, stuffing numbers in to memory was easy to do in high level Forth.
Why did I pick this scheme for Disk I/O? Seemed like the thing to do at the time, it's not based on anything. Why did I map my I/O at $C000? Same reason.
Now you can make your simulator more accurate, do interrupt processing for I/O, etc. I haven't. My needs haven't been that strict. But you can certainly go there.