-
Review
07/23/2017 at 13:42 • 0 commentsReview
I build a simple compiler tool train for Subleq but the results are quite disappointing. Subleq is very memory inefficient:
- 12 lines of high level language coverts to
- 74 lines of OpCodes (95 words), which converts to
- 3126 lines of Subleq including comments (5530 words).
So the minimum address space is 12 bits (16 bit would be more useful).
No chance of using a simple front panel for this CPU.
And it will be slow. Amazingly slow!
I have done some PCB and strip-board designs but input and output will need to be via an Arduino.
So overall a sad situation.
So I have decide to suspend this project.
AlanX
-
Macros
06/17/2017 at 00:57 • 0 commentsMacros Part 1
Coding directly in SUBLEQ is necessary initially but is very tedious. Macros are the way to go.
There are a couple of software packages that are suitable:
- M4
- cpp (yes the C Pre-Processor using the -E option)
- mcpp (another version of cpp)
- gpp (similar to cpp)
- NASM with the -E option
and others.
I did not like the syntax of M4 and NASM. Here is a NASM example:
/* macro TRUE_BRANCH: correctly handles underflow */ %define TRUE_BRANCH(b,GEZ,LTZ) \ T T ? \ b T TEST_GEZ \ T T LTZ \ TEST_GEZ: P T GEZ \ P T LTZ // Examples: TRUE_BRANCH(x,xGEZ,xLTZ)
Which produces:%line 1+1 macros.nasm /* macro TRUE_BRANCH: correctly handles underflow */ %line 8+1 macros.nasm // Examples: T T ? x T TEST_GEZ T T xLTZ TEST_GEZ: P T xGEZ P T xLTZ
Okay lets try the %macro:
/* macro TRUE_BRANCH: correctly handles underflow */ %macro TRUE_BRANCH 3 T T ? %1 T TEST_GEZ T T %3 TEST_GEZ: T T %2 T T %3 %endmacro // Examples: TRUE_BRANCH(x,xGEZ,xLTZ)
Which produces:
%line 1+1 macros.nasm /* macro TRUE_BRANCH: correctly handles underflow */ %line 9+1 macros.nasm // Examples: T T ? %line 11+0 macros.nasm x T TEST_GEZ T T xLTZ TEST_GEZ: T T xGEZ T T xLTZ
Not pretty and not easy to suppress the %Line comments (i.e. .nolist ?).The C Pre-Processer
The C pre-processors cpp, mcpp and gpp are very similar, but gpp stands out as it honours new lines in its standard mode and emits less commentary.
Here is gpp:
/* macro TRUE_BRANCH: correctly handles underflow */ #define TRUE_BRANCH(b,GEZ,LTZ) \ T T ? \ b T TEST_GEZ \ T T LTZ \ TEST_GEZ: P T GEZ \ P T LTZ
For:
TRUE_BRANCH(x,xGEZ,xLTZ)
Which gets expanded to this:
T T ? x T TEST_GEZ T T xLTZ TEST_GEZ: P T xGEZ P T xLTZ
The problem with cpp and mcpp is that they do not honour the newline, this is what you get:#line 1 "C:/AlanX/subleq/Macros/macros.inc" #line 10 "C:/AlanX/subleq/Macros/macros.inc" T T ? x T TEST_GEZ T T xLTZ TEST_GEZ: P T xGEZ P T xLTZ
I have to add a new line marker ("nl") and then use an AWK script to convert it to a real new line:
/* macro TRUE_BRANCH: correctly handles underflow */ #define TRUE_BRANCH(b,GEZ,LTZ) nl\ T T ? nl\ b T TEST_GEZ nl\ T T LTZ nl\ TEST_GEZ: P T GEZ nl\ P T LTZ // Examples: TRUE_BRANCH(x,xGEZ,xLTZ)
Now I get:#line 1 "C:/AlanX/subleq/Macros/macros.inc" #line 10 "C:/AlanX/subleq/Macros/macros.inc" T T ? x T TEST_GEZ T T xLTZ TEST_GEZ: P T xGEZ P T xLTZ
Which is pretty close.Here is the AWK script:
awk "{ gsub(/nl/,\"\n\"); print }" macros.tmp >macros.sub
It was an absolute headache working out how to get AWK to work with Windows for this one liner!
Macros Part 2
The purpose for this log is to record my macros:
( SUBLEQ MACROS ) ( System defaults ) #define system() \ ( DEFAULTS ) \ @INPUT -1 \ @OUTPUT -2 \ @HALT 0 \ @Z -9 \ @T -10 \ @P -11 \ @N -12 \ @SP -13 \
Z Z ? ; Z=0 \ T T ? ; T=0 \ -1 P ? ; P=1 \ 1 N ? ; N=-1 \ 32 SP ? ; SP=-32 \ ( FREE VARIABLES ) \ @a -14 \ @b -15 \ @c -16 \ @d -17 \ @e -18 \ @f -19 \ @g -20 \
( MACRO VARIABLES ) \ @t -21 ; Macro temporary variable \ @w -22 ; Word width test \ @TOP_OF_FREE_RAM -33 \ ( POINTER TO POINTER COPY EQUATES ) \ @PPC -32 \ @NDATA -23 \ @SRC -32 \ @DST -28 \ @RTN -24 ( Load Pointer to Pointer Copy to RAM ) #define LoadPPC() \ ( POINTER TO POINTER COPY EQUATES ) \ @NDATA -23 \ @SRC -32 \ @DST -28 \ @RTN -24 \ ( POINTER TO POINTER COPY ROUTINE LINES ) \ @PPC_L1 -32 ; SRC \ @PPC_L2 -31 ; NDATA \ @PPC_L3 -30 ; ? (=PPC_L4) \ @PPC_L4 -29 ; NDATA \ @PPC_L5 -28 ; DST \ @PPC_L6 -27 ; ? (=PPC_L7) \ @PPC_L7 -26 ; T \ @PPC_L8 -25 ; T \ @PPC_L9 -24 ; RTN \ ( COPY ROM CODE TO RAM - THE HARD WAY ) \ PPC_L1 PPC_L1 ? \ T T ? \ PPC_D1 T ? \ T PPC_L1 ? \ PPC_L2 PPC_L2 ? \ T T ? \ PPC_D2 T ? \ T PPC_L2 ? \ PPC_L3 PPC_L3 ? \ T T ? \ PPC_D3 T ? \ T PPC_L3 ? \ PPC_L4 PPC_L4 ? \ T T ? \ PPC_D4 T ? \ T PPC_L4 ? \ PPC_L5 PPC_L5 ? \ T T ? \ PPC_D5 T ? \ T PPC_L5 ? \ PPC_L6 PPC_L6 ? \ T T ? \ PPC_D6 T ? \ T PPC_L6 ? \ PPC_L7 PPC_L7 ? \ T T ? \ PPC_D7 T ? \ T PPC_L7 ? \ PPC_L8 PPC_L8 ? \ T T ? \ PPC_D8 T ? \ T PPC_L8 ? \ PPC_L9 PPC_L9 ? \ T T ? \ PPC_D9 T ? \ T PPC_L9 ? \
T T JMP ; Jump over data section \ ( POINTER TO POINTER COPY CODE TO BE MOVED ) \ .PPC_D1 SRC \ .PPC_D2 NDATA \ .PPC_D3 PPC_L4 \ .PPC_D4 NDATA \ .PPC_D5 DST \ .PPC_D6 PPC_L7 \ .PPC_D7 T \ .PPC_D8 T \ .PPC_D9 RTN \ JMP: T T ? ( Macro jmp: jump to address c ) #define jmp(c) \ T T c ; T=0 ( Macro sub: b=b-a ) #define sub(a,b) \ a b ? ; b=b-a ( Macro add: b=b+a ) #define add(a,b) \ T T ? ; T=0 \ a T ? ; T=-a \ T b ? ; b=b+a ( Macro copy: copy or move a to b ) #define copy(a,b) \ T T ? ; T=0 \ b b ? ; b=0 \ a T ? ; T=-a \ T b ? ; b=a ( Macro not: b=~a ) #define not(a,b) \ T T ? ; T=0 \ b b ? ; b=0 \ a b ? ; b=-a \ P b ? ; b=~a ( ~a=-a-1 )( Macro shl: a=a+a ) #define shl(a) \ T T ? ; T=0 \ a T ? ; T=-a \ T a ? ; a=a+a ( Macro newsub: c=b-a ) #define newsub(a,b,c) \ T T ? ; T=0 \ b T ? ; T=-b \ c c ? ; c=0 \ T c ? ; c=b \ a c ? ; c=b-a
( Macro newadd: c=b+a ) #define newadd(a,b,c) \ T T ? ; T=0 \ b T ? ; T=-b \ c c ? ; c=0 \ T c ? ; c=b \ T T ? ; T=0 \ a T : ; T=-a \ T c ? ; c=b+a
( Macro inc: a=a+1 ) #define inc(a) \ N a ? ; a++, N=-1 ( Macro dec: a=a-1 ) #define inc(a) \ P a ? ; a++, P=-1
( Macro JGEZ: jump to c if b >= 0 ) #define JGEZ(b,c) \ T T ? ; T=0 \ b T c ; T=-b ( Macro JLEZ: jump to c if b <= 0 ) #define JLEZ(b,c) \ Z b c ; b=b ( Macro branch ) #define branch(b,GTZ,EQZ,LTZ) \ Z b JMP \ Z Z GTZ \ JMP: b Z EQZ \ Z Z LTZ ( Macro true_test: correctly handles underflow ) #define true_test(b,GEZ,LTZ,MIN) \ T T ? \ b T TEST_GEZ \ T T LTZ \ TEST_GEZ: T T GEZ \ T T MIN ( Macro loop: for a=a to b, will execute at least once ) #define loop(a,b,LOOP) \ N a ? ; a++, N=-1 \T T ? ; until a>b \ a Z ? \ Z T ? \ Z Z ? \ b T LOOP ; goto LOOP ( Macro xor: c=b^a ) #define xor(a,b,c) \ c c ? ; c=0 \ w w ? ; w=0, used to test word width \ loop: \ ( c=c<<1 ) \ shl(c) \ ( TEST 1 a<0 AND b>=0 ) \ ; Test a<0 \ true_test(a,TEST3,TEST2,TEST2) \ TEST2: \ ; Test b>=0 \ true_test(b,SETBIT,TEST3,TEST3) \ TEST3: \ ( a>=0 AND b<0 ) \ ; Test b<0 \ true_test(b,NEXT,TEST4,TEST4) \ TEST4: \ ; Test a>=0 \ true_test(a,SETBIT,NEXT,NEXT) \ SETBIT: \ N c ? ; c++ \ NEXT: \ shl(a) ; a=a<<1 \ shl(b) ; b=b<<1 \ ( loop: w=w<<1+1 - avoid underflow bug! ) \ shl(w) \ add(N,w) \ w T loop ( Macro and: c=b&a ) #define and(a,b,c) \ c c ? ; c=0 \ w w ? ; w=0, used to test word width \ loop: \ ( c=c<<1 ) \ shl(c) \ ( TEST 1 a<0 AND b<0 ) \ ; Test a<0 \ true_test(a,NEXT,TEST2,TEST2) \ TEST2: \ ; Test b<0 \ true_test(b,SETBIT,NEXT,NEXT) \ SETBIT: \ N c ? ; c++ \ NEXT: \ shl(a) ; a=a<<1 \ shl(b) ; b=b<<1 \ ( loop: w=w<<1+1 - avoid underflow bug! ) \ shl(w) \ add(N,w) \ w T loop ( Macro or: c=b|a ) #define or(a,b,c) \ c c ? ; c=0 \ w w ? ; w=0, used to test word width \ loop: \ ( c=c<<1 ) \ shl(c) \ ( TEST 1 A<0 OR B<0 ) \ ; Test b<0 \ true_test(a,TEST2,SETBIT,SETBIT) \ TEST2: \ ; Test b<0 \ true_test(b,NEXT,SETBIT,SETBIT) \ SETBIT: \ N c ? ; c++ \ NEXT: \ shl(a) ; a=a<<1 \ shl(b) ; b=b<<1 \ ( loop: w=w<<1+1 - avoid underflow bug! ) \ shl(w) \ add(N,w) \ w T loop ( Macro nor: c=~(b|a) ) #define nor(a,b,c) \ or(a,b,t) \ not(c,t) ( Macro nand: c=~(b&a) ) #define nand(a,b,c) \ and(a,b,t) \ not(t,c) ( Macro ppc: pointer to pointer copy, [a] -> c -> [b] ) #define ppc(a,b,c) \ copy(a,SRC) \ copy(b,DST) \ copy(rtn,RTN) \ jmp(PPC_L1) \ NDATA c rtn:?
( Macro PtrRead: pointer read, [a] -> c ) #define pr(a,c) \ copy(a,SRC) \ copy(a,DST) \ copy(rtn,RTN) \ jmp(PPC_L1) \ NDATA c rtn:?
( Macro PtrWrite: pointer write, c -> [a] ) #define PtrWrite(a,c) \ NDATA NDATA ? \ c NDATA ? \ copy(a,DST) \ copy(rtn,RTN) \ jmp(PPC_L4) \ T T rtn:?
( Macro push: SP--, [SP]=a ) #define push(a) \ dec(SP) \ PtrWrite(SP,a) ( Macro pop: a=[SP]; SP++ ) #define push(a) \ PtrRead(SP,a) \ inc(SP)
TBC ...
AlanX
-
Self Modifying Code
06/11/2017 at 03:03 • 0 commentsSelf Modifying Code - Part 1
TTA (Transport Triggered Architecture) and SUBLEQ can use pointer but it requires the use self modifying code. Reading data from a memory location to a variable called DATA from an address stored in (pointed to by) a variable called ADDR is an example. We can write it as:
- DATA = [ADDR]
Not to be confused with copy or move:
- DATA = ADDR
DATA = [ADDR] can be coded as:
T T ? ; T=0 PTR PTR ? ; PTR=0 ADDR T ? ; T=-ADDR T PTR ? ; PTR=ADDR T T ? ; T=0 PTR: T T ? ; T=-PTR (self modifying code) DATA DATA ? ; DATA=0 T DATA ? ; DATA=[ADDR]
Although the assembler thinks the first T on the PTR: line is real and initially set with the address of "T", the code above overwrites the address with the value held in ADDR. The only problem is that code cannot reside in ROM (as it is self modifying).
A more general form would be:
- Copy [SRC] to [DST]
This would be coded as:
SRC_PTR SCR_PTR ? ; Copy SRC to SRC_PTR T T ? SRC T ? T SRC_PTR ? DST_PTR DST_PTR ? ; Copy DST to DST_PTR T T ? DST T ? T DST_PTR ? NDATA NDATA ? ; COPY [SRC] TO NDATA TO [DST] SRC_PTR: SRC_PTR NDATA ? ; SELF MODIFYING CODE NDATA DST_PTR: DST_PTR ? ; SELF MODIFYING CODE T T RTN ; RETURN
Getting code in ROM to RAM without self modifying code is tricky, but first an assembler update.
Assembler Updated
SYNTAX
A space is required between tokens.
SUBLEQ defined variables:
- Z ( Zero, if used must be cleared after use )
- T ( Temp, must be cleared before use )
SUBLEQ defined constants (value should not be changed):
- N ( Negative or -1 )
- P ( Positive or +1 )
Comments:
- ( begin in-line comment
- ) end in-line comment
- ; end of line comment
Address Labels:
- . next address
- ? next address
- label label for address reference or variable
- .label variable (location) for label (compiler sets address)
- label: address reference (goto location) for label (compiler sets address)
- @label absolute (fixed) address for label (no code generated)
Constants and Literals:
- Digits constant (example "1")
- -? constant (example "-1")
- -? literal (example "-end")
Examples of code (note: line numbers are not part of the assembler code):
- ( HELLO WORLD! )
- E OUTPUT ?
- L OUTPUT ?
- L OUTPUT ?
- O OUTPUT ?
- BLANK OUTPUT ?
- W OUTPUT ?
- O OUTPUT ?
- R OUTPUT ?
- L OUTPUT ?
- D OUTPUT ?
- BANG OUTPUT ?
- Z Z HALT ; And end program
- ( ASCII characters )
- .H 72
- .E 69
- .L 76
- .O 79
- .BLANK 32
- .W 87
- .R 82
- .D 68
- .BANG 33
- ( Predined variables and addresses )
- .Z 0
- .T 0
- .P 1
- .N -1
- .SP -17
- @OUTPUT -1 ; On my system
- @INPUT -2 ; On my system
- @HALT 0 ; Return to monitor
Example assembler output (note: line numbers are not part of the assembler output):
- 39 -1 3
- 41 -1 6
- 43 -1 9
- 43 -1 12
- 45 -1 15
- 47 -1 18
- 49 -1 21
- 45 -1 24
- 51 -1 27
- 43 -1 30
- 53 -1 33
- 55 -1 36
- 57 57 0
- 72
- 69
- 76
- 79
- 32
- 87
- 82
- 68
- 33
- 0
- 0
- 1
- -1
- -17
Note that the ".label" command exports variable values one per line while the "label:" command exports as a triplet (i.e. a SUBLEQ instruction).
Self Modifying Code - Part 2
The following code fragments sets up a pointer [ADDR1] to pointer [ADDR2] copy that returns the DATA copied:
( SET SYSTEM DEFAULTS) @INPUT -1 @OUTPUT -2 @HALT 0 @Z -9 @T -10 @SP -11 ( VARIABLES ) @A -12 @B -13 @C -14 @D -15 @E -16 @F -17 @L -18 @H -19 @I -20 @J -21 @K -22 @TOP_OF_FREE_RAM -33 ( POINTER TO POINTER COPY EQUATES ) @PPC -32 @NDATA -23 @SRC -32 @DST -28 @RTN -24 ( POINTER TO POINTER COPY ROUTINE LINES ) @PPC_L1 -32 ; SRC @PPC_L2 -31 ; NDATA @PPC_L3 -30 ; ? (=PPC_L4) @PPC_L4 -29 ; NDATA @PPC_L5 -28 ; DST @PPC_L6 -27 ; ? (=PPC_L7) @PPC_L7 -26 ; T @PPC_L8 -25 ; T @PPC_L9 -24 ; RTN ( COPY ROM CODE TO RAM - THE HARD WAY ) PPC_L1 PPC_L1 ? T T ? PPC_D1 T ? T PPC_L1 ? PPC_L2 PPC_L2 ? T T ? PPC_D2 T ? T PPC_L2 ? PPC_L3 PPC_L3 ? T T ? PPC_D3 T ? T PPC_L3 ? PPC_L4 PPC_L4 ? T T ? PPC_D4 T ? T PPC_L4 ? PPC_L5 PPC_L5 ? T T ? PPC_D5 T ? T PPC_L5 ? PPC_L6 PPC_L6 ? T T ? PPC_D6 T ? T PPC_L6 ? PPC_L7 PPC_L7 ? T T ? PPC_D7 T ? T PPC_L7 ? PPC_L8 PPC_L8 ? T T ? PPC_D8 T ? T PPC_L8 ? PPC_L9 PPC_L9 ? T T ? PPC_D9 T ? T PPC_L9 ?
T T JMP ( POINTER TO POINTER COPY CODE TO BE MOVED ) .PPC_D1 SRC .PPC_D2 NDATA .PPC_D3 PPC_L4 .PPC_D4 NDATA .PPC_D5 DST .PPC_D6 PPC_L7 .PPC_D7 T .PPC_D8 T .PPC_D9 RTN
JMP: ( CALL POINTER TO POINTER COPY WITH DATA COPY ) SRC SRC ? ; SET SOURCE ADDRESS T T ? ADDR1 T ? ; ADDR1 IS LOCAL T SRC ? DST DST ? ; SET DESTINATION ADDRESS T T ? ADDR2 T ? ; ADDR2 IS LOCAL T DST ? RTN RTN ? ; SET RETURN ADDRESS T T ? RETURN T ? ; RETURN IS LOCAL T RTN ? T T PPC ; JUMP TO POINTER TO POINTER COPY DATA DATA RETURN: ? ; RETURN ADDRESS NDATA DATA ? ; SAVE COPY IN LOCAL DATA
This seems to complete self modifying code as DATA=[ADDR] and [ADDR]=DATA can be generated with PPC (Pointer to Pointer Copy) and existing assembler tools (i.e. getting the address of the DATA variable).
AlanX
-
Input and Output
06/10/2017 at 12:19 • 0 commentsSUBLEQ IO
Input/Output (IO) in SUBLEQ is not as straight forward as you would expect.
Output
Output (i.e. copy A to [0xff] or (-1)):
- subleq T T ?
- subleq A T ?
- subleq T (-1) ?
where:
- A is the output value
- (-1) is the address (i.e. 0xFF) of the output register
- but the read value of address (-1) needs to be 0
If we just did:
- subleq A (-1)
then export value would be:
- B - A
where B is the value read.
if B = -1 then the exported value is -1 - A or ~B, then all that needs to be done is to invert the output.
So the following would work:
As I pull the address and data busses up with 22k resistors then the 74LS373 is redundant and can be removed (providing nothing else uses its address space). To save the output inverters we could use:
- subleq T T ?
- subleq 1 T ?
- subleq A T ?
- subleq T (-1) ?
For TTL and LEDs the following would work (note: the pull up resistors supply most of the current for the LEDs):
If we are writing to LEDs then all we really need is:
- subleq A (-1)
And:
Input
Input is similar:
- subleq T T ?
- subleq (-2) T ?
- subleq A A ?
- subleq T A?
And here is a switch design:
If the switches are inverted logic then
- subleq A A ?
- subleq 1 A ?
- subleq (-2) A ?
And here is a switch design:
Hardware Help
It would be useful to have a NAND in hardware. As the inputs to the NAND are inverted and the output is inverted than a NOR gate will result in a NAND function. Why NAND? All the other functions can be generated and the XOR can be generated as shown below but in software:
Summary
In summary:
- use bus pull up resistors
- use the simplest input and output registers
- Invert the logic before output (A=-A-1)
- negate the logic after input (A=-A)
For output:
- subleq T T ?
- subleq 1 T ?
- subleq A T ?
- subleq T (-1) ?
For Input:
- subleq T T ?
- subleq (-2) T ?
- subleq A A ?
- subleq T A ?
AlanX
-
Monitor
06/08/2017 at 14:35 • 0 commentsFront Panel
I added an 8 bit front panel to the schematic:
The Front Panel maps 128 bytes of PROM, 120 bytes of RAM and 8 bytes of IO. So I thought I better do the Monitor Program before I went any further as the PROM for the Weird CPU was only just enough.
The first problem was that writing to the IO port involves a read first and I had not anticipated that. Two fixes, make the port a register or map the port to a RAM address.
The second problem is that as I use momentary contact switches, I need an XOR to toggle the bits in the Data and Addr variables. As SUBLEQ has no hardware bit operations I wrote some C code with pseudo SUBLEQ code:
char xor(char a, char b) { char c=0; char w=0; loop1: // c=c+c subleq(c,&z); subleq(z,&c); subleq(z,&z); /* TEST 1 A<0 AND B>=0 */ // Test A<0 subleq(t,&t); if (subleq(a,&t)) goto TEST1A_GEZ; // TEST_GEZ if (subleq(t,&t)) goto TEST1B; // TRUE_LTZ TEST1A_GEZ: if (subleq(1,&t)) goto TEST2; // TRUE_GEZ // TRUE_LTZ (-128 found) TEST1B: // Test B>=0 subleq(t,&t); if (subleq(b,&t)) goto TEST1B_GEZ; // TEST_GEZ if (subleq(t,&t)) goto TEST2; // TRUE_LTZ TEST1B_GEZ: if (subleq(1,&t)) goto SETBIT; // TRUE_GEZ if (subleq(t,&t)) goto NEXT; // TRUE_LTZ (-128 found) TEST2: // A>=0 AND B<0 // Test B<0 subleq(t,&t); if (subleq(b,&t)) goto TEST2B_GEZ; // TEST_GEZ if (subleq(t,&t)) goto TEST2A; // TRUE_LTZ TEST2B_GEZ: if (subleq(1,&t)) goto NEXT; // TRUE_GEZ // TRUE_LTZ (-128 found) TEST2A: // Test A>=0 subleq(t,&t); if (subleq(a,&t)) goto TEST2A_GEZ; // TEST_GEZ if (subleq(t,&t)) goto NEXT; // TRUE_LTZ; TEST2A_GEZ: if (subleq(1,&t)) goto SETBIT; // TRUE_GEZ; if (subleq(t,&t)) goto NEXT; // TRUE_LTZ (-128 found) SETBIT: subleq(-1,&c); NEXT: // a=a+a subleq(a,&z); subleq(z,&a); subleq(z,&z); // b=b+b subleq(b,&z); subleq(z,&b); subleq(z,&z); // w=w+w+1 - Avoid -128 bug subleq(t,&t); subleq(w,&t); subleq(t,&w); subleq(-1,&w); subleq(t,&t); if (subleq(w,&t)) goto loop1; return c; }
I had lots of problems with underflow (i.e. the value -128) which took time to solve. The problem is that -(-128) == -128! The code is efficient and could be adapted to other bit hardware (i.e. AND, NAND, OR, NOR and SHR). The code works out the word width by itself.Unfortunately the code is about +34 instructions or +102 bytes long! I will need to add a hardware XOR to the schematic if I want to stay with 8 bits.
Test A >= 0
This test must be popular for SUBLEQ coders as it is only two instructions and A is not modified:
- subleq t t ?
- subleq a t JGEZ
- ...
Here is a true A >= 0:
- subleq t t ?
- subleq a t TEST_GEZ
- subleq t t TRUE_LTZ
- TEST_GEZ: subleq 1 t TRUE_GEZ
- TRUE_LTZ: ...
Does it really matter that we should always use the true A >+ 0 version? Consider the C code for loop:
- for (char i=-128;i<=127;i++);
The compiler will not mind (quite legal) but after counting from -128 to 127 it repeats infinitely.
It appears as if it does not matter that the comparison in the for loop fails.
Coding Conventions
In the above pseudo code I have used the z (=zero) convention of ensuring it is set to 0 after use. Where I can not do that I use t (=temp) where it must the zeroed before use.
Two other coding conventions would "n" for -1 and "p" for +1.
Synthesizing Arithmetic
I found a great site that helped with the general structure for XOR subroutine. Worth a look:
http://bisqwit.iki.fi/story/howto/bitmath/
Switches
Oh well, I am forced to use PCB mount slide switches:
So the Monitor pseudo code would be:
- Initialise
- LOOP:
- Read the IO Data register (the switches) to the DataIn variable
- Read the IO Addr register (the switches) to the Addr variable
- Write the Addr variable to the IO Addr register (the LEDs)
- Move the data from the memory pointed to by the Addr variable to the Data variable
- Write the Data variable to the IO Data register (the LEDs)
- Compare the IO Data register to the DataIn variable, if equal then go to step 9
- If Addr variable equal 0xFF then exit Monitor and go to 0x80 (i.e. run the user program)
- Move the IO Data register to the memory pointed to by the Addr variable
- Wait 10 ms for switch debounce
- REPEAT
Note: The move operation using the Addr variable as a pointer requires self modifying code and must reside in RAM.
Monitor Code Fail
I could not fit the monitor code in the allocated 128 bytes:
Its only a rough outline (the WAIT has not been coded) so some optimisation will help but not enough to make it fit. And I still need to move the self modifying code into RAM. A basic composite instruction (i.e. COPY) is about 12 bytes of memory. This compares to 6 byes for the transport Triggered Architecture (TTA) of the move only Weird CPU, and I thought that was inefficient!
The remaining option is to use an Arduino to program the CPU if I want to stay with 8 bits. One way to do this is for the Arduino to drive the !Reset low and to gate the !WE and !OE signals.
The other option is to step up to 16 bit.
Staying with 8 Bits
I will rework the decoder to allow an Arduino to take control of the bus for programming.
For input/ouptut the CPU will take to the Arduino.
AlanX
-
Software
06/03/2017 at 10:34 • 0 commentsSoftware
Today I looked at Mazonka's SUBLEQ wehpage (http://mazonka.com/subleq/) and his Higher SUBLEQ webpage (http://mazonka.com/subleq/hsq.html). Higher SUBLEQ is actually a C Compiler for SUBLEQ. His site also has an on-line C Compiler, Assembler and Interpreter (http://mazonka.com/subleq/online/hsqjs.cgi). The problem with Mazonka's work is it is a bit too advanced for me at the moment.
What I was really looking for was some code that I could have a look at. That I can manually step through so that I could get an idea how the language works. I came across Sandro Maffiodo "OI" webpage (http://www.assezeta.com/sandromaffiodo/oi/) which had what I wanted. His assembler syntax was very simple and yet does the job. Here is his "Hello World!":
(OI Assembler by Sandro Maffiodo This example write hello world) H -2 . E -2 . L -2 . L -2 . O -2 . BLANK -2 . W -2 . O -2 . R -2 . L -2 . D -2 . BANG -2 -1 (ASCII characters) .H 72 .E 69 .L 76 .O 79 .BLANK 32 .W 87 .R 82 .D 68 .BANG 33
So Let Have A LookFirst comments start with a "(" and end with a ")".
Next the lines are for the form "A B C" (except for data) which are constants.
The SUBLEQ microcode is:
- subtract the value stored at address "A"
- from the value stored at address "B", and
- replace the result of the value stored at address "B".
- If the value stored at address "B" is less than or equal to zero then jump to address "C".
On the first code line, the "A" address is called the letter H and is a label for an address that the compiler has to work out. The actual value for H is stored at ".H" and no surprise, is the value 72 or ASCII "H".
The "-2" (or "B" address) is a special symbol for the address of the ASCII input/output port.
The "." is the jump address for the next instruction that the compiler has to work out.
Step Through
Move the value 72 or "H" (stored at at address H) to the ASCII output port (-2) then jump to the next instruction. Repeat for the remaining letter of "HELLO WORLD!".
The last letter "!" however, jumps to address "-1" which is understood to mean "halt".
Now was that so hard?
Assembler Code
Maffiodo's webpage has an assembler and an interpreter for download. He even codes a SUBLEQ interpreter in "SUBLEQ". Then he gets a compiled "C Code" SUBLEQ interpreter to run (interpret) a the "SUBLEQ" SUBLEQ interpreter that runs another "SUBLEQ" SUBLEQ interpreter that runs another "SUBLEQ" SUBLEQ interpreter that runs the "SUBLEQ" program "Hello".
To do this the "SUBLEQ" interpreter has to relocate the code each time in memory. Pretty smart! The SUBLEQ SUBLEQ interpreter only uses 348 memory locations!
Anyway I downloaded his assembler program and rebuilt it. My version is longer as Maffiodo uses many coding tricks to reduce his code size. I have never actually see code that uses the "," operator! My coding background is Pascal so I code with very tight typing. Maffiodo is very fluid in this regard. The program is actually quite simple:
- a tokenizer (that dumps comments)
- a label reference search
- a code emitter.
I want to add an inline macro section for macros like:
- push
- pop
- call
- return
- block move
- etc.
Anyway here is my current version of Maffiodo's assembler code:
// SUBLEQ ASSEMBLER // ================= // // SYNTAX // ------ // // A space is required between tokens. // // SUBLEQ defined variables: // Z ( Zero, if used must be cleared after use ) // T ( Temp, must be cleared before use ) // // SUBLEQ defined constants (value should not be changed): // N ( Negative or -1 ) // P ( Positive or +1 ) // // Comments: // ( begin in-line comment // ) end in-line comment // ; end of line comment // // Address Labels: // . next address // ? next address // label label for address reference or variable // .label variable (location) for label (compiler sets address) // label: address reference (goto location) for label (compiler sets address) // @label absolute (fixed) address for label (no code generated) // // Constants or Literals: // Digits constant (example "1") // -? constant (example "-1") // -? literal (example "-end") // // Examples of use: // ( HELLO WORLD! ) // H OUTPUT ? // E OUTPUT ? // L OUTPUT ? // L OUTPUT ? // O OUTPUT ? // BLANK OUTPUT ? // W OUTPUT ? // O OUTPUT ? // R OUTPUT ? // L OUTPUT ? // D OUTPUT ? // BANG OUTPUT ? // Z Z HALT ; And end program // ( ASCII characters ) // .H 72 // .E 69 // .L 76 // .O 79 // .BLANK 32 // .W 87 // .R 82 // .D 68 // .BANG 33 // @OUTPUT -1 ( On my system ) // @INPUT -2 ( On my system ) // @HALT 0 ( SUBLEQ convention is -1 for halt but better to return to the monitor program ) #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <stdbool.h> int main() { int i,j,k; bool inLineComment=false; bool endLineComment=false; bool absAddrFlag=false; struct absAddr { char name[16]; int addr; }; struct absAddr absAddrs[256]; struct absAddr *a; int nAbsAddr; struct token { char tok[64]; int addr; bool found; }; struct token tokens[4096]; struct token *t; struct token *ts; int nTokens; char *tptr; char line[64]; // Load input (code) into token array t=tokens; nTokens=0; a=absAddrs; nAbsAddr=0; while (fgets(line,sizeof(line),stdin)!=NULL) { tptr=strtok(line," \t\r\n"); if (tptr!=NULL) strcpy(t->tok,tptr); while (tptr!=NULL) { if (t->tok[0]==';') { // End of line Comment endLineComment=true; } else if (absAddrFlag) { a->addr=atoi(t->tok); a++; nAbsAddr++; absAddrFlag=false; } else if ((t->tok[0]=='@')&&(t->tok[1]!='\0')) { // Absolute address reference strcpy(a->name,t->tok+1); absAddrFlag=true; } else if (t->tok[0]=='(') { inLineComment=true; if (t->tok[strlen(t->tok)-1]==')') inLineComment=false; } else if (inLineComment) { if (t->tok[strlen(t->tok)-1]==')') inLineComment=false; } else { t->addr=-1; t->found=false; t++; nTokens++; } tptr=strtok(NULL," \t\r\n"); // Test if end of line if (tptr==NULL) { // End of line processing } else { strcpy(t->tok,tptr); } // Test if end of line comment if (endLineComment) { endLineComment=false; tptr=NULL; } } } // Resolve labels t=tokens; k=0; for (i=0;i<nTokens;i++) { if (((t->tok[0]=='.')||(t->tok[0]=='?'))&&(t->tok[1]=='\0')) { // Next address "." or "?" t->addr=k+1; t->found=true; } else if ((t->tok[0]=='.')||(t->tok[strlen(t->tok)-1]==':')) { // Label ".label" or "label:" ts=tokens; for (j=0;j<nTokens;j++) { if (j!=i) { if ((t->tok[0]=='.')&&(strcmp(t->tok+1,ts->tok)==0)) { // Use nearest reference if (ts->found) { if (abs(ts->addr-i)>abs(j-i)) { ts->addr=k; } } else { ts->addr=k; ts->found=true; } } else if ((t->tok[strlen(t->tok)-1]==':')&&(strncmp(t->tok,ts->tok,strlen(t->tok)-1)==0)) { // Use nearest reference if (ts->found) { if (abs(ts->addr-i)>abs(j-i)) { ts->addr=k; } } else { ts->addr=k; ts->found=true; }; } } ts++; } k--; } t++; k++; } // Search for absolute address t=tokens; for (i=0;i<nTokens;i++) { if (t->found==false) { a=absAddrs; for (j=0;j<nAbsAddr;j++) { if (strcmp(t->tok,a->name)==0) { t->addr=a->addr; t->found=true; } a++; } } t++; } // Emit SUBLEQ code t=tokens; j=0; for (i=0;i<nTokens;i++) { // Check if literal or constant if (t->tok[0]=='-') { printf("%s ", t->tok); j++; if (j%3==0) printf("\n"); } else if (isdigit(t->tok[0])) { printf("%s ", t->tok); j++; if (j%3==0) printf("\n"); } else { // Check if reference label if (t->tok[strlen(t->tok)-1]==':') { // No action, next token } else if ((t->tok[0]=='.')&&(t->tok[1]!='\0')) { // No action, next token j=-1; // Seperate line for each variable } else if (t->found==false) { // If reference not resolved - Error! fprintf(stderr, "Error: symbol %s is undefined\n", t->tok); return -1; } else { // Okay, export address printf("%d ", t->addr); j++; if (j%3==0) printf("\n"); } } t++; } // End if (j%3!=0) printf("\n"); return 0; }
I have added some alternate syntaxes (i.e. "?" (equal to ".") and ":" ("label:" instead of ".label") as per Mazonka's assembler. I also made labels local (i.e. nearest reference).
SUBLEQ Underflow BUG
Its a bit like the FDiv bug, not important until it is! Basically for an 8 bit word, -(-128) equals -128 which is a problem for code that test the sign bit of a word for bit operations. For normal arithmetic operations underflow is a "fact of life" and not important.
The following code fails:
- T T ? (i.e. Zero T)
- A T Jump (i.e. Jump on A>=0)
Works fine except for 0x80 (-128)!
This code works (although the logic is inverted):
- T T ? (Move (copy) A to T)
- A Z ?
- Z T ?
- Z Z ?
- Subleq -1 T Jump (i.e. Jump on A<0)
SUBLEQ Opcodes
Now it is said that SUBLEQ is a one instruction language but it actually has two opcodes. The second opcode is HALT, accessed if the jump address (i.e. "C") is negative. For real CPUs the HALT instructions in not necessary (it is necessary to be Turing Complete but that is a theoretical concept). In my case I have used the negatives address space to access additional memory but for larger word sizes that is not important. Instead the high order bits could be used to specify the ALU action. For a 16 bit word:
- 0x0XXX XXXX would be SUBLEQ (i.e. SUB and jump on Less than or EQual)
- 0x1XXXXXXXX could be NANDJZ (i.e. NAND and Jump on Zero)
or for a 32 bit word:
- 0x0000 XXXX XXXX XXXX would be SUBLEQ (i.e. SUB and jump on Less than or EQual)
- 0x0001 XXXX XXXX XXXX could be NANDJZ (i.e. NAND and Jump on Zero)
- etc.
The modifications to the decoder would require the "C" address to be read before writing the "B" results.
Macros and Specifying Code Address for the Assemble
I may look at macro but easier to to "M4" the macro language as a pre-processor. (Still I may write my own!)
As IO and self-modifying code need to be in specific memory locations I will need to add a way to specify the memory address for code.
Assembler
The Assembler is now finished. Here is a modified version of "HELLO WORLD!" showing the main features:
@OUTPUT -1 ; On my system @INPUT -2 ; On my system @HALT 0 ; Return to monitor ( HELLO WORLD! ) H OUTPUT ? E OUTPUT ? L OUTPUT ? L OUTPUT ? O OUTPUT ? BLANK OUTPUT ? W OUTPUT ? O OUTPUT ? R OUTPUT ? L OUTPUT ? D OUTPUT ? BANG OUTPUT ? Z Z HALT ; And end program ( ASCII characters ) .H 72 .E 69 .L 76 .O 79 .BLANK 32 .W 87 .R 82 .D 68 .BANG 33 ( Predined variables and addresses ) .Z 0 .T 0 .P 1 .N -1 .SP -17
I have added an equate type command with the "@label". This allows the setting of absolute or fixed addresses. This is necessary to set the system INPUT/OUTPUT and HALT addresses. The @label is also necessary for relocatable code. Although the ".label" and "label:" are the same internally, I nominate the ".label" for variables and the "label:" address references. Upon output variables are one per line rather than in triplets (for SUBLEQ instructions). Refer to the output below:
39 -1 3 40 -1 6 41 -1 9 41 -1 12 42 -1 15 43 -1 18 44 -1 21 42 -1 24 45 -1 27 41 -1 30 46 -1 33 47 -1 36 48 48 0 72 69 76 79 32 87 82 68 33 0 0 1 -1 -17
Interpreter
I wrote an interpreter that closely models my 16 bit SUBLEQ design:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdbool.h> int main() { bool console; int mem[65536]; int *i=mem; int IP=0; // Instruction Pointer int AddrA=0; int AddrB=0; int AddrC=0; int DataA=-1; int DataB=-1; int Result=-1; // Test if console console=isatty(STDIN_FILENO); // Load Code printf("\nSUBLEQ Interpreter\n\n"); if (console) { printf("Input mode (Enter '!' to quit):\n"); // Exit on not a number // Note: <enter> will not exit while (scanf("%d",i++)==1); printf("Interpret mode:\n"); } else { while (scanf("%d ",i++)!=EOF); } // Interpret Code do { // Fetch Addresses AddrA=mem[IP]; IP++; AddrB=mem[IP]; IP++; AddrC=mem[IP]; IP++; // Fetch Data if ((AddrA<0)||(AddrA>=65528)) { // Fetch from Input (-2) if ((AddrA==-2)||(AddrA==65534)) { DataA=(int)getchar(); } else { // Not mapped DataA=-1; } } else { // Fetch ROM/RAM DataA=mem[AddrA]; } if ((AddrB<0)||(AddrA>=65528)) { // Fetch from Input (-2) if ((AddrB==-2)||(AddrA==65534)) { DataB=(int)getchar(); } else { // Not mapped DataB=-1; } } else { // Fetch ROM/RAM DataB=mem[AddrB]; } // Subtract Result=DataB-DataA; // Update IP if (Result<=0) { if (AddrC>=0) { IP=AddrC; } else { IP=65536-AddrC; } } else { IP++; } // Deposit if ((AddrB<0)||(AddrA>=65528)) { if ((AddrB==-1)||(AddrA==65535)) { // Deposit to Output (-1) // "Result" is inverted in hardware printf("%c",~((char)Result)); } else { // Not mapped } } else { mem[AddrB]=Result; } } while (IP!=0); printf("\n\nInterpreter finished\n"); return 0; }
The code is "spelt out" to avoid coding errors. I also detect if the user is using the console or file redirection (the usual/expected mode).
An OPCode Level SubLEq Assembler
I came across a SUBLEQ assembler (https://github.com/farlepet/subleq_assembler) that accepts "higher level operations" but exports SubLEq microcode. Very neat idea, the OpCodes are:
- NOP: Do nothing
- JMP loc: Jump to loc
- ADD a, b: Add b to a and store result in a
- MOV a, b: Copy b into a
- CLR a: Set a to zero
- SUB a, b: Subtract b from a and store result in a
- JZ a, loc: Jump to loc if a == 0
- JLZ a, loc: Jump to loc if a < 0
- JLEZ a, loc: Jump to loc if a <= 0 (note: much faster then JZ)
- MUL a, b: Multiply a by b and store result in a
- DIV a, b: Divide a by b and store result in (note: positive numbers only)
- PUSH a: Push a onto the stack
- POP a: Pop the uppermost value on the stack into a
- CALL a: Push return address to stack, then jump to a
- RET: Pop return address from stack, then jump to returned address
The assembler appears to assume RAM resident code(?). He shows some quite high level examples with functions etc. but it will take some time for me to understand his work.
Regards AlanX
-
Schematic Updates
05/31/2017 at 23:46 • 3 commentsSchematic Updates
Had a sleep on the schematic issues and decided to make few changes.
- Fixed the Memory Address Register (MAR) design over kill.
- Change the Decoder logic for 74LS373's (transparent latch) rather than 74LS374's (clocked D-latch). The reason for this is purely that the transistor count for a 74LS373 is much less than for a 74LS374.
- Reworked the Instruction Pointer (IP) for 74LS161 counters rather than 74LS83 adders. The reason for this is that I cannot initialise (upon reset) the current IP design.
- Reworked the ALU for signed integers rather than unsigned integers (as I have worked out how to do signed integer comparison).
Here is the updated Reset/Clock/Decoder:
And the Timing Signals:
Here is the Memory Address Register and Instruction Pointer:
Unfortunately the 74LS373 does not seem to be working in TinaTI so I can not test the circuit(?). In theory the simulation is an infinite loop (Subleq 0 0 0). And finally, here is the ALU:
More revisions:
- Added a strobe for IO output as the memory cycle is less than ideal (for static RAM without !OE).
- Removed the ALU comparator circuit as it is redundant (at least I think it is).
Here is the updated ALU:
As far as I can work out the Carry (i.e. Borrow) is all I need!
Memory Write and Bus Conflict
Bus conflicts are not usually a problem with simple systems as they do no damage. The reason is that TTL signals cannot supply much current (100s of uA). But an Arduino can supply ~100 mA so a bad bus conflict can do damage. I blew up my 4 Bit CPU RAM through careless testing of the memory (I have since modified the write cycle to solve this problem).
Here is the problem write cycle (1), the memory decoder fix for the 4 Bit CPU (2) and the CPU fix (3):
In case (3) the write cycle is less than ideal for latching registers so I have added an IO_STB signal o the decoder schematic (which can be inverted for IO_CLK in necessary).
PCB/Bus Planning
The problem with PCBs are:
- testing a complex PCB is hard
- schematic errors require a new set of PCBs to be manufactured (whic costs time and money)
- you get at least five copies (and you only need one that works!
So what is PCB/bus planning is about:
- splitting a complex board into several simpler boards so that it is easier to debug and only bad boards need to be re-worked. Call it divide and conquer.
- providing convenient test points for easier debugging.
- working out how to "stack" the boards (the bus) so that the spare boards are not wasted (and the complexity of each board is reduced). That is, make two 8 bit boards rather than one single 16 bit board.
Here my first pass at PCB/bus planning:
You can see that I have:
- I have extracted all the signals I need
- moved the clock and reset logic off the decoder board as I may want to use a different clock
- I have reworked the remaining boards so they can be stacked (a 64 bit CPU?)
- I have not actually designed the Front Panel etc., but have made provision.
Here is the updated schematic with provision for expansion:
Well I can say that it is not often a project get simpler as you progress!
Working
I swapped back to 74LS374s were I could and got the 74LS373s working (just!). I am really pushing my luck with TinaTI! I have got the simulation to execute the infinite loop which pretty well says I got it. The address bus and to a lesser extent the data bus is a glitch mess. I will have to analysis the decoder signals more closely, to find out why. It may just be TinaTI warming me it is close to falling over again. Here is the latest schematic:
I have swapped out the schmitt trigger relaxation oscillator for a gated crystal oscillator and the three 74LS74 D-Latches for a 74LS161.
The signals look okay with an 8 MHz crystal. The crystal oscillator is a little unusual in using a schmitt trigger for the active component but it works well.
I have swapped the strobe signals back to clock signals.
Some tweaks with the ALU carry in and carry out.
16 Bit RISC
I have great expectations for this CPU so I will go to 16 bit up front (two 8 bit stacks).
I need to make up the PROM/RAM/IO board and work out how I am going to interface to the system. I want to do more with this CPU than a Front Panel is reasonable for but have not worked it out yet. Still I think I am ready to get the first set of boards made.
Direct Memory Access
I am not using a Front Panel for this CPU but I still need a way to program the system. One options with a monitor program, seem to be:
- serial
- direct keyboard and video
Without a monitor program:
- direct memory access
Direct Memory Access (DMA) requires a Hold and Hold Acknowledge (like the 8080 system). If the machine cycle has a tri-state at the end of the cycle then I could gate off the clock. I can use T8 by making !OE high instead of low. T7 (the cpu has machine cycles T0 to T7) is only used to advance the IP or to set the IP with the C register (all internal operations) so the !OE does not need to be low.
With DMA, for development programming (i.e. debugging) an Arduino can program the system.
Schematic V9
Reworked the decoder for DMA using HOLD and returning !HLDA (hold acknowledge). The the CPU stop in T7 and the data and address bus go tri-state. The control signals all go high (multiplexing these will have to be done at the memory decoder).
I had to revert back to the old memory write logic (again the write logic will have to be modified at the memory decoder).
So here it is:
Here are the HOLD signals:
There is a glitch in the !HLDA from the !CLR signal but it is not material.
Back to a simplified 8 bit version
I was running around in circles so I gone back to the simplified 8 bit version. Being 8 bit a Front Panel will be fine (i.e. programs will not be very long). Here is the final 8 bit version that is ready for prototyping:
It still does not work in the TinaTI simulator properly but the timing signals look good and I can't find fault in the MAR, IP or ALU.
Now I remember, TinaTI has a limited network depth search. I ran into this problem before. I suspect that the Decoder is more complex than before thus the 74LS373 that used to work before no longer works.
Front Panel
Here is a basic Front Panel added to the schematic:
The Front Panel's RAM decoder includes the !WE fix to limit memory conflicts. Unfortunately, the IO write will not work with the SUBLEQ write cycle so will need to be modified. This design assumes momentary contact switches but I will have to use toggle/slide switches as PROM memory space is a super premium.
Opcode SUBLEQ
I want to rework the Decoder to delay the ALU write (to the B address) to after reading the C address so that I have the option to use the high bits of the address space for opcodes. Obviously not for the 8 bit version of the SUBLEQ CPU.
Reworking the CPU
Decoder
- I reworked Decoder, rearranged to fetch order so that "C" is fetched before the ALU result is deposited into "B". This allows the CPU to check the high order bits of the jump address (i.e. C address). For fun I linked the MSB of "C" to the HOLD signal and yes it stopped the CPU (with no way of resting it!). The real reason for this is so I can use the high order bits of the jump address as opcodes (for the 16 bit version of the CPU). The CPU really needs a NAND Jump on Zero second ALU.
- I added:
- !EXT_RST - EXTernal ReSeT for the Arduino
- !EXT_OE - EXTernal Output Enable for the Arduino
- !EXT_WE - EXTernal Write Enable for the Arduino
- HOLD - HOLD the CPU and make the Data and Address bus tri-state
- !HLDA - HoLD Acknowledge (the CPU is now waiting
- I moved the clock off board, the Arduino will provide the clock signal.
Input/Output
The easiest way for the CPU to talk to the external world is to parallel read and write to a port. To do this without timing or control signals is possible if one character is used a marker. May as will use \0 or "null" character. The output stream would consists of sequential characters at any speed (within reason). The CPU (when receiving) would detect a change in the character to signal a new character. Easy, but what about duplicate characters. Easy, insert a null between duplicates (but ignore the null). But what if I want to send a null, bad luck! Not really, we could use an escape sequence such as '\' as used in C code.
Here is the updated CPU schematic (no IO):
And here is the timing signals:The timing signals may be a jumble to you but the CPU start at address 0x00 (i.e. 0X00 -> 0X01 -> 0X02) then jumps to address 0xFF (i.e. 0xFF -> 0X00 -> 0X01) and then loops back to 0XFF. The ALU outputs 0x00 (=0xFF-0xFF). So I am confident the schematic is correct. How did I fix TinaTI? The schematic had a corrupted jumper (it looked okay but when I replaced it TinaTI started working!).TBC ...
AlanX
-
The SUBLEQ Interpreter
05/31/2017 at 08:56 • 0 commentsThe SUBLEQ Interpreter
Wikipedia presets the following SUBLEQ pseudocode:
int memory[], program_counter, a, b, c program_counter = 0 while (program_counter >= 0): a = memory[program_counter] b = memory[program_counter+1] c = memory[program_counter+2] if (a < 0 or b < 0): program_counter = -1 else: memory[b] = memory[b] - memory[a] if (memory[b] > 0): program_counter += 3 else: program_counter = c
In my case signed integers and a stopping condition are not required:
int memory[], program_counter, a, b, c program_counter = 0 while (true): a = memory[program_counter] b = memory[program_counter+1] c = memory[program_counter+2] if (memory[b] > memory[a]): memory[b] = memory[b] - memory[a] program_counter += 3 else: memory[b] = memory[b] - memory[a] program_counter = c
A Schematic for an 8 bit SUBLEQ CPU
The schematic considers the scaling up to any word width (i.e. 8, 16, 14 or 32 bit).
The Decoder
The first step is the Reset, Clock and Decoder logic.
Initially I used Logic Friday to develop a schematic but the schematic when modelled in Tina TI was badly glitched. I did fix it with D-latches but the chip count was highe than I have achieved using manual methods.
Here is a schematic for my decoder:
And here are the signals:
Static RAM Write Timing
I have been using a particular static RAM timing cycle that up until I blew up a RAM chip I thought was okay (i.e. after the !WE pulse the RAM chip did't care less with regard to memory conflicts - Wrong!).
So after carefully reviewing the RAM datasheet I now use the above !DATA_OUT, !OE (not used on my RAM chips) and !WE. !DATA_OUT waits until !WE disables the RAM output.
The Instruction Pointer
The instruction pointer uses an ADDER to increase the base memory address rather than a COUNTER. The DECODER provides the address increments (signal IP0 and IP1):
If "!JMP" is low then the Address C replaces IP (the 374) upon the LDC signal. Else the IP is incremented by 3 (via the ADDER and the Carry In).
The Memory Access Registers
The Memory Access Registers (MAR) hold the fetched memory address that is then used to load the pointed values. (much like my Weird CPU):
The ALU
Its not much, it just subtracts A from B and tests for a JMP (i.e. A >= B):
The schematics need review and I have not considered the Memory/Input/Output decoder or the Front Panel yet. I am also consider changing the signal logic to use 373's rather than 374's.
The number in the labels of each drawing is the number of chips so the CPU will require 29 chips.
AlanX