Work on the lisp machine has been progressing excruciatingly slowly. Also, water is wet.
Since last time:
I had gotten the simulator to the point where I wanted to test larger programs. I want to be able to run a simple REPL on the finished machine at the very least, so I started with that. But along the way I ended up doing a partial rewrite of the simulator.
The old design had a dedicated cons cell type called node-type-call
. The idea is that you would tell the lisp machine explicitly to execute a list, as opposed to it executing any list node that it came across. Not some amazing feature of the ISA or anything, it just made the microcode easier to write.
The problem is that, in the old design, you couldn't write a compiler that returns a node-type-call
. If you tried, the node-type-call
would be in the instr
register at the start of the next macrocycle and it would be immediately executed. You'd have to return a raw list containing the instructions. And there'd be no possible way to construct a node-type-call
that points to it either. You'd have to make a whole new instruction, called eval
, whose job is to execute lists. Which was supposed to be the job of node-type-call
. So I ended up getting rid of node-type-call
and just implemented the normal lisp calling convention: lists encountered while running instructions are executed, lists encountered while returning from calls are not.
I also made a simple little microcode compiler. Instead of listing out the literal microcode signal lines for each macrocycle like this:
(from-instr-cdr to-mem-addr swizzle-cdr->car)
(from-mem-mark from-mem-type from-mem-car from-mem-cdr to-instr-mark to-instr-type to-instr-car to-instr-cdr)
I now write something like this:
(instr (mem (cdr instr)))
This made the rewrite much easier. It's a little too simplistic for the gc phases, so I do those manually still. Which is unfortunate, because those are the most complicated, and it's where it would help a lot. The microcode compiler is a bit greedy with its use of temporaries, and the gc phase is short on extra register space. Hopefully I can go back sometime and make this better.
One of my longer-term goals is to make a microcode compiler that works from first principles: you feed it in the metacircular evaluator and it outputs the entire microcode for the computer. Outside of being dorky and cool, it would actually be nice to have a parameterizable machine. Currently, a bunch of cycles are wasted because the machine only looks one at one instruction at a time. It'd be nice if I could specify to the microcode compiler "take the metacircular evaluator, inline it n times, and tell me how many cpu states and registers you will need, and how many microcode instructions it will turn out to be."
But in any event, the compiler is now working. Meaning, I have a test case where the simulator is running a small lisp compiler, that compiles into its own native ISA, and then runs it. It's quite slow, but it does work.
In addition, there's now output targets for building and dumping the microcode ROM and instruction ROMs. Every step is a step closer to running real programs on a real physical machine!
Making the boards has been an exercise in patience, and a wrestling match with my own stubbornness.
I really wanted to do make the board without having to use a PCB manufacturer. I've tried JLCPCB for a previous keyboard project before and it was a nightmare:
So I ended up spending a couple months waffling around with etching PCBs.
My first experiment was to make this thing:
Just to get my feet wet, I made a little device that tests if a USBA-to-USBC cord has data lines, or just power lines. This is a practical tool on its own. All my devices have USBC charging ports, so I have a bunch of these cords. You plug both ends into their respective slots, and if all 4 leds light up, then all 4 lines are present. If only the outer two light up, then it's a power-only cord. If some other combination, your cord is broken.
I got some single-sided copper cladded PCB boards, and drew the design out freehand with a sharpie. I etched it in some ferric chloride, and it came out great on the first try!
Not the greatest craftsmanship, but it works.
Here's a test for part of one of the lisp machine's register boards. Uses 2 components, a 3-state bus transciever (74HC245) and an 8-bit latch (74HC573), with room for pins in the middle for testing.
I carefully marked out the spacing needed for the component pins, and freehanded the rest.
The etch on this one was really good! There were some spots where the sharpie was a bit thin and got etched away, but other than that it was very clean.
Again, worked perfectly first try. The trick to clean etches I think is to use fresh ferric chloride, and do it outside in a warm summer day. I definitely noticed the etches getting worse the more I used the current batch of ferric chloride.
A full register (reduced a bit from last time because the amount of parts and effort required for this fucking project is spiraling out of control) is 40 bits: 16bits for the car field, 16bits for the cdr field, and 8bits for the type/gc mark. I've been focusing on making some 16bit boards, since they'll all have the same layout.
I've tried a couple ways of making them:
In the end, I cracked and got a pcb manufactured. The process didn't go as horribly this time. Here's a 16bit board, fully assembled:
Of course, there was a dumb mistake on the boards. In the documentation I was reffering to, the footprint of the transistors, SOT23, it says that the distance from the centerline to the outer edge of the bottom pad is 1.35mm . I accidentally used that number as the distance to the center of the bottom pad, and so the transistors don't sit quite right on the board. I have to really double or triple up on the solder to get them to stick.
I tested the board, and the chips work fine, though two leds don't come on (because of the above problem).
The plan now is to: