Whether or not, to say "whether or not" or not.

A project log for Rubidium 2.0

This is an all in one spectrum and logic analyzer, robotics control platform, and modular synthesizer with audio in and sheet music out!

glgormanglgorman 10/07/2021 at 23:530 Comments

Or is it this or that, or neither this nor that?  Perhaps it doesn't matter.  That's not what this post is about, unless maybe it is.  Writing a compiler from scratch is no small task, on the one hand, but then again, on the hardware side of things there are always going to be those people who want to do things like "build" a compete CPU or at least an ALU with nothing but NOR gates; just like the Apollo guidance computer.  So while building such at beat is not in the cards, simulating on with software is certainly fair game, especially if there is also the possibility that a compiler for such a beast might emerge in the very near future.  So lets have a look at some code.

inline DWORD nor(DWORD r1, DWORD r2)
    DWORD result;
    result = ~(r1|r2);
    return result;

inline DWORD not(DWORD r1)
    DWORD result;
    result = nor(r1,r1);
    return result;

inline DWORD or(DWORD r1, DWORD r2)
    DWORD result;
    result = not(nor(r1,r2));
    return result;

inline DWORD and(DWORD r1, DWORD r2)
    DWORD result;
    result = nor(not(r1),not(r2));
    return result;

So far so good.  Just wait until you try to debug addition.  But first, take a look at how we do "and" via DeMorgan's law.  Fairly straightforward.  Now assuming that you have addition, and if you also have the square of the first 255 integers in a look-up table, 8-bit by 8-bit multiplication follows, like so:

inline unsigned short mult8 (unsigned char c1, unsigned char c2)
    unsigned short s0, s1, s2, s3, s4;
    if ((c1==0)|(c2==0))
        return 0;
    if (c1==1)
        return c2;
    if (c2==1)
        return c1;
    s0 = (unsigned short) add(c1,c2);
    s1 = (unsigned short) sub(c1,c2);
    bool m_odd = (s0&0x1?true:false);
    bool m_neg = (s1&0x8000?true:false); 
    if (m_odd) {
        s0 = (unsigned short) add(s0,1);
        s1 = (unsigned short) add(s1,1);
    s2 = (unsigned short)(m_neg?add(not(s1),1):s1);
    s3 = (unsigned short) sub(lut1[s0],lut1[s2]);
    if (m_odd)
        s4 = (unsigned short) sub(s3,c2);
        s4 = s3;
    return s4;

Yes, script kiddies and sugar friends, that does the trick.  Which kind of makes me wonder what it would look like if I had a good C++ 6502 compiler, or C++ for the NOR machine, that is.  Care to try for 16 bits?  I think that this works, but I haven't run every possible test:

inline DWORD square(unsigned short r1)
    DWORD t0,result;
    DWORD t1, t2, t3;
    unsigned char highbyte, lowbyte;
    lowbyte = r1&0xff;
    highbyte = (r1&0xff00)>>BYTESIZE;
    t0 = (lut1[highbyte]<<WORDSIZE)|(lut1[lowbyte]);
    t1 = mult8(highbyte,lowbyte);
    t2 = t1<<(BYTESIZE+1);    
    t3 = add(t0,t2);
    result = t3;
    return result;

inline DWORD mult16(unsigned short r1, unsigned short r2)
    DWORD result;
    DWORD t1, t2, t3, t4;
    t1 = add((DWORD)r1,(DWORD)r2);
    t2 = sub((DWORD)r1,(DWORD)r2);
    t3 = square(t1);
    t4 = square(t2);
    result = sub(t3,t4)>>2;
    return result;

Now, this is starting to look like it would be a good way to use overloaded operators in templates, that is to say, if a full-featured C++ compiler were available.  Conformant IEE-754?  Not quite there yet.   Even though I am not currently doing anything with this code, it is interesting from the point of view of compiler writing; that is to say - to the extent that it should be possible to write a complete compiler for any language that might use only a very limited instruction set, such as NOR, SHIFT, COMPARE, and JUMP, along with perhaps CALL, LOAD, PUSH and POP.  That means of course that when writing a compiler for a new platform, it might be useful to initially make use of only a subset of instructions, i.e., with respect to what is actually available with a new architecture.  Then, for example, one approach might be to use C++ classes which provide the facility for the use of virtual functions.  Thus, one might declare a "fake.cpu" which defines virtual functions for all of the primitives and then use a class that inherits from that class, and which provides overrides which in turn do interesting things, like spitting out "inline' assembly to a "debug file."  Presto!  Instant compiler construction kit for any platform without 99% of the bloat.  Check out the code for "fake_cpu.cpp" in the files section on this project page, as well as the Frame Lisp project on GitHub for further insights.