This is the architecture for a Lisp CPU, which should fit in a small FPGA, like the one used in the Spartan-3 Starter Kit. With "Lisp CPU" I mean that the core evaluates a binary form of s-expressions without compiling it to a lower machine code level, like described in Design of LISP-Based Processors or, SCHEME: A Dielectric LISP or, Finite Memories Considered Harmful or, LAMBDA: The Ultimate Opcode. My goal is not a full featured Common Lisp implementation, but a Lisp dialect which is good enough for writing applications like games, without the need to do all the low-level handlings like in C. While the application logic will be written in Lisp, special hardware functions and performance critical tasks, like sound generation, will be implemented in hardware and available with primitive Lisp functions.
Every value and pointer is saved in a word, with some extra bits for the type information.
Type | Meaning of the word without the type bits |
---|---|
fixnum | fixnum |
symbol | pointer to a symbol structure |
list | pointer to a list structure |
primitive | the identifier of a primitive function |
array | pointer to an array |
function | pointer to a function |
nil | the word is not used |
symbol structure: 3 words with type information:
list structure: 2 words with type information:
array structure: first fixnum specifies the size, followed by the typed values or pointers.
function structure: two list pointers: first list is a list of symbols for the parameters, second list is the function body.
+ - < > <= >= /= = * set quote setq defun progn if cons car cdr nil
set-led number : sets the LED bit-pattern (8 bits)
get-led : gets the LED bit-pattern (8 bits)
while condition body : a loop: if the evaluation of condition is not nil, body will be evaluated (implicit progn) and then it starts again with checking condition, until it is nil.
defun : the standard defun, but with dynamic scope and without special lambda list details, like default parameters, keyword arguments etc. All symbols are global and when a function is called, the function arguments are prepended in the value slot of the symbol and removed on function return.
(progn (set (quote test) 0) (while (< test 10) (set-led test) (set (quote test) (+ test 1))))
This program can be transformed into the binary s-epression representation and evaluated with lispcpu.lisp.txt.
It's more difficult than I thought to built a Lisp CPU. Perhaps the Verilog language is not so good, because some nice standard language featuers (forever-loop etc.) are missing in the Xilinx-Tools. But it is possible, the code looks only a bit more complicated. For learning how to built a CPU at all and how to implement RAM, ROM, program counter and an evaluator, I started with a simple CPU, which will be enhanced to the Lisp CPU:
module LispCPU(clk, led, segment, digit); input clk; // disable LEDs and 7 segment display output [7:0] led; output [7:0] segment; output [8:0] digit; assign segment[7:0] = 0; assign digit[8:0] = 'hff; // prescaler reg [32:0] devide = 0; reg [7:0] rLed = 0; assign led = rLed; `define INIT 1 `define EVALUATE 2 reg [15:0] ram [0:255]; // 256 words ram reg [7:0] pc = 0; // program counter reg [7:0] currentState = `INIT; reg [7:0] nextState = `INIT; reg [15:0] cmd; reg [15:0] accu; reg slowClock; `define CMD_LED_ON 1 `define CMD_LED_OFF 2 `define CMD_LOAD_NEXT_TO_ACCU 3 `define CMD_WAIT_UNTIL_ACCU_IS_ZERO 4 `define CMD_JUMP_TO_ZERO 5 always @(posedge clk) begin if (devide == 0) begin devide <= 5000000; slowClock <= ~slowClock; end else devide <= devide - 1; end always @(posedge slowClock) begin currentState = nextState; case (currentState) `INIT: begin pc = 0; ram[pc] = `CMD_LED_ON; pc = pc + 1; ram[pc] = `CMD_LOAD_NEXT_TO_ACCU; pc = pc + 1; ram[pc] = 'h03; // 3 to accu pc = pc + 1; ram[pc] = `CMD_WAIT_UNTIL_ACCU_IS_ZERO; pc = pc + 1; ram[pc] = `CMD_LED_OFF; pc = pc + 1; ram[pc] = `CMD_LOAD_NEXT_TO_ACCU; pc = pc + 1; ram[pc] = 'h09; // 9 to accu pc = pc + 1; ram[pc] = `CMD_WAIT_UNTIL_ACCU_IS_ZERO; pc = pc + 1; ram[pc] = `CMD_JUMP_TO_ZERO; pc = 0; nextState = `EVALUATE; end `EVALUATE: begin cmd = ram[pc]; pc = pc + 1; case (cmd) `CMD_LED_ON: rLed = 1; `CMD_LED_OFF: rLed = 0; `CMD_LOAD_NEXT_TO_ACCU: begin accu = ram[pc]; pc = pc + 1; end `CMD_WAIT_UNTIL_ACCU_IS_ZERO: begin if (accu != 0) begin pc = pc - 1; accu = accu - 1; end end `CMD_JUMP_TO_ZERO: pc = 0; endcase end endcase end always @(negedge slowClock) begin currentState <= nextState; end endmodule