-
First Pass Assembly
06/25/2018 at 13:26 • 0 commentsTTA8 (Paged Memory Version) Assembly
Here is the first pass assembly:
I found (by testing with the Arduino) a fault on the mother board, so I have order a ne PCB.
I also forgot a link on the Serial I/O board but that was easy to patch.
The timing board passed but I have not tested the other boards yet - To do!
Emulation
I wrote a simple file based emulator for the TTA8 (Weird CPU type) previously. But after talking to another "mad-man" at an electronics shop (by accident) I decided I should rework the emulator to be more graphical:
Now the emulator is really slow (about 10,000 times slower) so it is necessary to bypass the delay routine. This makes the monitor unstable as the likelyhood of a switch transitions during a critical sections of the code is almost certain. So I reworked the monitor code to work with the emulator.
This took about three or four days as I did not properly understand what was going wrong and why it works in real life. Anyway it work fine now, my most complex code so far works as expected. I am confident that any code that works on the emulator it will actually work on the real thing.
Next is to model the Page Memory TTA8 version.
AlanX
-
Designs Nearly Done
06/03/2018 at 14:01 • 0 commentsDesigns Nearly Done
Quite a bit of rework and error checking done and more to be done, but I have completed schematics and PCBs for:
- Mother board and front panel
- Timing board
- Program counter and memory address registers board
- Memory access registers and program counter board
- ALU board
- Timer and interrupt board
- Serial I/O board
All that is remaining is the memory board.
The idea of having so many boards is to make it easier to test the boards (individually) and to redesign them if (when!) required. A smaller board is less likely to have errors than a bigger board.
I dropped the crystal frequency to 4.9152 MHz and removed the first clock divide by two flip-flop. The serial rate is hard wired at 4800 baud. The interrupt service frequency is also 4.8 kHz. To begin I don't need the serial board. Still there are a lot of boards that need to work together before it works.
Sent
After checking the boards (every board except one needed editing), I sent them off for manufacture. I even sent th serial board even though it is a bit premature.
Now to buy the components I do not have.
Parts
I have in the past used 74LS parts but have migrated to 74HC parts. So after checking for parts availablity I have learnt that the 74HC93 is very rare. Basically I should use something else in future.
AlanX
-
Timing Board
05/31/2018 at 02:04 • 0 commentsTiming Board
I have added a Timer to the Timing Board. The timer triggers an interrupt every 256 instructions. If I use a 20 MHz crystal fro a 10 MHz clock (the max!), then an interrupt is triggered at 9766 Hz which is close enough to 9600 baud for a serial interface (for later).
I have also moved the interrupt register onto the timing board to free up a bus line for a Serial Clock (SCLK). The timer uses INTR7 (the highest priority) which is not longer accessable from the bus.
Here is the EasyEDA Schematic:
Only three boards remain:
- PC
- ALU
- Memory
Then possibly a Serial Board.
---
Modified the timing board for 16x the baud clock as I found a schematic for serial input:
(Source: http://digitarworld.uw.hu/circuits.html).
AlanX
-
TTA8 Mother Board
05/27/2018 at 08:24 • 0 commentsTTA8 Mother Board
I knocked up a schematic in Easy EDA for a Front Panel/Mother Board:
I have used REGA to set the address of REGD. This allows more I/O ports but at the cost of two steps to access a port rather than one. Hers is the Tina schematic:
I have also changed the schematic to suit HC rather than LS logic.
AlanX
-
TTA8 with XNOR
05/26/2018 at 04:37 • 0 commentsXNOR
One way to squeeze an interpreter and monitor into 128 bytes is to add a helper code. The one to add is XNOR, it will free up 28 bytes (currently 40 bytes).
This could be added to the existing Weird CPU board stack. Should be able to fit the interrupt hardware as well:
Ignoring the design time, making these stripboards takes about six to eight hours of cutting tracks and soldering. And there are always mistakes. So I am not that keen unless it actually "opens up new doors" for the TTA8 concept.
So I guess I should have a go at coding a new monitor.
Well yes the Interpreter and the Inline code fits but not enough room for Call and/or return. I could have worked this out before I started but sometimes you need to do it by hand to kill (or work out what you really want) the option properly.
Memory Paging
If stay with 8 bits then memory paging is now the only option. For memory paging the ROM (0x00 to 0x7F) can be completely swapped out. For RAM only 0x80 to 0xBF (64 bytes) can be swaped out as the top half of the RAM (0xC0 to 0xF7) needss to remain visable at all times as it is the CPU model and stack. The same with the hardware registers (0xF8 to 0xFF).
The other "trick" is that the page change should occur on a jump, not when the page register is set.
Here is my design:
The design can access 32kb of ROM and 16kb of RAM.
The main downside is that all memory addresses are two bytes instead of one byte.
If I go down the TTA8 with memory paging I would be looking at a PCB design.
I posted the full TTA8 schematic (image) to my files area (but I will kill the XNOR circuity shortly).
AlanX
-
FAT
05/26/2018 at 01:05 • 0 commentsFAT
That is File Allocation Table. I was wondering if I need a file system? But what is a FAT and what problem is it trying to solve? I had a look on the Internet and I was flooded with specifications but nothing on the "why"!
What is the Problem?
After a few hours (perhaps more) I worked out what the problem is:
- How do you find data (a file) if you don't know the address or exactly where it is (at least at compile time)? So you need a file name search routine that returns an address.
- If your adding and deleting files how do you manage or reuse your memory? You need a linked list of used and unused memory blocks.
- You don't execute code in the file system memory, so data need not be contiguous and can be in blocks.
I assume here you know what a linked list is!
So the role of the FAT is to keep a list of file names and the link to those blocks associated with the file. Much like the Forth dictionary (if you know anything about the internals of Forth), and a linked list of free blocks.
When you need blocks for a file you take them from the unallocated block list. When you delete files you return them to the unallocated block list. Formating a disk is the creation of the FAT and allocation of all the memory to the unallocated block list.
Internal Construction of the FAT
There are a number of ways of constructng the linked list(s). Some are simple and others are fast. I prefer simple. So a single linked lists with block headers, like the Forth dictionary, except unallocated blocks are at the end of the list (in a hidden file!). Unlike the Forth dictionary the blocks need to be of fixed size. Blocks need not be contiguous as code is not executed in the linked list.
More details when I return to this later.
AlanX
-
Simulating the O/S
05/25/2018 at 13:05 • 0 commentsSimulating the O/S
I coded in C the cut down version of the Mouse-79 interpreter.
Did not like the '$' for the end of code so I made '\n' the end of code marker (still '$' internally) and used '\' as a line continuation code.
The intrepreter does not have unary minus (i.e. you cannot enter negative number directly), so I fixed that.
Finally, the interpreter needs an "exchange" code for the two top of data stack values. In Forth it is called SWAP. It was fixed in Mouse-83 by using the assignment operator. I think the exchange operator is better. I used the '%' symbol for it.
There are a number of Forth operators that could be added but that is for another day.
The Code
I split the code into two halves, the Op-Codes I have written that will be converted into micro-code and the Mouse interpreter. Here is the mouse interpreter:
// MOUSE O/S Language Reference // // $ [36] CR/LF or NL end of program code, encoded with '$' // " [34] print up to next " // ! [33] print CR/LF inside "'s // ? [63] read number from stdin and push // ! [33] pop number from stack and print it // n [48-57] push the integer n onto stack // A-Z [65-90] push address of variable on stack // . [46] replace address on stack with value // = [61] pop value, pop address, write value at address // + [43] pop a, pop b, push a + b // - [45] pop a, pop b, push a - b // * [42] pop a, pop b, push a * b // / [47] pop a, pop b, push a / b // [ [91] pop c, if c<=0 then skip to ] on same level // ] [93] end if // ( [40] begin loop // ) [41] loop back to previous ( at same level // ^ [94] pop c, if c!=0 then break out of loop // ' ' [32] space, sometimes necessary to deliminate numbers // \ [92] continue on next line // % [37] exchange top two data stack variable // Example program: // // X 100 = // ( // X. ! " Bottle(s) of beer on the wall,!" // X. ! " bottle(s) of beer!" // "Take one down and pass it around,!" // X X. 1 - = // X. ! " bottle(s) of beer on the wall.!" // X. ^ // ) // $ #include <stdio.h> #include <stdlib.h> #include "OpCodes.c" char CODE[256]; int16_t DATA_STACK[20]; int16_t RETURN_STACK[20]; int16_t VAR[26]; int16_t DATA_PTR; int16_t CODE_PTR; int16_t RETURN_PTR; int16_t PARNUM; int16_t PARBAL; int16_t TEMPA; int16_t TEMPB; char CH; void GETCHAR(void) { CODE_PTR=CODE_PTR+1; CH=CODE[CODE_PTR]; } void PUSH_DATA(int16_t DATA) { DATA_PTR=DATA_PTR+1; DATA_STACK[DATA_PTR]=DATA; } int16_t POP_DATA(void) { int16_t tmp; tmp=DATA_STACK[DATA_PTR]; DATA_PTR=DATA_PTR-1; if (DATA_PTR<-1) DATA_PTR=-1; return(tmp); } void PUSH(void) { RETURN_PTR=RETURN_PTR+1; RETURN_STACK[RETURN_PTR]=CODE_PTR; } void POP(void) { CODE_PTR=RETURN_STACK[RETURN_PTR]; RETURN_PTR=RETURN_PTR-1; if (RETURN_PTR<-1) RETURN_PTR=-1; } void SKIP (char LCH,char RCH) { int16_t CNT; CNT=1; do { GETCHAR(); if (CH==LCH) { CNT=CNT+1; } else if (CH==RCH) { CNT=CNT-1; } } while (CNT!=0); } void GETINT(void) { int16_t temp,tmp2; temp=0; while ((CH>='0')&&(CH<='9')) { tmp2=temp+temp; temp=tmp2+tmp2; temp=temp+temp; temp=temp+tmp2+CH-48; GETCHAR(); } CODE_PTR=CODE_PTR-1; PUSH_DATA(temp); } void QUOTE(void) { do { GETCHAR(); if (CH=='!') { PutChar('\n'); } else if (CH!='"') { PutChar(CH); } } while (CH!='"'); } int main (void) { printf("RTN DATA"); RETURN_PTR=-1; DATA_PTR=-1; while (1) { PutChar('\n'); printf("%3d %4d",RETURN_PTR+1,DATA_PTR+1); PutChar('>'); // Read Evalute Repeat Loop CODE_PTR=-1; do { // Read command CH=GetChar(); if ((CH!='\r')&&(CH!='\n')&&(CH!='\\')&&(CH!='$')) { CODE_PTR=CODE_PTR+1; CODE[CODE_PTR]=CH; } else if (CH=='\r') { // Skip } else if (CH=='\n') { // Encode as $ CH='$'; CODE_PTR=CODE_PTR+1; CODE[CODE_PTR]=CH; } else if (CH=='\n') { // Skip } else { // '\' Extend line CODE_PTR=CODE_PTR+1; CODE[CODE_PTR]=' '; // Skip until \n while (GetChar()!='\n'); } } while (CH!='$'); // Evaluate command CODE_PTR=-1; do { GETCHAR(); switch (CH) { case ' ': case ']': case '$': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': GETINT(); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': TEMPA=CH-65; // 0 based PUSH_DATA(TEMPA); break; case '?': TEMPA=rdInt(); PUSH_DATA(TEMPA); break; case '!': TEMPA=POP_DATA(); wrInt(TEMPA); break; case '+': TEMPA=POP_DATA(); TEMPB=POP_DATA(); PUSH_DATA(Add(TEMPA,TEMPB)); break; case '-': GETCHAR(); if ((CH>='0')&&(CH<='9')) { GETINT(); TEMPA=0; TEMPB=POP_DATA(); } else { CODE_PTR=CODE_PTR-1; TEMPA=POP_DATA(); TEMPB=POP_DATA(); } PUSH_DATA(Sub(TEMPA,TEMPB)); break; case '*': TEMPA=POP_DATA(); TEMPB=POP_DATA(); PUSH_DATA(imul(TEMPA,TEMPB)); break; case '/': TEMPA=POP_DATA(); TEMPB=POP_DATA(); PUSH_DATA(idiv(TEMPA,TEMPB)); break; case '.': TEMPA=POP_DATA(); TEMPB=VAR[TEMPA]; PUSH_DATA(TEMPB); break; case'=': TEMPA=POP_DATA(); TEMPB=POP_DATA(); VAR[TEMPB]=TEMPA; break; case '"': QUOTE(); break; case '[': TEMPA=POP_DATA(); if (TEMPA<=0) SKIP('[',']'); break; case '(': PUSH(); break; case '^': TEMPA=POP_DATA(); if (TEMPA<=0) { POP(); SKIP('(',')'); } break; case ')': CODE_PTR=RETURN_STACK[RETURN_PTR]; break; case '%': TEMPA=POP_DATA(); TEMPB=POP_DATA(); PUSH_DATA(TEMPA); PUSH_DATA(TEMPB); break; } } while (CH!='$'); }; return 0; }
Here is a run:
alanx@alanx ~/Documents/AlanC_Ccode_Mint/MouseOS $ ./MouseOS RTN DATA 0 0>\ X 10 = \ ( \ X. ! " Bottle(s) of beer on the wall,!" \ X. ! " bottle(s) of beer!" \ "Take one down and pass it around,!" \ X X. 1 - = \ X. ! " bottle(s) of beer on the wall.!" \ X. ^ \ ) 10 Bottle(s) of beer on the wall, 10 bottle(s) of beer Take one down and pass it around, 9 bottle(s) of beer on the wall. 9 Bottle(s) of beer on the wall, 9 bottle(s) of beer Take one down and pass it around, 8 bottle(s) of beer on the wall. 8 Bottle(s) of beer on the wall, 8 bottle(s) of beer Take one down and pass it around, 7 bottle(s) of beer on the wall. 7 Bottle(s) of beer on the wall, 7 bottle(s) of beer Take one down and pass it around, 6 bottle(s) of beer on the wall. 6 Bottle(s) of beer on the wall, 6 bottle(s) of beer Take one down and pass it around, 5 bottle(s) of beer on the wall. 5 Bottle(s) of beer on the wall, 5 bottle(s) of beer Take one down and pass it around, 4 bottle(s) of beer on the wall. 4 Bottle(s) of beer on the wall, 4 bottle(s) of beer Take one down and pass it around, 3 bottle(s) of beer on the wall. 3 Bottle(s) of beer on the wall, 3 bottle(s) of beer Take one down and pass it around, 2 bottle(s) of beer on the wall. 2 Bottle(s) of beer on the wall, 2 bottle(s) of beer Take one down and pass it around, 1 bottle(s) of beer on the wall. 1 Bottle(s) of beer on the wall, 1 bottle(s) of beer Take one down and pass it around, 0 bottle(s) of beer on the wall. 0 0>^C
I have had enough to drink tonight!
AlanX
-
An Operating System for the TTA16
05/24/2018 at 14:10 • 0 commentsAn Operating System for the TTA16
It sounds big but really its just a "shell".
Forth
I first looked at Forth. I wrote a Forth interpreter in the early nineties (based on eForth I think) and compiled to 3.8kb (DOS com file) for the core (1974 lines). The forth library was a further 11.4kb (426 lines).
I wrote a second version for the 8031/51 CPU as I had an emulator to test the code. The 8031/51 is more primative than the 8086 so would make a better starting point. But still the assembler source code is 1735 lines long, so not small.
I found the beautified Buzzard Forth code (by Sean Barrett) which is very minimum bootstrap forth with its library. Its about 160 lines (excluding comments) and the library is about 6.6k (367 lines). No point taking about the gcc compiled file at 93.3kb!
Mouse
I came across Peter Grogono's mouse-79. A very small language, about 200 lines of pascal or C code. It superficially looks like Forth as a uses RPN logic, but it does not have a work dictionary and words cannot be defined. It uses subroutines (macros) that have local variables (i.e it is recursive).
O/S Purpose
The shell need not be a full language like Forth but something more like a unix shell. So if I strip out the macros, the shell has many language like features but no subroutines. Here is an example of "100 beer bottles on the wall":
X 100 = ( X. ! " Bottle(s) of beer on the wall,!" X. ! " bottle(s) of beer!" "Take one down and pass it around,!" X 1 X . - = X. ! " bottle(s) of beer on the wall.!" X . ^ ) $$
And here is the mouse language without macros:
- $$ end of program
- " print up to next "
- ! print CR/LF inside "'s
- ? read number from stdin and push
- ! pop number from stack and print it
- n push the integer n onto stack
- A-Z push address of variable on stack
- . replace address on stack with value
- = pop value, pop address, write value at address
- + pop a, pop b, push a + b
- - pop a, pop b, push a - b
- * pop a, pop b, push a * b
- / pop a, pop b, push a / b
- [ pop c, if c<=0 then skip to ] on same level
- ) loop back to previous ( at same level
- ^ pop c, if c!=0 then break out of loop
The code is still being reduced to suit its new application.
AlanX
-
Adding Input/Output to a PC
05/23/2018 at 11:32 • 0 commentsParallel/Serial Input/Output
Dealing with asynchronous serial in software and/or in DIY hardware is not a simple exercise. I need to keep things simple. Best option is to use a Nano to talk to my PC and present a parallel interface. Its a tight fit as I/O requires 2x 8 bits for data and 2x 2 bits for handshakes, i.e. 20 I/O lines.
Here is my proposed schematic:
I have included the interrupt input port on this schematic as it is need for the "DTR" (i.e. Data Terminal Ready signals). I have not written the code for the Nano but that should not be a big deal. The Nano will be off board (a Parallel to Serial to USB box!). Eventally I will get around to a DIY serial terminal but not today or in the near future.
More OpCodes
Added "LIT" or load immediate, and "JNZ" or jump on not zero. Run out of room.
I used 78 bytes for five instructions (excluding Inline at 2 bytes), or about 16 bytes per instruction. Add 28 bytes for reset (initialisation) and the interpreter, and 14 bytes for constants. That leaves six for bytes free out of 128 bytes in the ROM.
Not much chance of replacing the current monitor with anything useful without a wider address bus or memory paging. A wider address bus (i.e. 16 bits seems to be the simplest option).
Some Useful Code
For my SubLEq compiler I wrote some useful utilities:
- atoi (ascii string to integer)
- itoa (integer to ascii string)
- imul (an efficient signed integer multiply with add)
- idiv (an efficient signed integer division with add!)
The last one was tough but it is possible to build an efficient algorithm with just the add instructions.
itoa (in C)
Here is the C code for atoi:
void wrtAx(int16_t Ax) { int digit=0; int minus=45; int zero=48; int zeroFlag=0; if (Ax<0) { Ax=-Ax; digit=minus; putchar(digit); } digit=0; while (Ax>=10000) { digit++; Ax=Ax-10000; } zeroFlag=zeroFlag+digit; if (zeroFlag>0) { digit=digit+zero; putchar(digit); } digit=0; while (Ax>=1000) { digit++; Ax=Ax-1000; } zeroFlag=zeroFlag+digit; if (zeroFlag>0) { digit=digit+zero; putchar(digit); } digit=0; while (Ax>=100) { digit++; Ax=Ax-100; } zeroFlag=zeroFlag+digit; if (zeroFlag>0) { digit=digit+zero; putchar(digit); } digit=0; while (Ax>=10) { digit++; Ax=Ax-10; } zeroFlag=zeroFlag+digit; if (zeroFlag>0) { digit=digit+zero; putchar(digit); } digit=Ax+zero; putchar(digit); }
Not very complicated. Notice the code is delibrately simple so that it can be translated into microcode directly.
atoi (in C)
Here is the C code for atoi:
int16_t rdInt(void) { int16_t Ax; int16_t t1; int16_t t3; char digit; int16_t sgn=1; int16_t minus=45; int16_t zero=48; int16_t nine=57; int16_t number=0; Ax=-32768; NEXT: digit=getchar(); if (digit=='\n') goto DONE; if (digit==minus) { sgn=-sgn; goto NEXT; } if (digit<zero) goto ERROR; if (digit>nine) goto ERROR; t1=number+number; t3=t1+t1; t3=t3+t3; number=t3+t1; number=number+digit; number=number-48; if (number<0) goto ERROR; goto NEXT; DONE: if (sgn<=0) { number=-number; } Ax=number; ERROR: // Clear buffer while (digit!='\n') { digit=getchar(); } return Ax; }
Again the code is written in a way to make translation to microcode easier.
Another thing was I used the minimum integer value (i.e. -32768 for 16 bit word) as a return error code. You should not use -32768 in normal programming use anyway as -(-32768) equals -32768!
imul (in C)
Here is the C code for imul:
int16_t imul(int16_t Ax,int16_t Bx) { // Returns: // Ax = Ax * Bx int16_t sgn=1; int16_t res=0; int16_t word=1; int16_t MIN=-32768; if (Ax<0) { Ax=-Ax; sgn=-sgn; } if (Bx<0) { Bx=-Bx; sgn=-sgn; } LOOP: res=res<<1; if (res<0) { res=MIN; goto ERROR; } Ax=Ax<<1; if (Ax<0) { Ax=Ax-MIN; res=res+Bx; if (res<0) { res=MIN; goto ERROR; } } word=word+word+1; if (word>0) goto LOOP; if (sgn<0) { res=-res; } ERROR: return res; }
This code is straight forward except for the loop control "word=word+word+1;"
In SubLEq this avoids the -32768 "bug" and automatically adjusts for the word width. If the -32768 bug is not an issue than "word=word+word;" can be used.
Note: "Ax=Ax<<1;" is the same as "Ax=Ax+Ax;".
Again, -32768 is used as and error code (for overflow).
Idiv (in C)
Here is the idiv routine:
void idiv(int16_t *Ax,int16_t *Bx) { int16_t sgn=1; int16_t rem=0; int16_t res=0; int16_t word=1; int16_t MIN=-32768; res=*Ax; if (res<0) { res=-res; sgn=-sgn; } if (*Bx<0) { *Bx=-*Bx; sgn=-sgn; } if (*Bx>0) { LOOP: res=res<<1; rem=rem<<1; if (res<0) { // Use MIN sensitive test! res=res-MIN; rem++; } if (rem>=*Bx) { rem=rem-*Bx; res++; } word=word<<1; word=word+1; if (word>0) goto LOOP; if (sgn<0) { res=-res; } if (*Ax<0) { rem=-rem; } *Ax=res; *Bx=rem; } else { *Ax=MIN; } }
This routine is efficient but only uses the add code. So those who believe "SHR" or "SAR" or ">>" is a necessary primative for a CPU for effecient software division, they are wrong. As an aside I see XOR is mentioned as a necessary primative as well, this is also wrong.
Anyway, I played with translating the imul code in to microcode and it used about 60 bytes of code. So this four instruction will require around 300 bytes. Time to move on to the TTA16.
AlanX
-
Adding Interrupts (without hardware)
05/22/2018 at 07:27 • 0 commentsInterrupts
One way to add interrupts is for the interpreter to check each time it is called. This slows things down and imposes a condition. No process should "block" the interpreter.
An example of a blocking sunroutine is "getchar". It waits until a character arrives before returning. A better way is for the subrountine to fail and then to retry.
To set up interrupts I need to add:
- a interrupt read register (at address 0xFF seems a nice place), call it "REGI".
- a system variable to hold the value read, call it "INTR".
- a system variable to hold an interrupt mask call it "MASK".
- a system variable to hold the Interrupt Service Routine address, call it "ISR".
The best place to put these system variables in the CPU MODEL area:
You may also notice I have abandoned the "i8080 lite" CPU Model, the model is now more like a stack CPU with a Return Pointer (RP) and a Data Pointer (DP). Next will likely be a Top Of (data) Stack, "TOS". If you have not worked it out I am considering a micro-forth style operating system.
Okay, here is the interpertrer code:
All it does is read the interrupts and if anything is high (active) it calls the ISR subroutine to work it out. Initially the MASK is set all on (0xFF) but the ISR is diabled, it just calls _INTERP (okay I just saw the coding error where I called INTERP rather than _INTERP!).
The next bit of work is to look at "getchar" and "putchar", and some sort of terminal (hardware) interface.
AlanX