summaryrefslogtreecommitdiffstats
path: root/scripts/torture
diff options
context:
space:
mode:
authorYann Herklotz <git@yannherklotz.com>2022-11-24 18:49:44 +0000
committerYann Herklotz <git@yannherklotz.com>2022-11-24 18:49:44 +0000
commit3291d86ec38031e191ec1e7e5e8ddfa74b77cb7c (patch)
tree7d604206a2deb29c9f097ff1f3b7de78f44b34a3 /scripts/torture
downloadbutterstick-3291d86ec38031e191ec1e7e5e8ddfa74b77cb7c.tar.gz
butterstick-3291d86ec38031e191ec1e7e5e8ddfa74b77cb7c.zip
Squashed 'picorv32/' content from commit f00a88c
git-subtree-dir: picorv32 git-subtree-split: f00a88c36eaab478b64ee27d8162e421049bcc66
Diffstat (limited to 'scripts/torture')
-rw-r--r--scripts/torture/.gitignore14
-rw-r--r--scripts/torture/Makefile101
-rw-r--r--scripts/torture/README6
-rw-r--r--scripts/torture/asmcheck.py36
-rw-r--r--scripts/torture/config.py35
-rw-r--r--scripts/torture/riscv-isa-sim-notrap.diff16
-rw-r--r--scripts/torture/riscv-isa-sim-sbreak.diff26
-rw-r--r--scripts/torture/riscv-torture-genloop.diff40
-rw-r--r--scripts/torture/riscv-torture-rv32.diff141
-rw-r--r--scripts/torture/riscv_test.h13
-rw-r--r--scripts/torture/sections.lds9
-rw-r--r--scripts/torture/test.sh32
-rw-r--r--scripts/torture/testbench.cc18
-rw-r--r--scripts/torture/testbench.v134
14 files changed, 621 insertions, 0 deletions
diff --git a/scripts/torture/.gitignore b/scripts/torture/.gitignore
new file mode 100644
index 0000000..b58f70b
--- /dev/null
+++ b/scripts/torture/.gitignore
@@ -0,0 +1,14 @@
+.looplog
+riscv-fesvr
+riscv-isa-sim
+riscv-torture
+config.vh
+obj_dir
+tests
+test.S
+test.elf
+test.bin
+test.hex
+test.ref
+test.vvp
+test.vcd
diff --git a/scripts/torture/Makefile b/scripts/torture/Makefile
new file mode 100644
index 0000000..4dd075c
--- /dev/null
+++ b/scripts/torture/Makefile
@@ -0,0 +1,101 @@
+
+# Icarus Verilog
+#TESTBENCH_EXE = tests/testbench.vvp
+
+# Verilator
+TESTBENCH_EXE = obj_dir/Vtestbench
+
+test: riscv-torture/build.ok riscv-isa-sim/build.ok
+ bash test.sh
+
+riscv-torture/build.ok: riscv-torture-rv32.diff
+ rm -rf riscv-torture
+ git clone https://github.com/ucb-bar/riscv-torture.git riscv-torture
+ cd riscv-torture && git checkout 2bc0c42
+ cd riscv-torture && patch -p1 < ../riscv-torture-rv32.diff
+ cd riscv-torture && patch -p1 < ../riscv-torture-genloop.diff
+ cd riscv-torture && ./sbt generator/run && touch build.ok
+
+riscv-fesvr/build.ok:
+ rm -rf riscv-fesvr
+ git clone https://github.com/riscv/riscv-fesvr.git riscv-fesvr
+ +cd riscv-fesvr && git checkout 1c02bd6 && ./configure && make && touch build.ok
+
+riscv-isa-sim/build.ok: riscv-fesvr/build.ok
+ rm -rf riscv-isa-sim
+ git clone https://github.com/riscv/riscv-isa-sim.git riscv-isa-sim
+ cd riscv-isa-sim && git checkout 10ae74e
+ cd riscv-isa-sim && patch -p1 < ../riscv-isa-sim-sbreak.diff
+ cd riscv-isa-sim && patch -p1 < ../riscv-isa-sim-notrap.diff
+ cd riscv-isa-sim && LDFLAGS="-L../riscv-fesvr" ./configure --with-isa=RV32IMC
+ +cd riscv-isa-sim && ln -s ../riscv-fesvr/fesvr . && make && touch build.ok
+
+batch_size = 1000
+batch_list = $(shell bash -c 'for i in {0..$(shell expr $(batch_size) - 1)}; do printf "%03d\n" $$i; done')
+
+batch: $(addprefix tests/test_,$(addsuffix .ok,$(batch_list)))
+
+config.vh: config.py riscv-torture/build.ok
+ python3 config.py
+
+obj_dir/Vtestbench: testbench.v testbench.cc ../../picorv32.v config.vh
+ verilator --exe -Wno-fatal -DDEBUGASM --cc --top-module testbench testbench.v ../../picorv32.v testbench.cc
+ $(MAKE) -C obj_dir -f Vtestbench.mk
+
+tests/testbench.vvp: testbench.v ../../picorv32.v
+ mkdir -p tests
+ iverilog -o tests/testbench.vvp testbench.v ../../picorv32.v
+
+tests/generated.ok: config.vh riscv-torture/build.ok
+ mkdir -p tests
+ rm -f riscv-torture/output/test_*
+ cd riscv-torture && ./sbt 'generator/run -C config/test.config -n $(batch_size)'
+ touch tests/generated.ok
+
+define test_template
+tests/test_$(1).S: tests/generated.ok
+ mv riscv-torture/output/test_$(1).S tests/
+ touch tests/test_$(1).S
+
+tests/test_$(1).elf: tests/test_$(1).S
+ riscv32-unknown-elf-gcc `sed '/march=/ ! d; s,^// ,-,; y/RVIMC/rvimc/;' config.vh` -ffreestanding -nostdlib \
+ -Wl,-Bstatic,-T,sections.lds -I. -o tests/test_$(1).elf tests/test_$(1).S
+
+tests/test_$(1).bin: tests/test_$(1).elf
+ riscv32-unknown-elf-objcopy -O binary tests/test_$(1).elf tests/test_$(1).bin
+
+tests/test_$(1).dmp: tests/test_$(1).elf
+ riscv32-unknown-elf-objdump -d tests/test_$(1).elf > tests/test_$(1).dmp
+
+tests/test_$(1).hex: tests/test_$(1).bin
+ python3 ../../firmware/makehex.py tests/test_$(1).bin 4096 > tests/test_$(1).hex
+
+tests/test_$(1).ref: tests/test_$(1).elf riscv-isa-sim/build.ok
+ LD_LIBRARY_PATH="./riscv-isa-sim:./riscv-fesvr" ./riscv-isa-sim/spike tests/test_$(1).elf > tests/test_$(1).ref
+
+tests/test_$(1).ok: $(TESTBENCH_EXE) tests/test_$(1).hex tests/test_$(1).ref tests/test_$(1).dmp
+ $(TESTBENCH_EXE) +hex=tests/test_$(1).hex +ref=tests/test_$(1).ref > tests/test_$(1).out
+ grep -q PASSED tests/test_$(1).out || { cat tests/test_$(1).out; false; }
+ python3 asmcheck.py tests/test_$(1).out tests/test_$(1).dmp
+ mv tests/test_$(1).out tests/test_$(1).ok
+endef
+
+$(foreach id,$(batch_list),$(eval $(call test_template,$(id))))
+
+loop:
+ date +"%s %Y-%m-%d %H:%M:%S START" >> .looplog
+ +set -ex; while true; do \
+ rm -rf tests obj_dir config.vh; $(MAKE) batch; \
+ date +"%s %Y-%m-%d %H:%M:%S NEXT" >> .looplog; \
+ done
+
+clean:
+ rm -rf tests obj_dir
+ rm -f config.vh test.S test.elf test.bin
+ rm -f test.hex test.ref test.vvp test.vcd
+
+mrproper: clean
+ rm -rf riscv-torture riscv-fesvr riscv-isa-sim
+
+.PHONY: test batch loop clean mrproper
+
diff --git a/scripts/torture/README b/scripts/torture/README
new file mode 100644
index 0000000..d62671d
--- /dev/null
+++ b/scripts/torture/README
@@ -0,0 +1,6 @@
+Use UCB-BAR's RISC-V Torture Test Generator to test PicoRV32.
+
+You might need to install the following addition dependecies:
+
+sudo apt-get install python3-pip
+pip3 install numpy
diff --git a/scripts/torture/asmcheck.py b/scripts/torture/asmcheck.py
new file mode 100644
index 0000000..8a22c67
--- /dev/null
+++ b/scripts/torture/asmcheck.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+import sys, re
+
+dmp_pattern = re.compile('^\s+([0-9a-f]+):\s+([0-9a-f]+)\s+(\S+)')
+disassembled_elf = dict()
+
+def match_insns(s1, s2):
+ if s1 == "*" or s1 == s2: return True
+ if s1 == "jal" and s2.startswith("j"): return True
+ if s1 == "addi" and s2 in ["li", "mv"]: return True
+ if s1 == "xori" and s2 == "not": return True
+ if s1 == "sub" and s2 == "neg": return True
+ if s1.startswith("b") and s2.startswith("b"): return True
+ if s1.startswith("s") and s2.startswith("s"): return True
+ print(s1, s2)
+ return False
+
+with open(sys.argv[2], "r") as f:
+ for line in f:
+ match = dmp_pattern.match(line)
+ if match:
+ addr = match.group(1).rjust(8, '0')
+ opcode = match.group(2).rjust(8, '0')
+ insn = match.group(3)
+ disassembled_elf[addr] = (opcode, insn)
+
+with open(sys.argv[1], "r") as f:
+ for line in f:
+ line = line.split()
+ if len(line) < 1 or line[0] != "debugasm":
+ continue
+ assert(line[1] in disassembled_elf)
+ assert(line[2] == disassembled_elf[line[1]][0])
+ assert(match_insns(line[3], disassembled_elf[line[1]][1]))
+
diff --git a/scripts/torture/config.py b/scripts/torture/config.py
new file mode 100644
index 0000000..478f046
--- /dev/null
+++ b/scripts/torture/config.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+
+import numpy as np
+
+compressed_isa = np.random.randint(2)
+enable_mul = np.random.randint(2)
+enable_div = np.random.randint(2)
+
+with open("config.vh", "w") as f:
+ print("// march=RV32I%s%s" % (
+ "M" if enable_mul or enable_div else "",
+ "C" if compressed_isa else ""), file=f)
+ print(".ENABLE_COUNTERS(%d)," % np.random.randint(2), file=f)
+ print(".ENABLE_COUNTERS64(%d)," % np.random.randint(2), file=f)
+ print(".ENABLE_REGS_DUALPORT(%d)," % np.random.randint(2), file=f)
+ print(".TWO_STAGE_SHIFT(%d)," % np.random.randint(2), file=f)
+ print(".BARREL_SHIFTER(%d)," % np.random.randint(2), file=f)
+ print(".TWO_CYCLE_COMPARE(%d)," % np.random.randint(2), file=f)
+ print(".TWO_CYCLE_ALU(%d)," % np.random.randint(2), file=f)
+ print(".CATCH_MISALIGN(%d)," % np.random.randint(2), file=f)
+ print(".CATCH_ILLINSN(%d)," % np.random.randint(2), file=f)
+ print(".COMPRESSED_ISA(%d)," % compressed_isa, file=f)
+ print(".ENABLE_MUL(%d)," % enable_mul, file=f)
+ print(".ENABLE_DIV(%d)" % enable_div, file=f)
+
+with open("riscv-torture/config/default.config", "r") as fi:
+ with open("riscv-torture/config/test.config", "w") as fo:
+ for line in fi:
+ line = line.strip()
+ if line.startswith("torture.generator.mul "):
+ line = "torture.generator.mul %s" % ("true" if enable_mul else "false")
+ if line.startswith("torture.generator.divider "):
+ line = "torture.generator.divider %s" % ("true" if enable_div else "false")
+ print(line, file=fo)
+
diff --git a/scripts/torture/riscv-isa-sim-notrap.diff b/scripts/torture/riscv-isa-sim-notrap.diff
new file mode 100644
index 0000000..df7c059
--- /dev/null
+++ b/scripts/torture/riscv-isa-sim-notrap.diff
@@ -0,0 +1,16 @@
+diff --git a/riscv/processor.cc b/riscv/processor.cc
+index 3b834c5..e112029 100644
+--- a/riscv/processor.cc
++++ b/riscv/processor.cc
+@@ -201,9 +201,10 @@ void processor_t::set_privilege(reg_t prv)
+
+ void processor_t::take_trap(trap_t& t, reg_t epc)
+ {
+- if (debug)
++ // if (debug)
+ fprintf(stderr, "core %3d: exception %s, epc 0x%016" PRIx64 "\n",
+ id, t.name(), epc);
++ exit(1);
+
+ // by default, trap to M-mode, unless delegated to S-mode
+ reg_t bit = t.cause();
diff --git a/scripts/torture/riscv-isa-sim-sbreak.diff b/scripts/torture/riscv-isa-sim-sbreak.diff
new file mode 100644
index 0000000..9728fc8
--- /dev/null
+++ b/scripts/torture/riscv-isa-sim-sbreak.diff
@@ -0,0 +1,26 @@
+diff --git a/riscv/insns/c_ebreak.h b/riscv/insns/c_ebreak.h
+index a17200f..af3a7ad 100644
+--- a/riscv/insns/c_ebreak.h
++++ b/riscv/insns/c_ebreak.h
+@@ -1,2 +1,9 @@
+ require_extension('C');
++
++for (int i = 0; i < 16*1024; i += 4) {
++ unsigned int dat = MMU.load_int32(i);
++ printf("%08x\n", dat);
++}
++exit(0);
++
+ throw trap_breakpoint();
+diff --git a/riscv/insns/sbreak.h b/riscv/insns/sbreak.h
+index c22776c..31397dd 100644
+--- a/riscv/insns/sbreak.h
++++ b/riscv/insns/sbreak.h
+@@ -1 +1,7 @@
++for (int i = 0; i < 16*1024; i += 4) {
++ unsigned int dat = MMU.load_int32(i);
++ printf("%08x\n", dat);
++}
++exit(0);
++
+ throw trap_breakpoint();
diff --git a/scripts/torture/riscv-torture-genloop.diff b/scripts/torture/riscv-torture-genloop.diff
new file mode 100644
index 0000000..a0a2fd1
--- /dev/null
+++ b/scripts/torture/riscv-torture-genloop.diff
@@ -0,0 +1,40 @@
+diff --git a/generator/src/main/scala/main.scala b/generator/src/main/scala/main.scala
+index 7c78982..1572771 100644
+--- a/generator/src/main/scala/main.scala
++++ b/generator/src/main/scala/main.scala
+@@ -8,7 +8,7 @@ import java.util.Properties
+ import scala.collection.JavaConversions._
+
+ case class Options(var outFileName: String = "test",
+- var confFileName: String = "config/default.config")
++ var confFileName: String = "config/default.config", var numOutFiles: Int = 0)
+
+ object Generator extends App
+ {
+@@ -17,15 +17,25 @@ object Generator extends App
+ val parser = new OptionParser[Options]("generator/run") {
+ opt[String]('C', "config") valueName("<file>") text("config file") action {(s: String, c) => c.copy(confFileName = s)}
+ opt[String]('o', "output") valueName("<filename>") text("output filename") action {(s: String, c) => c.copy(outFileName = s)}
++ opt[Int]('n', "numfiles") valueName("<num_files>") text("number of output files") action {(n: Int, c) => c.copy(numOutFiles = n)}
+ }
+ parser.parse(args, Options()) match {
+ case Some(opts) =>
+- generate(opts.confFileName, opts.outFileName)
++ generate_loop(opts.confFileName, opts.outFileName, opts.numOutFiles)
+ case None =>
+ System.exit(1) //error message printed by parser
+ }
+ }
+
++ def generate_loop(confFile: String, outFileName: String, numOutFiles: Int) = {
++ if (numOutFiles > 0) {
++ for (i <- 0 to (numOutFiles-1))
++ generate(confFile, outFileName + ("_%03d" format (i)))
++ } else {
++ generate(confFile, outFileName)
++ }
++ }
++
+ def generate(confFile: String, outFileName: String): String = {
+ val config = new Properties()
+ val in = new FileInputStream(confFile)
diff --git a/scripts/torture/riscv-torture-rv32.diff b/scripts/torture/riscv-torture-rv32.diff
new file mode 100644
index 0000000..fef49b3
--- /dev/null
+++ b/scripts/torture/riscv-torture-rv32.diff
@@ -0,0 +1,141 @@
+diff --git a/config/default.config b/config/default.config
+index b671223..c0b2bb4 100644
+--- a/config/default.config
++++ b/config/default.config
+@@ -1,18 +1,18 @@
+ torture.generator.nseqs 1000
+ torture.generator.memsize 1024
+ torture.generator.fprnd 0
+-torture.generator.amo true
++torture.generator.amo false
+ torture.generator.mul true
+ torture.generator.divider true
+ torture.generator.run_twice true
+
+ torture.generator.mix.xmem 10
+ torture.generator.mix.xbranch 20
+-torture.generator.mix.xalu 50
+-torture.generator.mix.fgen 10
+-torture.generator.mix.fpmem 5
+-torture.generator.mix.fax 3
+-torture.generator.mix.fdiv 2
++torture.generator.mix.xalu 70
++torture.generator.mix.fgen 0
++torture.generator.mix.fpmem 0
++torture.generator.mix.fax 0
++torture.generator.mix.fdiv 0
+ torture.generator.mix.vec 0
+
+ torture.generator.vec.vf 1
+diff --git a/generator/src/main/scala/HWRegPool.scala b/generator/src/main/scala/HWRegPool.scala
+index de2ad8d..864bcc4 100644
+--- a/generator/src/main/scala/HWRegPool.scala
++++ b/generator/src/main/scala/HWRegPool.scala
+@@ -86,7 +86,7 @@ trait PoolsMaster extends HWRegPool
+
+ class XRegsPool extends ScalarRegPool
+ {
+- val (name, regname, ldinst, stinst) = ("xreg", "reg_x", "ld", "sd")
++ val (name, regname, ldinst, stinst) = ("xreg", "reg_x", "lw", "sw")
+
+ hwregs += new HWReg("x0", true, false)
+ for (i <- 1 to 31)
+diff --git a/generator/src/main/scala/Prog.scala b/generator/src/main/scala/Prog.scala
+index 6fb49e2..685c2f8 100644
+--- a/generator/src/main/scala/Prog.scala
++++ b/generator/src/main/scala/Prog.scala
+@@ -385,7 +385,7 @@ class Prog(memsize: Int, veccfg: Map[String,String], run_twice: Boolean)
+ "\n" +
+ (if (using_vec) "RVTEST_RV64UV\n"
+ else if (using_fpu) "RVTEST_RV64UF\n"
+- else "RVTEST_RV64U\n") +
++ else "RVTEST_RV32U\n") +
+ "RVTEST_CODE_BEGIN\n" +
+ (if (using_vec) init_vector() else "") +
+ "\n" +
+diff --git a/generator/src/main/scala/Rand.scala b/generator/src/main/scala/Rand.scala
+index a677d2d..ec0745f 100644
+--- a/generator/src/main/scala/Rand.scala
++++ b/generator/src/main/scala/Rand.scala
+@@ -15,7 +15,7 @@ object Rand
+ low + Random.nextInt(span)
+ }
+
+- def rand_shamt() = rand_range(0, 63)
++ def rand_shamt() = rand_range(0, 31)
+ def rand_shamtw() = rand_range(0, 31)
+ def rand_seglen() = rand_range(0, 7)
+ def rand_imm() = rand_range(-2048, 2047)
+diff --git a/generator/src/main/scala/SeqALU.scala b/generator/src/main/scala/SeqALU.scala
+index a1f27a5..18d6d7b 100644
+--- a/generator/src/main/scala/SeqALU.scala
++++ b/generator/src/main/scala/SeqALU.scala
+@@ -68,17 +68,12 @@ class SeqALU(xregs: HWRegPool, use_mul: Boolean, use_div: Boolean) extends InstS
+ candidates += seq_src1_immfn(SRAI, rand_shamt)
+ candidates += seq_src1_immfn(ORI, rand_imm)
+ candidates += seq_src1_immfn(ANDI, rand_imm)
+- candidates += seq_src1_immfn(ADDIW, rand_imm)
+- candidates += seq_src1_immfn(SLLIW, rand_shamtw)
+- candidates += seq_src1_immfn(SRLIW, rand_shamtw)
+- candidates += seq_src1_immfn(SRAIW, rand_shamtw)
+
+ val oplist = new ArrayBuffer[Opcode]
+
+ oplist += (ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND)
+- oplist += (ADDW, SUBW, SLLW, SRLW, SRAW)
+- if (use_mul) oplist += (MUL, MULH, MULHSU, MULHU, MULW)
+- if (use_div) oplist += (DIV, DIVU, REM, REMU, DIVW, DIVUW, REMW, REMUW)
++ if (use_mul) oplist += (MUL, MULH, MULHSU, MULHU)
++ if (use_div) oplist += (DIV, DIVU, REM, REMU)
+
+ for (op <- oplist)
+ {
+diff --git a/generator/src/main/scala/SeqBranch.scala b/generator/src/main/scala/SeqBranch.scala
+index bba9895..0d257d7 100644
+--- a/generator/src/main/scala/SeqBranch.scala
++++ b/generator/src/main/scala/SeqBranch.scala
+@@ -75,7 +75,7 @@ class SeqBranch(xregs: HWRegPool) extends InstSeq
+ val reg_mask = reg_write_visible(xregs)
+
+ insts += ADDI(reg_one, reg_read_zero(xregs), Imm(1))
+- insts += SLL(reg_one, reg_one, Imm(63))
++ insts += SLL(reg_one, reg_one, Imm(31))
+ insts += ADDI(reg_mask, reg_read_zero(xregs), Imm(-1))
+ insts += XOR(reg_mask, reg_mask, reg_one)
+ insts += AND(reg_dst1, reg_src, reg_mask)
+@@ -95,7 +95,7 @@ class SeqBranch(xregs: HWRegPool) extends InstSeq
+ val reg_mask = reg_write_visible(xregs)
+
+ insts += ADDI(reg_one, reg_read_zero(xregs), Imm(1))
+- insts += SLL(reg_one, reg_one, Imm(63))
++ insts += SLL(reg_one, reg_one, Imm(31))
+ insts += ADDI(reg_mask, reg_read_zero(xregs), Imm(-1))
+ insts += XOR(reg_mask, reg_mask, reg_one)
+ insts += AND(reg_dst1, reg_src1, reg_mask)
+diff --git a/generator/src/main/scala/SeqMem.scala b/generator/src/main/scala/SeqMem.scala
+index 3c180ed..89200f6 100644
+--- a/generator/src/main/scala/SeqMem.scala
++++ b/generator/src/main/scala/SeqMem.scala
+@@ -51,7 +51,7 @@ class SeqMem(xregs: HWRegPool, mem: Mem, use_amo: Boolean) extends InstSeq
+
+ def getRandOpAndAddr (dw_addr: Int, is_store: Boolean): (Opcode, Int) =
+ {
+- val typ = AccessType.values.toIndexedSeq(rand_range(0,6))
++ val typ = AccessType.values.toIndexedSeq(rand_range(0,4))
+ if (is_store)
+ {
+ if (typ == byte || typ ==ubyte) (SB, dw_addr + rand_addr_b(8))
+@@ -110,13 +110,10 @@ class SeqMem(xregs: HWRegPool, mem: Mem, use_amo: Boolean) extends InstSeq
+ candidates += seq_load_addrfn(LH, rand_addr_h)
+ candidates += seq_load_addrfn(LHU, rand_addr_h)
+ candidates += seq_load_addrfn(LW, rand_addr_w)
+- candidates += seq_load_addrfn(LWU, rand_addr_w)
+- candidates += seq_load_addrfn(LD, rand_addr_d)
+
+ candidates += seq_store_addrfn(SB, rand_addr_b)
+ candidates += seq_store_addrfn(SH, rand_addr_h)
+ candidates += seq_store_addrfn(SW, rand_addr_w)
+- candidates += seq_store_addrfn(SD, rand_addr_d)
+
+ if (use_amo)
+ {
diff --git a/scripts/torture/riscv_test.h b/scripts/torture/riscv_test.h
new file mode 100644
index 0000000..36c5b54
--- /dev/null
+++ b/scripts/torture/riscv_test.h
@@ -0,0 +1,13 @@
+#ifndef RISCV_TEST_H
+#define RISCV_TEST_H
+
+#define RVTEST_RV32U
+#define RVTEST_CODE_BEGIN
+#define RVTEST_CODE_END
+#define RVTEST_DATA_BEGIN
+#define RVTEST_DATA_END
+
+#define RVTEST_FAIL ebreak
+#define RVTEST_PASS ebreak
+
+#endif
diff --git a/scripts/torture/sections.lds b/scripts/torture/sections.lds
new file mode 100644
index 0000000..a9487e2
--- /dev/null
+++ b/scripts/torture/sections.lds
@@ -0,0 +1,9 @@
+SECTIONS {
+ .memory : {
+ . = 0x000000;
+ *(.text);
+ *(*);
+ end = .;
+ }
+}
+
diff --git a/scripts/torture/test.sh b/scripts/torture/test.sh
new file mode 100644
index 0000000..17c5a7c
--- /dev/null
+++ b/scripts/torture/test.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+set -ex
+
+
+## Generate test case
+
+if ! test -f config.vh; then
+ python3 config.py
+fi
+
+if ! test -f test.S; then
+ cd riscv-torture
+ ./sbt "generator/run -C config/test.config"
+ cp output/test.S ../test.S
+ cd ..
+fi
+
+
+## Compile test case and create reference
+
+riscv32-unknown-elf-gcc `sed '/march=/ ! d; s,^// ,-,; y/RVIMC/rvimc/;' config.vh` -ffreestanding -nostdlib -Wl,-Bstatic,-T,sections.lds -o test.elf test.S
+LD_LIBRARY_PATH="./riscv-isa-sim:./riscv-fesvr" ./riscv-isa-sim/spike test.elf > test.ref
+riscv32-unknown-elf-objcopy -O binary test.elf test.bin
+python3 ../../firmware/makehex.py test.bin 4096 > test.hex
+
+
+## Run test
+
+iverilog -o test.vvp testbench.v ../../picorv32.v
+vvp test.vvp +vcd +hex=test.hex +ref=test.ref
+
diff --git a/scripts/torture/testbench.cc b/scripts/torture/testbench.cc
new file mode 100644
index 0000000..2925d0b
--- /dev/null
+++ b/scripts/torture/testbench.cc
@@ -0,0 +1,18 @@
+#include "Vtestbench.h"
+#include "verilated.h"
+
+int main(int argc, char **argv, char **env)
+{
+ Verilated::commandArgs(argc, argv);
+ Vtestbench* top = new Vtestbench;
+
+ top->clk = 0;
+ while (!Verilated::gotFinish()) {
+ top->clk = !top->clk;
+ top->eval();
+ }
+
+ delete top;
+ exit(0);
+}
+
diff --git a/scripts/torture/testbench.v b/scripts/torture/testbench.v
new file mode 100644
index 0000000..326de0e
--- /dev/null
+++ b/scripts/torture/testbench.v
@@ -0,0 +1,134 @@
+module testbench (
+`ifdef VERILATOR
+ input clk
+`endif
+);
+`ifndef VERILATOR
+ reg clk = 1;
+ always #5 clk = ~clk;
+`endif
+ reg resetn = 0;
+ wire trap;
+
+ wire mem_valid;
+ wire mem_instr;
+ reg mem_ready;
+ wire [31:0] mem_addr;
+ wire [31:0] mem_wdata;
+ wire [3:0] mem_wstrb;
+ reg [31:0] mem_rdata;
+
+ wire mem_la_read;
+ wire mem_la_write;
+ wire [31:0] mem_la_addr;
+ wire [31:0] mem_la_wdata;
+ wire [3:0] mem_la_wstrb;
+
+ reg [31:0] x32 = 314159265;
+ reg [31:0] next_x32;
+
+ always @(posedge clk) begin
+ if (resetn) begin
+ next_x32 = x32;
+ next_x32 = next_x32 ^ (next_x32 << 13);
+ next_x32 = next_x32 ^ (next_x32 >> 17);
+ next_x32 = next_x32 ^ (next_x32 << 5);
+ x32 <= next_x32;
+ end
+ end
+
+ picorv32 #(
+`include "config.vh"
+ ) uut (
+ .clk (clk ),
+ .resetn (resetn ),
+ .trap (trap ),
+
+ .mem_valid (mem_valid ),
+ .mem_instr (mem_instr ),
+ .mem_ready (mem_ready ),
+ .mem_addr (mem_addr ),
+ .mem_wdata (mem_wdata ),
+ .mem_wstrb (mem_wstrb ),
+ .mem_rdata (mem_rdata ),
+
+ .mem_la_read (mem_la_read ),
+ .mem_la_write(mem_la_write),
+ .mem_la_addr (mem_la_addr ),
+ .mem_la_wdata(mem_la_wdata),
+ .mem_la_wstrb(mem_la_wstrb)
+ );
+
+ localparam integer filename_len = 18;
+ reg [8*filename_len-1:0] hex_filename;
+ reg [8*filename_len-1:0] ref_filename;
+
+ reg [31:0] memory [0:4095];
+ reg [31:0] memory_ref [0:4095];
+ integer i, errcount;
+ integer cycle = 0;
+
+ initial begin
+ if ($value$plusargs("hex=%s", hex_filename)) $readmemh(hex_filename, memory);
+ if ($value$plusargs("ref=%s", ref_filename)) $readmemh(ref_filename, memory_ref);
+`ifndef VERILATOR
+ if ($test$plusargs("vcd")) begin
+ $dumpfile("test.vcd");
+ $dumpvars(0, testbench);
+ end
+`endif
+ end
+
+ always @(posedge clk) begin
+ mem_ready <= 0;
+ mem_rdata <= 'bx;
+
+ if (!trap || !resetn) begin
+ if (x32[0] && resetn) begin
+ if (mem_la_read) begin
+ mem_ready <= 1;
+ mem_rdata <= memory[mem_la_addr >> 2];
+ end else
+ if (mem_la_write) begin
+ mem_ready <= 1;
+ if (mem_la_wstrb[0]) memory[mem_la_addr >> 2][ 7: 0] <= mem_la_wdata[ 7: 0];
+ if (mem_la_wstrb[1]) memory[mem_la_addr >> 2][15: 8] <= mem_la_wdata[15: 8];
+ if (mem_la_wstrb[2]) memory[mem_la_addr >> 2][23:16] <= mem_la_wdata[23:16];
+ if (mem_la_wstrb[3]) memory[mem_la_addr >> 2][31:24] <= mem_la_wdata[31:24];
+ end else
+ if (mem_valid && !mem_ready) begin
+ mem_ready <= 1;
+ if (mem_wstrb) begin
+ if (mem_wstrb[0]) memory[mem_addr >> 2][ 7: 0] <= mem_wdata[ 7: 0];
+ if (mem_wstrb[1]) memory[mem_addr >> 2][15: 8] <= mem_wdata[15: 8];
+ if (mem_wstrb[2]) memory[mem_addr >> 2][23:16] <= mem_wdata[23:16];
+ if (mem_wstrb[3]) memory[mem_addr >> 2][31:24] <= mem_wdata[31:24];
+ end else begin
+ mem_rdata <= memory[mem_addr >> 2];
+ end
+ end
+ end
+ end else begin
+ errcount = 0;
+ for (i=0; i < 4096; i=i+1) begin
+ if (memory[i] !== memory_ref[i]) begin
+ $display("Signature check failed at %04x: mem=%08x ref=%08x", i << 2, memory[i], memory_ref[i]);
+ errcount = errcount + 1;
+ end
+ end
+ if (errcount)
+ $display("FAILED: Got %1d errors for %1s => %1s!", errcount, hex_filename, ref_filename);
+ else
+ $display("PASSED %1s => %1s.", hex_filename, ref_filename);
+ $finish;
+ end
+
+ if (cycle > 100000) begin
+ $display("FAILED: Timeout!");
+ $finish;
+ end
+
+ resetn <= cycle > 10;
+ cycle <= cycle + 1;
+ end
+endmodule