diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d931c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +work/ diff --git a/README.md b/README.md index 6f3d122..173c3f1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ -# TIS-100-FPGA +# TIS-100 in Lucid for FPGA -A try to implement TIS 100 on a FPGA \ No newline at end of file +I tried to implement [TIS-100](http://www.zachtronics.com/tis-100/) on a FPGA. I used the [Mojo-v3](https://alchitry.com/products/mojo-v3) from alchitry. + +You need a tool (i am going to create one soon) to send a savefile from you computer to the programed FPGA. + +Things currently not supported: + * ANY register - would just act like the NIL register + * graphical Output - nowhere to output too + * Stackstorage Nodes - comimg later + * debugging in any way + * speed control (step, fast run) + +Keep in mind, that this is just a small Project to play around with a FPGA. I am pretty new to Lucid (the Language used) and i probably made every mistake - and bug - possible. So there is no warranty at all! But if you find something feel free to send me a message at `y.g.2 (at) gmx (dot) de` + +###### Licence + +This Project is Licenced under AGPLv3 a copy of the Licence should be alongside with the code. diff --git a/source/mojo_top.luc b/source/mojo_top.luc new file mode 100644 index 0000000..08014a0 --- /dev/null +++ b/source/mojo_top.luc @@ -0,0 +1,33 @@ +module mojo_top ( + input clk, // 50MHz clock + input rst_n, // reset button (active low) + output led [8], // 8 user controllable LEDs + input cclk, // configuration clock, AVR ready when high + output spi_miso, // AVR SPI MISO + input spi_ss, // AVR SPI Slave Select + input spi_mosi, // AVR SPI MOSI + input spi_sck, // AVR SPI Clock + output spi_channel [4], // AVR general purpose pins (used by default to select ADC channel) + input avr_tx, // AVR TX (FPGA RX) + output avr_rx, // AVR RX (FPGA TX) + input avr_rx_busy // AVR RX buffer full + ) { + + sig rst; // reset signal + + .clk(clk) { + // The reset conditioner is used to synchronize the reset signal to the FPGA + // clock. This ensures the entire FPGA comes out of reset at the same time. + reset_conditioner reset_cond; + } + + always { + reset_cond.in = ~rst_n; // input raw inverted reset signal + rst = reset_cond.out; // conditioned reset + + led = 8h00; // turn LEDs off + spi_miso = bz; // not using SPI + spi_channel = bzzzz; // not using flags + avr_rx = bz; // not using serial port + } +} \ No newline at end of file diff --git a/source/node.luc b/source/node.luc new file mode 100644 index 0000000..4105df4 --- /dev/null +++ b/source/node.luc @@ -0,0 +1,110 @@ + +global GNode { + const ROWS = 14; //number of commands to hold + const CMDLEN = 16; //number of bits a command is in size + const WORDLEN = 11; //bits to store a number (-999 to 999) + const REGISTERADDRLEN = 3; //bits to address a register + + struct cmd { + cmd[4], + arg_is_const, //1 = argument is a 11bit signed integer, 0 argument is a register number + args[11] //11 bit number or at most 2 reigster (mov cmd) + } +} + + + +/** +Register numbers: + 0 = NIL + 1 = ACC + 2 = UP + 3 = RIGHT + 4 = DOWN + 5 = LEFT + 6 = LAST + 7 = ANY + + Subclk: + 0 = Read cmd + 1 = interpret cmd + 2 = Mark input as read + 3 = try to read + 4 = mark output as read + 5 = write to output + 6 = increment pc and reset everything thats not needed anymore + + cycles are skiped, if node is waiting for another node to read or write a connection +*/ + +module node ( + input clk, // clock + input rst, // reset + + input up_in[GNode.WORDLEN], + output up_out, + + input right_in[GNode.WORDLEN], + output right_out, + + input down_in[GNode.WORDLEN], + output down_out, + + input left_in[GNode.WORDLEN], + output left_out, + + output error, + input subclk + ) { + + .clk(clk), .rst(rst) { + dff pc[4](#INIT(0)); //program counter + + dff cmds[GNode.ROWS]; + + dff lastreg[3](#INIT(0)); // default = NIL + + dff acc[GNode.WORDLEN](#INIT(0)); + dff bak[GNode.WORDLEN](#INIT(0)); + + dff is_reading(#INIT(0)); + dff is_writing(#INIT(0)); + + dff currentCmd; + fsm currentCmdFSM = {MOV, // 2 args: 1const, 1reg or 2 reg + JEZ, JNZ, JGZ, JLZ, JRO, // 1 arg: 5bit(signed) const offset + ADD, SUB, // 1 arg: 1reg or 1 signed const + NEG, SWP, SAV}; // no arg + dff currentCmd_has_const(#INIT(0)); + dff currentCmd_const[GNode.WORDLEN](#INIT(0)); + dff currentCmd_has_reg[2](#INIT(0)); // only mov has two arguments (dst ist first; src ist second) + dff currentCmd_reg[2][3];//registers to use TODO: add #INIT(0) + + registerMultiplex reg1(.selection(currentCmd_reg[0]), .acc(acc), .up(up), .right(right), .down(down), .left(left), .last(lastreg)); + } + + always { + error = 0; + if(subclk == 0 && is_reading.q == 0 && is_writing.q == 0) { + //read command + currentCmd.d = cmds.q[pc.q]; + currentCmdFSM.d = cmds.q[pc.q]; + } else if(subclk == 1 && is_reading.q == 0 && is_writing.q == 0) { + //interpret + + //set them for all commands just set the _has_ flag if reguired + currentCmd_reg.d[0] = currentCmd.q.args[ GNode.REGISTERADDRLEN -1 : 0]; //3 LSB bytes = register 1 + currentCmd_reg.d[1] = currentCmd.q.args[(2*GNode.REGISTERADDRLEN) -1 : GNode.REGISTERADDRLEN]; //3 next bytes = register 2 + currentCmd_const.d = currentCmd.q.args; + + //set the _has_ flags + currentCmd_has_const.d = (currentCmdFSM.q >= currentCmdFSM.JEZ && currentCmdFSM.q <= currentCmdFSM.JLZ) || currentCmd.q.arg_is_const == 1; //last bit of cmd indicates a constant for cmd's that can do both + currentCmd_has_reg.d[0] = (currentCmdFSM.q == currentCmdFSM.MOV || currentCmd.q.arg_is_const == 0); //mov cmd -> at least 1 reg or no constant + currentCmd_has_reg.d[1] = (currentCmdFSM.q == currentCmdFSM.MOV && currentCmd.q.arg_is_const == 0); //mov cmd und arg ist keine constante -> 2 register + } else if(subclk == 2 && is_reading.q == 0 && is_writing.q == 0) { + //mark input as read + is_reading.d = 1; + + + } +} diff --git a/source/nodeCon.luc b/source/nodeCon.luc new file mode 100644 index 0000000..e761ec0 --- /dev/null +++ b/source/nodeCon.luc @@ -0,0 +1,64 @@ +/** + + A Connection between two Nodes, blocks + + +*/ + +global G_nodeCon { + const WIDTH = 10; + + struct nodeConIn { + write, + read, + enable, + in[WIDTH] + } +} + +module nodeCon ( + + input clk, // clock + input rst, // reset + + input a_in, + output a_out[G_nodeCon.WIDTH], + + output done, //erledigt (blokirung kann uafgehoben werden) + + /*input b_write, + input b_read, + input b_enable, + input b_in[WIDTH],*/ + input b_in, + output b_out[G_nodeCon.WIDTH], + + output error + ) { + + dff internalBuff[G_nodeCon.WIDTH](#INIT(0), .clk(clk), .rst(rst)); + + always { + error = ((a_in.write && b_in.write) || (a_in.read && b_in.read)) && ~rst && a_in.enable && b_in.enable; // gleichzeitiges lesen oder schreiben und nicht reset + + done = (a_in.write && b_in.write) || (a_in.read && b_in.write) && a_in.enable && b_in.enable; + + a_out = 0; + if(a_in.enable) { + if(a_in.write) { + internalBuff.d = a_in; + } else if(a_in.read) { + a_out = internalBuff.q; + } + } + + b_out = 0; + if(b_in.enable) { + if(b_in.write) { + internalBuff.d = b_in; + } else if(b_in.read) { + b_out = internalBuff.q; + } + } + } +} diff --git a/source/registerMultiplex.luc b/source/registerMultiplex.luc new file mode 100644 index 0000000..d443668 --- /dev/null +++ b/source/registerMultiplex.luc @@ -0,0 +1,58 @@ +module registerMultiplex ( + input clk, // clock + input rst, // reset + + + input selection[3], //which register? see node.luc for enumeration + + input acc[GNode.WORDLEN], + input up[GNode.WORDLEN], + input right[GNode.WORDLEN], + input down[GNode.WORDLEN], + input left[GNode.WORDLEN], + input last[GNode.REGISTERADDRLEN], //CAREFULL! this is NOT a register! this is a address! + input any[GNode.WORDLEN], + + output out[GNode.WORDLEN], + input in[GNode.WORDLEN], + input write, + input read + + ) { + + dff outb[GNode.WORDLEN](.clk(clk), .rst(rst), #INIT(0)); + + always { + //reading + if(read) { + if(selection == 0) { + outb.d = 0; + } else if(selection == 1) { + outb.d = acc; + } else if(selection == 2) { + outb.d = up; + } else if(selection == 3) { + outb.d = right; + } else if(selection == 4) { + outb.d = down; + } else if(selection == 5) { + outb.d = left; + } else if(selection == 6) { + if(last == 0) { //inenr selection for last + + } + } else if(selection == 7) { + outb.d = any; + } else { + outb.d = 0; + } + } else { + outb.d = 0; + } + out = outb.q; + + if(write) { + //TODO: fwd in to the register and set writing flags + } + } +} \ No newline at end of file diff --git a/source/tis100.luc b/source/tis100.luc new file mode 100644 index 0000000..c6de7c9 --- /dev/null +++ b/source/tis100.luc @@ -0,0 +1,10 @@ +module tis100 ( + input clk, // clock + input rst, // reset + output out + ) { + + always { + out = 0; + } +} diff --git a/tis-100.alp b/tis-100.alp new file mode 100644 index 0000000..83d87c6 --- /dev/null +++ b/tis-100.alp @@ -0,0 +1,12 @@ + + + + node.luc + tis100.luc + mojo_top.luc + nodeCon.luc + registerMultiplex.luc + reset_conditioner.luc + mojo.ucf + +