|
... for which there will certainly be no more adequate place to post it. And I don't really expect more than some moral support.
So here we go: I'm planing to build a computer again, with up to 8 CPUs (CDP1802, what else?), up to 8 Mb RAM, Graphics based on the old MC6847 graphics chip (with one of the CPUs dedicated as graphics processor). An 8 bit machine with processors from 1976, which would have outperformed typical 16 bit Amigas or Atari STs from 1990.
I also want PS/2 ports for the keyboard and a mouse, but don't want to use microcontrollers. I have read, that in IBM's first AT models the Intel 8255 parallel port was used in bit mode for that purpose. Does anybody have more information about that?
The old computers based on the CDP1802 worked quite well without any OS, but this will obviously not be the case here. So I fired up both the assembler and the C compiler and started coding.
The code generated by the C compiler was more than eight times longer than that produced by the assembler. So much for compilers generating much better code than you possibly could manually. But, to be fair, it is no secret that 8 bit processors are generally not well suited for C.
Looking at the machine code confirmed that. The C compiler needed all that extra code to pass parameters to functions and keep local variables on the stack. The oldschool assembly techniques relied far more on global variables or holding values in the CDP1802's large register set.
Before I really can get to work now, I will need to work out some kind of calling convention which does not bloat the code too much, does not bog down the CPU by doing actually more work to pass parameters and variables than anything else, while trying to at least reduce the need for globals. Still, it now seems very strange that even the processor's handbook saw no need to discuss any techniques beyond global data. For recursive calls they worried about keeping the return address on the stack, but that's all. Who cares about parameters or local variables?
Edit: Just for fun: A small part of the assembler's listing:
(1) 118/ 34 : ; =========================================================================================
(1) 119/ 34 : ; Interrupt and DMA service routine for the CDP1861 to display an effective resolution
(1) 120/ 34 : ; of 64 x 64 pixels, using a display buffer of 512 bytes.
(1) 121/ 34 : ; =========================================================================================
(1) 122/ 34 :
(1) 123/ 34 : =>TRUE IF Resolution == 40H
(1) 124/ 34 :
(1) 125/ 34 : 72 ExitInterrupt: LDXA
(1) 126/ 35 : 70 RET
(1) 127/ 36 : C4 DisplayInt: NOP
(1) 128/ 37 : 22 DEC R2
(1) 129/ 38 : 78 SAV
(1) 130/ 39 : 22 DEC R2
(1) 131/ 3A : 52 STR R2
(1) 132/ 3B : F8 01 LDI DisplayBuffer >> 8
(1) 133/ 3D : B0 PHI R0
(1) 134/ 3E : F8 00 LDI 00H
(1) 135/ 40 : A0 PLO R0
(1) 136/ 41 : C4 NOP
(1) 137/ 42 : C4 NOP
(1) 138/ 43 : E2 SEX R2
(1) 139/ 44 : 80 DisplayLoop: GLO R0
(1) 140/ 45 : E2 SEX R2
(1) 141/ 46 : 20 DEC R0
(1) 142/ 47 : A0 PLO R0
(1) 143/ 48 : E2 SEX R2
(1) 144/ 49 : 3C 44 BN1 DisplayLoop
(1) 145/ 4B : 80 Rest: GLO R0
(1) 146/ 4C : E2 SEX R2
(1) 147/ 4D : 20 DEC R0
(1) 148/ 4E : A0 PLO R0
(1) 149/ 4F : 34 4B B1 Rest
(1) 150/ 51 : 30 34 BR ExitInterrupt
(1) 151/ 53 :
(1) 152/ 53 : [123] ENDIF
modified 18-Nov-12 4:59am.
|
|
|
|
|
|
Yes, the designers of CPU instruction sets even had humor in 1972 (those instructions should already have been in the CDP1801's instruction set).
|
|
|
|
|
When programming in z80 assembly, I've always used/use a rather ad-hoc "let's use these registers, the function takes fewer ld's that way"-calling convention, but of course it has fewer registers and many are special-purpose so there isn't that much choice to begin with. It may at first seem like that would mean more ld's in the callers (and thus more overall bloat), but it usually doesn't work out like that - the ld's in the callers are generally impossible to get rid of when they're needed, and are generally already absent when the "special-purpose" stuff shows up because the same rules apply to both caller and callee.
C compilers also fail spectacularly to produce sane code for z80. Most of the code is bloat that does stupid things with the stack and ix (frame pointers are a terrible idea on z80).
none of this is helpful, I know
|
|
|
|
|
No, that's exactly the problem. It appears like they did not waste much time on passing parameters or allocating local data on the stack when they designed 8 bit CPUs.
The old CDP1802 has 16 general purpose registers, 16 bit each. You can pick any of them to be the program counter or stack pointer, beyond that you are almost entirely free to use them as you wish. That's one of the features that make me like this old processor so much, but it also makes the bad habit of passing values in registers too easy.
|
|
|
|
|
I have to agree - the IAR Z80-C compiler is particularly bad at this. But then, I guess the Z80 when it was originally designed never had much of a memory space anyway (although 64K was a very good size for embedded work in those days)
And come to think of it, I haven't seen an IAR compiler I did like the output of anyway!
If you get an email telling you that you can catch Swine Flu from tinned pork then just delete it. It's Spam.
|
|
|
|
|
Indeed, even by 1980 most computers still had 16k or less. I can remember huge (S-100) memory boards that used some Intel DRAM controller and had up to 32 4016 16k x 1 DRAMS, 64k RAM total. Those things cost between 800 and 1000 Dollars (far beyond my budget with being only 14 years old ) and nororiously suffered from issues with the DRAM's refresh.
4k static RAM was all I could afford, but it still works. That was not really much, but already enough to get some things working. Hey, I could even load a debugger (2.25 k) and debug programs of up to 1.75k. No debugging anymore if they got bigger than that.
|
|
|
|
|
Lucky bugger! We had 8K, but had to write our own debuggers - and we couldn't have spared 2.25K for it! I think it was probably a couple of hundred bytes, and could only display register and memory values, pause that kind of thing. Variable names? No chance - look up the address on the assembler output (no linker) and look at that!
If you get an email telling you that you can catch Swine Flu from tinned pork then just delete it. It's Spam.
|
|
|
|
|
It was a machine code debugger, so there were no variable names as well. You could, however, set breakpoints, single step through the instructions and examine the CPU registers or the screen output of the program. And it relocated the entire program to the memory behind it automatically, while displaying everything as if it were located at its normal starting address.
The first software I ever bought (for something like 10 or 15 Dollars). It was a good investment. I have it running right now. It has been converted from tape to binary with the program I wrote about a few days ago. Now I use Visual Studio to edit the sources and start the assembler, then I can load the debugger and the program into an emulator (with 64k RAM ) and see if it works. For now I'm emulating the old hardware, since I have not yet built my new version.
|
|
|
|
|
Ooo! Breakpoints would have been lovely! (As would single stepping).
But there was no hardware support, and our code ran in EPROM becasue we had so little memory we couldn't fit the code in and still do anything. I looked at doing both in software, but with a variable length instruction set and no MMU it wasn't an option I could spend enough time on.
If you get an email telling you that you can catch Swine Flu from tinned pork then just delete it. It's Spam.
|
|
|
|
|
IAR is still in business and still makes compilers and I believe IDE for embedded systems.
|
|
|
|
|
If I understand you correctly, it's early and just now getting my first cup-o-joe you are looking to find a convention for passing variables to subroutine. Check out my article AVR Assembly 101[^] the section "Mixing Languages", about half way down explains a way.
|
|
|
|
|
Thanks. If I understand it correctly, you are passing parameters directly in registers. That's ecactly what I'm doing right now. Even with a large register set the number of parameters is obviously limited.
The way C functions are called is more interesting: The parameters are pushed onto the stack and stay there until the function exits. Local variables are allocated on the stack as well. Nested function calls are no problem, because those values stay on the stack until the nested function returns.
That is nice and well, but accessing the variables on the stack can become very awkward with 8 bit processors and twice as awkward with my little CDP1802. It's a RISC processor and has no fancy addressing modes that could help. Whenever I would want to access a variable on the stack, I would have to load the base address into a register and perform some math on that address for it to finaly point at the right place. And that kind of stuff is exactly what bloated up the C compiler's code so much.
The best compromise I have come up with yet would be to push the register's contents onto the stack before calling another function and restoring them when it returns. It's nice that C compilers hide away those things, but it also tends to hide that you are actually writing awkward and slow code.
|
|
|
|
|
Well, why don't you throw out those old CDP1802 processors and get some Pentiums? They're still old but are much better with C!
- Life in the fast lane is only fun if you live in a country with no speed limits.
- Of all the things I have lost, it is my mind that I miss the most.
- I vaguely remember having a good memory...
|
|
|
|
|
Throw out...? And replace them with ... Intel? Blasphemy!
|
|
|
|
|
CDP1802 wrote: Even with a large register set the number of parameters is obviously limited.
It is very limited but is the less painful way if you only have a few parameters.
CDP1802 wrote: That is nice and well, but accessing the variables on the stack can become very awkward with 8 bit processors and twice as awkward with my little CDP1802. It's a RISC processor and has no fancy addressing modes that could help. Whenever I would want to access a variable on the stack, I would have to load the base address into a register and perform some math on that address for it to finaly point at the right place. And that kind of stuff is exactly what bloated up the C compiler's code so much.
If you are passing a large number of variables there really isn't an elegant way to do it.
CDP1802 wrote: It's nice that C compilers hide away those things, but it also tends to hide that you are actually writing awkward and slow code.
Yes it depends a lot on the compiler and how well they optimize the code. Like anything else there is good and bad.
|
|
|
|
|
CDP1802 wrote: it is no secret that 8 bit processors are generally not well suited for C.
True; C was written for 12-bit processors and performs poorly with any other word width.
|
|
|
|
|
Why in the world do you want to use a CDP1802? I gave away my Apple IIc because it used that heap of crap! No LONG CALL instruction (though a LONG JMP was implemented) made it messy to program anything useful. I moved to an Epson QX-16 instead, since it used a far more logical instruction set, with symmetric CALL and JMP instructions, and used a 2 bit register to enable 4 separate banks of RAM and extend my addressing capacity to 256k.
The 8255 was a great I/O controller, by the way, as was the Signetics 2651 USART, which is what you want for a PS/2 serial port. IIRC, the PS/2 is a 4-pin interface that includes TxD, RxD, Signal Ground, and Frame Ground as a four-wire RS232 port, relying on software handshaking (Xon/Xoff) rather than the older hardware handshaking (RTS/CTS). Both will do the job, though the 8255 was designed to integrate nicely with the Z80 bus structure.
Will Rogers never met me.
|
|
|
|
|
Roger Wright wrote: I gave away my Apple IIc because it used that heap of crap! If you had not done that, you could now get a small bundle of cash for it from the fanboys. And the processor was a 6502, like in the 8 bit Ataris or C64s (their 6510 was a clone with minor modifications).
Roger Wright wrote: Both will do the job, though the 8255 was designed to integrate nicely with the
Z80 bus structure.
No wonder. The 8255 was designed for Intel processors, while the Z80 was intended to be an enhanced 8080 clone.
|
|
|
|
|
CDP1802 wrote: 6502
Yup, you're right - memories fade in time...
Will Rogers never met me.
|
|
|
|
|
Just to shock you: The CDP1802 does not have any call instruction, long or short.
|
|
|
|
|
My own technique : push the registers, use the registers for anything you need them, pull the registers.
~RaGE();
I think words like 'destiny' are a way of trying to find order where none exists. - Christian Graus
Do not feed the troll ! - Common proverb
|
|
|
|
|
Why not try compiling some C for a couple of subroutines that reflect your application for an alternative RISC machine ? I suggest you download a free toolchain such as CodeSourcery G++, find out how to select compilation for an earlier ARM CPU, switch on every optimisation possible and see what comes out in the mixed lising, maybe get some ideas ? If that is not to your taste maybe there is another RISC as resource-absent as the CPD1802 you could do something similar for ? Not sure how old the CPD1802 C compiler is...I could always hammer a compiler with a.n.other assembler for smaller chips but I wouldn't be so sanguine in the face of recent compiler advances...
If that doesn't work maybe there is a fully static CPD1802 model in VHDL you could pop into a fast small FPGA and generate your own CPU running at warp speed and hence making your execution problems vanish - faster bus-interfacing problems aside, that is...
What is your application anyway ? Is the CPD1802 the only non-Intel chip licensed for space flight ? What is wrong with an ARM Cortex-based microcontroller ? Was Rick Deckard really Nexus 6 ?
Anyway, the best of luck with your project, I hope you have a lot of fun solving your puzzles and get a great result.
|
|
|
|
|
Just in case you'd miss this post,here a link[^].
The poster answered me instead of you.
~RaGE();
I think words like 'destiny' are a way of trying to find order where none exists. - Christian Graus
Do not feed the troll ! - Common proverb
|
|
|
|
|
Have you tried liberal use of the "register" keyword?
Its supposed to keep variables in registers, if possible. And I recall something about it passing parameters to functions in registers instead of on the stack if used in the function parameter delcaration.
You can write some amazingly small code in C if you try.. like the main loop of a marching pattern memory test in 15 bytes (just small enough to completely fit in the 16 byte instruction cache of the 386 and scream).
[has anybody noticed, if your VI fingers take over and you press esc twice in a row while typing in the message entry box, you loose your entire message with no warning and way to recover it? Guess we now know that codeproject coders have never used VI ]
We can program with only 1's, but if all you've got are zeros, you've got nothing.
|
|
|
|