├── cocotb ├── gcd_tb │ ├── .gitignore │ ├── Makefile │ └── gcd_tb.py ├── neuromorphic_tb │ ├── mapping │ ├── ClockBufferBB.sv │ ├── Makefile │ └── neuromorphic_processor_tb.py ├── pyproject.toml └── poetry.lock ├── vlog ├── NeuromorphicProcessor │ ├── mapping │ ├── Makefile │ └── NeuromorphicProcessor_tb.sv └── DecoupledGcd │ ├── Makefile │ └── DecoupledGcd_tb.sv ├── mapping ├── test ├── resources │ └── NeuromorphicProcessor │ │ ├── mapping │ │ └── meminit │ │ │ ├── constc2.mem │ │ │ ├── constc3.mem │ │ │ ├── potrefc2.mem │ │ │ ├── potrefc3.mem │ │ │ ├── biasthreshc2e0.mem │ │ │ ├── biasthreshc2e1.mem │ │ │ ├── biasthreshc2e2.mem │ │ │ ├── biasthreshc2e3.mem │ │ │ ├── biasthreshc2e4.mem │ │ │ ├── biasthreshc2e5.mem │ │ │ ├── biasthreshc2e6.mem │ │ │ ├── biasthreshc2e7.mem │ │ │ ├── biasthreshc3e0.mem │ │ │ ├── biasthreshc3e1.mem │ │ │ ├── biasthreshc3e2.mem │ │ │ ├── biasthreshc3e3.mem │ │ │ ├── biasthreshc3e4.mem │ │ │ ├── biasthreshc3e5.mem │ │ │ ├── biasthreshc3e6.mem │ │ │ └── biasthreshc3e7.mem │ │ ├── ClockBufferBB.v │ │ ├── results_round.txt │ │ ├── results.txt │ │ ├── results_toInt.txt │ │ ├── image2sv │ │ ├── README.md │ │ ├── image.txt │ │ ├── results_round.sv │ │ └── image.sv ├── benchmarks │ ├── package.scala │ ├── NeuromorphicProcessorTester.scala │ ├── NeuromorphicProcessorCommandTester.scala │ ├── NeuromorphicProcessorChiseltestTester.scala │ ├── NeuromorphicProcessorManualThreadTester.scala │ ├── NeuromorphicProcessorRawSimulatorTester.scala │ └── DecoupledGcdChiseltestTester.scala └── simcommand │ ├── CombinatorialSpec.scala │ ├── DecoupledSpec.scala │ ├── PrimitivesSpec.scala │ ├── LibrarySpec.scala │ ├── ChannelSpec.scala │ ├── user │ └── ForkSpec.scala │ └── UARTCommandSpec.scala ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── src ├── benchmarks │ ├── NeuromorphicProcessor.scala │ └── DecoupledGcd.scala └── simcommand │ ├── SimcommandScalatestTester.scala │ ├── vips │ ├── Decoupled.scala │ └── UART.scala │ ├── runtime │ ├── Bridges.scala │ ├── Primitives.scala │ └── Interpreter.scala │ └── package.scala ├── LICENSE ├── mill └── README.md /cocotb/gcd_tb/.gitignore: -------------------------------------------------------------------------------- 1 | sim_build 2 | -------------------------------------------------------------------------------- /cocotb/neuromorphic_tb/mapping: -------------------------------------------------------------------------------- 1 | ../../mapping/ -------------------------------------------------------------------------------- /vlog/NeuromorphicProcessor/mapping: -------------------------------------------------------------------------------- 1 | ../../mapping/ -------------------------------------------------------------------------------- /mapping: -------------------------------------------------------------------------------- 1 | test/resources/NeuromorphicProcessor/mapping -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/constc2.mem: -------------------------------------------------------------------------------- 1 | 00101 2 | 00101 3 | 00111 4 | 00000 5 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/constc3.mem: -------------------------------------------------------------------------------- 1 | 01111 2 | 00101 3 | 00011 4 | 00000 5 | -------------------------------------------------------------------------------- /test/benchmarks/package.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import org.scalatest.Tag 4 | 5 | object IVerilog extends Tag("IVerilog") 6 | object Verilator extends Tag("Verilator") 7 | object VCS extends Tag("VCS") -------------------------------------------------------------------------------- /cocotb/neuromorphic_tb/ClockBufferBB.sv: -------------------------------------------------------------------------------- 1 | 2 | /* verilator lint_off UNOPTFLAT */ 3 | module ClockBufferBB(input I, input CE, output O); 4 | reg en_latched /*verilator clock_enable*/; 5 | always_latch @(*) if (!I) en_latched = CE; 6 | assign O = en_latched & I; 7 | endmodule 8 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/ClockBufferBB.v: -------------------------------------------------------------------------------- 1 | 2 | /* verilator lint_off UNOPTFLAT */ 3 | module ClockBufferBB(input I, input CE, output O); 4 | reg en_latched /*verilator clock_enable*/; 5 | always_latch if (!I) en_latched = CE; 6 | assign O = en_latched & I; 7 | endmodule 8 | 9 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/results_round.txt: -------------------------------------------------------------------------------- 1 | 23,22,21,20,31,30,29,28,27,26,25,24,39,38,37,36,35,60,42,55,51,48,63,101,100,104,114,158,164,50,58,85,82,88,85,93,88,85,93,88,85,93,88,88,85,93,85,93,85,93,88,85,93,88,85,93,88,85,93,88,85,93,88,93,88,85,93,88,85,93,88,93,85,88,85,93,88,93,85,88,85,88,85,88,20,85,93,88,20,85,93,88,20,85,93,88,20,85,93,88,20,85,93,88,93,20,85,88 -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/results.txt: -------------------------------------------------------------------------------- 1 | 23,22,21,20,31,30,29,28,27,26,25,24,39,38,37,36,35,60,42,55,51,48,63,101,100,104,114,158,164,50,58,82,50,58,82,93,50,58,82,93,50,58,82,93,50,58,82,93,50,58,82,93,50,58,82,93,50,58,82,93,50,58,82,93,50,58,82,93,93,50,58,82,82,50,58,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82,50,58,82 -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/results_toInt.txt: -------------------------------------------------------------------------------- 1 | 23,22,21,20,31,30,29,28,27,26,25,24,39,38,37,36,35,60,42,55,51,48,63,101,104,114,158,164,161,50,58,82,82,93,20,50,58,20,50,58,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,58,20,50,85,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,20,50,58,85,58,20,85,20,85,20,58,85,20,58,85,20,58,85,20,58,85,20,58,85 -------------------------------------------------------------------------------- /cocotb/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "neuromorphic-proc-testbench" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [] 6 | # readme = "README.md" 7 | packages = [ 8 | {include = "neuromorphic_tb"} 9 | ] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | cocotb = "1.6.2" 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /vlog/DecoupledGcd/Makefile: -------------------------------------------------------------------------------- 1 | default: DecoupledGcd.ivl DecoupledGcd.vcs 2 | 3 | IVERILOG = iverilog -g2012 4 | VCS = vcs -full64 -sverilog 5 | 6 | DUT = ../../test_run_dir/DecoupledGcd_should_work_with_Command_API_and_verilator/DecoupledGcd.sv 7 | 8 | DecoupledGcd.ivl: DecoupledGcd_tb.sv $(DUT) 9 | $(IVERILOG) -o $@ $^ 10 | 11 | DecoupledGcd.vcs: DecoupledGcd_tb.sv $(DUT) 12 | $(VCS) -o $@ $^ 13 | 14 | clean: 15 | rm -f DecoupledGcd.ivl 16 | rm -f DecoupledGcd.vcs ucli.key 17 | rm -rf csrc *.daidir 18 | rm -f *.vcd 19 | 20 | .PHONY: default clean 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: SimCommand CI 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Setup JDK 12 | uses: actions/setup-java@v3 13 | with: 14 | distribution: temurin 15 | java-version: 8 16 | - name: Install Verilator 17 | run: | 18 | sudo apt-get install -y verilator 19 | verilator --version 20 | - name: Build and Test 21 | run: ./mill simcommand.test -l IVerilog -l VCS -l org.scalatest.tags.Slow 22 | 23 | -------------------------------------------------------------------------------- /cocotb/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "cocotb" 3 | version = "1.6.2" 4 | description = "cocotb is a coroutine based cosimulation library for writing VHDL and Verilog testbenches in Python." 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [package.extras] 10 | bus = ["cocotb-bus"] 11 | 12 | [metadata] 13 | lock-version = "1.1" 14 | python-versions = "^3.8" 15 | content-hash = "2d109a0c010e3ed9c0e75a7661989951b699b6d27ccc89a98cfc5b7c76d10d4c" 16 | 17 | [metadata.files] 18 | cocotb = [ 19 | {file = "cocotb-1.6.2.tar.gz", hash = "sha256:498fb5ef6ec36d6320e829e61f0f24fd53f477005162cce7f98cb2bd95e0bd4b"}, 20 | ] 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .metals* 2 | .bloop* 3 | .bsp* 4 | .vscode* 5 | .idea* 6 | project/* 7 | !project/build.properties 8 | !project/plugins.sbt 9 | target/* 10 | *.hex 11 | *.mif 12 | test_run_dir/ 13 | build/ 14 | netwkdev/data* 15 | netwkdev/mnist* 16 | netwkdev/smnist* 17 | netwkdev/pretrained* 18 | netwkdev/kwsonsnn/__pycache__* 19 | domaintest* 20 | 21 | mapping/meminit 22 | mapping/networkData_fp.json 23 | cocotb/testbench/sim_build 24 | __pycache__ 25 | results.xml 26 | 27 | a.out 28 | *.vcd 29 | *.ivl 30 | *.vcs 31 | csrc 32 | *.daidir 33 | ucli.key 34 | *.vpd 35 | *.fsdb 36 | novas_dump.log 37 | .nfs* 38 | 39 | sim_build 40 | 41 | docs/* 42 | out/* 43 | .DS_Store 44 | -------------------------------------------------------------------------------- /cocotb/gcd_tb/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # defaults 4 | SIM ?= verilator 5 | TOPLEVEL_LANG ?= verilog 6 | 7 | VERILOG_SOURCES += $(PWD)/../../test_run_dir/DecoupledGcd_should_work_with_chiseltest_single_threaded_and_verilator/DecoupledGcd.sv 8 | # use VHDL_SOURCES for VHDL files 9 | COMPILE_ARGS += -DRANDOMIZE_REG_INIT -DRANDOMIZE_MEM_INIT 10 | EXTRA_ARGS += --trace --trace-structs 11 | 12 | # TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file 13 | TOPLEVEL = DecoupledGcd 14 | 15 | # MODULE is the basename of the Python test file 16 | MODULE = gcd_tb 17 | 18 | # include cocotb's make rules to take care of the simulator setup 19 | include $(shell cocotb-config --makefiles)/Makefile.sim 20 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/image2sv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | image_file = sys.argv[1] 6 | sv_file = sys.argv[2] 7 | name = sys.argv[3] 8 | 9 | with open(image_file, 'r') as f: 10 | image = f.readline() 11 | split = image.split(',') 12 | with open(sv_file, 'w') as sv: 13 | """ 14 | sv.write(f"logic [15:0] {name} [{len(split)}] = '{{\n") 15 | for i, pixel in enumerate(split): 16 | if i == len(split) - 1: 17 | sv.write(f" 'd{pixel}\n") 18 | else: 19 | sv.write(f" 'd{pixel},\n") 20 | sv.write("};") 21 | """ 22 | sv.write(f"logic [15:0] {name} [{len(split)}];\n") 23 | for i, pixel in enumerate(split): 24 | sv.write(f"assign {name}[{i}] = 'd{pixel};\n") 25 | 26 | -------------------------------------------------------------------------------- /src/benchmarks/NeuromorphicProcessor.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import chisel3._ 4 | import chisel3.util.HasBlackBoxResource 5 | 6 | class NeuromorphicProcessorBBWrapper extends Module { 7 | val io = IO(new Bundle { 8 | val io_uartTx = Output(Bool()) 9 | val io_uartRx = Input(Bool()) 10 | }) 11 | val bb = Module(new NeuromorphicProcessor()) 12 | bb.io.clock := clock 13 | bb.io.reset := reset 14 | io.io_uartTx := bb.io.io_uartTx 15 | bb.io.io_uartRx := io.io_uartRx 16 | } 17 | 18 | class NeuromorphicProcessor extends BlackBox with HasBlackBoxResource { 19 | val io = IO(new Bundle { 20 | val clock = Input(Clock()) 21 | val reset = Input(Bool()) 22 | val io_uartTx = Output(Bool()) 23 | val io_uartRx = Input(Bool()) 24 | }) 25 | addResource("NeuromorphicProcessor/NeuromorphicProcessor.sv") 26 | addResource("NeuromorphicProcessor/ClockBufferBB.v") 27 | } 28 | -------------------------------------------------------------------------------- /vlog/NeuromorphicProcessor/Makefile: -------------------------------------------------------------------------------- 1 | default: NeuromorphicProcessor.ivl NeuromorphicProcessor.vcs 2 | 3 | IVERILOG = iverilog -g2012 4 | VCS = vcs -full64 -sverilog 5 | 6 | DUT = ../../test_run_dir/NeuromorphicProcessorRawSimulatorTester/NeuromorphicProcessor.sv ../../test_run_dir/NeuromorphicProcessorRawSimulatorTester/ClockBufferBB.v 7 | TB = NeuromorphicProcessor_tb.sv 8 | 9 | NeuromorphicProcessor.ivl: $(TB) $(DUT) 10 | $(IVERILOG) -o $@ $^ 11 | 12 | NeuromorphicProcessor.vcs: $(TB) $(DUT) 13 | #$(VCS) +define+RANDOMIZE_REG_INIT +define+RANDOMIZE_MEM_INIT -debug_access -o $@ $^ 14 | #$(VCS) +vcs+initreg+random -debug_access -o $@ $^ 15 | $(VCS) +vcs+initreg+random -o $@ $^ 16 | 17 | run_vcs: NeuromorphicProcessor.vcs 18 | ./NeuromorphicProcessor.vcs +vcs+initreg+0 19 | 20 | clean: 21 | rm -f *.ivl 22 | rm -f *.vcs ucli.key 23 | rm -rf csrc *.daidir 24 | rm -f *.vcd 25 | 26 | .PHONY: default clean run_vcs 27 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/README.md: -------------------------------------------------------------------------------- 1 | This is a benchmark circuit of an SNN accelerator written in Chisel. 2 | The only top-level IOs are clock, reset, and a 2-wire UART. 3 | 4 | Build instructions (from https://github.com/ekiwi/simulator-independent-coverage/tree/main/benchmarks/NeuromorphicProcessor): 5 | 6 | ```shell 7 | git clone https://github.com/hansemandse/KWSonSNN 8 | git checkout f086afeee49a6155d5de13ebaba3ae432c683b7a 9 | cd KWSonSNN 10 | make proc 11 | sbt 12 | sbt:kwsonsnn> testOnly neuroproc.systemtests.NeuromorphicProcessorTester 13 | ``` 14 | 15 | Ctrl+C (the test takes about 30 minutes) 16 | 17 | Copy the Verilog sources from `test_run_dir/Neuromorphic_Processor_should_process_an_image/`. 18 | 19 | The Verilog also has inline `$readmemb` calls so you will need to copy `KWSonSNN/mapping/meminit` too. 20 | 21 | And finally you have to copy over the reference images `KWSonSNN/src/test/scala/neuroproc/systemtests/{image.txt, results_round.txt, results_toInt.txt` to `test/resources/NeuromorphicProcessor/.`. -------------------------------------------------------------------------------- /test/benchmarks/NeuromorphicProcessorTester.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import chiseltest._ 4 | import org.scalatest.flatspec.AnyFlatSpec 5 | 6 | import scala.io.Source 7 | 8 | abstract class NeuromorphicProcessorTester extends AnyFlatSpec with ChiselScalatestTester { 9 | behavior of s"Neuromorphic Processor" 10 | 11 | // Copied from https://github.com/hansemandse/KWSonSNN/blob/master/src/main/scala/neuroproc/package.scala#L6 12 | val FREQ = 80000000 // in Hz 13 | val BAUDRATE = 115200 14 | val USEROUNDEDWGHTS = true 15 | 16 | val bitDelay = FREQ / BAUDRATE + 1 17 | 18 | def fetch(resource: String) = { 19 | Source.fromResource(resource).getLines().mkString.split(",").map(_.toInt) 20 | } 21 | 22 | // Reference image and results 23 | val image = fetch("NeuromorphicProcessor/image.txt") 24 | val results = if (USEROUNDEDWGHTS) { 25 | fetch("NeuromorphicProcessor/results_round.txt") 26 | } else { 27 | fetch("NeuromorphicProcessor/results_toInt.txt") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/image.txt: -------------------------------------------------------------------------------- 1 | 0,0,0,0,0,0,0,0,0,0,500,49,36,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,221,15,9,30,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,42,12,14,117,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,18,9,31,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,8,15,332,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,10,10,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,9,10,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,10,9,57,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,125,47,43,17,9,9,42,0,0,0,0,0,0,0,0,0,0,0,0,500,91,25,14,11,10,9,8,8,11,15,16,36,0,0,0,0,0,0,0,500,91,16,11,9,8,8,8,8,8,8,8,8,8,10,41,0,0,0,0,0,500,50,14,8,8,8,8,8,8,8,8,10,10,11,10,13,50,0,0,0,0,0,166,15,8,8,8,8,8,8,8,8,10,38,80,91,69,166,0,0,0,0,0,0,181,15,8,8,8,8,8,8,8,9,18,221,0,0,0,0,0,0,0,0,0,0,221,16,8,8,8,8,8,8,10,17,71,0,0,0,0,0,0,0,0,0,0,0,199,16,9,8,8,9,10,16,54,500,500,0,0,0,0,0,0,0,0,0,0,0,0,74,23,17,17,28,51,332,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -------------------------------------------------------------------------------- /cocotb/neuromorphic_tb/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # defaults 4 | SIM ?= verilator 5 | TOPLEVEL_LANG ?= verilog 6 | 7 | VERILOG_SOURCES += $(PWD)/../../test_run_dir/Neuromorphic_Processor_should_process_an_image/NeuromorphicProcessor.sv \ 8 | $(PWD)/ClockBufferBB.sv 9 | # use VHDL_SOURCES for VHDL files 10 | # COMPILE_ARGS += -DRANDOMIZE_REG_INIT -DRANDOMIZE_MEM_INIT 11 | # ALERT!!!! 12 | # Verilator initializes all state to 0 by default 13 | # Chiseltest doesn't use Chisel randomization flags either 14 | # Therefore, setting these defines causes different behavior than we observe with chiseltest 15 | # The DUT uses some uninitialized state on purpose - it assumes it's being run on an FPGA where all state is forced to 0 at time 0 16 | #EXTRA_ARGS += --trace --trace-structs 17 | 18 | # TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file 19 | TOPLEVEL = NeuromorphicProcessor 20 | 21 | # MODULE is the basename of the Python test file 22 | MODULE = neuromorphic_processor_tb 23 | 24 | # include cocotb's make rules to take care of the simulator setup 25 | include $(shell cocotb-config --makefiles)/Makefile.sim 26 | -------------------------------------------------------------------------------- /test/simcommand/CombinatorialSpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import simcommand.runtime.{Config,CombinatorialDependencyException} 5 | 6 | class CombinatorialSpec extends AnyFlatSpec with SimcommandScalatestTester { 7 | "combinatorial detection" should "allow same thread read then write" in { 8 | val a = binding(0) 9 | 10 | val program = for { 11 | _ <- poke(a, 6) 12 | r <- peek(a) 13 | } yield r 14 | 15 | val result = unsafeRun(program, FakeClock(), Config()) 16 | assert(result.retval == 6) 17 | } 18 | 19 | "combinatorial detection" should "detect simple combinatorial dependency" in { 20 | val a = binding(0) 21 | 22 | val t0 = poke(a, 5) 23 | val t1 = for { 24 | v <- peek(a) 25 | _ <- poke(a, v+1) 26 | r <- peek(a) 27 | } yield r 28 | 29 | val program = for { 30 | a <- fork(t0, "t0") 31 | b <- fork(t1, "t1") 32 | _ <- join(a) 33 | r <- join(b) 34 | } yield r 35 | 36 | assertThrows[CombinatorialDependencyException] { unsafeRun(program, FakeClock(), Config()) } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/simcommand/DecoupledSpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import chisel3._ 4 | import chisel3.util.Queue 5 | import chiseltest.{ChiselScalatestTester, WriteVcdAnnotation} 6 | import chiseltest.internal.NoThreadingAnnotation 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import simcommand.vips.Decoupled 9 | 10 | class DecoupledSpec extends AnyFlatSpec with ChiselScalatestTester { 11 | "decoupled commands" should "drive and fetch from a FIFO" in { 12 | val proto = UInt(16.W) 13 | val data = (0 until 100).map(_.U) 14 | test(new Queue(proto, 16)).withAnnotations(Seq(NoThreadingAnnotation, WriteVcdAnnotation)) { c => 15 | val enqCmds = new Decoupled(c.io.enq) 16 | val deqCmds = new Decoupled(c.io.deq) 17 | val test = for { 18 | enqThread <- fork(enqCmds.enqueueSeq(data), "enqueue") 19 | _ <- step(10) 20 | deqThread <- fork(deqCmds.dequeueN(data.length), "dequeue") 21 | _ <- join(enqThread) 22 | out <- join(deqThread) 23 | } yield out 24 | val result = unsafeRun(test, c.clock) 25 | assert(result.retval.map(_.litValue) == data.map(_.litValue)) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/simcommand/SimcommandScalatestTester.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import java.io.File; 4 | import chiseltest.{TestResult, defaults} 5 | import chiseltest.internal.{Context, NoThreadingAnnotation, TestEnvInterface} 6 | import firrtl.AnnotationSeq 7 | import firrtl.options.TargetDirAnnotation 8 | import org.scalatest.Assertions 9 | 10 | trait SimcommandScalatestTester extends Assertions { 11 | val testName: String = s"chisel_test_${System.currentTimeMillis()}" 12 | 13 | class ChiselTestBuilder[R <: chisel3.Module](val dutGen: () => R, val annotationSeq: AnnotationSeq) extends TestEnvInterface { 14 | def apply(testFn: R => Unit): TestResult = { 15 | batchedFailures.clear() 16 | Context.run(defaults.createDefaultTester(dutGen, annotationSeq), this, testFn) 17 | } 18 | 19 | def withAnnotations(annotationSeq: AnnotationSeq): ChiselTestBuilder[R] = { 20 | new ChiselTestBuilder[R](dutGen, this.annotationSeq ++ annotationSeq) 21 | } 22 | 23 | override def topFileName: Option[String] = None 24 | } 25 | 26 | def testChisel[T <: chisel3.Module](dutGen: => T): ChiselTestBuilder[T] = { 27 | val annotations = Seq(TargetDirAnnotation("test_run_dir" + File.separator + testName), NoThreadingAnnotation) 28 | new ChiselTestBuilder(() => dutGen, annotations) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/simcommand/vips/Decoupled.scala: -------------------------------------------------------------------------------- 1 | package simcommand.vips 2 | 3 | import chisel3._ 4 | import chisel3.util.DecoupledIO 5 | import simcommand._ 6 | 7 | class Decoupled[T <: Data](io: DecoupledIO[T]) { 8 | def enqueue(data: T): Command[Unit] = for { 9 | _ <- poke(io.bits, data) 10 | _ <- poke(io.valid, true.B) 11 | _ <- waitForValue(io.ready, true.B) 12 | _ <- step(1) 13 | _ <- poke(io.valid, false.B) 14 | } yield () 15 | 16 | def enqueueSeq(data: Seq[T]): Command[Unit] = { 17 | // TODO: replace with repeat 18 | val enqueueCmds: Seq[Command[Unit]] = data.map(d => enqueue(d)) 19 | sequence(enqueueCmds.toVector).map(_ => ()) 20 | } 21 | 22 | def dequeue(): Command[T] = for { 23 | _ <- waitForValue(io.valid, true.B) 24 | _ <- poke(io.ready, true.B) 25 | value <- peek(io.bits) 26 | _ <- step(1) 27 | } yield value 28 | 29 | def dequeue[R <: Data](extract: T => R): Command[R] = for { 30 | _ <- waitForValue(io.valid, true.B) 31 | _ <- poke(io.ready, true.B) 32 | value <- peek(extract(io.bits)) 33 | _ <- step(1) 34 | } yield value 35 | 36 | def dequeueN(n: Int): Command[Seq[T]] = { 37 | repeatCollect(dequeue(), n) 38 | } 39 | 40 | def monitorOne: Command[T] = for { 41 | _ <- waitForValue(io.valid, true.B) 42 | _ <- waitForValue(io.ready, true.B) 43 | data <- peek(io.bits) 44 | _ <- step(1) 45 | } yield data 46 | } 47 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/potrefc2.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00000000000000000 34 | 00000000000000000 35 | 00000000000000000 36 | 00000000000000000 37 | 00000000000000000 38 | 00000000000000000 39 | 00000000000000000 40 | 00000000000000000 41 | 00000000000000000 42 | 00000000000000000 43 | 00000000000000000 44 | 00000000000000000 45 | 00000000000000000 46 | 00000000000000000 47 | 00000000000000000 48 | 00000000000000000 49 | 00000000000000000 50 | 00000000000000000 51 | 00000000000000000 52 | 00000000000000000 53 | 00000000000000000 54 | 00000000000000000 55 | 00000000000000000 56 | 00000000000000000 57 | 00000000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/potrefc3.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00000000000000000 34 | 00000000000000000 35 | 00000000000000000 36 | 00000000000000000 37 | 00000000000000000 38 | 00000000000000000 39 | 00000000000000000 40 | 00000000000000000 41 | 00000000000000000 42 | 00000000000000000 43 | 00000000000000000 44 | 00000000000000000 45 | 00000000000000000 46 | 00000000000000000 47 | 00000000000000000 48 | 00000000000000000 49 | 00000000000000000 50 | 00000000000000000 51 | 00000000000000000 52 | 00000000000000000 53 | 00000000000000000 54 | 00000000000000000 55 | 00000000000000000 56 | 00000000000000000 57 | 00000000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e0.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01001001000100100 34 | 01010001011100011 35 | 01010011000001101 36 | 01000100001101010 37 | 01000001001010100 38 | 01000101011111111 39 | 01010110110001101 40 | 01011000010011110 41 | 01010100001110001 42 | 01001111111100110 43 | 01011001011000110 44 | 01010100101010100 45 | 01001101010100111 46 | 00111110100010110 47 | 01001011011011000 48 | 01001000101101001 49 | 00111110101101001 50 | 01000010100111101 51 | 01000110010111011 52 | 01001101011100000 53 | 01001000111010111 54 | 01001000111101010 55 | 01011101111001011 56 | 01010001110100100 57 | 01000011101100000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e1.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01010011001110101 34 | 01001010110101000 35 | 01010010011110010 36 | 01001101110111000 37 | 00110111011011001 38 | 01001111110000000 39 | 01001100001001001 40 | 01000111011000110 41 | 01001100010010110 42 | 01000001011010110 43 | 01000011011110101 44 | 01001010011011010 45 | 00111010111010011 46 | 00110011111010000 47 | 00111101011111101 48 | 00111111001010110 49 | 01000111010101000 50 | 00111110010011100 51 | 01010011110010000 52 | 01000011000110011 53 | 01010101101111000 54 | 01010000100100000 55 | 01010011010111001 56 | 01000001101010011 57 | 01001110010001001 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e2.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01011111111010000 34 | 01000111101101110 35 | 01000110010011100 36 | 01001000110001011 37 | 00111110001011001 38 | 01010011111011111 39 | 01010101101110011 40 | 01001010001010011 41 | 01001111100001101 42 | 01001100100111100 43 | 00110101111001011 44 | 00110100000000010 45 | 01001001010111110 46 | 01001011000000111 47 | 00111110000001110 48 | 01010011000100111 49 | 00111110100101001 50 | 01001000111000111 51 | 01001000001110011 52 | 01010000110010001 53 | 01010100001011111 54 | 01001111000010111 55 | 01100010101110101 56 | 01000011110010110 57 | 00111100100100010 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e3.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01011011100111110 34 | 00111100011000110 35 | 01010100111110011 36 | 01010000110010010 37 | 00110001100001000 38 | 00111010000001111 39 | 01001111010101011 40 | 01010101000111000 41 | 01001010001001101 42 | 01001110000010000 43 | 01010011101000010 44 | 01010000000000010 45 | 00111010000001110 46 | 01001110111110001 47 | 01000101110011100 48 | 01010100000011001 49 | 01000110011010000 50 | 01000001001010000 51 | 01001010011001010 52 | 01010010011010101 53 | 01011111111000100 54 | 00111010110011011 55 | 01000100100110111 56 | 01010001111110010 57 | 01000011100111000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e4.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01001100001011110 34 | 01010101111111101 35 | 01011001111100110 36 | 01001001010000000 37 | 01000010001100001 38 | 01000010110010000 39 | 01000001011000100 40 | 00111101011110011 41 | 01001001111011111 42 | 01010001111010100 43 | 01010100101011001 44 | 01000110100111000 45 | 00111101001001111 46 | 00111000110111001 47 | 01000000000001100 48 | 01010110110010111 49 | 01001000110110101 50 | 01000001101111100 51 | 00111111000101011 52 | 01000101100010100 53 | 01011011000001101 54 | 01010011111001111 55 | 01010011000010010 56 | 01000001101110101 57 | 01011111001101100 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e5.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01010101101101001 34 | 00111110110111011 35 | 00110110110011100 36 | 00111111001011000 37 | 00111000010100011 38 | 01010110001010101 39 | 01000000000100001 40 | 01000000100011111 41 | 01001111111000010 42 | 01010010101110110 43 | 01011101101101110 44 | 01001010110011000 45 | 01000010111001101 46 | 01001110111010000 47 | 01001010011011110 48 | 01010111110101110 49 | 01000110101101110 50 | 01001001111111011 51 | 01001101011111001 52 | 01000110100001110 53 | 01001000111110010 54 | 01001111111000101 55 | 01010010101001010 56 | 01000110010101010 57 | 01001000101110000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e6.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01101010111000100 34 | 01000111000100111 35 | 00111100000100111 36 | 01010000111001100 37 | 01000100011111110 38 | 01000000111011010 39 | 01001011101010000 40 | 01010100110101110 41 | 01010111100111100 42 | 01000111000001010 43 | 01001011100111100 44 | 01000111100111110 45 | 01001011011101111 46 | 00110100110010111 47 | 01001101110111110 48 | 01001000000100011 49 | 00111110101100111 50 | 01000100111101100 51 | 01000111000011101 52 | 00111001101111010 53 | 01001010100010000 54 | 01100110011001001 55 | 01000010111101111 56 | 01000110011111100 57 | 00110111000000111 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc2e7.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 01011111101111000 34 | 01010111011001001 35 | 01001111000000000 36 | 00111101011100110 37 | 01001010010100011 38 | 00110000001100101 39 | 01010111000011110 40 | 00111001111001011 41 | 01010011010010111 42 | 01010011011011100 43 | 01000010011101100 44 | 01000110010100010 45 | 00111110010100110 46 | 01010001110000110 47 | 00110010011111011 48 | 00110100100011001 49 | 01000100011011010 50 | 00111101001101101 51 | 01011011001110100 52 | 01011001101101101 53 | 01000001100101000 54 | 00110111100111100 55 | 01010100101111100 56 | 01001100110100111 57 | 01001000111110010 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e0.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e1.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e2.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e3.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e4.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e5.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e6.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/mapping/meminit/biasthreshc3e7.mem: -------------------------------------------------------------------------------- 1 | 00000000000000000 2 | 00000000000000000 3 | 00000000000000000 4 | 00000000000000000 5 | 00000000000000000 6 | 00000000000000000 7 | 00000000000000000 8 | 00000000000000000 9 | 00000000000000000 10 | 00000000000000000 11 | 00000000000000000 12 | 00000000000000000 13 | 00000000000000000 14 | 00000000000000000 15 | 00000000000000000 16 | 00000000000000000 17 | 00000000000000000 18 | 00000000000000000 19 | 00000000000000000 20 | 00000000000000000 21 | 00000000000000000 22 | 00000000000000000 23 | 00000000000000000 24 | 00000000000000000 25 | 00000000000000000 26 | 00000000000000000 27 | 00000000000000000 28 | 00000000000000000 29 | 00000000000000000 30 | 00000000000000000 31 | 00000000000000000 32 | 00000000000000000 33 | 00101000000000000 34 | 00101000000000000 35 | 00101000000000000 36 | 00101000000000000 37 | 00101000000000000 38 | 00101000000000000 39 | 00101000000000000 40 | 00101000000000000 41 | 00101000000000000 42 | 00101000000000000 43 | 00101000000000000 44 | 00101000000000000 45 | 00101000000000000 46 | 00101000000000000 47 | 00101000000000000 48 | 00101000000000000 49 | 00101000000000000 50 | 00101000000000000 51 | 00101000000000000 52 | 00101000000000000 53 | 00101000000000000 54 | 00101000000000000 55 | 00101000000000000 56 | 00101000000000000 57 | 00101000000000000 58 | 00000000000000000 59 | 00000000000000000 60 | 00000000000000000 61 | 00000000000000000 62 | 00000000000000000 63 | 00000000000000000 64 | 00000000000000000 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, The Regents of the University of California (Regents) 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /src/simcommand/runtime/Bridges.scala: -------------------------------------------------------------------------------- 1 | package simcommand.runtime 2 | 3 | import simcommand.runtime.Primitives._ 4 | import chisel3.Data 5 | 6 | // Bridges between the pure Scala Command primitives and RTL simulator I/Os and clocks 7 | object Bridges { 8 | sealed trait Interactable[I] { 9 | def set(value: I): Unit 10 | def get(): I 11 | def compare(value: I): Command[Boolean] 12 | } 13 | 14 | implicit class Chisel3Interactor[I <: Data](value: I) extends Interactable[I] { 15 | val tester = chiseltest.testableData(value) 16 | override def set(p: I): Unit = tester.poke(p) 17 | override def get(): I = tester.peek() 18 | // TODO: Implement a better comparator for chisel3 datatypes, this only works for non-aggregate data types 19 | override def compare(v: I): Command[Boolean] = Peek(this)(SourceInfo.getSourceInfo).map(_.litValue == v.litValue) 20 | } 21 | 22 | case class PrimitiveInteractor[I](var value: I) extends Interactable[I] { 23 | override def set(p: I): Unit = value = p 24 | override def get(): I = value 25 | override def compare(v: I): Command[Boolean] = value match { 26 | case x: Data => Peek(this)(SourceInfo.getSourceInfo).map(_.asInstanceOf[Data].litValue == v.asInstanceOf[Data].litValue) 27 | case _ => Peek(this)(SourceInfo.getSourceInfo).map(_ == v) 28 | } 29 | override def hashCode(): Int = System.identityHashCode(this) 30 | } 31 | 32 | trait Steppable { 33 | def step(cycles: Int): Unit 34 | } 35 | 36 | case class Chisel3Clock(clock: chisel3.Clock) extends Steppable { 37 | def step(cycles: Int): Unit = chiseltest.testableClock(clock).step(cycles) 38 | } 39 | 40 | case class FakeClock() extends Steppable { 41 | def step(cycles: Int): Unit = {} 42 | } 43 | } -------------------------------------------------------------------------------- /test/simcommand/PrimitivesSpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import chisel3._ 4 | import chiseltest._ 5 | import chisel3.util.Counter 6 | import org.scalatest.flatspec.AnyFlatSpec 7 | 8 | class PrimitivesSpec extends AnyFlatSpec with ChiselScalatestTester { 9 | "peek" should "inspect the circuit IO and get its value" in { 10 | class Peekable extends Module { 11 | val a = IO(Output(UInt(32.W))) 12 | val c = Counter(16) 13 | c.inc() // free running counter 14 | a := c.value 15 | } 16 | 17 | test(new Peekable()) { c => 18 | val program = for { 19 | v1 <- peek(c.a) 20 | _ <- step(10) 21 | v2 <- peek(c.a) 22 | _ <- step(6) 23 | v3 <- peek(c.a) 24 | _ <- step(1) 25 | v4 <- peek(c.a) 26 | } yield (v1.litValue, v2.litValue, v3.litValue, v4.litValue) 27 | val result = unsafeRun(program, c.clock) 28 | assert(result.retval == (0, 10, 0, 1)) 29 | } 30 | } 31 | 32 | "poke" should "drive the circuit IO" in { 33 | class Pokable extends Module { 34 | val a = IO(Input(UInt(32.W))) 35 | val aOut = IO(Output(UInt(32.W))) 36 | val aReg = RegNext(a) // pipelined loopback 37 | aOut := aReg 38 | } 39 | 40 | def pokeOne(signal: UInt, value: UInt): Command[Unit] = 41 | for { 42 | _ <- poke(signal, value) 43 | _ <- step(1) 44 | } yield () 45 | 46 | test(new Pokable()) { c => 47 | val program = for { 48 | _ <- pokeOne(c.a, 100.U) 49 | v1 <- peek(c.aOut) 50 | _ <- step(100) 51 | v2 <- peek(c.aOut) 52 | _ <- pokeOne(c.a, 200.U) 53 | v3 <- peek(c.aOut) 54 | } yield (v1.litValue, v2.litValue, v3.litValue) 55 | val result = unsafeRun(program, c.clock) 56 | assert(result.retval == (100, 100, 200)) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/simcommand/LibrarySpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import chisel3._ 4 | import chisel3.util.Counter 5 | import chiseltest.{VerilatorBackendAnnotation, WriteVcdAnnotation, testableClock} 6 | import org.scalatest.flatspec.AnyFlatSpec 7 | 8 | class LibrarySpec extends AnyFlatSpec with SimcommandScalatestTester { 9 | class PokeCounter extends Module { 10 | val in = IO(Input(Bool())) 11 | val out = IO(Output(UInt(32.W))) 12 | val ct = Counter(2 << 16) 13 | 14 | when(in) { ct.inc() } 15 | out := ct.value 16 | } 17 | 18 | class LongDelay extends Module { 19 | val a = IO(Output(Bool())) 20 | val b = IO(Output(UInt(32.W))) 21 | val c = Counter(2 << 16) 22 | c.inc() 23 | a := c.value > 100000.U 24 | b := c.value 25 | } 26 | 27 | "repeat" should "run command repeatedly" in { 28 | testChisel(new PokeCounter()).withAnnotations(Seq(WriteVcdAnnotation, VerilatorBackendAnnotation)) { c => 29 | val program = for { 30 | _ <- repeat(for { 31 | _ <- poke(c.in, true.B) 32 | _ <- step(1) 33 | _ <- poke(c.in, false.B) 34 | _ <- step(1) 35 | } yield (), 1000) 36 | pokeCount <- peek(c.out) 37 | } yield pokeCount 38 | val result = unsafeRun(program, c.clock) 39 | assert(result.retval.litValue == 1000) 40 | } 41 | } 42 | 43 | "repeatCollect" should "collect values" in { 44 | testChisel(new LongDelay()).withAnnotations(Seq(WriteVcdAnnotation, VerilatorBackendAnnotation)) { c => 45 | val program = repeatCollect(for { 46 | r <- peek(c.b) 47 | _ <- step(1) 48 | } yield r.litValue, 10000) 49 | c.clock.setTimeout(200000) 50 | 51 | val result = unsafeRun(program, c.clock) 52 | val expected = Seq.range(0, 10000) 53 | 54 | assert(result.retval == expected) 55 | } 56 | } 57 | 58 | "doWhile" should "not overflow the stack" in { 59 | testChisel(new LongDelay()).withAnnotations(Seq(WriteVcdAnnotation, VerilatorBackendAnnotation)) { c => 60 | val program = waitForValue(c.a, 1.B) 61 | c.clock.setTimeout(200000) 62 | val result = unsafeRun(program, c.clock) 63 | assert(result.cycles > 100000) 64 | assert(result.threadsSpawned == 1) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/benchmarks/DecoupledGcd.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | // source: 4 | // https://github.com/ucb-bar/testers-regression/blob/94e23d1ee1bfc137ecd5a541a1b094fd0feb229f/src/main/scala/gcd/DecoupledGCD.scala 5 | 6 | import chisel3._ 7 | import chisel3.util.Decoupled 8 | 9 | class GcdInputBundle(val w: Int) extends Bundle { 10 | val value1 = UInt(w.W) 11 | val value2 = UInt(w.W) 12 | } 13 | 14 | class GcdOutputBundle(override val w: Int) extends GcdInputBundle(w) { 15 | val gcd = UInt(w.W) 16 | } 17 | 18 | /** 19 | * Compute Gcd using subtraction method. 20 | * Subtracts the smaller from the larger until register y is zero. 21 | * value input register x is then the Gcd. 22 | * Unless first input is zero then the Gcd is y. 23 | * Can handle stalls on the producer or consumer side 24 | */ 25 | class DecoupledGcd(val bitWidth: Int) extends Module { 26 | val input = IO(Flipped(Decoupled(new GcdInputBundle(bitWidth)))) 27 | val output = IO(Decoupled(new GcdOutputBundle(bitWidth))) 28 | 29 | val xInitial = Reg(UInt(bitWidth.W)) 30 | val yInitial = Reg(UInt(bitWidth.W)) 31 | val x = Reg(UInt(bitWidth.W)) 32 | val y = Reg(UInt(bitWidth.W)) 33 | val busy = RegInit(false.B) 34 | val resultValid = RegInit(false.B) 35 | 36 | input.ready := ! busy 37 | output.valid := resultValid 38 | output.bits := DontCare 39 | 40 | val cycle = RegInit(0.U(32.W)) 41 | cycle := cycle + 1.U 42 | 43 | // printf("%d xi %d yi %d c %d x %d y %d busy %d valid out\n", 44 | // xInitial, yInitial, cycle, x, y, busy.asUInt, resultValid.asUInt) 45 | 46 | when(busy) { 47 | when(x >= y) { 48 | x := x - y 49 | }.otherwise { 50 | y := y - x 51 | } 52 | when(x === 0.U || y === 0.U) { 53 | when(x === 0.U) { 54 | output.bits.gcd := y 55 | }.otherwise { 56 | output.bits.gcd := x 57 | } 58 | 59 | output.bits.value1 := xInitial 60 | output.bits.value2 := yInitial 61 | resultValid := true.B 62 | 63 | when(output.ready && resultValid) { 64 | busy := false.B 65 | resultValid := false.B 66 | } 67 | } 68 | }.otherwise { 69 | when(input.valid) { 70 | val bundle = input.deq() 71 | x := bundle.value1 72 | y := bundle.value2 73 | xInitial := bundle.value1 74 | yInitial := bundle.value2 75 | busy := true.B 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /mill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This is a wrapper script, that automatically download mill from GitHub release pages 4 | # You can give the required mill version with MILL_VERSION env variable 5 | # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION 6 | 7 | set -e 8 | 9 | if [ -z "${DEFAULT_MILL_VERSION}" ] ; then 10 | DEFAULT_MILL_VERSION=0.11.1 11 | fi 12 | 13 | if [ -z "$MILL_VERSION" ] ; then 14 | if [ -f ".mill-version" ] ; then 15 | MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" 16 | elif [ -f ".config/mill-version" ] ; then 17 | MILL_VERSION="$(head -n 1 .config/mill-version 2> /dev/null)" 18 | elif [ -f "mill" ] && [ "$0" != "mill" ] ; then 19 | MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) 20 | else 21 | MILL_VERSION=$DEFAULT_MILL_VERSION 22 | fi 23 | fi 24 | 25 | if [ "x${XDG_CACHE_HOME}" != "x" ] ; then 26 | MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" 27 | else 28 | MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" 29 | fi 30 | MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" 31 | 32 | version_remainder="$MILL_VERSION" 33 | MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" 34 | MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" 35 | 36 | if [ ! -s "$MILL_EXEC_PATH" ] ; then 37 | mkdir -p "$MILL_DOWNLOAD_PATH" 38 | if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then 39 | ASSEMBLY="-assembly" 40 | fi 41 | DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download 42 | MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') 43 | MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION_TAG}/$MILL_VERSION${ASSEMBLY}" 44 | curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" 45 | chmod +x "$DOWNLOAD_FILE" 46 | mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" 47 | unset DOWNLOAD_FILE 48 | unset MILL_DOWNLOAD_URL 49 | fi 50 | 51 | if [ -z "$MILL_MAIN_CLI" ] ; then 52 | MILL_MAIN_CLI="${0}" 53 | fi 54 | 55 | MILL_FIRST_ARG="" 56 | if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then 57 | # Need to preserve the first position of those listed options 58 | MILL_FIRST_ARG=$1 59 | shift 60 | fi 61 | 62 | unset MILL_DOWNLOAD_PATH 63 | unset MILL_VERSION 64 | 65 | exec $MILL_EXEC_PATH $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" 66 | -------------------------------------------------------------------------------- /cocotb/gcd_tb/gcd_tb.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | import cocotb 3 | from cocotb.clock import Clock 4 | from cocotb.triggers import Timer, ClockCycles, Event, RisingEdge, Join, RisingEdge, NextTimeStep, ReadOnly 5 | import datetime 6 | import math 7 | 8 | 9 | async def enqueue(dut, value1: int, value2: int): 10 | dut.input_bits_value1.value = value1 11 | dut.input_bits_value2.value = value2 12 | dut.input_valid.value = 1 13 | #print(dut.input_bits_value1.value, dut.input_bits_value2.value) 14 | await ReadOnly() 15 | while dut.input_ready.value != 1: 16 | await RisingEdge(dut.clock) 17 | await ReadOnly() 18 | await RisingEdge(dut.clock) 19 | dut.input_valid.value = 0 20 | 21 | async def enqueueSeq(dut, data: List[Tuple[int, int]]): 22 | for value1, value2 in data: 23 | await enqueue(dut, value1, value2) 24 | 25 | async def dequeue(dut) -> (int, int, int): 26 | dut.output_ready.value = 1 27 | while dut.output_valid.value != 1: 28 | await ClockCycles(dut.clock, 1) 29 | value1, value2, gcd = (dut.output_bits_value1.value, dut.output_bits_value2.value, dut.output_bits_gcd.value) 30 | await ClockCycles(dut.clock, 1) 31 | dut.output_ready.value = 0 32 | return (value1, value2, gcd) 33 | 34 | async def dequeueN(dut, n: int) -> List[Tuple[int, int, int]]: 35 | values = [] 36 | for i in range(n): 37 | deq = await dequeue(dut) 38 | values.append(deq) 39 | return values 40 | 41 | @cocotb.test() 42 | async def gcd_tb(dut): 43 | (maxX, maxY) = (100, 100) 44 | #(maxX, maxY) = (10, 10) 45 | testValues = [] 46 | for x in range(2, maxX + 1): 47 | for y in range(2, maxY + 1): 48 | testValues.append((x, y, math.gcd(x, y))) 49 | bitWidth = 60 50 | #print(testValues) 51 | 52 | print(datetime.datetime.now()) 53 | # Clock is generated inside Python 54 | cocotb.start_soon(Clock(dut.clock, 2, units="ps").start()) 55 | await ClockCycles(dut.clock, 2) 56 | 57 | # Reset inputs 58 | dut.reset.value = 1 59 | dut.input_valid.value = 0 60 | dut.output_ready.value = 0 61 | await ClockCycles(dut.clock, 1) 62 | dut.reset.value = 0 63 | await ClockCycles(dut.clock, 1) 64 | 65 | print("reset done") 66 | # Drive inputs and get outputs in 2 threads 67 | driver = cocotb.start_soon(enqueueSeq(dut, [(x[0], x[1]) for x in testValues])) 68 | reader = cocotb.start_soon(dequeueN(dut, len(testValues))) 69 | 70 | await Join(driver) 71 | results = await Join(reader) 72 | for r in zip(results, testValues): 73 | assert r[0][0] == r[1][0] 74 | assert r[0][1] == r[1][1] 75 | assert r[0][2] == r[1][2] 76 | print(datetime.datetime.now()) 77 | -------------------------------------------------------------------------------- /test/simcommand/ChannelSpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import chisel3._ 4 | import chiseltest.ChiselScalatestTester 5 | import chisel3.util.Counter 6 | import org.scalatest.flatspec.AnyFlatSpec 7 | 8 | class ChannelSpec extends AnyFlatSpec with ChiselScalatestTester { 9 | class Timer extends Module { 10 | val a = IO(Output(UInt(32.W))) 11 | val c = Counter(16) 12 | c.inc() 13 | a := c.value 14 | } 15 | 16 | "getBlocking" should "inspect the circuit IO and get its value" in { 17 | test(new Timer()) { c => 18 | def putter(ch: ChannelHandle[BigInt]): Command[Unit] = for { 19 | _ <- put(ch, BigInt(1)) 20 | _ <- put(ch, BigInt(2)) 21 | _ <- step(1) 22 | _ <- put(ch, BigInt(3)) 23 | _ <- put(ch, BigInt(4)) 24 | _ <- step(1) 25 | _ <- put(ch, BigInt(5)) 26 | _ <- put(ch, BigInt(6)) 27 | _ <- step(1) 28 | _ <- put(ch, BigInt(7)) 29 | _ <- put(ch, BigInt(8)) 30 | _ <- step(1) 31 | } yield () 32 | 33 | def getter(ch: ChannelHandle[BigInt]): Command[Seq[BigInt]] = for { 34 | t0 <- peek(c.a) 35 | v1 <- getBlocking(ch) 36 | t1 <- peek(c.a) 37 | v2 <- getBlocking(ch) 38 | t2 <- peek(c.a) 39 | v3 <- getBlocking(ch) 40 | t3 <- peek(c.a) 41 | v4 <- getBlocking(ch) 42 | t4 <- peek(c.a) 43 | v5 <- getBlocking(ch) 44 | t5 <- peek(c.a) 45 | v6 <- getBlocking(ch) 46 | t6 <- peek(c.a) 47 | v7 <- getBlocking(ch) 48 | t7 <- peek(c.a) 49 | v8 <- getBlocking(ch) 50 | } yield (Seq(t0.litValue, v1, t1.litValue, v2, t2.litValue, v3, t3.litValue, v4, t4.litValue, v5, t5.litValue, v6, t6.litValue, v7, t7.litValue, v8)) 51 | 52 | val program = for { 53 | ch <- makeChannel[BigInt](1) 54 | // Spawn thread which will put two elements repeatedly into a channel 55 | t1 <- fork(putter(ch), "put") 56 | // Spawn thread which pops whenever an element is available 57 | t2 <- fork(getter(ch), "getter") 58 | result <- join(t2) 59 | } yield (result) 60 | 61 | val result = unsafeRun(program, c.clock) 62 | 63 | assert(result.retval == Seq(0, 1, 0, 2, 0, 3, 1, 4, 1, 5, 2, 6, 2, 7, 3, 8)) 64 | } 65 | } 66 | 67 | "nonEmpty" should "test whether or not a channel is empty" in { 68 | test(new Timer()) { c => 69 | val program = for { 70 | ch <- makeChannel[BigInt](1) 71 | v1 <- nonEmpty(ch) 72 | _ <- put[BigInt](ch, 2) 73 | v2 <- nonEmpty(ch) 74 | _ <- getBlocking(ch) 75 | v3 <- nonEmpty(ch) 76 | } yield (v1, v2, v3) 77 | 78 | val result = unsafeRun(program, c.clock) 79 | 80 | assert(result.retval == (false, true, false)) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/simcommand/vips/UART.scala: -------------------------------------------------------------------------------- 1 | package simcommand.vips 2 | 3 | import chisel3._ 4 | import simcommand._ 5 | 6 | class UART(uartIn: Interactable[chisel3.Bool], uartOut: Interactable[chisel3.Bool], cyclesPerBit: Int) { 7 | val bitsPerSymbol = 10 8 | 9 | def sendReset(): Command[Unit] = { 10 | // Keep idle high for an entire symbol period to reset any downstream receivers 11 | for { 12 | _ <- poke(uartIn, 1.B) 13 | _ <- step(cyclesPerBit * (bitsPerSymbol + 1)) 14 | } yield () 15 | } 16 | 17 | def sendBit(bit: Int): Command[Unit] = { 18 | for { 19 | _ <- poke(uartIn, bit.B) 20 | _ <- step(cyclesPerBit) 21 | } yield () 22 | } 23 | 24 | def sendByte(byte: Int): Command[Unit] = { 25 | for { 26 | _ <- sendBit(0) 27 | _ <- concat((0 until 8).map(i => sendBit((byte >> i) & 0x1))) 28 | _ <- sendBit(1) 29 | } yield () 30 | } 31 | 32 | def sendBytes(bytes: Seq[Int]): Command[Unit] = { 33 | val cmds = bytes.map(b => sendByte(b)) 34 | concat(cmds) 35 | } 36 | 37 | def receiveBit(): Command[Int] = { 38 | // Assuming that a start bit has already been seen and current time is at the midpoint of the start bit 39 | for { 40 | _ <- step(cyclesPerBit) 41 | b <- peek(uartOut) 42 | } yield b.litValue.toInt 43 | } 44 | 45 | def receiveByte(): Command[Int] = { 46 | for { 47 | _ <- waitForValue(uartOut, false.B, cyclesPerBit/4) // Wait for the start bit // TODO: reduce polling frequency 48 | _ <- step(cyclesPerBit / 2) // shift time to center-of-symbol 49 | bits <- sequence(Seq.fill(8)(receiveBit())) 50 | _ <- step(cyclesPerBit + cyclesPerBit / 2) // advance time past 1/2 of last data bit and stop bit 51 | byte = bits.zipWithIndex.foldLeft(0) { 52 | case (byte, (bit, index)) => byte | (bit << index) 53 | } 54 | } yield byte 55 | } 56 | 57 | def receiveBytes(nBytes: Int): Command[Seq[Int]] = { 58 | val cmds = Seq.fill(nBytes)(receiveByte()) 59 | sequence(cmds) 60 | } 61 | } 62 | 63 | class UARTChecker(serialLine: Interactable[chisel3.Bool]) { 64 | def checkByte(bitDelay: Int): Command[Boolean] = { 65 | for { 66 | _ <- waitForValue(serialLine, false.B) // Wait for the start bit 67 | stableStartBit <- checkStable(serialLine, false.B, bitDelay) // Start bit should be stable until symbol edge 68 | _ <- step(bitDelay*8) // Let the 8 data bits pass 69 | stableStopBit <- checkStable(serialLine, true.B, bitDelay) // Stop bit should be stable until byte finished 70 | } yield stableStartBit && stableStopBit 71 | } 72 | 73 | def checkBytes(nBytes: Int, bitDelay: Int): Command[Boolean] = { 74 | val checks = Seq.fill(nBytes)(checkByte(bitDelay)) 75 | for { 76 | checks <- sequence(checks) 77 | } yield checks.forall(b => b) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/benchmarks/NeuromorphicProcessorCommandTester.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import simcommand._ 4 | import chisel3._ 5 | import chiseltest._ 6 | import chiseltest.internal.NoThreadingAnnotation 7 | import simcommand.vips.UART 8 | 9 | class NeuromorphicProcessorCommandTester extends NeuromorphicProcessorTester { 10 | it should "process an image" taggedAs(Verilator) in { 11 | val startElab = System.nanoTime() 12 | test(new NeuromorphicProcessorBBWrapper()) 13 | .withAnnotations(Seq(VerilatorBackendAnnotation, NoThreadingAnnotation)) { dut => 14 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation") 15 | val startTest = System.nanoTime() 16 | dut.clock.setTimeout(FREQ) 17 | 18 | // Reset inputs 19 | dut.io.io_uartRx.poke(true.B) 20 | dut.io.io_uartTx.expect(true.B) 21 | dut.reset.poke(true.B) 22 | dut.clock.step() 23 | dut.reset.poke(false.B) 24 | dut.io.io_uartTx.expect(true.B) 25 | 26 | // Load an image into the accelerator ... 27 | val bytes = image.indices.flatMap { i => 28 | Seq((i >> 8) & 0xff, i & 0xff, (image(i) >> 8) & 0xff, image(i) & 0xff) 29 | } 30 | val commands = new UART(dut.io.io_uartRx, dut.io.io_uartTx, bitDelay) 31 | val receiver = commands.receiveBytes(110) 32 | val sender = commands.sendBytes(bytes) 33 | 34 | val program: Command[Seq[Int]] = 35 | for { 36 | rxThread <- simcommand.fork(receiver, "receiver") 37 | txThread <- simcommand.fork(sender, "sender") 38 | _ <- { 39 | println("Loading image into accelerator") 40 | noop() 41 | } 42 | _ <- join(txThread) 43 | _ <- { 44 | println("Done loading image") 45 | println("getting accelerator's response") 46 | noop() 47 | } 48 | resp <- join(rxThread) // Step(FREQ/2) used in original testbench 49 | _ <- { 50 | println("Response received - comparing results") 51 | noop() 52 | } 53 | } yield resp 54 | 55 | val result = unsafeRun(program, dut.clock) 56 | val spikes = result.retval.filter(_ < 200) 57 | assert(spikes.length == results.length, "number of spikes does not match expected") 58 | assert(spikes.zip(results).map(x => x._1 == x._2).reduce(_ && _), "spikes do not match expected") 59 | 60 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 61 | println(s"Took ${deltaSeconds}s to run test with the Command API using the chiseltest interface with the NoThreadingAnnotation") 62 | println(s"Executed ${result.cycles} cycles at an average frequency of ${result.cycles / deltaSeconds} Hz") 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/results_round.sv: -------------------------------------------------------------------------------- 1 | logic [15:0] results [108]; 2 | assign results[0] = 'd23; 3 | assign results[1] = 'd22; 4 | assign results[2] = 'd21; 5 | assign results[3] = 'd20; 6 | assign results[4] = 'd31; 7 | assign results[5] = 'd30; 8 | assign results[6] = 'd29; 9 | assign results[7] = 'd28; 10 | assign results[8] = 'd27; 11 | assign results[9] = 'd26; 12 | assign results[10] = 'd25; 13 | assign results[11] = 'd24; 14 | assign results[12] = 'd39; 15 | assign results[13] = 'd38; 16 | assign results[14] = 'd37; 17 | assign results[15] = 'd36; 18 | assign results[16] = 'd35; 19 | assign results[17] = 'd60; 20 | assign results[18] = 'd42; 21 | assign results[19] = 'd55; 22 | assign results[20] = 'd51; 23 | assign results[21] = 'd48; 24 | assign results[22] = 'd63; 25 | assign results[23] = 'd101; 26 | assign results[24] = 'd100; 27 | assign results[25] = 'd104; 28 | assign results[26] = 'd114; 29 | assign results[27] = 'd158; 30 | assign results[28] = 'd164; 31 | assign results[29] = 'd50; 32 | assign results[30] = 'd58; 33 | assign results[31] = 'd85; 34 | assign results[32] = 'd82; 35 | assign results[33] = 'd88; 36 | assign results[34] = 'd85; 37 | assign results[35] = 'd93; 38 | assign results[36] = 'd88; 39 | assign results[37] = 'd85; 40 | assign results[38] = 'd93; 41 | assign results[39] = 'd88; 42 | assign results[40] = 'd85; 43 | assign results[41] = 'd93; 44 | assign results[42] = 'd88; 45 | assign results[43] = 'd88; 46 | assign results[44] = 'd85; 47 | assign results[45] = 'd93; 48 | assign results[46] = 'd85; 49 | assign results[47] = 'd93; 50 | assign results[48] = 'd85; 51 | assign results[49] = 'd93; 52 | assign results[50] = 'd88; 53 | assign results[51] = 'd85; 54 | assign results[52] = 'd93; 55 | assign results[53] = 'd88; 56 | assign results[54] = 'd85; 57 | assign results[55] = 'd93; 58 | assign results[56] = 'd88; 59 | assign results[57] = 'd85; 60 | assign results[58] = 'd93; 61 | assign results[59] = 'd88; 62 | assign results[60] = 'd85; 63 | assign results[61] = 'd93; 64 | assign results[62] = 'd88; 65 | assign results[63] = 'd93; 66 | assign results[64] = 'd88; 67 | assign results[65] = 'd85; 68 | assign results[66] = 'd93; 69 | assign results[67] = 'd88; 70 | assign results[68] = 'd85; 71 | assign results[69] = 'd93; 72 | assign results[70] = 'd88; 73 | assign results[71] = 'd93; 74 | assign results[72] = 'd85; 75 | assign results[73] = 'd88; 76 | assign results[74] = 'd85; 77 | assign results[75] = 'd93; 78 | assign results[76] = 'd88; 79 | assign results[77] = 'd93; 80 | assign results[78] = 'd85; 81 | assign results[79] = 'd88; 82 | assign results[80] = 'd85; 83 | assign results[81] = 'd88; 84 | assign results[82] = 'd85; 85 | assign results[83] = 'd88; 86 | assign results[84] = 'd20; 87 | assign results[85] = 'd85; 88 | assign results[86] = 'd93; 89 | assign results[87] = 'd88; 90 | assign results[88] = 'd20; 91 | assign results[89] = 'd85; 92 | assign results[90] = 'd93; 93 | assign results[91] = 'd88; 94 | assign results[92] = 'd20; 95 | assign results[93] = 'd85; 96 | assign results[94] = 'd93; 97 | assign results[95] = 'd88; 98 | assign results[96] = 'd20; 99 | assign results[97] = 'd85; 100 | assign results[98] = 'd93; 101 | assign results[99] = 'd88; 102 | assign results[100] = 'd20; 103 | assign results[101] = 'd85; 104 | assign results[102] = 'd93; 105 | assign results[103] = 'd88; 106 | assign results[104] = 'd93; 107 | assign results[105] = 'd20; 108 | assign results[106] = 'd85; 109 | assign results[107] = 'd88; 110 | -------------------------------------------------------------------------------- /test/simcommand/user/ForkSpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand.user 2 | 3 | import chisel3._ 4 | import chisel3.experimental.BundleLiterals._ 5 | import chisel3.util.{ShiftRegister, Valid} 6 | import chiseltest.{ChiselScalatestTester, WriteVcdAnnotation} 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import org.scalatest.Assertions.{assert => scalaAssert} 9 | import simcommand._ 10 | import simcommand.runtime.Config 11 | 12 | class ForkSpec extends AnyFlatSpec with ChiselScalatestTester { 13 | class ValidDelayLine(delay: Int) extends Module { 14 | val a = IO(Input(Valid(UInt(10.W)))) 15 | val b = IO(Output(Valid(UInt(10.W)))) 16 | val default = Valid(UInt(10.W)).Lit(_.valid -> false.B, _.bits -> 0.U) 17 | b := ShiftRegister(a, delay, default, 1.B) 18 | } 19 | 20 | class ValidDelayLineVIPs(a: Valid[UInt], b: Valid[UInt], proto: Valid[UInt]) { 21 | def poker(nElems: Int): Command[Unit] = { 22 | def pokeOneElem(value: Int): Command[Unit] = for { 23 | _ <- poke(a, proto.Lit(_.valid -> 1.B, _.bits -> value.U)) 24 | _ <- step(1) 25 | } yield () 26 | val pokeCmds = (0 until nElems).map(i => pokeOneElem(i)) 27 | for { 28 | _ <- sequence(pokeCmds).map(_ => ()) 29 | _ <- poke(a, proto.Lit(_.valid -> 0.B, _.bits -> 0.U)) 30 | _ <- step(1) 31 | } yield () 32 | } 33 | 34 | def peeker(nElems: Int): Command[Seq[Int]] = { 35 | def peekCmd(): Command[Int] = { 36 | for { 37 | valid <- peek(b.valid) 38 | data <- peek(b.bits) 39 | _ <- step(1) 40 | bits <- { 41 | if (!valid.litToBoolean) 42 | peekCmd() // TODO: recursive call in flatMap, will blow up stack eventually 43 | else 44 | lift(data.litValue.toInt) 45 | } 46 | } yield bits 47 | } 48 | val peekCmds = Seq.fill(nElems)(peekCmd()) 49 | sequence(peekCmds) 50 | } 51 | 52 | def program(nElems: Int): Command[Seq[Int]] = { 53 | for { 54 | _ <- step(1) 55 | peekerThread <- fork(peeker(nElems)) 56 | pokerThread <- fork(poker(nElems)) 57 | pokerJoin <- join(pokerThread) 58 | peekerJoin <- join(peekerThread) 59 | } yield peekerJoin 60 | } 61 | } 62 | 63 | "fork" should "create a thread that operates independently of the main thread" in { 64 | val nElems = 10 65 | test(new ValidDelayLine(delay=nElems/2)).withAnnotations(Seq(WriteVcdAnnotation)) { c => 66 | val vips = new ValidDelayLineVIPs(c.a, c.b, Valid(UInt(10.W))) 67 | val result = unsafeRun(vips.program(nElems), c.clock) 68 | scalaAssert(result.retval == (0 until nElems)) 69 | } 70 | } 71 | 72 | "fork" should "support threads that terminate on the same cycle they are spawned" in { 73 | val program = for { 74 | _ <- fork(lift(())) 75 | _ <- fork(lift(())) 76 | _ <- fork(lift(())) 77 | _ <- fork(lift(())) 78 | } yield () 79 | val retval = unsafeRun(program, FakeClock(), Config().copy(recordActions = true)) 80 | scalaAssert(retval.actions.isDefined) 81 | retval.actions.foreach { actions => 82 | scalaAssert(actions.isEmpty) 83 | } 84 | scalaAssert(retval.threadsSpawned == 5) 85 | } 86 | 87 | "fork" should "terminate the child threads when the parent terminates" in { 88 | val program = for { 89 | _ <- fork(step(10)) 90 | _ <- step(4) 91 | } yield () 92 | val retval = unsafeRun(program, FakeClock(), Config().copy(recordActions = true)) 93 | scalaAssert(retval.actions.isDefined) 94 | retval.actions.foreach { actions => 95 | scalaAssert(true) 96 | println(actions) 97 | } 98 | scalaAssert(retval.cycles == 4) 99 | scalaAssert(retval.threadsSpawned == 2) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/benchmarks/NeuromorphicProcessorChiseltestTester.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import chiseltest._ 4 | import chisel3._ 5 | import org.scalatest.tagobjects.Slow 6 | 7 | class NeuromorphicProcessorChiseltestTester extends NeuromorphicProcessorTester { 8 | it should "process an image" taggedAs(Verilator, Slow) in { 9 | val startElab = System.nanoTime() 10 | test(new NeuromorphicProcessorBBWrapper()) 11 | .withAnnotations(Seq(VerilatorBackendAnnotation)) { dut => 12 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation") 13 | val startTest = System.nanoTime() 14 | var cycles = 1L // don't want to spawn a new thread to count cycles 15 | dut.clock.setTimeout(FREQ) 16 | 17 | def receiveByte(byte: UInt): Unit = { 18 | // Start bit 19 | dut.io.io_uartRx.poke(false.B) 20 | dut.clock.step(bitDelay) 21 | // Byte 22 | for (i <- 0 until 8) { 23 | dut.io.io_uartRx.poke(byte(i)) 24 | dut.clock.step(bitDelay) 25 | } 26 | // Stop bit 27 | dut.io.io_uartRx.poke(true.B) 28 | dut.clock.step(bitDelay) 29 | } 30 | 31 | def transferByte(): Int = { 32 | var byte = 0 33 | // Assumes start bit has already been seen 34 | dut.clock.step(bitDelay) 35 | // Byte 36 | for (i <- 0 until 8) { 37 | byte = (dut.io.io_uartTx.peek().litValue << i).toInt | byte 38 | dut.clock.step(bitDelay) 39 | } 40 | // Stop bit 41 | dut.io.io_uartTx.expect(true.B) 42 | //dut.clock.step(bitDelay) 43 | byte 44 | } 45 | 46 | // Reset inputs 47 | dut.io.io_uartRx.poke(true.B) 48 | dut.io.io_uartTx.expect(true.B) 49 | dut.reset.poke(true.B) 50 | dut.clock.step() 51 | dut.reset.poke(false.B) 52 | dut.io.io_uartTx.expect(true.B) 53 | 54 | // Spawn a receiver thread 55 | var spikes = Array[Int]() 56 | var receive = true 57 | val rec = fork { 58 | while (receive) { 59 | if (!dut.io.io_uartTx.peek().litToBoolean) { 60 | val s = transferByte() 61 | if (s < 200) 62 | spikes = spikes :+ s 63 | println(s"Received spike ${s}") 64 | } 65 | dut.clock.step() 66 | } 67 | } 68 | //rec.fork() 69 | 70 | // Load an image into the accelerator ... 71 | println("Loading image into accelerator") 72 | for (i <- image.indices) { 73 | // Write top byte of index, bottom byte of index, top byte 74 | // of rate, and bottom byte of rate 75 | receiveByte((i >> 8).U(8.W)) 76 | receiveByte((i & 0xff).U(8.W)) 77 | receiveByte((image(i) >> 8).U(8.W)) 78 | receiveByte((image(i) & 0xff).U(8.W)) 79 | } 80 | print("Done loading image - ") 81 | 82 | // ... get its response 83 | println("getting accelerator's response") 84 | dut.clock.step(FREQ/2) 85 | receive = false 86 | rec.join() 87 | 88 | println("Response received - comparing results") 89 | 90 | println(spikes.mkString(",")) 91 | 92 | assert(spikes.length == results.length, "number of spikes does not match expected") 93 | assert(spikes.zip(results).map(x => x._1 == x._2).reduce(_ && _), "spikes do not match expected") 94 | 95 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 96 | println(s"Took ${deltaSeconds}s to run test with chiseltest threading and using the chiseltest interface") 97 | // println(s"Executed $cycles cycles at an average frequency of ${cycles / deltaSeconds} Hz") 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vlog/DecoupledGcd/DecoupledGcd_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ps/1ps 2 | module DecoupledGcd_tb(); 3 | 4 | reg clock = 0; 5 | always #(1) clock <= ~clock; 6 | reg reset = 0; 7 | 8 | wire input_ready; 9 | reg input_valid = 1'b0; 10 | reg [59:0] input_bits_value1 = 'd0; 11 | reg [59:0] input_bits_value2 = 'd0; 12 | reg output_ready = 1'b0; 13 | wire output_valid; 14 | wire [59:0] output_bits_value1; 15 | wire [59:0] output_bits_value2; 16 | wire [59:0] output_bits_gcd; 17 | 18 | DecoupledGcd dut ( 19 | .clock(clock), 20 | .reset(reset), 21 | .input_ready(input_ready), 22 | .input_valid(input_valid), 23 | .input_bits_value1(input_bits_value1), 24 | .input_bits_value2(input_bits_value2), 25 | .output_ready(output_ready), 26 | .output_valid(output_valid), 27 | .output_bits_value1(output_bits_value1), 28 | .output_bits_value2(output_bits_value2), 29 | .output_bits_gcd(output_bits_gcd) 30 | ); 31 | 32 | task enqueue(input [59:0] value1, input [59:0] value2); 33 | begin 34 | input_valid = 'b1; 35 | input_bits_value1 = value1; 36 | input_bits_value2 = value2; 37 | while (!input_ready) begin 38 | @(posedge clock); #1; 39 | end 40 | @(posedge clock); #1; 41 | input_valid = 'b0; 42 | end 43 | endtask 44 | 45 | task dequeue(output [59:0] value1, output [59:0] value2, output [59:0] gcd); 46 | begin 47 | output_ready = 'b1; 48 | while (!output_valid) begin 49 | @(posedge clock); #1; 50 | end 51 | value1 = output_bits_value1; 52 | value2 = output_bits_value2; 53 | gcd = output_bits_gcd; 54 | @(posedge clock); #1; 55 | output_ready = 'b0; 56 | end 57 | endtask 58 | 59 | function [59:0] gcd(input [59:0] a, b); 60 | integer t; 61 | begin 62 | while (b != 0) begin 63 | t = b; 64 | b = a % b; 65 | a = t; 66 | end 67 | gcd = a; 68 | end 69 | endfunction 70 | 71 | reg [59:0] x, y; 72 | reg [(60*3)-1:0] stim [$]; 73 | reg [(60*3)-1:0] stim_temp; 74 | reg [60-1:0] result_value1; 75 | reg [60-1:0] result_value2; 76 | reg [60-1:0] result_gcd; 77 | reg [(60*3)-1:0] results [$]; 78 | integer maxX = 100;//100; 79 | integer maxY = 100;//100; 80 | integer i, j; 81 | initial begin 82 | //$dumpfile("DecoupledGcd_tb.vcd"); 83 | //$dumpvars(0, DecoupledGcd_tb); 84 | for (x = 2; x <= maxX; x = x + 1) begin 85 | for (y = 2; y <= maxY; y = y + 1) begin 86 | stim.push_back({gcd(x, y), y, x}); 87 | end 88 | end 89 | 90 | 91 | reset = 'b1; 92 | @(posedge clock); #1; 93 | reset = 'b0; 94 | @(posedge clock); #1; 95 | 96 | //$system("date +\"%s:%N\""); 97 | fork 98 | // Enqueuing thread 99 | begin 100 | for (i = 0; i < stim.size(); i = i + 1) begin 101 | //$display(stim[i]);//], stim[i][1], stim[i][2]); 102 | stim_temp = stim[i]; 103 | enqueue(stim_temp[60-1:0], stim_temp[60*2-1:60]); 104 | end 105 | end 106 | // Dequeueing thread 107 | begin 108 | for (j = 0; j < stim.size(); j = j + 1) begin 109 | dequeue(result_value1, result_value2, result_gcd); 110 | results.push_back({result_gcd, result_value2, result_value1}); 111 | end 112 | end 113 | join 114 | 115 | for (i = 0; i < results.size(); i = i + 1) begin 116 | //$display(results[i]); 117 | assert(results[i] == stim[i]); 118 | end 119 | $display("%t", $time); 120 | 121 | $finish(); 122 | end 123 | endmodule 124 | -------------------------------------------------------------------------------- /cocotb/neuromorphic_tb/neuromorphic_processor_tb.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import cocotb 3 | from cocotb.clock import Clock 4 | from cocotb.triggers import Timer, ClockCycles, Event, RisingEdge 5 | import datetime 6 | 7 | 8 | FREQ = 80000000 # in Hz 9 | BAUDRATE = 115200 10 | RANKORDERENC = False 11 | USEROUNDEDWGHTS = True 12 | 13 | 14 | def fetch(file: str) -> List[int]: 15 | with open(file, 'r') as f: 16 | lines = [x for x in f] 17 | assert len(lines) == 1 18 | return [int(x) for x in lines[0].split(',')] 19 | 20 | 21 | async def receiveByte(dut, bitDelay: int, byte: int): 22 | print(f"Sending byte {byte}") 23 | # Start bit 24 | dut.io_uartRx.value = 0 25 | await ClockCycles(dut.clock, bitDelay) 26 | # Byte 27 | for i in range(8): 28 | dut.io_uartRx.value = (byte >> i) & 0x1 29 | await ClockCycles(dut.clock, bitDelay) 30 | # Stop bit 31 | dut.io_uartRx.value = 1 32 | await ClockCycles(dut.clock, bitDelay) 33 | print(f"Sent byte {byte}") 34 | 35 | 36 | async def transferByte(dut, bitDelay: int) -> int: 37 | print("Receiving a byte") 38 | byte = 0 39 | # Assumes start bit has already been seen 40 | await ClockCycles(dut.clock, bitDelay) 41 | # Byte 42 | for i in range(8): 43 | byte = dut.io_uartTx.value << i | byte 44 | await ClockCycles(dut.clock, bitDelay) 45 | # Stop bit 46 | assert dut.io_uartTx.value == 1 47 | await ClockCycles(dut.clock, bitDelay) 48 | print(f"Received {byte}") 49 | return byte 50 | 51 | spikes: List[int] = [] 52 | 53 | 54 | async def fetchSpikes(dut, receiveDone: Event, bitDelay: int) -> List[int]: 55 | while True: 56 | if not receiveDone.is_set(): 57 | if dut.io_uartTx.value == 0: 58 | s = await transferByte(dut, bitDelay) 59 | if s < 200: 60 | spikes.append(s) 61 | print(f"Received spike {s}") 62 | else: 63 | await ClockCycles(dut.clock, 1) 64 | else: 65 | break 66 | return spikes 67 | 68 | @cocotb.test() 69 | async def neuromorphic_processor_tb(dut): 70 | bitDelay = int(FREQ / BAUDRATE) + 1 71 | # Reference image and results 72 | image = fetch("../../test/resources/NeuromorphicProcessor/image.txt") 73 | results = fetch("../../test/resources/NeuromorphicProcessor/results_round.txt") if (USEROUNDEDWGHTS) else fetch("../../test/resources/NeuromorphicProcessor/results_toInt.txt") 74 | 75 | print(datetime.datetime.now()) 76 | # Clock is generated inside Python 77 | cocotb.start_soon(Clock(dut.clock, 2, units="ps").start()) 78 | await ClockCycles(dut.clock, 2) 79 | 80 | # Reset inputs 81 | dut.io_uartRx.value = 1 82 | # assert dut.io_uartTx.value == 1 83 | dut.reset.value = 1 84 | await ClockCycles(dut.clock, 1) 85 | dut.reset.value = 0 86 | await ClockCycles(dut.clock, 1) 87 | assert dut.io_uartTx.value == 1 88 | 89 | # Spawn a receiver thread 90 | receiveDone = Event("receiving done") 91 | cocotb.start_soon(fetchSpikes(dut, receiveDone, bitDelay)) 92 | 93 | # Load an image into the accelerator ... 94 | print("Loading image into accelerator") 95 | for i in range(len(image)): 96 | # Write top byte of index, bottom byte of index, top byte of rate, 97 | # and bottom byte of rate 98 | await receiveByte(dut, bitDelay, (i >> 8) & 0xff) 99 | await receiveByte(dut, bitDelay, i & 0xff) 100 | await receiveByte(dut, bitDelay, (image[i] >> 8) & 0xff) 101 | await receiveByte(dut, bitDelay, image[i] & 0xff) 102 | print("Done loading image - ") 103 | 104 | # ... get its response 105 | print("getting accelerator's response") 106 | await ClockCycles(dut.clock, int(FREQ/2)) 107 | receiveDone.set() 108 | 109 | print(datetime.datetime.now()) 110 | print("Response received - comparing results") 111 | assert len(spikes) == len(results), "number of spikes does not match expected" 112 | for i in range(len(spikes)): 113 | assert spikes[i] == results[i], "spikes do not match expected: {spikes[i]} != {results[i]}" 114 | print(datetime.datetime.now()) 115 | -------------------------------------------------------------------------------- /vlog/NeuromorphicProcessor/NeuromorphicProcessor_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ps/1ps 2 | 3 | module NeuromorphicProcessor_tb(); 4 | reg clock = 0; 5 | always #(1) clock = ~clock; 6 | reg reset = 0; 7 | 8 | wire uartTx; 9 | reg uartRx = 1'b1; 10 | parameter FREQ = 'd80_000_000; 11 | parameter BAUDRATE = 'd115200; 12 | parameter bitDelay = $rtoi(FREQ / BAUDRATE) + 1; 13 | 14 | `include "../../test/resources/NeuromorphicProcessor/image.sv" 15 | `include "../../test/resources/NeuromorphicProcessor/results_round.sv" 16 | 17 | NeuromorphicProcessor dut ( 18 | .clock(clock), 19 | .reset(reset), 20 | .io_uartTx(uartTx), 21 | .io_uartRx(uartRx) 22 | ); 23 | 24 | task receiveByte(input [7:0] b); 25 | integer i; 26 | begin 27 | //$display("Sending byte %d\n", b); 28 | // Start bit 29 | uartRx = 1'b0; 30 | repeat (bitDelay) @(posedge clock); 31 | // Byte 32 | for (i = 0; i < 8; i = i + 1) begin 33 | uartRx = b[i]; 34 | repeat (bitDelay) @(posedge clock); 35 | end 36 | // Stop bit 37 | uartRx = 1'b1; 38 | repeat (bitDelay) @(posedge clock); 39 | end 40 | endtask 41 | 42 | task transferByte(output [7:0] b); 43 | integer j; 44 | begin 45 | b = 0; 46 | // Start bit, assume start bit has already been seen 47 | repeat (bitDelay) @(posedge clock); 48 | // Byte 49 | for (j = 0; j < 8; j = j + 1) begin 50 | b = (uartTx << j) | b; 51 | repeat (bitDelay) @(posedge clock); 52 | end 53 | // Stop bit 54 | assert(uartTx == 1'b1); // stop bit 55 | // repeat (bitDelay) @(posedge clock); 56 | // $display("Received byte %d\n", b); 57 | end 58 | endtask 59 | 60 | reg [7:0] spikes [$]; 61 | reg receive = 1'b1; 62 | reg [7:0] byteSeen; 63 | integer k; 64 | initial begin 65 | // $vcdpluson; 66 | //$fsdbDumpfile("dump.fsdb"); 67 | //$fsdbDumpvars(); 68 | //$fsdbDumpon; 69 | 70 | @(posedge clock); 71 | uartRx = 1'b1; 72 | // assert(uartTx == 1'b1); 73 | reset = 1'b1; 74 | repeat (3) @(posedge clock); #1; 75 | reset = 1'b0; 76 | assert(uartTx == 1'b1); 77 | // @(posedge clock); 78 | 79 | fork 80 | begin 81 | while (receive) begin 82 | if (!uartTx) begin 83 | transferByte(byteSeen); 84 | if (byteSeen < 200) begin 85 | spikes.push_back(byteSeen); 86 | end 87 | $display("Received spike %d", byteSeen); 88 | end 89 | @(posedge clock); 90 | end 91 | end 92 | begin 93 | // Load an image into the accelerator ... 94 | $display("Loading image into accelerator"); 95 | for (k = 0; k < $size(image); k = k + 1) begin 96 | // Write top byte of index, bottom byte of index, top byte 97 | // of rate, and bottom byte of rate 98 | receiveByte((k >> 8)); 99 | receiveByte((k & 8'hff)); 100 | receiveByte((image[k] >> 8)); 101 | receiveByte((image[k] & 8'hff)); 102 | end 103 | $display("Done loading image - "); 104 | 105 | // ... get its response 106 | $display("getting accelerator's response"); 107 | repeat (FREQ/2) @(posedge clock); 108 | receive = 1'b0; 109 | // rec.join, receiver thread will finish on its own 110 | end 111 | join 112 | 113 | 114 | $display("Response received - comparing results"); 115 | // println(spikes.deep.mkString(",")) 116 | 117 | assert($size(spikes) == $size(results)) else $error("number of spikes does not match expected"); 118 | for (k = 0; k < $size(results); k = k + 1) begin 119 | assert(results[k] == spikes[k]) else $error("spikes do not match, got %d expected %d", spikes[k], results[k]); 120 | end 121 | 122 | $display("%t", $time); 123 | $finish(); 124 | end 125 | 126 | endmodule 127 | -------------------------------------------------------------------------------- /src/simcommand/runtime/Primitives.scala: -------------------------------------------------------------------------------- 1 | package simcommand.runtime 2 | 3 | import simcommand.runtime.Bridges._ 4 | import sourcecode.{Enclosing, FileName, Line} 5 | 6 | object Primitives { 7 | case class SourceInfo(line: Line, fileName: FileName, enclosing: Enclosing) 8 | object SourceInfo { 9 | def getSourceInfo(implicit line: Line, fileName: FileName, enclosing: Enclosing): SourceInfo = { 10 | SourceInfo(line, fileName, enclosing) 11 | } 12 | } 13 | 14 | /** 15 | * This class represents an RTL simulation command and its return value 16 | * 17 | * @tparam R Type of the command's return value 18 | */ 19 | sealed abstract class Command[R](implicit sourceInfo: SourceInfo) { 20 | val name: String 21 | 22 | final def map[R2](f: R => R2): Command[R2] = { 23 | flatMap(r => Return(f(r))) 24 | } 25 | 26 | final def flatMap[R2](f: R => Command[R2]): Command[R2] = { 27 | this match { 28 | case Return(retval) => f(retval) 29 | case c: Command[R] => Cont(c, f) 30 | } 31 | } 32 | 33 | // tailRec provides an efficient tail recursion primitive. If the provided 34 | // function takes in arguments and returns Command[Left(newArguments)], it 35 | // flatMaps again with the new arguments. If given a 36 | // Command[Right(result)], it will instead lift the internal result. 37 | final def tailRec[R2](f: R => Command[Either[R, R2]]): Command[R2] = { 38 | this.flatMap(Rec(_, f)) 39 | } 40 | 41 | // tailRecM is the functional version of tailRecM. This version is 42 | // also safe, but tends to be significantly slower. 43 | // Similar implementation as cats.Free: https://github.com/typelevel/cats/pull/1041/files#diff-7349edfd077f9612f7181fe1f8caca63ac667c847ce83b53dceae4d08040fd55 44 | final def tailRecM[R2](f: R => Command[Either[R, R2]]): Command[R2] = { 45 | this.flatMap(f).flatMap { 46 | // recursion here is lazy so the stack won't blow up 47 | case Left(_) => tailRecM(f) 48 | case Right(result) => Return(result) 49 | } 50 | } 51 | 52 | def sourceInfoString: String = { 53 | name + "(" + sourceInfo.fileName.value + ":" + sourceInfo.line.value + ")" 54 | } 55 | } 56 | 57 | // Lift a pure value into the Command monad 58 | private[simcommand] case class Return[R](retval: R)(implicit sourceInfo: SourceInfo) extends Command[R] { 59 | override val name: String = "return" 60 | } 61 | 62 | // Continuations, recursion primitives 63 | private[simcommand] case class Cont[R1, R2](a: Command[R1], f: R1 => Command[R2])(implicit sourceInfo: SourceInfo) extends Command[R2] { 64 | override val name: String = "cont" 65 | } 66 | private[simcommand] case class Rec[R1, R2](st: R1, f: R1 => Command[Either[R1, R2]])(implicit sourceInfo: SourceInfo) extends Command[R2] { 67 | override val name: String = "rec" 68 | } 69 | 70 | // Basic DUT interaction 71 | private[simcommand] case class Poke[I](signal: Interactable[I], value: I)(implicit sourceInfo: SourceInfo) extends Command[Unit] { 72 | override val name: String = "poke" 73 | } 74 | private[simcommand] case class Peek[I](signal: Interactable[I])(implicit sourceInfo: SourceInfo) extends Command[I] { 75 | override val name: String = "peek" 76 | } 77 | private[simcommand] case class Step(cycles: Int)(implicit sourceInfo: SourceInfo) extends Command[Unit] { 78 | override val name: String = "step" 79 | } 80 | 81 | // Fork/Join simulation threading 82 | private[simcommand] case class ThreadHandle[R](id: Int) 83 | private[simcommand] case class Fork[R](c: Command[R], threadName: Option[String], order: Int)(implicit sourceInfo: SourceInfo) extends Command[ThreadHandle[R]] { 84 | def makeThreadHandle(id: Int): ThreadHandle[R] = ThreadHandle[R](id) 85 | override val name: String = "fork" 86 | } 87 | private[simcommand] case class Join[R](threadHandle: ThreadHandle[R])(implicit sourceInfo: SourceInfo) extends Command[R] { 88 | override val name: String = "join" 89 | } 90 | private[simcommand] case class Kill[R](threadHandle: ThreadHandle[R])(implicit sourceInfo: SourceInfo) extends Command[R] { 91 | override val name: String = "kill" 92 | } 93 | 94 | // Inter-thread communication via channels 95 | private[simcommand] case class ChannelHandle[T](id: Int) 96 | private[simcommand] case class MakeChannel[T](size: Int)(implicit sourceInfo: SourceInfo) extends Command[ChannelHandle[T]] { 97 | override val name: String = "makeChannel" 98 | } 99 | private[simcommand] case class Put[T](chan: ChannelHandle[T], data: T)(implicit sourceInfo: SourceInfo) extends Command[Unit] { 100 | override val name: String = "put" 101 | } 102 | private[simcommand] case class GetBlocking[T](chan: ChannelHandle[T])(implicit sourceInfo: SourceInfo) extends Command[T] { 103 | override val name: String = "getBlocking" 104 | } 105 | private[simcommand] case class NonEmpty[T](chan: ChannelHandle[T])(implicit sourceInfo: SourceInfo) extends Command[Boolean] { 106 | override val name: String = "nonEmpty" 107 | } 108 | } -------------------------------------------------------------------------------- /test/benchmarks/NeuromorphicProcessorManualThreadTester.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import chiseltest._ 4 | import chisel3._ 5 | import chiseltest.internal.NoThreadingAnnotation 6 | import org.scalatest.tagobjects.Slow 7 | 8 | import scala.collection.mutable 9 | 10 | class NeuromorphicProcessorManualThreadTester extends NeuromorphicProcessorTester { 11 | it should "process an image" taggedAs(Verilator, Slow) in { 12 | val startElab = System.nanoTime() 13 | test(new NeuromorphicProcessorBBWrapper()) 14 | .withAnnotations(Seq(VerilatorBackendAnnotation, NoThreadingAnnotation)) { dut => 15 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation") 16 | val startTest = System.nanoTime() 17 | var cycles = 1L 18 | dut.clock.setTimeout(FREQ) 19 | 20 | // Reset inputs 21 | dut.io.io_uartRx.poke(true.B) 22 | dut.io.io_uartTx.expect(true.B) 23 | dut.reset.poke(true.B) 24 | dut.clock.step() 25 | cycles += 1 26 | dut.reset.poke(false.B) 27 | dut.io.io_uartTx.expect(true.B) 28 | 29 | // Spawn a receiver thread 30 | val spikeRxThread = new SpikeReceiver(bitDelay) 31 | 32 | // Load an image into the accelerator ... 33 | val bytes = image.indices.flatMap { i => 34 | Seq((i >> 8) & 0xff, i & 0xff, (image(i) >> 8) & 0xff, image(i) & 0xff) 35 | } 36 | val txThread = new UartTxThread(bitDelay, bytes) 37 | 38 | def step(): Unit = { 39 | spikeRxThread.step(dut) 40 | txThread.step(dut) 41 | dut.clock.step() 42 | cycles += 1 43 | } 44 | 45 | println("Loading image into accelerator") 46 | while (!txThread.done) { 47 | step() 48 | } 49 | print("Done loading image - ") 50 | 51 | // ... get its response 52 | println("getting accelerator's response") 53 | (0 until (FREQ / 2)).foreach { ii => 54 | step() 55 | } 56 | // join 57 | while (!spikeRxThread.done) { 58 | step() 59 | } 60 | 61 | println("Response received - comparing results") 62 | val spikes = spikeRxThread.spikes 63 | assert(spikes.length == results.length, "number of spikes does not match expected") 64 | assert(spikes.zip(results).map(x => x._1 == x._2).reduce(_ && _), "spikes do not match expected") 65 | 66 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 67 | println(s"Took ${deltaSeconds}s to run test with manual threading and using the chiseltest interface with the NoThreadingAnnotation") 68 | println(s"Executed $cycles cycles at an average frequency of ${cycles / deltaSeconds} Hz") 69 | } 70 | } 71 | } 72 | 73 | trait IsThread { 74 | def done: Boolean 75 | def step(dut: NeuromorphicProcessorBBWrapper): Unit 76 | } 77 | 78 | class SpikeReceiver(bitDelay: Int) extends IsThread { 79 | private val rx = new UartRxThread(bitDelay) 80 | var spikes = Array[Int]() 81 | def done: Boolean = rx.done 82 | def step(dut: NeuromorphicProcessorBBWrapper): Unit = { 83 | rx.step(dut) 84 | rx.get() match { 85 | case Some(s) => 86 | if (s < 200) 87 | spikes = spikes :+ s 88 | println(s"Received spike ${s}") 89 | case None => 90 | } 91 | } 92 | } 93 | 94 | class UartTxThread(bitDelay: Int, toSend: Seq[Int]) extends IsThread { 95 | private var state = 0; 96 | private var delay_count = 0; 97 | private var byte = 0; 98 | private val bytes = mutable.Queue[Int]() 99 | bytes ++= toSend 100 | 101 | def done: Boolean = bytes.isEmpty && delay_count == 0 && state == 0 102 | def step(dut: NeuromorphicProcessorBBWrapper): Unit = { 103 | if (delay_count > 0) { 104 | delay_count -= 1 105 | } else { 106 | state = if (state == 0) { 107 | if(bytes.nonEmpty) { 108 | byte = bytes.dequeue() 109 | // Start bit 110 | dut.io.io_uartRx.poke(false.B) 111 | delay_count = bitDelay - 1 112 | 1 113 | } else { 114 | 0 115 | } 116 | } else if(state >= 1 && state < 9) { 117 | val bit = ((byte >> (state - 1)) & 1) == 1 118 | dut.io.io_uartRx.poke(bit) 119 | delay_count = bitDelay - 1 120 | state + 1 121 | } else { 122 | assert(state == 9) 123 | // Stop bit 124 | dut.io.io_uartRx.poke(true.B) 125 | delay_count = bitDelay - 1 126 | 0 127 | } 128 | } 129 | } 130 | } 131 | 132 | 133 | class UartRxThread(bitDelay: Int) extends IsThread { 134 | private var state = 0; 135 | private var delay_count = 0; 136 | private var byte = 0; 137 | private val bytes = mutable.Queue[Int]() 138 | def get(): Option[Int] = if(bytes.isEmpty) { None } else { Some(bytes.dequeue()) } 139 | 140 | def done: Boolean = bytes.isEmpty && delay_count == 0 && state == 0 141 | def step(dut: NeuromorphicProcessorBBWrapper): Unit = { 142 | if (delay_count > 0) { 143 | delay_count -= 1 144 | } else { 145 | state = if (state == 0) { 146 | if (!dut.io.io_uartTx.peekBoolean()) { 147 | delay_count = bitDelay - 1 148 | byte = 0 149 | 1 150 | } else { 151 | 0 152 | } 153 | } else if(state >= 1 && state < 9) { 154 | byte = (dut.io.io_uartTx.peek().litValue.toInt << (state - 1)) | byte 155 | delay_count = bitDelay - 1 156 | state + 1 157 | } else { 158 | assert(state == 9) 159 | bytes.enqueue(byte) 160 | dut.io.io_uartTx.expect(true) 161 | 0 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /test/simcommand/UARTCommandSpec.scala: -------------------------------------------------------------------------------- 1 | package simcommand 2 | 3 | import chisel3._ 4 | import chiseltest.{VerilatorBackendAnnotation, WriteVcdAnnotation, testableClock} 5 | import chisel3.util.log2Ceil 6 | import chiseltest.internal.NoThreadingAnnotation 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import simcommand.runtime.{Config, Result} 9 | import simcommand.vips.{UART, UARTChecker} 10 | 11 | class UARTCommandSpec extends AnyFlatSpec with SimcommandScalatestTester { 12 | class UARTMock(txBytes: Seq[Int], bitDelay: Int) extends Module { 13 | val rx = IO(Input(Bool())) 14 | val tx = IO(Output(Bool())) 15 | 16 | def byteToPerCycleBits(bitDelay: Int, byte: Int): Seq[Bool] = { 17 | Seq.fill(bitDelay)(1.B) ++ // Idle 18 | Seq.fill(bitDelay)(0.B) ++ // start bit 19 | (0 until 8).flatMap { i => Seq.fill(bitDelay)(((byte >> i) & 0x1).B) } ++ 20 | Seq.fill(bitDelay)(1.B) // stop bit 21 | } 22 | 23 | if (txBytes.isEmpty) { 24 | tx := 1.B 25 | } else { 26 | val bitsToSend = VecInit(txBytes.flatMap(byteToPerCycleBits(bitDelay, _))) 27 | val cycle = RegInit(0.U(log2Ceil(bitsToSend.length).W)) 28 | cycle := cycle + 1.U 29 | tx := bitsToSend(cycle) 30 | } 31 | } 32 | 33 | class UARTLoopback() extends Module { 34 | val rx = IO(Input(Bool())) 35 | val tx = IO(Output(Bool())) 36 | 37 | tx := RegNext(rx, 1.B) 38 | } 39 | 40 | "sendByte" should "produce the right sequence" in { 41 | val testByte = 0x55 42 | val bitDelay = 4 43 | testChisel(new UARTMock(Seq.empty, bitDelay)).withAnnotations(Seq(WriteVcdAnnotation)) { c => 44 | c.clock.setTimeout(100) 45 | val cmds = new UART(uartIn=c.rx, uartOut=c.tx, cyclesPerBit = bitDelay) 46 | val chkr = new UARTChecker(c.rx) 47 | val program = for { 48 | _ <- cmds.sendReset() 49 | checkerHandle <- fork(chkr.checkByte(bitDelay), "checker") 50 | _ <- cmds.sendByte(testByte) 51 | _ <- step(bitDelay*5) 52 | j <- join(checkerHandle) 53 | } yield j 54 | val result = unsafeRun(program, c.clock) 55 | assert(result.retval) // This only checks that the start and stop bits are stable and full 56 | } // TODO: need a check on the sending itself that doesn't depend on receiveByte 57 | } 58 | 59 | "receiveByte" should "receive a single byte sent by the UART" in { 60 | val testByte = Seq(0x55) 61 | val bitDelay = 4 62 | testChisel(new UARTMock(testByte, bitDelay)).withAnnotations(Seq(VerilatorBackendAnnotation, NoThreadingAnnotation)) { c => 63 | val uart = new UART(uartIn = c.rx, uartOut = c.tx, cyclesPerBit = bitDelay) 64 | val result = unsafeRun(uart.receiveByte(), c.clock) 65 | assert(result.retval == testByte.head) 66 | } 67 | } 68 | 69 | "receiveByte" should "receive a single byte sent by the UART (command mock)" in { 70 | val testByte = 0x55 71 | val rx = binding(0.B) 72 | val tx = binding(0.B) 73 | val bitDelay = 4 74 | 75 | val uart = new UART(uartIn = rx, uartOut = tx, cyclesPerBit = bitDelay) 76 | val mock = for { 77 | _ <- poke(tx, 0.B) 78 | _ <- step(bitDelay) 79 | _ <- concat((0 until 8).map(i => for { 80 | _ <- poke(tx, ((testByte >> i) & 0x1).B) 81 | _ <- step(bitDelay) 82 | } yield ())) 83 | _ <- poke(tx, 1.B) 84 | _ <- step(bitDelay) 85 | } yield () 86 | 87 | val program = for { 88 | mockHandle <- fork(mock, name="mock") 89 | receiveHandle <- fork(uart.receiveByte(), name="receiveByte", order=1) 90 | _ <- join(mockHandle) 91 | recv <- join(receiveHandle) 92 | } yield recv 93 | 94 | val result = unsafeRun(program, FakeClock(), Config(timeout=bitDelay * 10)) 95 | assert(result.retval == testByte) 96 | } 97 | 98 | "receiveBytes" should "receive multiple bytes sent by the UART" in { 99 | val testBytes = Seq(0x55, 0xff, 0x00, 0xaa) 100 | val bitDelay = 4 101 | testChisel(new UARTMock(testBytes, bitDelay)).withAnnotations(Seq(WriteVcdAnnotation)) { c => 102 | val cmds = new UART(uartIn = c.rx, uartOut = c.tx, cyclesPerBit = bitDelay) 103 | val result = unsafeRun(cmds.receiveBytes(testBytes.length), c.clock) 104 | assert(result.retval == testBytes) 105 | } 106 | } 107 | 108 | "sendBytes" should "successfully send bytes to a forked receiveBytes through a UART loopback" in { 109 | val testBytes = Seq(0x00, 0x00, 0x55, 0xff, 0x00, 0xaa) 110 | val bitDelay = 4 111 | testChisel(new UARTLoopback()).withAnnotations(Seq(WriteVcdAnnotation)) { c => 112 | val cmds = new UART(uartIn = c.rx, uartOut = c.tx, cyclesPerBit = bitDelay) 113 | val rxChk = new UARTChecker(c.rx) 114 | val txChk = new UARTChecker(c.tx) 115 | 116 | val sender = for { 117 | _ <- cmds.sendReset() 118 | _ <- cmds.sendBytes(testBytes) 119 | } yield () 120 | val receiver = cmds.receiveBytes(testBytes.length) 121 | val rxChecker = rxChk.checkBytes(testBytes.length, bitDelay) 122 | val txChecker = txChk.checkBytes(testBytes.length, bitDelay) 123 | 124 | val program = for { 125 | senderThread <- fork(sender, "sender") 126 | receiverThread <- fork(receiver, "receiver") 127 | rxCheckerThread <- fork(rxChecker, "rxChecker") 128 | txCheckerThread <- fork(txChecker, "txChecker") 129 | _ <- step(bitDelay * cmds.bitsPerSymbol * (testBytes.length + 1)) 130 | receivedBytes <- join(receiverThread) 131 | rxCheckStatus <- join(rxCheckerThread) 132 | txCheckStatus <- join(txCheckerThread) 133 | _ <- join(senderThread) 134 | } yield (receivedBytes, rxCheckStatus && txCheckStatus) 135 | 136 | val result: Result[(Seq[Int], Boolean)] = unsafeRun(program, c.clock) 137 | assert(result.retval._1 == testBytes) 138 | assert(result.retval._2) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /test/benchmarks/NeuromorphicProcessorRawSimulatorTester.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import chiseltest._ 4 | import chisel3._ 5 | import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, DesignAnnotation} 6 | import firrtl.options.TargetDirAnnotation 7 | import chiseltest.simulator.{SimulatorContext} 8 | import firrtl.{AnnotationSeq, EmittedCircuitAnnotation} 9 | import firrtl.annotations.{Annotation, DeletedAnnotation} 10 | import firrtl.stage.FirrtlCircuitAnnotation 11 | import logger.LogLevelAnnotation 12 | import org.scalatest.tagobjects.Slow 13 | 14 | import scala.collection.mutable 15 | 16 | class NeuromorphicProcessorRawSimulatorTester extends NeuromorphicProcessorTester { 17 | 18 | def runTest(dut: SimulatorContext): Long = { 19 | var cycles = 0 20 | // Reset inputs 21 | dut.poke("io_io_uartRx", 1) 22 | assert(dut.peek("io_io_uartTx") == 1) 23 | dut.poke("reset", 1) 24 | dut.step() 25 | cycles += 1 26 | dut.poke("reset", 0) 27 | assert(dut.peek("io_io_uartTx") == 1) 28 | 29 | // Spawn a receiver thread 30 | val spikeRxThread = new SpikeReceiverSim(bitDelay) 31 | 32 | // Load an image into the accelerator ... 33 | val bytes = image.indices.flatMap { i => 34 | Seq((i >> 8) & 0xff, i & 0xff, (image(i) >> 8) & 0xff, image(i) & 0xff) 35 | } 36 | val txThread = new UartTxThreadSim(bitDelay, bytes) 37 | 38 | def step(): Unit = { 39 | spikeRxThread.step(dut) 40 | txThread.step(dut) 41 | dut.step() 42 | cycles += 1 43 | } 44 | 45 | println("Loading image into accelerator") 46 | while (!txThread.done) { 47 | step() 48 | } 49 | print("Done loading image - ") 50 | 51 | // ... get its response 52 | println("getting accelerator's response") 53 | (0 until (FREQ / 2)).foreach { ii => 54 | step() 55 | } 56 | // join 57 | while (!spikeRxThread.done) { 58 | step() 59 | } 60 | 61 | println("Response received - comparing results") 62 | val spikes = spikeRxThread.spikes 63 | assert(spikes.length == results.length, "number of spikes does not match expected") 64 | assert(spikes.zip(results).map(x => x._1 == x._2).reduce(_ && _), "spikes do not match expected") 65 | cycles 66 | } 67 | 68 | it should "process an image" taggedAs(Verilator, Slow) in { 69 | val startElab = System.nanoTime() 70 | 71 | // elaborate and compile to low firrtl 72 | val targetDir = TargetDirAnnotation("test_run_dir/NeuromorphicProcessorRawSimulatorTester") 73 | val stage = new chisel3.stage.ChiselStage() 74 | val r = stage.run(Seq( 75 | ChiselGeneratorAnnotation(() => new NeuromorphicProcessorBBWrapper()), 76 | targetDir, 77 | )) 78 | val state = annosToState(r) 79 | val dut = VerilatorBackendAnnotation.getSimulator.createContext(state) 80 | 81 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation") 82 | 83 | val startTest = System.nanoTime() 84 | // perform reset which is normally implicitly done by the chiseltest library 85 | dut.poke("reset", 1) ; dut.step() ; dut.poke("reset", 0) 86 | val cycles = runTest(dut) + 1 87 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 88 | println(s"Took ${deltaSeconds}s to run test with manual threading and raw simulator interface") 89 | println(s"Executed $cycles cycles at an average frequency of ${cycles / deltaSeconds} Hz") 90 | } 91 | 92 | // copied over from chiseltest 93 | private def annosToState(annos: AnnotationSeq): firrtl.CircuitState = { 94 | val circuit = annos.collectFirst { case FirrtlCircuitAnnotation(c) => c }.get 95 | val filteredAnnos = annos.filterNot(isInternalAnno) 96 | firrtl.CircuitState(circuit, filteredAnnos) 97 | } 98 | private def isInternalAnno(a: Annotation): Boolean = a match { 99 | case _: FirrtlCircuitAnnotation | _: DesignAnnotation[_] | _: ChiselCircuitAnnotation | _: DeletedAnnotation | 100 | _: EmittedCircuitAnnotation[_] | _: LogLevelAnnotation => 101 | true 102 | case _ => false 103 | } 104 | } 105 | 106 | trait IsThreadSim { 107 | def done: Boolean 108 | def step(dut: SimulatorContext): Unit 109 | } 110 | 111 | class SpikeReceiverSim(bitDelay: Int) extends IsThreadSim { 112 | private val rx = new UartRxThreadSim(bitDelay) 113 | var spikes = Array[Int]() 114 | def done: Boolean = rx.done 115 | def step(dut: SimulatorContext): Unit = { 116 | rx.step(dut) 117 | rx.get() match { 118 | case Some(s) => 119 | if (s < 200) 120 | spikes = spikes :+ s 121 | println(s"Received spike ${s}") 122 | case None => 123 | } 124 | } 125 | } 126 | 127 | class UartTxThreadSim(bitDelay: Int, toSend: Seq[Int]) extends IsThreadSim { 128 | private var state = 0; 129 | private var delay_count = 0; 130 | private var byte = 0; 131 | private val bytes = mutable.Queue[Int]() 132 | bytes ++= toSend 133 | 134 | def done: Boolean = bytes.isEmpty && delay_count == 0 && state == 0 135 | def step(dut: SimulatorContext): Unit = { 136 | if (delay_count > 0) { 137 | delay_count -= 1 138 | } else { 139 | state = if (state == 0) { 140 | if(bytes.nonEmpty) { 141 | byte = bytes.dequeue() 142 | // Start bit 143 | dut.poke("io_io_uartRx", 0) 144 | delay_count = bitDelay - 1 145 | 1 146 | } else { 147 | 0 148 | } 149 | } else if(state >= 1 && state < 9) { 150 | val bit = ((byte >> (state - 1)) & 1) 151 | dut.poke("io_io_uartRx", bit) 152 | delay_count = bitDelay - 1 153 | state + 1 154 | } else { 155 | assert(state == 9) 156 | // Stop bit 157 | dut.poke("io_io_uartRx", 1) 158 | delay_count = bitDelay - 1 159 | 0 160 | } 161 | } 162 | } 163 | } 164 | 165 | 166 | class UartRxThreadSim(bitDelay: Int) extends IsThreadSim { 167 | private var state = 0; 168 | private var delay_count = 0; 169 | private var byte = 0; 170 | private val bytes = mutable.Queue[Int]() 171 | def get(): Option[Int] = if(bytes.isEmpty) { None } else { Some(bytes.dequeue()) } 172 | 173 | def done: Boolean = bytes.isEmpty && delay_count == 0 && state == 0 174 | def step(dut: SimulatorContext): Unit = { 175 | if (delay_count > 0) { 176 | delay_count -= 1 177 | } else { 178 | state = if (state == 0) { 179 | if (dut.peek("io_io_uartTx") == 0) { 180 | delay_count = bitDelay - 1 181 | byte = 0 182 | 1 183 | } else { 184 | 0 185 | } 186 | } else if(state >= 1 && state < 9) { 187 | byte = (dut.peek("io_io_uartTx").toInt << (state - 1)) | byte 188 | delay_count = bitDelay - 1 189 | state + 1 190 | } else { 191 | assert(state == 9) 192 | bytes.enqueue(byte) 193 | assert(dut.peek("io_io_uartTx") == 1, "stop bit") 194 | 0 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/simcommand/package.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.mutable.ArrayBuffer 2 | import sourcecode.{Enclosing, FileName, Line} 3 | import simcommand.runtime.Primitives._ 4 | import simcommand.runtime.Bridges._ 5 | import simcommand.runtime.{Bridges, Config, Imperative, Result} 6 | 7 | package object simcommand { 8 | type Command[R] = simcommand.runtime.Primitives.Command[R] 9 | type Interactable[I] = simcommand.runtime.Bridges.Interactable[I] 10 | type ChannelHandle[T] = simcommand.runtime.Primitives.ChannelHandle[T] 11 | val FakeClock: Bridges.FakeClock.type = simcommand.runtime.Bridges.FakeClock 12 | 13 | // Public API 14 | def unsafeRun[R](cmd: Command[R], clock: chisel3.Clock, cfg: Config = Config()): Result[R] = { 15 | unsafeRun(cmd, Chisel3Clock(clock), cfg) 16 | } 17 | 18 | def unsafeRun[R](cmd: Command[R], clock: Steppable, cfg: Config): Result[R] = { 19 | Imperative.unsafeRun(cmd, clock, cfg) 20 | } 21 | 22 | def poke[I](signal: Interactable[I], value: I)(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[Unit] = { 23 | Poke(signal, value)(SourceInfo(line, fileName, enclosing)) 24 | } 25 | 26 | def peek[I](signal: Interactable[I])(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[I] = { 27 | Peek(signal)(SourceInfo(line, fileName, enclosing)) 28 | } 29 | 30 | def step(cycles: Int)(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[Unit] = { 31 | Step(cycles)(SourceInfo(line, fileName, enclosing)) 32 | } 33 | 34 | def lift[R](value: R)(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[R] = { 35 | Return(value)(SourceInfo(line, fileName, enclosing)) 36 | } 37 | 38 | def noop()(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[Unit] = { 39 | lift(()) 40 | } 41 | 42 | def fork[R](cmd: Command[R])(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[ThreadHandle[R]] = { 43 | Fork(cmd, None, order = 0)(SourceInfo(line, fileName, enclosing)) 44 | } 45 | 46 | def fork[R](cmd: Command[R], name: String, order: Int = 0)(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[ThreadHandle[R]] = { 47 | Fork(cmd, Some(name), order)(SourceInfo(line, fileName, enclosing)) 48 | } 49 | 50 | def join[R](handle: ThreadHandle[R])(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[R] = { 51 | Join(handle)(SourceInfo(line, fileName, enclosing)) 52 | } 53 | 54 | def makeChannel[R](size: Int)(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[ChannelHandle[R]] = { 55 | MakeChannel(size)(SourceInfo(line, fileName, enclosing)) 56 | } 57 | 58 | def put[R](chan: ChannelHandle[R], data: R)(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[Unit] = { 59 | Put(chan, data)(SourceInfo(line, fileName, enclosing)) 60 | } 61 | 62 | def getBlocking[R](chan: ChannelHandle[R])(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[R] = { 63 | GetBlocking(chan)(SourceInfo(line, fileName, enclosing)) 64 | } 65 | 66 | def nonEmpty[R](chan: ChannelHandle[R])(implicit line: Line, fileName: FileName, enclosing: Enclosing): Command[Boolean] = { 67 | NonEmpty(chan)(SourceInfo(line, fileName, enclosing)) 68 | } 69 | 70 | def binding[I](value: I): Interactable[I] = { 71 | PrimitiveInteractor(value) 72 | } 73 | 74 | // Command combinators (functions that take Commands and return Commands) 75 | def repeat(cmd: Command[_], n: Int): Command[Unit] = { 76 | lift(0).tailRec { iteration => 77 | if (iteration == n) lift(Right(())) 78 | else for { 79 | _ <- cmd 80 | } yield Left(iteration + 1) 81 | } 82 | } 83 | 84 | def repeatCollect[R](cmd: Command[R], n: Int): Command[Seq[R]] = { 85 | lift((0, ArrayBuffer.empty[R])).tailRec { case (it, buf) => 86 | if (it == n) lift(Right(buf.toSeq)) 87 | else cmd.flatMap {value => lift(Left((it + 1, buf += value)))} 88 | } 89 | } 90 | 91 | def doWhile(cmd: Command[Boolean]): Command[Unit] = { 92 | lift(()).tailRec { _: Unit => 93 | cmd.flatMap {if (_) lift(Left(())) else lift(Right(()))} 94 | } 95 | } 96 | 97 | def doWhileCollect[R](cmd: Command[(R, Boolean)]): Command[Seq[R]] = { 98 | lift(ArrayBuffer.empty[R]).tailRec { buf: ArrayBuffer[R] => 99 | for { 100 | result <- cmd 101 | retval <- { 102 | val (v, cond) = result 103 | if (cond) lift(Left(buf += v)) else lift(Right(buf.toSeq)) 104 | } 105 | } yield retval 106 | } 107 | } 108 | 109 | def doWhileCollectLast[R](cmd: Command[(R, Boolean)]): Command[R] = { 110 | lift(()).tailRec { _: Unit => 111 | for { 112 | result <- cmd 113 | retval <- { 114 | val (v, cond) = result 115 | if (cond) lift(Left(())) else lift(Right(v)) 116 | } 117 | } yield retval 118 | } 119 | } 120 | 121 | def whileM[R, S](cond: S => Boolean, action: Command[(R, S)], initialState: S): Command[Seq[R]] = { 122 | lift((initialState, Seq[R]())).tailRec { t: (S, Seq[R]) => 123 | val (state: S, seq: Seq[R]) = t 124 | if (cond(state)) for { 125 | result <- action 126 | value <- { 127 | val (v, newState) = result 128 | lift(Left((newState, seq :+ v))) 129 | } 130 | } yield value 131 | else lift(Right(seq)) 132 | } 133 | } 134 | 135 | def forever(cmd: Command[_]): Command[Nothing] = { 136 | lift(()).tailRec[Nothing] { _: Unit => 137 | lift(Left(())) 138 | } 139 | } 140 | 141 | def zip[R1, R2](cmd1: Command[R1], cmd2: Command[R2]): Command[(R1, R2)] = { 142 | for { 143 | v1 <- cmd1 144 | v2 <- cmd2 145 | } yield (v1, v2) 146 | } 147 | 148 | def map2[R1, R2, R3](cmd1: Command[R1], cmd2: Command[R2], f: (R1, R2) => R3): Command[R3] = { 149 | for { 150 | v1 <- cmd1 151 | v2 <- cmd2 152 | } yield f(v1, v2) 153 | } 154 | 155 | // See Cats 'Traverse' which provides 'sequence' which is exactly this type signature 156 | def sequence[R](cmds: Seq[Command[R]]): Command[Seq[R]] = { 157 | lift((cmds, Vector.empty[R])).tailRec { case (cmds, retvalSeq) => 158 | if (cmds.isEmpty) lift(Right(retvalSeq)) 159 | else for { 160 | retval <- cmds.head 161 | } yield Left((cmds.tail, retvalSeq :+ retval)) 162 | } 163 | } 164 | 165 | def traverse[R, R2](cmds: Seq[Command[R]])(f: R => Command[R2]): Command[Seq[R2]] = { 166 | sequence(cmds.map(_.flatMap(f))) 167 | } 168 | 169 | def concat[R](cmds: Seq[Command[R]]): Command[Unit] = { 170 | lift(cmds).tailRec { cmds => 171 | if (cmds.isEmpty) lift(Right(())) 172 | else for { 173 | _ <- cmds.head 174 | } yield Left(cmds.tail) 175 | } 176 | } 177 | 178 | def chain[R](cmds: Seq[R => Command[R]], initialRetval: R): Command[R] = { 179 | val initProgram: Command[R] = for { 180 | r <- lift(initialRetval) 181 | c <- cmds.head(r) 182 | } yield c 183 | 184 | cmds.tail.foldLeft(initProgram) { (c1: Command[R], c2: R => Command[R]) => 185 | for { 186 | c1Ret <- c1 187 | c2Ret <- c2(c1Ret) 188 | } yield c2Ret 189 | } 190 | } 191 | 192 | def waitForValue[I](signal: Interactable[I], value: I, cycles: Int = 1): Command[Unit] = { 193 | // TODO: return # of cycles this program waited 194 | doWhile(for { 195 | done <- signal.compare(value) 196 | _ <- {if (!done) step(cycles) else noop()} 197 | } yield !done) 198 | } 199 | 200 | def checkStable[I](signal: Interactable[I], value: I, cycles: Int): Command[Boolean] = { 201 | repeatCollect( 202 | for { 203 | good <- signal.compare(value) 204 | _ <- step(1) 205 | } yield good, 206 | cycles 207 | ).map(_.forall(x => x)) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /test/benchmarks/DecoupledGcdChiseltestTester.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import chisel3._ 4 | import chisel3.experimental.BundleLiterals._ 5 | import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, DesignAnnotation} 6 | import chiseltest._ 7 | import chiseltest.internal.NoThreadingAnnotation 8 | import chiseltest.simulator.{SimulatorAnnotation, SimulatorContext} 9 | import firrtl.annotations.{Annotation, DeletedAnnotation} 10 | import firrtl.options.TargetDirAnnotation 11 | import firrtl.stage.FirrtlCircuitAnnotation 12 | import firrtl.{AnnotationSeq, EmittedCircuitAnnotation} 13 | import logger.LogLevelAnnotation 14 | import org.scalatest.flatspec.AnyFlatSpec 15 | import org.scalatest.tagobjects.Slow 16 | import simcommand._ 17 | import simcommand.vips.Decoupled 18 | 19 | class DecoupledGcdChiseltestTester extends AnyFlatSpec with ChiselScalatestTester { 20 | behavior of "DecoupledGcd" 21 | 22 | // play with these to adjust test execution time 23 | val (maxX, maxY) = (40, 40) 24 | val testValues = for {x <- 2 to maxX; y <- 2 to maxY} yield (BigInt(x), BigInt(y), BigInt(x).gcd(y)) 25 | val bitWidth = 60 26 | val inputBundles = testValues.map { case (a, b, _) => 27 | new GcdInputBundle(bitWidth).Lit(_.value1 -> a.U, _.value2 -> b.U) 28 | } 29 | val outputBundles = testValues.map { case (a, b, c) => 30 | new GcdOutputBundle(bitWidth).Lit(_.value1 -> a.U, _.value2 -> b.U, _.gcd -> c.U) 31 | } 32 | 33 | def runWithChiseltestThreads(backend: SimulatorAnnotation): Unit = { 34 | val simName = backend.getSimulator.name 35 | 36 | val startElab = System.nanoTime() 37 | test(new DecoupledGcd(bitWidth)).withAnnotations(Seq(backend)) { dut => 38 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation w/ $simName") 39 | val startTest = System.nanoTime() 40 | 41 | dut.reset.poke(true.B) 42 | dut.clock.step(2) 43 | dut.reset.poke(false.B) 44 | 45 | dut.input.initSource().setSourceClock(dut.clock) 46 | dut.output.initSink().setSinkClock(dut.clock) 47 | dut.clock.setTimeout(0) 48 | 49 | chiseltest.fork { 50 | dut.input.enqueueSeq(inputBundles) 51 | }.fork { 52 | dut.output.expectDequeueSeq(outputBundles) 53 | }.join() 54 | 55 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 56 | println(s"Took ${deltaSeconds}s to run test with chiseltest threads w/ $simName") 57 | } 58 | } 59 | 60 | def runWithCommandAPI(backend: SimulatorAnnotation): Unit = { 61 | val simName = backend.getSimulator.name 62 | 63 | val startElab = System.nanoTime() 64 | test(new DecoupledGcd(bitWidth)).withAnnotations(Seq(backend, NoThreadingAnnotation, WriteVcdAnnotation)) { dut => 65 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation w/ $simName") 66 | val startTest = System.nanoTime() 67 | val inputCmds = new Decoupled(dut.input) 68 | val outputCmds = new Decoupled(dut.output) 69 | 70 | dut.reset.poke(true.B) 71 | dut.clock.step(2) 72 | dut.reset.poke(false.B) 73 | dut.clock.setTimeout(1000) 74 | 75 | val program: Command[Seq[UInt]] = for { 76 | pushThread <- simcommand.fork(inputCmds.enqueueSeq(inputBundles.toVector), "push") 77 | pullThread <- simcommand.fork(repeatCollect(outputCmds.dequeue(_.gcd), outputBundles.length),"pull") 78 | _ <- join(pushThread) 79 | output <- join(pullThread) 80 | } yield output 81 | 82 | val result = unsafeRun(program, dut.clock) 83 | Predef.assert(result.retval.length == outputBundles.length) 84 | result.retval.zip(outputBundles).foreach { case (actual, gold) => 85 | Predef.assert(actual.litValue == gold.gcd.litValue) 86 | // Predef.assert(actual.value1.litValue == gold.value1.litValue) 87 | // Predef.assert(actual.value2.litValue == gold.value2.litValue) 88 | } 89 | 90 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 91 | println(s"Took ${deltaSeconds}s to run test with command API w/ $simName") 92 | println(s"Executed ${result.cycles} cycles at an average frequency of ${result.cycles / deltaSeconds} Hz") 93 | } 94 | } 95 | 96 | private def runTestDut(dut: DecoupledGcd, testValues: Iterable[(BigInt, BigInt, BigInt)]): Long = { 97 | var cycles = 0L 98 | dut.reset.poke(true.B) 99 | dut.clock.step(2) 100 | cycles += 2 101 | dut.reset.poke(false.B) 102 | 103 | dut.output.ready.poke(true.B) 104 | for((i, j, expected) <- testValues) { 105 | dut.input.bits.value1.poke(i.U) 106 | dut.input.bits.value2.poke(j.U) 107 | dut.input.valid.poke(true.B) 108 | dut.clock.step(1) 109 | cycles += 1 110 | 111 | while(!dut.output.valid.peek().litToBoolean) { 112 | dut.clock.step(1) 113 | cycles += 1 114 | } 115 | dut.output.bits.gcd.expect(expected.U) 116 | dut.output.valid.expect(true.B) 117 | } 118 | 119 | cycles 120 | } 121 | 122 | def runWithChiseltestSingleThread(backend: SimulatorAnnotation): Unit = { 123 | val simName = backend.getSimulator.name 124 | val startElab = System.nanoTime() 125 | test(new DecoupledGcd(bitWidth)).withAnnotations(Seq(backend, NoThreadingAnnotation)) { dut => 126 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation w/ $simName") 127 | val startTest = System.nanoTime() 128 | val cycles = runTestDut(dut, testValues) 129 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 130 | println(s"Took ${deltaSeconds}s to run test with chiseltest but no threads w/ $simName") 131 | println(s"Executed $cycles cycles at an average frequency of ${cycles / deltaSeconds} Hz") 132 | } 133 | } 134 | 135 | 136 | private def runTestSim(dut: SimulatorContext, testValues: Iterable[(BigInt, BigInt, BigInt)]): Long = { 137 | var cycles = 0L 138 | dut.poke("reset", 1) 139 | dut.step(2) 140 | cycles += 2 141 | dut.poke("reset", 0) 142 | 143 | dut.poke("output_ready", 1) 144 | for((i, j, expected) <- testValues) { 145 | dut.poke("input_bits_value1", i) 146 | dut.poke("input_bits_value2", j) 147 | dut.poke("input_valid", 1) 148 | dut.step(1) 149 | cycles += 1 150 | 151 | while(dut.peek("output_valid") == 0) { 152 | dut.step(1) 153 | cycles += 1 154 | } 155 | assert(dut.peek("output_bits_gcd") == expected) 156 | assert(dut.peek("output_valid") == 1) 157 | } 158 | 159 | cycles 160 | } 161 | 162 | def runWithRawSimSingleThread(backend: SimulatorAnnotation): Unit = { 163 | val simName = backend.getSimulator.name 164 | 165 | val startElab = System.nanoTime() 166 | 167 | // elaborate and compile to low firrtl 168 | val targetDir = TargetDirAnnotation("test_run_dir/gcd_raw_sim_with_" + simName) 169 | val stage = new chisel3.stage.ChiselStage() 170 | val r = stage.run(Seq( 171 | ChiselGeneratorAnnotation(() => new DecoupledGcd(bitWidth)), 172 | targetDir, 173 | )) 174 | val state = annosToState(r) 175 | val dut = backend.getSimulator.createContext(state) 176 | 177 | println(s"Took ${(System.nanoTime() - startElab) / 1e9d}s to elaborate, compile and create simulation w/ $simName") 178 | 179 | val startTest = System.nanoTime() 180 | val cycles = runTestSim(dut, testValues) 181 | val deltaSeconds = (System.nanoTime() - startTest) / 1e9d 182 | println(s"Took ${deltaSeconds}s to run test with the raw simulator interface and no threads w/ $simName") 183 | println(s"Executed $cycles cycles at an average frequency of ${cycles / deltaSeconds} Hz") 184 | 185 | } 186 | 187 | // copied over from chiseltest 188 | private def annosToState(annos: AnnotationSeq): firrtl.CircuitState = { 189 | val circuit = annos.collectFirst { case FirrtlCircuitAnnotation(c) => c }.get 190 | val filteredAnnos = annos.filterNot(isInternalAnno) 191 | firrtl.CircuitState(circuit, filteredAnnos) 192 | } 193 | private def isInternalAnno(a: Annotation): Boolean = a match { 194 | case _: FirrtlCircuitAnnotation | _: DesignAnnotation[_] | _: ChiselCircuitAnnotation | _: DeletedAnnotation | 195 | _: EmittedCircuitAnnotation[_] | _: LogLevelAnnotation => 196 | true 197 | case _ => false 198 | } 199 | 200 | it should "work with chiseltest threads and treadle" taggedAs(Slow) in { 201 | runWithChiseltestThreads(TreadleBackendAnnotation) 202 | } 203 | 204 | it should "work with chiseltest threads and verilator" taggedAs(Slow, Verilator) in { 205 | runWithChiseltestThreads(VerilatorBackendAnnotation) 206 | } 207 | 208 | it should "work with chiseltest single threaded and treadle" taggedAs(Slow) in { 209 | runWithChiseltestSingleThread(TreadleBackendAnnotation) 210 | } 211 | 212 | it should "work with chiseltest single threaded and verilator" taggedAs(Slow, Verilator) in { 213 | runWithChiseltestSingleThread(VerilatorBackendAnnotation) 214 | } 215 | 216 | it should "work with Command API and verilator" taggedAs(Verilator) in { 217 | runWithCommandAPI(VerilatorBackendAnnotation) 218 | } 219 | 220 | it should "work with Command API and iverilog" taggedAs (IVerilog) in { 221 | runWithCommandAPI(IcarusBackendAnnotation) 222 | } 223 | 224 | it should "work with Command API and VCS" taggedAs (VCS) in { 225 | runWithCommandAPI(VcsBackendAnnotation) 226 | } 227 | 228 | it should "work with raw simulator and single threaded and treadle" taggedAs(Slow) in { 229 | runWithRawSimSingleThread(TreadleBackendAnnotation) 230 | } 231 | 232 | it should "work with raw simulator and single threaded and verilator" taggedAs(Slow, Verilator) in { 233 | runWithRawSimSingleThread(VerilatorBackendAnnotation) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/simcommand/runtime/Interpreter.scala: -------------------------------------------------------------------------------- 1 | package simcommand.runtime 2 | 3 | import simcommand.runtime.Bridges._ 4 | import simcommand.runtime.Primitives._ 5 | 6 | import scala.collection.mutable 7 | 8 | class CombinatorialDependencyException(name: Option[String], order: Int, cmd: Command[_]) 9 | extends Exception("Detected combinatorial loop in thread '" + name + "' with order " + order + " caused by command " + cmd.sourceInfoString) 10 | 11 | case class Config( 12 | // Whether or not to print debug output 13 | print: Boolean = false, 14 | // Whether or not to record actions the program takes 15 | recordActions: Boolean = false, 16 | // How many idle cycles the interpreter should allow. A timeout of zero implies no timeout 17 | timeout: Int = 0, 18 | // Fixed point iteration resolves combinatorial dependencies, but is very computationally intensive. (EXPERIMENTAL) 19 | fixedPointIteration: Boolean = false 20 | ) 21 | 22 | sealed trait Action 23 | case class StepAct(time: Int, cycles: Int) extends Action 24 | case class PokeAct[I](time: Int, signal: Interactable[I], value: I) extends Action 25 | case class PeekAct[I](time: Int, signal: Interactable[I], value: I) extends Action 26 | 27 | case class Result[R](retval: R, cycles: Int, threadsSpawned: Int, actions: Option[Seq[Action]]) 28 | 29 | trait Interpreter { 30 | def unsafeRun[R](cmd: Command[R], clock: Steppable, cfg: Config): Result[R] 31 | } 32 | 33 | object Imperative extends Interpreter { 34 | override def unsafeRun[R](cmd: Command[R], clock: Steppable, cfg: Config): Result[R] = { 35 | val vm = new Imperative[R](clock, cfg) 36 | vm.unsafeRun(cmd) 37 | } 38 | } 39 | 40 | class Imperative[R](clock: Steppable, cfg: Config) { 41 | // Thread management 42 | private var time = 0 43 | private val alive = new mutable.TreeSet[Thread[_]]() 44 | private val queue = new mutable.Queue[Thread[_]]() 45 | private val waiting = new mutable.TreeSet[Thread[_]]() 46 | 47 | // Channels 48 | private val channelMap = new mutable.WeakHashMap[ChannelHandle[_], Channel[_]]() 49 | private var channelCounter = 0 50 | private val threadMap = new mutable.WeakHashMap[ThreadHandle[_], Thread[_]]() 51 | private var threadCounter = 0 52 | 53 | // Debugging 54 | private val actions = new mutable.ArrayBuffer[Action]() 55 | private val touched = new mutable.HashMap[Interactable[_], (Int, Int)]() 56 | 57 | private def lookupThread[R1](handle: ThreadHandle[R1]): Thread[R1] = { 58 | threadMap(handle).asInstanceOf[Thread[R1]] 59 | } 60 | 61 | private def lookupChannel[R1](handle: ChannelHandle[R1]): Channel[R1] = { 62 | channelMap(handle).asInstanceOf[Channel[R1]] 63 | } 64 | 65 | def unsafeRun(cmd: Command[R]): Result[R] = { 66 | val main = new Thread(cmd, Some("main"), 0) 67 | while (alive.nonEmpty) { 68 | stepClock() 69 | if (cfg.timeout != 0 && cfg.timeout < time) { 70 | throw new Error("simcommand timed out") 71 | } 72 | } 73 | Result(main.status.get.asInstanceOf[R], time, threadCounter, if (cfg.recordActions) Some(actions.toSeq) else None) 74 | } 75 | 76 | private def stepClock(): Unit = { 77 | queue.clear() 78 | waiting.clear() 79 | touched.clear() 80 | 81 | queue ++= alive 82 | var nextTime = time + 128 83 | 84 | while (queue.nonEmpty) { 85 | while (queue.nonEmpty) { 86 | val thread = queue.dequeue() 87 | thread.status = thread.continue() 88 | if (!thread.status.isDone) { 89 | thread.monitor match { 90 | case Some(monitor) => monitor match { 91 | case TimeMonitor(t) => if (t < nextTime) nextTime = t 92 | case _ => () 93 | } 94 | } 95 | } 96 | if (thread.status.isDone) alive -= thread 97 | else if (thread.monitor.forall(_.canRunThisCycle)) waiting += thread 98 | } 99 | 100 | waiting.foreach {thread => if (thread.monitor.forall(_.isResolved)) { 101 | queue += thread 102 | waiting -= thread 103 | }} 104 | } 105 | if (alive.nonEmpty) { 106 | clock.step(nextTime - time) 107 | time = nextTime 108 | } 109 | } 110 | 111 | sealed trait Monitor[M]{ 112 | def isResolved: Boolean 113 | def canRunThisCycle: Boolean 114 | def resolve(): M 115 | } 116 | case class ThreadMonitor[M](thread: Thread[M]) extends Monitor[M] { 117 | def isResolved: Boolean = thread.status.isDone 118 | def canRunThisCycle: Boolean = !isResolved 119 | def resolve(): M = thread.status.get.asInstanceOf[M] 120 | } 121 | case class TimeMonitor(time: Int) extends Monitor[Unit] { 122 | def isResolved: Boolean = time <= Imperative.this.time 123 | def canRunThisCycle: Boolean = false 124 | def resolve(): Unit = () 125 | } 126 | case class SendMonitor[M](channel: Channel[M], data: M) extends Monitor[Unit] { 127 | var sent = false 128 | def isResolved: Boolean = { 129 | if (!sent && channel.hasSpace) { 130 | channel.push(data) 131 | sent = true 132 | } 133 | sent 134 | } 135 | def canRunThisCycle = true 136 | def resolve(): Unit = () 137 | } 138 | case class RecvMonitor[M](channel: Channel[M]) extends Monitor[M] { 139 | var item: Option[M] = None 140 | def isResolved: Boolean = { 141 | if (item.isEmpty && !channel.isEmpty) { 142 | item = Some(channel.pop()) 143 | } 144 | item.isDefined 145 | } 146 | def canRunThisCycle = true 147 | def resolve(): M = item.get 148 | } 149 | 150 | class Frame(var parent: Option[Frame], var cmd: Command[_]) 151 | 152 | sealed trait ThreadStatus[+N] { 153 | val isDone: Boolean 154 | def get: N 155 | } 156 | case class Done[N](v: N) extends ThreadStatus[N] { 157 | val isDone = true 158 | def get: N = v 159 | } 160 | case object Running extends ThreadStatus[Nothing] { 161 | val isDone = false 162 | def get: Nothing = throw new Error("Cannot call get on a Running thread's ThreadStatus") 163 | } 164 | case object Killed extends ThreadStatus[Nothing] { 165 | val isDone = true 166 | def get: Nothing = throw new Error("Cannot call get on a Killed thread's ThreadStatus") 167 | } 168 | 169 | class Channel[M](size: Int) { 170 | val buffer: mutable.Queue[M] = new mutable.Queue[M]() 171 | val handle: ChannelHandle[M] = ChannelHandle(threadCounter) 172 | 173 | channelMap += (handle -> this) 174 | channelCounter += 1 175 | 176 | def push(v: M): Unit = buffer += v 177 | def pop(): M = buffer.dequeue() 178 | def hasSpace: Boolean = buffer.size < size 179 | def isEmpty: Boolean = buffer.isEmpty 180 | } 181 | 182 | class Thread[R1](start: Command[R1], name: Option[String], val order: Int) extends Ordered[Thread[_]] { 183 | var frame = new Frame(None, start) 184 | var monitor: Option[Monitor[_]] = None 185 | var status: ThreadStatus[_] = Running 186 | val handle: ThreadHandle[_] = ThreadHandle(threadCounter) 187 | 188 | def compare(other: Thread[_]): Int = 189 | if (order == other.order) 190 | handle.id - other.handle.id 191 | else 192 | order - other.order 193 | 194 | threadMap += (handle -> this) 195 | threadCounter += 1 196 | alive += this 197 | queue += this 198 | 199 | // Continue execution of the thread until it encounters a yield point or 200 | // completes execution. 201 | def continue(): ThreadStatus[R1] = { 202 | if (status.isDone) return status.asInstanceOf[ThreadStatus[R1]] 203 | if (!monitor.forall(_.isResolved)) return Running.asInstanceOf[ThreadStatus[R1]] 204 | 205 | while (monitor.forall(_.isResolved) && !status.isDone) { 206 | if (monitor.isDefined && monitor.get.isResolved) { 207 | ret(monitor.get.resolve()) 208 | monitor = None 209 | } 210 | 211 | if (status.isDone) 212 | status.asInstanceOf[ThreadStatus[R1]] 213 | 214 | frame.cmd match { 215 | case Cont(cmd, _) => frame = new Frame(Some(frame), cmd) 216 | case Rec(st, fn) => frame = new Frame(Some(frame), fn.asInstanceOf[Any => Command[Either[Any,Any]]](st)) 217 | case Step(cycles) => monitor = Some(TimeMonitor(Imperative.this.time + cycles)) 218 | case Fork(cmd, name, order) => 219 | val thread = new Thread(cmd, name, order) 220 | ret(thread.handle) 221 | case Join(thread) => lookupThread(thread).status match { 222 | case Done(v) => ret(v) 223 | case Killed => throw new RuntimeException("Cannot join on a killed thread") 224 | case Running => monitor = Some(ThreadMonitor(lookupThread(thread))) 225 | } 226 | case Poke(signal, value) => 227 | if (cfg.recordActions) actions += PokeAct(time, signal, value) 228 | // 1. If this port has not previously been touched, then we mark it as having changed with the current 229 | // thread order. 230 | // 2. If the mark happened in a lower order, this is still a valid and defined poke ordering, 231 | // so we update it with the current thread's order. 232 | // 3. If the order of the previous poker is the same as the current thread or higher, then this is an 233 | // inversion of poke order and is undefined behavior 234 | touched(signal) = 235 | if (!touched.contains(signal) || touched(signal)._1 < this.order) (this.order, this.handle.id) 236 | else throw new CombinatorialDependencyException(this.name, this.order, frame.cmd) 237 | ret(signal.set(value)) 238 | case Peek(signal) => 239 | val value = signal.get() 240 | if (cfg.recordActions) actions += PeekAct(time, signal, value) 241 | if ( 242 | touched.contains(signal) && 243 | (touched(signal)._1 > this.order || (touched(signal)._1 == this.order && touched(signal)._2 != this.handle.id)) 244 | ) { 245 | throw new CombinatorialDependencyException(this.name, this.order, frame.cmd) 246 | } 247 | ret(value) 248 | case Return(r) => ret(r) 249 | case Kill(thread) => 250 | lookupThread(thread).status = Killed 251 | ret(()) 252 | case MakeChannel(size) => 253 | val chan = new Channel(size) 254 | ret(chan.handle) 255 | case Put(chan, data) => monitor = Some(SendMonitor(lookupChannel(chan), data)) 256 | case GetBlocking(chan) => monitor = Some(RecvMonitor(lookupChannel(chan))) 257 | case NonEmpty(chan) => ret(!lookupChannel(chan).isEmpty) 258 | } 259 | } 260 | 261 | status.asInstanceOf[ThreadStatus[R1]] 262 | } 263 | 264 | def ret(value: Any): Unit = { 265 | frame.parent match { 266 | // If there is a parent frame, we check whether or not it is a 267 | // Continuation or a Recursion parent frame. 268 | // 269 | // 1. If it is a continuation frame , we replace the continuation 270 | // frame with the command resolved by the continuation function. 271 | // 2. If it is a recursion frame, we check if we have to continue the 272 | // recursion or if we are done. If the continuation function 273 | // returns a Left, it indicates we have to rerun this current 274 | // frame with new parameters. 275 | // 3. If it is a recursion frame but the continuation function 276 | // returns a Right, then we are done with the recursion and we 277 | // replace the recursion frame with a frame with a Value(R) which 278 | // the next loop will lift in the next iteration 279 | case Some(parent) => parent.cmd match { 280 | case Cont(_, cn) => 281 | frame.parent = parent.parent 282 | frame.cmd = cn.asInstanceOf[Any => Command[Any]](value) 283 | case Rec(_, f) => value match { 284 | case Left(l) => 285 | frame.parent = Some(parent) 286 | frame.cmd = f.asInstanceOf[Any => Command[Any]](l) 287 | case Right(r) => 288 | frame = parent 289 | ret(r) 290 | } 291 | } 292 | // If there is no frame parent, then returning from this frame ends 293 | // the current thread so we should set the thread's status to Done 294 | case None => status = Done(value.asInstanceOf[R1]) 295 | } 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /test/resources/NeuromorphicProcessor/image.sv: -------------------------------------------------------------------------------- 1 | logic [15:0] image [484]; 2 | assign image[0] = 'd0; 3 | assign image[1] = 'd0; 4 | assign image[2] = 'd0; 5 | assign image[3] = 'd0; 6 | assign image[4] = 'd0; 7 | assign image[5] = 'd0; 8 | assign image[6] = 'd0; 9 | assign image[7] = 'd0; 10 | assign image[8] = 'd0; 11 | assign image[9] = 'd0; 12 | assign image[10] = 'd500; 13 | assign image[11] = 'd49; 14 | assign image[12] = 'd36; 15 | assign image[13] = 'd133; 16 | assign image[14] = 'd0; 17 | assign image[15] = 'd0; 18 | assign image[16] = 'd0; 19 | assign image[17] = 'd0; 20 | assign image[18] = 'd0; 21 | assign image[19] = 'd0; 22 | assign image[20] = 'd0; 23 | assign image[21] = 'd0; 24 | assign image[22] = 'd0; 25 | assign image[23] = 'd0; 26 | assign image[24] = 'd0; 27 | assign image[25] = 'd0; 28 | assign image[26] = 'd0; 29 | assign image[27] = 'd0; 30 | assign image[28] = 'd0; 31 | assign image[29] = 'd0; 32 | assign image[30] = 'd0; 33 | assign image[31] = 'd0; 34 | assign image[32] = 'd221; 35 | assign image[33] = 'd15; 36 | assign image[34] = 'd9; 37 | assign image[35] = 'd30; 38 | assign image[36] = 'd500; 39 | assign image[37] = 'd0; 40 | assign image[38] = 'd0; 41 | assign image[39] = 'd0; 42 | assign image[40] = 'd0; 43 | assign image[41] = 'd0; 44 | assign image[42] = 'd0; 45 | assign image[43] = 'd0; 46 | assign image[44] = 'd0; 47 | assign image[45] = 'd0; 48 | assign image[46] = 'd0; 49 | assign image[47] = 'd0; 50 | assign image[48] = 'd0; 51 | assign image[49] = 'd0; 52 | assign image[50] = 'd0; 53 | assign image[51] = 'd0; 54 | assign image[52] = 'd0; 55 | assign image[53] = 'd0; 56 | assign image[54] = 'd500; 57 | assign image[55] = 'd42; 58 | assign image[56] = 'd12; 59 | assign image[57] = 'd14; 60 | assign image[58] = 'd117; 61 | assign image[59] = 'd0; 62 | assign image[60] = 'd0; 63 | assign image[61] = 'd0; 64 | assign image[62] = 'd0; 65 | assign image[63] = 'd0; 66 | assign image[64] = 'd0; 67 | assign image[65] = 'd0; 68 | assign image[66] = 'd0; 69 | assign image[67] = 'd0; 70 | assign image[68] = 'd0; 71 | assign image[69] = 'd0; 72 | assign image[70] = 'd0; 73 | assign image[71] = 'd0; 74 | assign image[72] = 'd0; 75 | assign image[73] = 'd0; 76 | assign image[74] = 'd0; 77 | assign image[75] = 'd0; 78 | assign image[76] = 'd0; 79 | assign image[77] = 'd500; 80 | assign image[78] = 'd18; 81 | assign image[79] = 'd9; 82 | assign image[80] = 'd31; 83 | assign image[81] = 'd500; 84 | assign image[82] = 'd0; 85 | assign image[83] = 'd0; 86 | assign image[84] = 'd0; 87 | assign image[85] = 'd0; 88 | assign image[86] = 'd0; 89 | assign image[87] = 'd0; 90 | assign image[88] = 'd0; 91 | assign image[89] = 'd0; 92 | assign image[90] = 'd0; 93 | assign image[91] = 'd0; 94 | assign image[92] = 'd0; 95 | assign image[93] = 'd0; 96 | assign image[94] = 'd0; 97 | assign image[95] = 'd0; 98 | assign image[96] = 'd0; 99 | assign image[97] = 'd0; 100 | assign image[98] = 'd0; 101 | assign image[99] = 'd0; 102 | assign image[100] = 'd22; 103 | assign image[101] = 'd8; 104 | assign image[102] = 'd15; 105 | assign image[103] = 'd332; 106 | assign image[104] = 'd0; 107 | assign image[105] = 'd0; 108 | assign image[106] = 'd0; 109 | assign image[107] = 'd0; 110 | assign image[108] = 'd0; 111 | assign image[109] = 'd0; 112 | assign image[110] = 'd0; 113 | assign image[111] = 'd0; 114 | assign image[112] = 'd0; 115 | assign image[113] = 'd0; 116 | assign image[114] = 'd0; 117 | assign image[115] = 'd0; 118 | assign image[116] = 'd0; 119 | assign image[117] = 'd0; 120 | assign image[118] = 'd0; 121 | assign image[119] = 'd0; 122 | assign image[120] = 'd0; 123 | assign image[121] = 'd0; 124 | assign image[122] = 'd36; 125 | assign image[123] = 'd10; 126 | assign image[124] = 'd10; 127 | assign image[125] = 'd74; 128 | assign image[126] = 'd0; 129 | assign image[127] = 'd0; 130 | assign image[128] = 'd0; 131 | assign image[129] = 'd0; 132 | assign image[130] = 'd0; 133 | assign image[131] = 'd0; 134 | assign image[132] = 'd0; 135 | assign image[133] = 'd0; 136 | assign image[134] = 'd0; 137 | assign image[135] = 'd0; 138 | assign image[136] = 'd0; 139 | assign image[137] = 'd0; 140 | assign image[138] = 'd0; 141 | assign image[139] = 'd0; 142 | assign image[140] = 'd0; 143 | assign image[141] = 'd0; 144 | assign image[142] = 'd0; 145 | assign image[143] = 'd0; 146 | assign image[144] = 'd30; 147 | assign image[145] = 'd9; 148 | assign image[146] = 'd10; 149 | assign image[147] = 'd69; 150 | assign image[148] = 'd0; 151 | assign image[149] = 'd0; 152 | assign image[150] = 'd0; 153 | assign image[151] = 'd0; 154 | assign image[152] = 'd0; 155 | assign image[153] = 'd0; 156 | assign image[154] = 'd0; 157 | assign image[155] = 'd0; 158 | assign image[156] = 'd0; 159 | assign image[157] = 'd0; 160 | assign image[158] = 'd0; 161 | assign image[159] = 'd0; 162 | assign image[160] = 'd0; 163 | assign image[161] = 'd0; 164 | assign image[162] = 'd0; 165 | assign image[163] = 'd0; 166 | assign image[164] = 'd0; 167 | assign image[165] = 'd0; 168 | assign image[166] = 'd34; 169 | assign image[167] = 'd10; 170 | assign image[168] = 'd9; 171 | assign image[169] = 'd57; 172 | assign image[170] = 'd0; 173 | assign image[171] = 'd0; 174 | assign image[172] = 'd0; 175 | assign image[173] = 'd0; 176 | assign image[174] = 'd0; 177 | assign image[175] = 'd0; 178 | assign image[176] = 'd0; 179 | assign image[177] = 'd0; 180 | assign image[178] = 'd0; 181 | assign image[179] = 'd0; 182 | assign image[180] = 'd0; 183 | assign image[181] = 'd0; 184 | assign image[182] = 'd0; 185 | assign image[183] = 'd0; 186 | assign image[184] = 'd0; 187 | assign image[185] = 'd125; 188 | assign image[186] = 'd47; 189 | assign image[187] = 'd43; 190 | assign image[188] = 'd17; 191 | assign image[189] = 'd9; 192 | assign image[190] = 'd9; 193 | assign image[191] = 'd42; 194 | assign image[192] = 'd0; 195 | assign image[193] = 'd0; 196 | assign image[194] = 'd0; 197 | assign image[195] = 'd0; 198 | assign image[196] = 'd0; 199 | assign image[197] = 'd0; 200 | assign image[198] = 'd0; 201 | assign image[199] = 'd0; 202 | assign image[200] = 'd0; 203 | assign image[201] = 'd0; 204 | assign image[202] = 'd0; 205 | assign image[203] = 'd0; 206 | assign image[204] = 'd500; 207 | assign image[205] = 'd91; 208 | assign image[206] = 'd25; 209 | assign image[207] = 'd14; 210 | assign image[208] = 'd11; 211 | assign image[209] = 'd10; 212 | assign image[210] = 'd9; 213 | assign image[211] = 'd8; 214 | assign image[212] = 'd8; 215 | assign image[213] = 'd11; 216 | assign image[214] = 'd15; 217 | assign image[215] = 'd16; 218 | assign image[216] = 'd36; 219 | assign image[217] = 'd0; 220 | assign image[218] = 'd0; 221 | assign image[219] = 'd0; 222 | assign image[220] = 'd0; 223 | assign image[221] = 'd0; 224 | assign image[222] = 'd0; 225 | assign image[223] = 'd0; 226 | assign image[224] = 'd500; 227 | assign image[225] = 'd91; 228 | assign image[226] = 'd16; 229 | assign image[227] = 'd11; 230 | assign image[228] = 'd9; 231 | assign image[229] = 'd8; 232 | assign image[230] = 'd8; 233 | assign image[231] = 'd8; 234 | assign image[232] = 'd8; 235 | assign image[233] = 'd8; 236 | assign image[234] = 'd8; 237 | assign image[235] = 'd8; 238 | assign image[236] = 'd8; 239 | assign image[237] = 'd8; 240 | assign image[238] = 'd10; 241 | assign image[239] = 'd41; 242 | assign image[240] = 'd0; 243 | assign image[241] = 'd0; 244 | assign image[242] = 'd0; 245 | assign image[243] = 'd0; 246 | assign image[244] = 'd0; 247 | assign image[245] = 'd500; 248 | assign image[246] = 'd50; 249 | assign image[247] = 'd14; 250 | assign image[248] = 'd8; 251 | assign image[249] = 'd8; 252 | assign image[250] = 'd8; 253 | assign image[251] = 'd8; 254 | assign image[252] = 'd8; 255 | assign image[253] = 'd8; 256 | assign image[254] = 'd8; 257 | assign image[255] = 'd8; 258 | assign image[256] = 'd10; 259 | assign image[257] = 'd10; 260 | assign image[258] = 'd11; 261 | assign image[259] = 'd10; 262 | assign image[260] = 'd13; 263 | assign image[261] = 'd50; 264 | assign image[262] = 'd0; 265 | assign image[263] = 'd0; 266 | assign image[264] = 'd0; 267 | assign image[265] = 'd0; 268 | assign image[266] = 'd0; 269 | assign image[267] = 'd166; 270 | assign image[268] = 'd15; 271 | assign image[269] = 'd8; 272 | assign image[270] = 'd8; 273 | assign image[271] = 'd8; 274 | assign image[272] = 'd8; 275 | assign image[273] = 'd8; 276 | assign image[274] = 'd8; 277 | assign image[275] = 'd8; 278 | assign image[276] = 'd8; 279 | assign image[277] = 'd10; 280 | assign image[278] = 'd38; 281 | assign image[279] = 'd80; 282 | assign image[280] = 'd91; 283 | assign image[281] = 'd69; 284 | assign image[282] = 'd166; 285 | assign image[283] = 'd0; 286 | assign image[284] = 'd0; 287 | assign image[285] = 'd0; 288 | assign image[286] = 'd0; 289 | assign image[287] = 'd0; 290 | assign image[288] = 'd0; 291 | assign image[289] = 'd181; 292 | assign image[290] = 'd15; 293 | assign image[291] = 'd8; 294 | assign image[292] = 'd8; 295 | assign image[293] = 'd8; 296 | assign image[294] = 'd8; 297 | assign image[295] = 'd8; 298 | assign image[296] = 'd8; 299 | assign image[297] = 'd8; 300 | assign image[298] = 'd9; 301 | assign image[299] = 'd18; 302 | assign image[300] = 'd221; 303 | assign image[301] = 'd0; 304 | assign image[302] = 'd0; 305 | assign image[303] = 'd0; 306 | assign image[304] = 'd0; 307 | assign image[305] = 'd0; 308 | assign image[306] = 'd0; 309 | assign image[307] = 'd0; 310 | assign image[308] = 'd0; 311 | assign image[309] = 'd0; 312 | assign image[310] = 'd0; 313 | assign image[311] = 'd221; 314 | assign image[312] = 'd16; 315 | assign image[313] = 'd8; 316 | assign image[314] = 'd8; 317 | assign image[315] = 'd8; 318 | assign image[316] = 'd8; 319 | assign image[317] = 'd8; 320 | assign image[318] = 'd8; 321 | assign image[319] = 'd10; 322 | assign image[320] = 'd17; 323 | assign image[321] = 'd71; 324 | assign image[322] = 'd0; 325 | assign image[323] = 'd0; 326 | assign image[324] = 'd0; 327 | assign image[325] = 'd0; 328 | assign image[326] = 'd0; 329 | assign image[327] = 'd0; 330 | assign image[328] = 'd0; 331 | assign image[329] = 'd0; 332 | assign image[330] = 'd0; 333 | assign image[331] = 'd0; 334 | assign image[332] = 'd0; 335 | assign image[333] = 'd199; 336 | assign image[334] = 'd16; 337 | assign image[335] = 'd9; 338 | assign image[336] = 'd8; 339 | assign image[337] = 'd8; 340 | assign image[338] = 'd9; 341 | assign image[339] = 'd10; 342 | assign image[340] = 'd16; 343 | assign image[341] = 'd54; 344 | assign image[342] = 'd500; 345 | assign image[343] = 'd500; 346 | assign image[344] = 'd0; 347 | assign image[345] = 'd0; 348 | assign image[346] = 'd0; 349 | assign image[347] = 'd0; 350 | assign image[348] = 'd0; 351 | assign image[349] = 'd0; 352 | assign image[350] = 'd0; 353 | assign image[351] = 'd0; 354 | assign image[352] = 'd0; 355 | assign image[353] = 'd0; 356 | assign image[354] = 'd0; 357 | assign image[355] = 'd0; 358 | assign image[356] = 'd74; 359 | assign image[357] = 'd23; 360 | assign image[358] = 'd17; 361 | assign image[359] = 'd17; 362 | assign image[360] = 'd28; 363 | assign image[361] = 'd51; 364 | assign image[362] = 'd332; 365 | assign image[363] = 'd0; 366 | assign image[364] = 'd0; 367 | assign image[365] = 'd0; 368 | assign image[366] = 'd0; 369 | assign image[367] = 'd0; 370 | assign image[368] = 'd0; 371 | assign image[369] = 'd0; 372 | assign image[370] = 'd0; 373 | assign image[371] = 'd0; 374 | assign image[372] = 'd0; 375 | assign image[373] = 'd0; 376 | assign image[374] = 'd0; 377 | assign image[375] = 'd0; 378 | assign image[376] = 'd0; 379 | assign image[377] = 'd0; 380 | assign image[378] = 'd0; 381 | assign image[379] = 'd0; 382 | assign image[380] = 'd0; 383 | assign image[381] = 'd0; 384 | assign image[382] = 'd0; 385 | assign image[383] = 'd0; 386 | assign image[384] = 'd0; 387 | assign image[385] = 'd0; 388 | assign image[386] = 'd0; 389 | assign image[387] = 'd0; 390 | assign image[388] = 'd0; 391 | assign image[389] = 'd0; 392 | assign image[390] = 'd0; 393 | assign image[391] = 'd0; 394 | assign image[392] = 'd0; 395 | assign image[393] = 'd0; 396 | assign image[394] = 'd0; 397 | assign image[395] = 'd0; 398 | assign image[396] = 'd0; 399 | assign image[397] = 'd0; 400 | assign image[398] = 'd0; 401 | assign image[399] = 'd0; 402 | assign image[400] = 'd0; 403 | assign image[401] = 'd0; 404 | assign image[402] = 'd0; 405 | assign image[403] = 'd0; 406 | assign image[404] = 'd0; 407 | assign image[405] = 'd0; 408 | assign image[406] = 'd0; 409 | assign image[407] = 'd0; 410 | assign image[408] = 'd0; 411 | assign image[409] = 'd0; 412 | assign image[410] = 'd0; 413 | assign image[411] = 'd0; 414 | assign image[412] = 'd0; 415 | assign image[413] = 'd0; 416 | assign image[414] = 'd0; 417 | assign image[415] = 'd0; 418 | assign image[416] = 'd0; 419 | assign image[417] = 'd0; 420 | assign image[418] = 'd0; 421 | assign image[419] = 'd0; 422 | assign image[420] = 'd0; 423 | assign image[421] = 'd0; 424 | assign image[422] = 'd0; 425 | assign image[423] = 'd0; 426 | assign image[424] = 'd0; 427 | assign image[425] = 'd0; 428 | assign image[426] = 'd0; 429 | assign image[427] = 'd0; 430 | assign image[428] = 'd0; 431 | assign image[429] = 'd0; 432 | assign image[430] = 'd0; 433 | assign image[431] = 'd0; 434 | assign image[432] = 'd0; 435 | assign image[433] = 'd0; 436 | assign image[434] = 'd0; 437 | assign image[435] = 'd0; 438 | assign image[436] = 'd0; 439 | assign image[437] = 'd0; 440 | assign image[438] = 'd0; 441 | assign image[439] = 'd0; 442 | assign image[440] = 'd0; 443 | assign image[441] = 'd0; 444 | assign image[442] = 'd0; 445 | assign image[443] = 'd0; 446 | assign image[444] = 'd0; 447 | assign image[445] = 'd0; 448 | assign image[446] = 'd0; 449 | assign image[447] = 'd0; 450 | assign image[448] = 'd0; 451 | assign image[449] = 'd0; 452 | assign image[450] = 'd0; 453 | assign image[451] = 'd0; 454 | assign image[452] = 'd0; 455 | assign image[453] = 'd0; 456 | assign image[454] = 'd0; 457 | assign image[455] = 'd0; 458 | assign image[456] = 'd0; 459 | assign image[457] = 'd0; 460 | assign image[458] = 'd0; 461 | assign image[459] = 'd0; 462 | assign image[460] = 'd0; 463 | assign image[461] = 'd0; 464 | assign image[462] = 'd0; 465 | assign image[463] = 'd0; 466 | assign image[464] = 'd0; 467 | assign image[465] = 'd0; 468 | assign image[466] = 'd0; 469 | assign image[467] = 'd0; 470 | assign image[468] = 'd0; 471 | assign image[469] = 'd0; 472 | assign image[470] = 'd0; 473 | assign image[471] = 'd0; 474 | assign image[472] = 'd0; 475 | assign image[473] = 'd0; 476 | assign image[474] = 'd0; 477 | assign image[475] = 'd0; 478 | assign image[476] = 'd0; 479 | assign image[477] = 'd0; 480 | assign image[478] = 'd0; 481 | assign image[479] = 'd0; 482 | assign image[480] = 'd0; 483 | assign image[481] = 'd0; 484 | assign image[482] = 'd0; 485 | assign image[483] = 'd0; 486 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simulation Command API for Fast Multithreaded RTL Testbenches 2 | 3 | SimCommand is a library for writing multi-threaded high-performance RTL testbenches in Scala. 4 | It is primarily designed for testing circuits [written in Chisel](https://github.com/chipsalliance/chisel3), but can also be used with [Chisel's Verilog blackboxes](https://www.chisel-lang.org/chisel3/docs/explanations/blackboxes.html) to test any single-clock synchronous Verilog RTL. 5 | This library depends on [chiseltest](https://www.chisel-lang.org/chiseltest/) ([repo](https://github.com/ucb-bar/chiseltest)), which is a Scala library for interacting with RTL simulators (including treadle, Verilator, and VCS). 6 | 7 | ## Docs 8 | 9 | ### A Simple Example 10 | 11 | Let's test this simple Chisel circuit of a register sitting between a 32-bit input and output. 12 | ```scala 13 | import chisel3._ 14 | class Register extends Module { 15 | val in = IO(Input(UInt(32.W))) 16 | val out = IO(Output(UInt(32.W))) 17 | out := RegNext(in) 18 | } 19 | ``` 20 | 21 | You can use the chiseltest `test` function to elaborate the `Register` circuit, compile an RTL simulation, and get access to a handle to the DUT: 22 | ```scala 23 | import chiseltest._ 24 | import org.scalatest.flatspec.AnyFlatSpec 25 | class RegisterTester extends AnyFlatSpec with ChiselScalatestTester { 26 | test(new Register()) { dut => 27 | // testing code here 28 | } 29 | } 30 | ``` 31 | 32 | The core datatype of SimCommand is `Command[R]` which is a *description* of an interaction with the DUT. 33 | The type parameter `R` is the type of the value that a `Command` will terminate with. 34 | There are several user functions that can be used to construct a `Command` such as `peek(signal)`, `poke(signal, value)` and `step(numCycles)`. 35 | 36 | ```scala 37 | import chiseltest._ 38 | import simcommand._ 39 | test(new Register()) { dut => 40 | val poker: Command[Unit] = poke(dut.in, 100.U) 41 | val stepper: Command[Unit] = step(cycles=1) 42 | val peeker: Command[UInt] = peek(dut.out) 43 | } 44 | ``` 45 | 46 | Note that constructing the `peeker`, `poker`, and `stepper` `Command`s doesn't actually do anything - each of these values just *describes* a simulator interaction, but doesn't perform it. 47 | This is in contrast to chiseltest's `poke`, `peek` and `step` functions which *eagerly perform* their associated actions. 48 | 49 | Note that `poke` and `step` both return `Command[Unit]` which indicates that they terminate with `(): Unit` (since they have no information to return to the testbench). 50 | In contrast, `peek` returns `Command[I]` where `I` is the type of the signal being peeked. 51 | In this example, `I` is a Chisel `UInt`: a hardware unsigned integer. 52 | 53 | To actually run any of these commands, we have to explicitly call `unsafeRun` which calls the underlying command in chiseltest. 54 | ```scala 55 | val poker: Command[Unit] = poke(dut.in, 100.U) 56 | val dummy1: Unit = unsafeRun(poker, dut.clock) 57 | 58 | val stepper: Command[Unit] = step(cycles=1) 59 | val dummy2: Unit = unsafeRun(stepper, dut.clock) 60 | 61 | val peeker: Command[UInt] = peek(dut.out) 62 | val value: UInt = unsafeRun(peeker, dut.clock) 63 | val correctBehavior = value.litValue == 100 64 | ``` 65 | 66 | Of course, this is tedious, so we want a way to describe running multiple `Command`s sequentially so that we can call `unsafeRun` only once at the very end of our testbench description. 67 | 68 | ### Chaining Commands 69 | 70 | `Command[R]` has two functions defined on it: 71 | - `flatMap[R2](f: R => Command[R2]): Command[R2]` which allows one to 'unwrap' the `R` from a `Command[R]` and continue with another `Command[R2]` 72 | - `map[R2](f: R => R2): Command[R2]` which maps the inner value of type `R` to a value of type `R2` via `f` 73 | 74 | Let's use these functions to chain the `Command`s from the previous example into a single `Command`, which terminates with a Boolean which is true if the circuit behaved correctly. 75 | ```scala 76 | val program: Command[Boolean] = 77 | poke(dut.in, 100.U).flatMap { _: Unit => 78 | step(1).flatMap { _: Unit => 79 | peek(dut.out).map { value: UInt => 80 | value.litValue == 100 81 | } 82 | } 83 | } 84 | val correctBehavior: Boolean = unsafeRun(program, dut.clock) 85 | assert(correctBehavior) 86 | ``` 87 | 88 | Notice how `flatMap` is used to 'extract' the return value from a `Command` and follow it up with another `Command`. 89 | The inner-most call to `peek` is followed by a `map` which extracts the return value of the peek and evaluates a function to return a `Boolean`. 90 | 91 | In Scala, for-comprehensions are syntactic sugar for expressing nested calls to `flatMap` followed by a final call to `map`. 92 | The code above can be expressed like this: 93 | ```scala 94 | val program: Command[Boolean] = for { 95 | _ <- poke(dut.in, 100.U) 96 | _ <- step(1) 97 | value <- peek(dut.out) 98 | } yield value.litValue == 100 99 | ``` 100 | 101 | Now our `program` looks a lot like a sequence of imperative statements - *but* it actually is just a description of a simulation program - it is a *value* which can be interpreted and executed by the SimCommand runtime. 102 | 103 | ### Command Combinators 104 | 105 | Expressing simulation programs as *values* means we can write functions that take *programs* as arguments and return new *programs*. 106 | The SimCommand library comes with a set of combinators that makes it easy to compose larger testbench programs from smaller programs. 107 | 108 | ```scala 109 | // Return a program that repeats a command n times 110 | def repeat(cmd: Command[_], n: Int): Command[Unit] 111 | val tenInteractions: Command[Unit] = repeat(program, n=10) 112 | 113 | // Take a list of programs and return a new program that executes each one back-to-back 114 | def concat[R](cmds: Seq[Command[R]]): Command[Unit] 115 | val fiftyInteractions: Command[Unit] = concat(Seq.fill(50)(program)) 116 | 117 | // Take a list of programs and return a new program that executes each one AND aggregates their results 118 | def sequence[R](cmds: Seq[Command[R]]): Command[Seq[R]] 119 | val hundredInteractions: Command[Seq[Boolean]] = sequence(Seq.fill(100)(program)) 120 | ``` 121 | 122 | Note that implementing looping constructs using this functional style needs to be done using recursion. 123 | However, `flatMap` isn't stack safe, so nested recursive calls to `flatMap` will eventually blow the stack. 124 | We have provided a set of stack safe looping constructs: 125 | 126 | ```scala 127 | // Run this program until it returns false 128 | def doWhile(cmd: Command[Boolean]): Command[Unit] 129 | 130 | val program: Command[Boolean] = for { 131 | value <- peek(dut.out) 132 | _ <- step(1) 133 | } yield value == 0 134 | doWhile(program) 135 | 136 | // Run this program continuously 137 | def forever(cmd: Command[_]): Command[Nothing] 138 | forever(program) 139 | ``` 140 | 141 | The SimCommand library also has a set of small programs that you can use to build larger ones: 142 | ```scala 143 | // Step the clock until the signal == value 144 | def waitForValue[I <: Data](signal: I, value: I): Command[Unit] 145 | 146 | val program = for { 147 | _ <- waitForValue(dut.out, 100.U) 148 | _ <- poke(dut.in, 101.U) 149 | } yield () 150 | ``` 151 | 152 | ### Multithreading 153 | 154 | A `Command` can be forked off to begin its execution on a different simulation thread using `fork`. 155 | This gives you a thread handle that can be used to block on that thread's return value using `join`. 156 | 157 | Let's create one thread to drive the DUT's input while another thread observes the DUT's output. 158 | 159 | ```scala 160 | def driver(nElems: Int, start: Int): Command[Unit] = { 161 | def driveOne(value: Int): Command[Unit] = for { 162 | _ <- poke(dut.in, i.U) 163 | _ <- step(1) 164 | } yield () 165 | 166 | concat((start until start + nElems).map { i => driveOne(i) }) 167 | } 168 | 169 | def receiver(nElems: Int): Command[Seq[Int]] = { 170 | val receiveOne(): Command[Int] = for { 171 | _ <- step(1) 172 | value <- peek(dut.out) 173 | } yield value.litValue.toInt 174 | 175 | concat(Seq.fill(nElems)(receiveOne())) 176 | } 177 | ``` 178 | 179 | There are 2 user functions available: 180 | - `fork[R](cmd: Command[R], name: String): Command[ThreadHandle[R]]` 181 | - Calling `fork` gives you a `Command` that returns a `ThreadHandle` which you can use when joining 182 | - `join[R](handle: ThreadHandle[R]): Command[R]` 183 | - Calling `join` with a `ThreadHandle` describes blocking the current thread until the joining thread has termianted with a value of type `R` 184 | 185 | ```scala 186 | val program: Command[Boolean] = for { 187 | drvThread: ThreadHandle[Unit] <- fork(driver(100, 0), "driver") 188 | recvThread: ThreadHandle[Seq[Int]] <- fork(receiver(100), "receiver") 189 | _ <- join(drvThread) 190 | vals <- join(recvThread) 191 | } yield vals == Seq.tabulate(100)(i => i) 192 | ``` 193 | This is a `Command` that describes running these `driver` and `receiver` in parallel, waiting on both of them to complete, extracting the terminating return value from the receiver, and checking the results. 194 | 195 | Note that you can pass `ThreadHandle`s outside the `Command` that `fork`ed the thread, but each thread can only be `join`ed once. 196 | 197 | ### Debugging and Errors 198 | 199 | WIP 200 | 201 | ### Runtime / Scheduler 202 | 203 | You can use `Command` and its combinators to build up a description of a complete simulation program. 204 | Then, at the very end of your test function, you can call `unsafeRun` to execute the `Command` and get back a `Result` object, which contains the return value and some metadata about the simulation. 205 | **You should use** the `NoThreadingAnnotation` on `chiseltest`'s test function to achieve the best performance. 206 | 207 | ```scala 208 | import chiseltest.internal.NoThreadingAnnotation 209 | test(new Register()).withAnnotations(Seq(NoThreadingAnnotation)) { dut => 210 | val program: Command[Boolean] 211 | val result = unsafeRun(program, dut.clock, print=false) 212 | println(result.retval) // retval: Boolean 213 | println(result.cycles) 214 | println(result.threadsSpawned) 215 | } 216 | ``` 217 | 218 | #### Algorithm 219 | 220 | The `unsafeRun` function invokes the SimCommand interpreter which performs the following event loop: 221 | 222 | - while(true) 223 | - Iterate through the list of running threads, for each thread `t` do the following until it has hit a synchronization point (`step`, `join`, or `return`) for this timestep 224 | - As `t` is being run, proxy any calls to `peek` or `poke` to the chiseltest API 225 | - If `t` hits a `fork`, add the forked thread to the list of running threads and continue running `t` 226 | - If `t` hits a `step`, overwrite the `t` pointer with the `Command` that comes after the `step` and record the time at which `t` will be woken up 227 | - If `t` hits a `join`, mark `t` as asleep until the thread being joined has returned 228 | - If `t` hits a `return`, add `t`'s return value to a global map and mark any threads which have requested a join on `t` to be awakened on this timestep 229 | - If the main thread has returned with `retval`, *exit the loop* with `retval` 230 | - Step the clock in the simulator 231 | 232 | Note that the interpreter doesn't prevent intra-cycle race conditions when two threads are peeking and poking the same DUT IO. 233 | Support is being considered for catching these issues at runtime, like the threaded chiseltest backend does. 234 | 235 | ### VIPs 236 | 237 | There is a small library of verification IPs built using SimCommand. 238 | They are just functions that return a `Command` that describes an interaction for a given bus specification. 239 | 240 | #### UART 241 | 242 | ```scala 243 | // Create an instance of UARTCommands with bindings to your DUT's UART IOs and specify the baud rate 244 | val uartCmds = new UARTCommands(dut.uartTx, dut.uartRx, cyclesPerBit) 245 | 246 | // Then call functions from the instance to produce Command's you can use in your testbench 247 | uartCmds.sendByte(24) // : Command[Unit] 248 | uartCmds.sendBytes(Seq(4, 23, 12)) // : Command[Unit] 249 | uartCmds.receiveByte() // : Command[Int] 250 | uartCmds.receiveBytes(n: Int) // : Command[Seq[Int]] 251 | ``` 252 | 253 | #### Ready/Valid (Decoupled) 254 | 255 | ```scala 256 | val dut = Queue(UInt(32.W)) 257 | val enqCmds = new DecoupledCommands(dut.enq) 258 | enqCmds.enqueue(100.U) // : Command[Unit] 259 | enqCmds.enqueueSeq(Seq(1.U, 2.U, 3.U)) // : Command[Unit] 260 | 261 | val deqCmds = new DecoupledCommands(dut.deq) 262 | deqCmds.dequeue() // : Command[UInt] 263 | deqCmds.dequeueN(n: Int) // : Command[Seq[UInt]] 264 | ``` 265 | 266 | #### TileLink 267 | 268 | WIP 269 | 270 | ## Testbenches 271 | 272 | This repo contains an implementation and benchmarks of a simulation command monad in Scala that is used with [chiseltest](https://github.com/ucb-bar/chiseltest) to describe and execute multithreaded RTL simulations with minimal threading overhead. 273 | 274 | - DecoupledGCD: artificial example with 2 ready-valid interfaces 275 | - `gcd.DecoupledGcdChiseltestTester` (chiseltest with standard threading, chiseltest with manually interleaved threads, chiseltest with Command API, chiseltest with manually interleaved threads + raw simulator API) (verilator + treadle) 276 | - `cocotb/gcd_tb` (cocotb) 277 | - NeuromorphicProcessor: system-level testbench with top-level UART interaction 278 | - `neuroproc.systemtests.NeuromorphicProcessorChiseltestTester` (chiseltest with standard threading API) 279 | - `neuroproc.systemtests.NeuromorphicProcessorManualThreadTester` (chiseltest with single threaded backend, manually interleaved threading, and Chisel API) 280 | - `neuroproc.systemtests.NeuromorphicProcessorRawSimulatorTester` (chiseltest with single threaded backend, manually interleaved threading, and raw FIRRTL API) 281 | - `neuroproc.systemtests.NeuromorphicProcessorCommandTester` (chiseltest with single threaded backend, and Command interpreter providing threading support) 282 | - `cocotb/testbench` (cocotb) 283 | 284 | ### Benchmark Results 285 | 286 | | Simulation API | DecoupledGCD | NeuromorphicProcessor | 287 | |----------------------------------------------------|-------------------|-----------------------| 288 | | cocotb | 3.8 kHz, 43.2 sec | 9.9 kHz, 89:38 min | 289 | | Chiseltest with threading | 7.8 kHz, 21 sec | 32.6 kHz, 27:21 min | 290 | | Command API | 67 kHz, 2.4 sec | 165 kHz, 5:23 min | 291 | | Chiseltest with manual threading | 218 kHz, 0.75 sec | 432 kHz, 2:03 min | 292 | | Chiseltest with manual threading + raw FIRRTL API} | | 453 kHz, 1:58 min | 293 | 294 | ### Caveats for cocotb NeuromorphicProcessor Testbench 295 | 296 | - Use `timescale 1ps/1ps` at the top of `NeuromorphicProcessor.sv` to match chiseltest 297 | - Use a 2ps period clock to match chiseltest 298 | - For iverilog 299 | - Remove `@(*)` after `always_latch` in `ClockBufferBB.sv` (event sensitivity lists are automatically inferred) 300 | - Add iverilog vcd dumping to `NeuromorphicProcessor.sv` if needed 301 | ```verilog 302 | `ifdef COCOTB_SIM 303 | initial begin 304 | $dumpfile ("NeuromorphicProcessor.vcd"); 305 | $dumpvars (0, NeuromorphicProcessor); 306 | #1; 307 | end 308 | `endif 309 | ``` 310 | --------------------------------------------------------------------------------