├── quartus ├── lipsi.qpf └── lipsi.qsf ├── asm ├── test.asm ├── echo.asm ├── ldstind.asm ├── immop.asm ├── aluop.asm ├── blink.asm └── branch.asm ├── Makefile ├── Lipsi.gtkw ├── LICENSE ├── src ├── test │ └── scala │ │ └── lipsi │ │ ├── LipsiTester.scala │ │ └── LipsiCoSim.scala └── main │ └── scala │ └── lipsi │ ├── Memory.scala │ ├── sim │ └── LipsiSim.scala │ ├── util │ └── Assembler.scala │ └── Lipsi.scala └── README.md /quartus/lipsi.qpf: -------------------------------------------------------------------------------- 1 | PROJECT_REVISION = "lipsi" 2 | -------------------------------------------------------------------------------- /asm/test.asm: -------------------------------------------------------------------------------- 1 | # 2 | # general test program 3 | # 4 | 5 | ldi 0x23 6 | addi 0x32 7 | subi 0x55 8 | exit 9 | -------------------------------------------------------------------------------- /asm/echo.asm: -------------------------------------------------------------------------------- 1 | # 2 | # Echo IO 3 | # read input, add 1, write to output 4 | # 5 | 6 | loop: 7 | io 0x0 8 | addi 0x01 9 | br loop -------------------------------------------------------------------------------- /asm/ldstind.asm: -------------------------------------------------------------------------------- 1 | # 2 | # Test indirect load and store 3 | # 4 | 5 | ldi 0x73 6 | st r1 7 | ldi 0x34 8 | stind (r1) 9 | ldi 0xff 10 | ldi 0xff 11 | st r1 12 | ldi 0x73 13 | st r2 14 | ldind (r2) 15 | subi 0x34 16 | exit 17 | -------------------------------------------------------------------------------- /asm/immop.asm: -------------------------------------------------------------------------------- 1 | # 2 | # Minimal test program with immediate ALU operations 3 | # 4 | # End simulation with IO access to address 0xf (= exit) 5 | # Simulation success with accumulator 0 6 | # 7 | 8 | ldi 0x12 9 | addi 0x34 10 | subi 0x12 11 | andi 0xf0 12 | ori 0x03 13 | xori 0xff 14 | subi 0xcc 15 | exit 16 | -------------------------------------------------------------------------------- /asm/aluop.asm: -------------------------------------------------------------------------------- 1 | # 2 | # Register ALU operations 3 | # 4 | 5 | ldi 0x12 6 | st r1 7 | ldi 0x34 8 | st r2 9 | ldi 0 10 | add r1 11 | add r2 12 | # now it is 0x46 13 | st r15 14 | or r2 # 0x76 15 | st r14 16 | ldi 0xf0 17 | st r13 18 | ld r14 # 0x76 19 | and r13 # 0x70 20 | addi 0x0f # 0x7f 21 | xor r1 # 0x6d 22 | subi 0x6d 23 | add r15 24 | subi 0x46 25 | exit 26 | -------------------------------------------------------------------------------- /asm/blink.asm: -------------------------------------------------------------------------------- 1 | # 2 | # A blinking and counting LED as hello world 3 | # 4 | 5 | ldi 0x00 6 | st r1 7 | outer: 8 | ld r1 9 | addi 0x01 10 | st r1 11 | io 0x0 12 | 13 | ldi 0x7f 14 | st r3 15 | loop3: 16 | ldi 0xff 17 | st r2 18 | loop2: 19 | ldi 0xff 20 | loop: 21 | subi 0x01 22 | brnz loop 23 | ld r2 24 | subi 0x01 25 | st r2 26 | brnz loop2 27 | ld r3 28 | subi 0x01 29 | st r3 30 | brnz loop3 31 | 32 | br outer 33 | -------------------------------------------------------------------------------- /asm/branch.asm: -------------------------------------------------------------------------------- 1 | # 2 | # branch test program 3 | # 4 | 5 | ldi 0x73 6 | br dest 7 | ldi 0x34 8 | dest: 9 | subi 0x73 10 | st r1 11 | brz dozbr 12 | ldi 0x11 13 | dozbr: 14 | add r1 15 | st r1 16 | ldi 0x23 17 | brz dontzbr 18 | subi 0x23 19 | dontzbr: 20 | add r1 21 | st r1 22 | ldi 0x55 23 | brnz donzbr 24 | addi 0x11 25 | donzbr: 26 | subi 0x55 27 | add r1 28 | st r1 29 | brnz dontnzbr 30 | ldi 0x88 31 | dontnzbr: 32 | subi 0x88 33 | add r1 34 | exit 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Make Lipsi 2 | 3 | SBT = sbt 4 | 5 | # the program 6 | APP ?= asm/test.asm 7 | 8 | test: 9 | $(SBT) "test:runMain lipsi.LipsiTester $(APP)" 10 | 11 | hw: 12 | $(SBT) "runMain lipsi.LipsiMain $(APP)" 13 | 14 | sim: 15 | $(SBT) "test:runMain lipsi.sim.LipsiSim $(APP)" 16 | 17 | cosim: 18 | $(SBT) "test:runMain lipsi.LipsiCoSim $(APP)" 19 | 20 | wave: 21 | gtkwave generated/Lipsi.vcd Lipsi.gtkw 22 | 23 | test-all: 24 | make test APP=asm/immop.asm 25 | make test APP=asm/aluop.asm 26 | make test APP=asm/ldstind.asm 27 | make test APP=asm/branch.asm 28 | 29 | test-cosim: 30 | make cosim APP=asm/immop.asm 31 | make cosim APP=asm/aluop.asm 32 | make cosim APP=asm/ldstind.asm 33 | make cosim APP=asm/branch.asm 34 | 35 | # Danger zone, removes all unversioned files 36 | # Including the Eclipse project fiels generated with "sbt ecplipse" 37 | clean: 38 | git clean -fd 39 | -------------------------------------------------------------------------------- /Lipsi.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.73 (w)1999-2016 BSI 3 | [*] Tue Nov 14 12:06:46 2017 4 | [*] 5 | [dumpfile] "./generated/Lipsi.vcd" 6 | [dumpfile_mtime] "Tue Nov 14 12:06:17 2017" 7 | [dumpfile_size] 1562 8 | [savefile] "./Lipsi.gtkw" 9 | [timestart] 0 10 | [size] 1000 600 11 | [pos] -1 -1 12 | *-3.048199 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] Lipsi. 14 | [sst_width] 193 15 | [signals_width] 150 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 168 18 | @28 19 | Lipsi.clk 20 | Lipsi.reset 21 | @22 22 | Lipsi.pcReg[7:0] 23 | @28 24 | Lipsi.exitReg 25 | @22 26 | Lipsi.accuReg[7:0] 27 | @28 28 | Lipsi.funcReg[2:0] 29 | Lipsi.enaAccuReg 30 | @200 31 | -Memory 32 | @22 33 | Lipsi.mem.rdAddrReg[8:0] 34 | Lipsi.mem.io_rdAddr[8:0] 35 | Lipsi.mem.io_rdData[7:0] 36 | Lipsi.mem.io_wrAddr[8:0] 37 | Lipsi.mem.io_wrData[7:0] 38 | @28 39 | Lipsi.mem.io_wrEna 40 | @200 41 | -IO 42 | @22 43 | Lipsi.io_din[7:0] 44 | @23 45 | Lipsi.io_dout[7:0] 46 | [pattern_trace] 1 47 | [pattern_trace] 0 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, DTU 4 | Author: Martin Schoeberl 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /src/test/scala/lipsi/LipsiTester.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: 2017, Technical University of Denmark, DTU Compute 3 | * Author: Martin Schoeberl (martin@jopdesign.com) 4 | * License: Simplified BSD License 5 | * 6 | * Test Lipsi. 7 | */ 8 | 9 | package lipsi 10 | 11 | 12 | import chisel3._ 13 | import chisel3.iotesters.PeekPokeTester 14 | 15 | class LipsiTester(dut: Lipsi) extends PeekPokeTester(dut) { 16 | if (dut.debug == false) { 17 | println(s"Lipsi must be built in debug mode") 18 | fail 19 | } 20 | else { 21 | var run = true 22 | var maxInstructions = 30 23 | while(run) { 24 | peek(dut.io.dbg.get.pc) 25 | peek(dut.io.dbg.get.accu) 26 | // peek(dut.mem.io.rdAddr) 27 | // peek(dut.stateReg) possible in Chisel 2 28 | step(1) 29 | maxInstructions -= 1 30 | run = peek(dut.io.dbg.get.exit) == 0 && maxInstructions > 0 31 | // poke(dut.io.din, maxInstructions) 32 | } 33 | expect(dut.io.dbg.get.accu, 0, "Accu shall be zero at the end of a test case.\n") 34 | } 35 | } 36 | 37 | object LipsiTester { 38 | def main(args: Array[String]): Unit = { 39 | println("Testing Lipsi") 40 | iotesters.Driver.execute( 41 | Array("--generate-vcd-output", "on", 42 | "--target-dir", "generated", 43 | "--top-name", "Lipsi"), 44 | () => new Lipsi(args(0), debug = false)) { 45 | c => new LipsiTester(c) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/lipsi/Memory.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: 2017, Technical University of Denmark, DTU Compute 3 | * Author: Martin Schoeberl (martin@jopdesign.com) 4 | * License: Simplified BSD License 5 | * 6 | * Lipsi, a very minimalistic processor. 7 | */ 8 | 9 | package lipsi 10 | 11 | import chisel3._ 12 | import lipsi.util._ 13 | 14 | /** 15 | * The memory for Lipsi. 16 | * 17 | * 256 byte instructions and 256 bytes data, using exactly one FPGA memory block 18 | * with preinitialized data. 19 | * 20 | * As we cannot express initialized memory in Chisel (yet) we have a multiplexer between 21 | * memory and the instruction ROM table. Shall be substituted by a BlackBox and generated VHDL or Verilog. 22 | */ 23 | class Memory(prog: String, size: Int) extends Module { 24 | val io = IO(new Bundle { 25 | val rdAddr = Input(UInt(9.W)) 26 | val rdData = Output(UInt(8.W)) 27 | val wrEna = Input(Bool()) 28 | val wrData = Input(UInt(8.W)) 29 | val wrAddr = Input(UInt(9.W)) 30 | }) 31 | 32 | val regPC = RegInit(0.U(8.W)) 33 | val rdAddrReg = RegInit(0.U(9.W)) 34 | rdAddrReg := io.rdAddr 35 | 36 | val program = VecInit(Assembler.getProgram(prog).map(_.U)) 37 | val instr = program(rdAddrReg(7, 0)) 38 | 39 | /* Chisel 2 val mem = Mem(UInt(width = 8), 256, seqRead = true) */ 40 | val mem = Mem(size, UInt(8.W)) 41 | val data = mem(rdAddrReg(7, 0)) 42 | when(io.wrEna) { 43 | mem(io.wrAddr) := io.wrData 44 | } 45 | 46 | // Output MUX for now 47 | io.rdData := Mux(rdAddrReg(8), data, instr) 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/lipsi/LipsiCoSim.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: 2017, Technical University of Denmark, DTU Compute 3 | * Author: Martin Schoeberl (martin@jopdesign.com) 4 | * License: Simplified BSD License 5 | * 6 | * Test Lipsi. 7 | */ 8 | 9 | package lipsi 10 | 11 | import lipsi.sim._ 12 | import chisel3._ 13 | import chisel3.iotesters.PeekPokeTester 14 | 15 | class LipsiCoSim(dut: Lipsi, arg0: String) extends PeekPokeTester(dut) { 16 | 17 | if (dut.debug == false) { 18 | println("Lipsi needs to be built in debug mode") 19 | fail 20 | } 21 | else { 22 | val lsim = new LipsiSim(arg0) 23 | 24 | var run = true 25 | var maxInstructions = 30 26 | while(run) { 27 | 28 | expect(dut.io.dbg.get.pc, lsim.pc, "PC shall be equal.\n") 29 | expect(dut.io.dbg.get.accu, lsim.accu, "Accu shall be equal.\n") 30 | 31 | step(1) 32 | lsim.step() 33 | maxInstructions -= 1 34 | run = peek(dut.io.dbg.get.exit) == 0 && maxInstructions > 0 35 | } 36 | expect(dut.io.dbg.get.accu, 0, "Accu shall be zero at the end of a test case.\n") 37 | } 38 | } 39 | 40 | object LipsiCoSim { 41 | def main(args: Array[String]): Unit = { 42 | println("Co-simulation of Lipsi") 43 | iotesters.Driver.execute(Array[String](), () => new Lipsi(args(0), debug = false)) { 44 | c => new LipsiCoSim(c, args(0)) 45 | } 46 | 47 | /* Chisel 2 48 | chiselMainTest(Array("--genHarness", "--test", "--backend", "c", 49 | "--compile", "--vcd", "--targetDir", "generated"), 50 | () => Module(new Lipsi(args(0)))) { 51 | f => new LipsiCoSim(f, args(0)) 52 | } 53 | */ 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/lipsi/sim/LipsiSim.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: 2017, Technical University of Denmark, DTU Compute 3 | * Author: Martin Schoeberl (martin@jopdesign.com) 4 | * License: Simplified BSD License 5 | * 6 | * A software simulator of Lipsi, a very tiny processor. 7 | */ 8 | 9 | package lipsi.sim 10 | 11 | import lipsi.util._ 12 | 13 | class LipsiSim(asm: String) { 14 | 15 | val prog = Assembler.getProgram(asm) 16 | // The complete processor state. 17 | // We ignore for now using Int instead of bytes. 18 | // We will mask out the bits when it matters. 19 | var pc = 0 20 | var accu = 0 21 | var mem = new Array[Int](512) 22 | 23 | var accuNext = 0 24 | var delayOne = false 25 | var delayUpdate = false 26 | var delayTwoUpdate = false 27 | var noPcIncr = false 28 | var doBranch = false 29 | 30 | var run = true 31 | 32 | for (i <- 0 until prog.length) mem(i) = prog(i) 33 | 34 | def alu(func: Int, op: Int): Int = { 35 | func match { 36 | case 0x0 => accu + op 37 | case 0x1 => accu - op 38 | case 0x2 => accu + op // TODO: carry 39 | case 0x3 => accu - op 40 | case 0x4 => accu & op 41 | case 0x5 => accu | op 42 | case 0x6 => accu ^ op 43 | case 0x7 => op 44 | } 45 | } 46 | 47 | def step(): Unit = { 48 | 49 | if (delayOne) { 50 | delayOne = false 51 | } else if (delayUpdate) { 52 | accu = accuNext 53 | delayUpdate = false 54 | } else if (delayTwoUpdate) { 55 | delayUpdate = true 56 | noPcIncr = true 57 | delayTwoUpdate = false 58 | } else { 59 | val instr = mem(pc) 60 | if ((instr & 0x80) == 0) { 61 | accuNext = alu((instr >> 4) & 0xf, mem((instr & 0x0f) + 256)) 62 | delayUpdate = true 63 | noPcIncr = true 64 | } else { 65 | ((instr >> 4) & 0x7) match { 66 | case 0x0 => mem((instr & 0x0f) + 256) = accu 67 | case 0x1 => 68 | case 0x2 => { 69 | accuNext = mem(mem((instr & 0x0f) + 256) + 256) 70 | delayTwoUpdate = true 71 | noPcIncr = true 72 | } 73 | case 0x3 => { 74 | mem(mem((instr & 0x0f) + 256) + 256) = accu 75 | delayOne = true 76 | noPcIncr = true 77 | } 78 | case 0x4 => { 79 | accuNext = alu(instr & 0x07, mem(pc + 1)) 80 | delayUpdate = true 81 | } 82 | case 0x5 => { 83 | doBranch = (instr & 0x03) match { 84 | case 0x0 => true 85 | case 0x2 => accu == 0 86 | case 0x3 => accu != 0 87 | } 88 | delayOne = true 89 | } 90 | case 0x6 => 91 | case 0x7 => 92 | } 93 | } 94 | 95 | } 96 | 97 | if (noPcIncr) { 98 | noPcIncr = false 99 | } else { 100 | if (doBranch && !delayOne) { 101 | pc = mem(pc) 102 | doBranch = false 103 | } else { 104 | pc += 1 105 | } 106 | } 107 | run = pc < prog.length 108 | } 109 | } 110 | 111 | object LipsiSim extends App { 112 | 113 | val lsim = new LipsiSim(args(0)) 114 | 115 | while (lsim.run) { 116 | lsim.step 117 | printf("(pc:0x%02x 0x%02x) ", lsim.pc, lsim.accu) 118 | } 119 | println 120 | 121 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lipsi: Probably the Smallest Processor in the World 2 | 3 | This repository contains the source code of Lipsi and supports following paper: 4 | 5 | *Martin Schoeberl, 6 | [Lipsi: Probably the Smallest Processor in the World](https://www.jopdesign.com/doc/lipsi.pdf), 7 | Architecture of Computing Systems -- ARCS 2018, 8 | Springer International Publishing, 2018, 18-30* 9 | 10 | While research on high-performance processors is important, it is also interesting to explore processor architectures at the other end of the spectrum: tiny processor cores for auxiliary tasks. While it is common to implement small circuits for auxiliary duties, such as a serial port, in dedicated hardware, usually as a state machine or a combination of communicating state machines, these functionalities may also be implemented by a small processor. In this paper we present Lipsi a very tiny processor to enable implementing classic finite state machine logic in software without overhead. 11 | 12 | Lipsi is probably the smallest processor around. Possible evaluations of Lipsi: (1) an implementation of a serial port completely in software; (2) as Lipsi is so small we can explore a massive parallel multicore processor consisting of more than 100 Lipsi cores in a low-cost FPGA (with simple point-to-point connections between cores). 13 | 14 | Lipsi is written in [Chisel](https://chisel.eecs.berkeley.edu/) and contains: 15 | (1) the hardware description, (2) an assembler, (3) a software simulator, and 16 | (4) testers for individual testing and for co-simulation, all written in 17 | Chisel/Scala and combined in a single program. 18 | Chisel made it possible that the design of all of the above took less than 19 | [14 hours](log.md). 20 | 21 | ## Tapeout 22 | 23 | The Lipsi processor is beeing taped out [with Tiny Tapeout](https://github.com/schoeberl/tt06-lipsi). 24 | See a rendering of the actual [GDS II](https://schoeberl.github.io/tt06-lipsi/) file. 25 | 26 | ## Getting Started 27 | 28 | You need `sbt` and a `JVM` installed. Scala and Chisel are downloaded when 29 | first used. 30 | 31 | A plain 32 | ```bash 33 | make 34 | ``` 35 | runs the default program as a test. 36 | The wave form can then be viewed with: 37 | ``` 38 | make wave 39 | ``` 40 | The default program can be overwritten with the variable `APP`: 41 | ``` 42 | make APP=asm/immop.asm 43 | ``` 44 | 45 | Lipsi executing the embedded hello world program, blinking and counting LEDs, can be 46 | generated as follows: 47 | ``` 48 | make hw APP=asm/blink.asm 49 | ``` 50 | The project contains a Quartus project in folder `quartus`. 51 | 52 | All test cases are run with: 53 | 54 | ``` 55 | make test-all 56 | ``` 57 | The SW simulator of Lipsi is run with: 58 | ``` 59 | make sim 60 | ``` 61 | 62 | The co-simulation (for all tests) with the processor description in hardware and 63 | the SW simulator are run with: 64 | ``` 65 | make test-cosim 66 | ``` 67 | 68 | Folder `asm` contains various assembler program. E.g., `echo.asm` reads the keys from 69 | the FPGA board, adds 1, and puts out the result on the LEDs (on the DE2-115). 70 | Default IO devices are an 8-bit input port connected to the keys and 8-bit output 71 | port connected to the LEDs 72 | 73 | To build a 432 cores manycore version of Lipsi, change the value `many` to 74 | `val many = true` in `LipsiTop`. The cores are then connected in a pipeline. 75 | The `echo.asm` program can be used to execute 432 additions and show the result 76 | on the LEDs. 77 | 78 | As usual, have fun and feedback is appreciated, 79 | 80 | Martin 81 | -------------------------------------------------------------------------------- /quartus/lipsi.qsf: -------------------------------------------------------------------------------- 1 | 2 | set_global_assignment -name FAMILY "Cyclone IV E" 3 | set_global_assignment -name DEVICE EP4CE115F29C7 4 | 5 | set_global_assignment -name TOP_LEVEL_ENTITY LipsiTop 6 | set_global_assignment -name VERILOG_FILE ../generated/LipsiTop.v 7 | set_global_assignment -name VERILOG_MACRO "SYNTHESIS=" 8 | 9 | set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top 10 | set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top 11 | set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top 12 | set_global_assignment -name USE_CONFIGURATION_DEVICE OFF 13 | set_global_assignment -name STRATIX_DEVICE_IO_STANDARD "3.3-V LVCMOS" 14 | 15 | 16 | set_location_assignment PIN_Y2 -to clk 17 | 18 | set_location_assignment PIN_G12 -to iUartPins_rxd 19 | set_location_assignment PIN_G9 -to oUartPins_txd 20 | 21 | set_location_assignment PIN_G19 -to io_dout[0] 22 | set_location_assignment PIN_F19 -to io_dout[1] 23 | set_location_assignment PIN_E19 -to io_dout[2] 24 | set_location_assignment PIN_F21 -to io_dout[3] 25 | set_location_assignment PIN_F18 -to io_dout[4] 26 | set_location_assignment PIN_E18 -to io_dout[5] 27 | set_location_assignment PIN_J19 -to io_dout[6] 28 | set_location_assignment PIN_H19 -to io_dout[7] 29 | set_location_assignment PIN_J17 -to io_dout[8] 30 | set_location_assignment PIN_G17 -to io_dout[9] 31 | 32 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[0] 33 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[1] 34 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[2] 35 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[3] 36 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[4] 37 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[5] 38 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[6] 39 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[7] 40 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[8] 41 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_dout[9] 42 | 43 | set_location_assignment PIN_AB28 -to io_din[0] 44 | set_location_assignment PIN_AC28 -to io_din[1] 45 | set_location_assignment PIN_AC27 -to io_din[2] 46 | set_location_assignment PIN_AD27 -to io_din[3] 47 | set_location_assignment PIN_AB27 -to io_din[4] 48 | set_location_assignment PIN_AC26 -to io_din[5] 49 | set_location_assignment PIN_AD26 -to io_din[6] 50 | set_location_assignment PIN_AB26 -to io_din[7] 51 | set_location_assignment PIN_AC25 -to io_din[8] 52 | set_location_assignment PIN_AB25 -to io_din[9] 53 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[0] 54 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[1] 55 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[2] 56 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[3] 57 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[4] 58 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[5] 59 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[6] 60 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[7] 61 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[8] 62 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_din[9] 63 | 64 | set_location_assignment PIN_M23 -to io_btn[0] 65 | set_location_assignment PIN_M21 -to io_btn[1] 66 | set_location_assignment PIN_N21 -to io_btn[2] 67 | set_location_assignment PIN_R24 -to reset 68 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_btn[0] 69 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_btn[1] 70 | set_instance_assignment -name IO_STANDARD "2.5 V" -to io_btn[2] 71 | set_instance_assignment -name IO_STANDARD "2.5 V" -to reset 72 | 73 | set_global_assignment -name LAST_QUARTUS_VERSION "16.1.0 Lite Edition" 74 | set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED WITH WEAK PULL-UP" 75 | 76 | 77 | 78 | set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top -------------------------------------------------------------------------------- /src/main/scala/lipsi/util/Assembler.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: 2017, Technical University of Denmark, DTU Compute 3 | * Author: Martin Schoeberl (martin@jopdesign.com) 4 | * License: Simplified BSD License 5 | */ 6 | 7 | package lipsi.util 8 | 9 | import scala.io.Source 10 | //import Chisel._ 11 | 12 | object Assembler { 13 | 14 | val prog = Array[Int]( 15 | 0xc7, 0x12, // ldi 0x12 16 | 0xc0, 0x34, // addi 0x34 17 | 0xc1, 0x12, // subi 0x12 18 | 0xc4, 0xf0, // andi 0xf0 19 | 0xc5, 0x03, // ori 0x03 20 | 0xc6, 0xff, // xori 0xff 21 | 0x82, // st r2 22 | 0x00) 23 | 24 | // collect destination addresses in first pass 25 | val symbols = collection.mutable.Map[String, Int]() 26 | 27 | def getProgramFix() = prog 28 | 29 | def getProgram(prog: String) = assemble(prog) 30 | 31 | def assemble(prog: String): Array[Int] = { 32 | assemble(prog, false) 33 | assemble(prog, true) 34 | } 35 | 36 | def assemble(prog: String, pass2: Boolean): Array[Int] = { 37 | 38 | val source = Source.fromFile(prog) 39 | var program = List[Int]() 40 | var pc = 0 41 | 42 | def toInt(s: String): Int = { 43 | if (s.startsWith("0x")) { 44 | Integer.parseInt(s.substring(2), 16) 45 | } else { 46 | Integer.parseInt(s) 47 | } 48 | } 49 | 50 | def regNumber(s: String): Int = { 51 | assert(s.startsWith("r")) 52 | s.substring(1).toInt 53 | } 54 | 55 | def regIndirect(s: String): Int = { 56 | assert(s.startsWith("(r")) 57 | assert(s.endsWith(")")) 58 | s.substring(2, s.length - 1).toInt 59 | } 60 | 61 | for (line <- source.getLines()) { 62 | if (!pass2) println(line) 63 | val tokens = line.trim.split(" ") 64 | // println(s"length: ${tokens.length}") 65 | // tokens.foreach(println) 66 | val Pattern = "(.*:)".r 67 | val instr = tokens(0) match { 68 | case "#" => // comment 69 | case Pattern(l) => if (!pass2) symbols += (l.substring(0, l.length - 1) -> pc) 70 | case "add" => 0x00 + regNumber(tokens(1)) 71 | case "sub" => 0x10 + regNumber(tokens(1)) 72 | case "adc" => 0x20 + regNumber(tokens(1)) 73 | case "sbb" => 0x30 + regNumber(tokens(1)) 74 | case "and" => 0x40 + regNumber(tokens(1)) 75 | case "or" => 0x50 + regNumber(tokens(1)) 76 | case "xor" => 0x60 + regNumber(tokens(1)) 77 | case "ld" => 0x70 + regNumber(tokens(1)) 78 | case "addi" => (0xc0, toInt(tokens(1))) 79 | case "subi" => (0xc1, toInt(tokens(1))) 80 | case "adci" => (0xc2, toInt(tokens(1))) 81 | case "sbbi" => (0xc3, toInt(tokens(1))) 82 | case "andi" => (0xc4, toInt(tokens(1))) 83 | case "ori" => (0xc5, toInt(tokens(1))) 84 | case "xori" => (0xc6, toInt(tokens(1))) 85 | case "ldi" => (0xc7, toInt(tokens(1))) 86 | case "st" => 0x80 + regNumber(tokens(1)) 87 | case "ldind" => 0xa0 + regIndirect(tokens(1)) 88 | case "stind" => 0xb0 + regIndirect(tokens(1)) 89 | case "br" => (0xd0, if (pass2) symbols(tokens(1)) else 0) 90 | case "brz" => (0xd2, if (pass2) symbols(tokens(1)) else 0) 91 | case "brnz" => (0xd3, if (pass2) symbols(tokens(1)) else 0) 92 | case "io" => 0xf0 + toInt(tokens(1)) 93 | case "exit" => (0xff) 94 | case "" => // println("Empty line") 95 | case t: String => throw new Exception("Assembler error: unknown instruction") 96 | case _ => throw new Exception("Assembler error") 97 | } 98 | // println(instr) 99 | 100 | instr match { 101 | case (a: Int) => { 102 | program = a :: program 103 | pc += 1 104 | } 105 | case (a: Int, b: Int) => { 106 | program = a :: program 107 | program = b :: program 108 | pc += 2 109 | } 110 | case _ => // println("Something else") 111 | } 112 | } 113 | val finalProg = program.reverse.toArray 114 | if (!pass2) { 115 | println(s"The program:") 116 | finalProg.foreach(printf("0x%02x ", _)) 117 | println() 118 | } 119 | finalProg 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/scala/lipsi/Lipsi.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: 2017, Technical University of Denmark, DTU Compute 3 | * Author: Martin Schoeberl (martin@jopdesign.com) 4 | * License: Simplified BSD License 5 | * 6 | * Lipsi, a very tiny processor. 7 | */ 8 | 9 | package lipsi 10 | 11 | import chisel3._ 12 | import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} 13 | import chisel3.util._ 14 | 15 | /* 16 | 17 | Instruction encoding (plus timing): 18 | 19 | 0fff rrrr ALU register (2 cc) 20 | 1000 rrrr st rx (1 cc) 21 | 1001 rrrr brl rx 22 | 1010 rrrr ldind (rx) (3 cc) 23 | 1011 rrrr stind (rx) (2 cc) 24 | 1100 -fff + nnnn nnnn ALU imm (2 cc) 25 | 1101 --00 + aaaa aaaa br (2 cc) 26 | 1101 --10 + aaaa aaaa brz 27 | 1101 --11 + aaaa aaaa brnz 28 | 1110 --ff ALU shift 29 | 1111 aaaa IO (1 cc) 30 | 1111 1111 exit for the tester 31 | 32 | ALU function: 33 | 34 | add, sub, adc, sbb, and, or, xor, ld 35 | 36 | */ 37 | 38 | class DebugData extends Bundle { 39 | val pc = UInt(8.W) 40 | val accu = UInt(8.W) 41 | val exit = Bool() 42 | } 43 | 44 | class Lipsi(prog: String, val memSize: Int = 256, val debug: Boolean = false ) extends Module { 45 | val io = IO(new Bundle { 46 | val dout = Output(UInt(8.W)) 47 | val din = Input(UInt(8.W)) 48 | val dbg = if (debug) Some(Output(new DebugData)) else None 49 | }) 50 | 51 | 52 | 53 | val pcReg = RegInit(0.U(8.W)) 54 | val accuReg = RegInit(0.U(8.W)) 55 | val enaAccuReg = RegInit(false.B) 56 | 57 | val enaPcReg = RegInit(false.B) 58 | 59 | val funcReg = RegInit(0.U(3.W)) 60 | // debug(funcReg) Chisel 2 61 | 62 | // IO register 63 | val outReg = RegInit(0.U(8.W)) 64 | val enaIoReg = RegInit(false.B) 65 | 66 | val mem = Module(new Memory(prog, memSize)) 67 | 68 | // val selPC = Bool(true) 69 | // val selData = Bool(false) 70 | 71 | val rdData = mem.io.rdData 72 | 73 | // the following is used? 74 | val regInstr = RegNext(rdData) 75 | 76 | // val rdAddr = Mux(selPC, Cat(UInt(0, 1), regPC + UInt(1)), 77 | // Cat(UInt(1, 1), Mux(selData, rdData, regA))) 78 | 79 | // Do we need a support of storing the PC? 80 | // Probably, but it should be simple into a fixed register (15)) 81 | val isCall = false.B 82 | 83 | val wrEna = Wire(Bool()) 84 | val wrAddr = Wire(UInt()) 85 | val rdAddr = Wire(UInt()) 86 | val updPC = Wire(Bool()) 87 | 88 | mem.io.rdAddr := rdAddr 89 | mem.io.wrAddr := Cat(1.U(1.W), wrAddr(7, 0)) 90 | mem.io.wrData := Mux(isCall, pcReg, accuReg) 91 | mem.io.wrEna := wrEna 92 | 93 | val nextPC = Wire(UInt()) 94 | // defaults 95 | wrEna := false.B 96 | wrAddr := rdData 97 | rdAddr := Cat(0.U(1.W), nextPC) 98 | updPC := true.B 99 | nextPC := pcReg + 1.U 100 | 101 | when(enaPcReg) { 102 | nextPC := rdData 103 | } 104 | when(updPC) { 105 | pcReg := nextPC 106 | } 107 | 108 | val fetch :: execute :: stind :: ldind1 :: ldind2 :: exit :: Nil = Enum(6) 109 | val stateReg = RegInit(fetch) 110 | // debug(stateReg) 111 | 112 | val exitReg = RegInit(false.B) 113 | // debug(exitReg) Chisel 2 114 | 115 | val accuZero = accuReg === 0.U 116 | 117 | val doBranch = (rdData(1, 0) === 0.U) || 118 | ((rdData(1, 0) === 2.U) && accuZero) || 119 | ((rdData(1, 0) === 3.U) && !accuZero) 120 | 121 | enaAccuReg := false.B 122 | enaPcReg := false.B 123 | enaIoReg := false.B 124 | 125 | // debug(enaAccuReg) Chisel 2 126 | switch(stateReg) { 127 | is(fetch) { 128 | stateReg := execute 129 | funcReg := rdData(6, 4) 130 | // ALU register 131 | when(rdData(7) === 0.U) { 132 | updPC := false.B 133 | funcReg := rdData(6, 4) 134 | enaAccuReg := true.B 135 | rdAddr := Cat(0x10.U, rdData(3, 0)) 136 | } 137 | // st rx, is just a single cycle 138 | when(rdData(7, 4) === 0x8.U) { 139 | wrAddr := Cat(0.U, rdData(3, 0)) 140 | wrEna := true.B 141 | stateReg := fetch 142 | } 143 | // ldind 144 | when(rdData(7, 4) === 0xa.U) { 145 | updPC := false.B 146 | rdAddr := Cat(0x10.U, rdData(3, 0)) 147 | stateReg := ldind1 148 | } 149 | // stind 150 | when(rdData(7, 4) === 0xb.U) { 151 | updPC := false.B 152 | rdAddr := Cat(0x10.U, rdData(3, 0)) 153 | stateReg := stind 154 | } 155 | // ALU imm 156 | when(rdData(7, 4) === 0xc.U) { 157 | funcReg := rdData(2, 0) 158 | enaAccuReg := true.B 159 | } 160 | // Branch 161 | when(rdData(7, 4) === 0xd.U) { 162 | when(doBranch) { 163 | enaPcReg := true.B 164 | } 165 | } 166 | // IO 167 | when(rdData === 0xf0.U) { 168 | outReg := accuReg 169 | enaIoReg := true.B 170 | stateReg := fetch 171 | } 172 | // exit (for the tester) 173 | when(rdData === 0xff.U) { 174 | stateReg := exit 175 | } 176 | } 177 | is(stind) { 178 | wrEna := true.B 179 | stateReg := fetch 180 | } 181 | is(execute) { 182 | stateReg := fetch 183 | } 184 | is(ldind1) { 185 | updPC := false.B 186 | funcReg := 7.U 187 | enaAccuReg := true.B 188 | rdAddr := Cat(0x1.U, rdData) 189 | stateReg := ldind2 190 | } 191 | is(ldind2) { 192 | stateReg := fetch 193 | } 194 | is(exit) { 195 | exitReg := true.B 196 | } 197 | } 198 | 199 | val op = rdData 200 | val res = Wire(UInt()) 201 | res := 0.U(8.W) 202 | 203 | val add :: sub :: adc :: sbb :: and :: or :: xor :: ld :: Nil = Enum(8) 204 | switch(funcReg) { 205 | is(add) { res := accuReg + op } 206 | is(sub) { res := accuReg - op } 207 | is(adc) { res := accuReg + op } // TODO: adc 208 | is(sbb) { res := accuReg - op } // TODO: sbb 209 | is(and) { res := accuReg & op } 210 | is(or) { res := accuReg | op } 211 | is(xor) { res := accuReg ^ op } 212 | is(ld) { res := op } 213 | } 214 | when(enaAccuReg) { 215 | accuReg := res 216 | } 217 | when(enaIoReg) { 218 | accuReg := io.din 219 | } 220 | 221 | io.dout := outReg 222 | if (debug) { 223 | io.dbg.get.accu := accuReg 224 | io.dbg.get.pc := pcReg 225 | io.dbg.get.exit := exitReg 226 | } 227 | 228 | } 229 | 230 | class LipsiTop(prog: String, val debug : Boolean = false) extends Module { 231 | val io = IO(new Bundle { 232 | val dout = Output(UInt(8.W)) 233 | val din = Input(UInt(8.W)) 234 | val dbg = if (debug) Some(Output(new DebugData)) else None 235 | }) 236 | 237 | 238 | 239 | val x = Wire(Bool()) 240 | x := RegNext(reset) 241 | val y = Wire(Bool()) 242 | y := !x 243 | 244 | val resetRegs = RegNext(y) 245 | 246 | // val resetRegs = RegNext(RegNext(reset)) 247 | 248 | // val resetRegs = RegNext(!RegNext(reset)) 249 | 250 | // val resetRegs = reset 251 | 252 | val many = false 253 | val N = 432 254 | 255 | if (many) { 256 | val lipsis = new Array[Lipsi](N) 257 | for (i <- 0 until N) { 258 | lipsis(i) = Module(new Lipsi(prog)) 259 | lipsis(i).reset := resetRegs 260 | } 261 | lipsis(0).io.din := io.din 262 | io.dout := lipsis(N - 1).io.dout 263 | for (i <- 1 until N) lipsis(i).io.din := lipsis(i - 1).io.dout 264 | 265 | } else { 266 | val lipsi = Module(new Lipsi(prog)) 267 | 268 | if (debug) { 269 | io.dbg.get <> lipsi.io.dbg.get 270 | } 271 | 272 | 273 | lipsi.reset := resetRegs 274 | io.dout <> lipsi.io.dout 275 | io.din <> lipsi.io.din 276 | } 277 | } 278 | 279 | object LipsiMain { 280 | def main(args: Array[String]): Unit = { 281 | println("Generating the Lipsi hardware") 282 | (new chisel3.stage.ChiselStage).execute(Array("--target-dir", "generated"), 283 | Seq(ChiselGeneratorAnnotation(() => new LipsiTop(args(0))))) 284 | /* Chisel 2 285 | chiselMain(Array("--backend", "v", "--targetDir", "generated"), 286 | () => Module(new LipsiTop(args(0)))) 287 | */ 288 | } 289 | } 290 | --------------------------------------------------------------------------------