• Review

    agp.cooper07/23/2017 at 13:42 0 comments

    Review

    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

    agp.cooper06/17/2017 at 00:57 0 comments

    Macros 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...
    Read more »

  • Self Modifying Code

    agp.cooper06/11/2017 at 03:03 0 comments

    Self 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):

    1. ( HELLO WORLD! )
    2. E OUTPUT ?
    3. L OUTPUT ?
    4. L OUTPUT ?
    5. O OUTPUT ?
    6. BLANK OUTPUT ?
    7. W OUTPUT ?
    8. O OUTPUT ?
    9. R OUTPUT ?
    10. L OUTPUT ?
    11. D OUTPUT ?
    12. BANG OUTPUT ?
    13. Z Z HALT ; And end program
    14. ( ASCII characters )
    15. .H 72
    16. .E 69
    17. .L 76
    18. .O 79
    19. .BLANK 32
    20. .W 87
    21. .R 82
    22. .D 68
    23. .BANG 33
    24. ( Predined variables and addresses )
    25. .Z 0
    26. .T 0
    27. .P 1
    28. .N -1
    29. .SP -17
    30. @OUTPUT -1 ; On my system
    31. @INPUT -2 ; On my system
    32. @HALT 0 ; Return to monitor

    Example assembler output (note: line numbers are not part of the assembler output):

    1. 39 -1 3
    2. 41 -1 6
    3. 43 -1 9
    4. 43 -1 12
    5. 45 -1 15
    6. 47 -1 18
    7. 49 -1 21
    8. 45 -1 24
    9. 51 -1 27
    10. 43 -1 30
    11. 53 -1 33
    12. 55 -1 36
    13. 57 57 0
    14. 72
    15. 69
    16. 76
    17. 79
    18. 32
    19. 87
    20. 82
    21. 68
    22. 33
    23. 0
    24. 0
    25. 1
    26. -1
    27. -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...

    Read more »

  • Input and Output

    agp.cooper06/10/2017 at 12:19 0 comments

    SUBLEQ 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

    agp.cooper06/08/2017 at 14:35 0 comments

    Front 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:

    20-pc-3-Pin-PCB-2-Position-SPDT-1P2T-Miniature-Vertical-Slide-Switch-SS12D07-VG4So the Monitor pseudo code would be:

    • Initialise
    • LOOP:
    1. Read the IO Data register (the switches) to the DataIn variable
    2. Read the IO Addr register (the switches) to the Addr variable
    3. Write the Addr variable to the IO Addr register (the LEDs)
    4. Move the data from the memory pointed to by the Addr variable to the Data variable
    5. Write the Data variable to the IO Data register (the LEDs)
    6. Compare the IO Data register to the DataIn variable, if equal then go to step 9
    7. If Addr variable equal 0xFF then exit Monitor and go to 0x80 (i.e. run the user program)
    8. Move the IO Data register...
    Read more »

  • Software

    agp.cooper06/03/2017 at 10:34 0 comments

    Software

    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 Look

    First 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:

    1. subtract the value stored at address "A"
    2. from the value stored at address "B", and
    3. replace the result of the value stored at address "B".
    4. 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:

    1. a tokenizer (that dumps comments)
    2. a label reference search
    3. 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...
    Read more »

  • Schematic Updates

    agp.cooper05/31/2017 at 23:46 3 comments

    Schematic 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...

    Read more »

  • The SUBLEQ Interpreter

    agp.cooper05/31/2017 at 08:56 0 comments

    The 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
    

    (Source: https://en.wikipedia.org/wiki/One_instruction_set_computer#Subtract_and_branch_if_less_than_or_equal_to_zero)

    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