Skip to content

Commit

Permalink
introduce new testing framework
Browse files Browse the repository at this point in the history
  • Loading branch information
longfangsong committed May 23, 2022
1 parent a913040 commit debfd43
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 62 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/scala.yml → .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Scala CI
name: CI

on:
push:
Expand All @@ -21,10 +21,16 @@ jobs:
run: |
sbt scalastyle
sbt test:scalastyle
- name: Run tests
- name: Run unit tests
run: sbt test
- name: Upload vcd files
uses: actions/upload-artifact@v2
with:
name: vcds
path: ./test_run_dir/**/*.vcd
- name: Set up python for integration test
uses: actions/setup-python@v3
- name: Install integration test dependency
run: pip install jinja2
- name: Run integration test
run: make integration_test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -745,3 +745,5 @@ $RECYCLE.BIN/
*.fir
*.v
target
.dccache
*.thex
1 change: 1 addition & 0 deletions .java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
11
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
integration_test:
python3 ./scripts/integration_test/run.py
clean:
python3 ./scripts/integration_test/clean.py

29 changes: 29 additions & 0 deletions scripts/integration_test/checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pathlib import Path
import sys
from jinja2 import Environment, PackageLoader, select_autoescape


def intToBin32(i):
return int((bin(((1 << 32) - 1) & i)[2:]).zfill(32), 2)

def render_program_rom(expected):
env = Environment(
loader=PackageLoader("checker", "templates"),
autoescape=select_autoescape()
)
template = env.get_template("TopTest.scala.template")
content = template.render(expected=expected)
print(content)


def main():
expected_file = Path(sys.argv[1])
expected = []
with expected_file.open('r') as f:
for line in (line.strip() for line in f if line.strip() != ""):
expected.append(intToBin32(int(line)))
render_program_rom(expected)


if __name__ == "__main__":
main()
10 changes: 10 additions & 0 deletions scripts/integration_test/clean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
import shutil


shutil.copyfile("./src/main/scala/ProgramROM.scala.backup",
"./src/main/scala/ProgramROM.scala")
shutil.copyfile("./src/test/scala/TopTest.scala.backup",
"./src/test/scala/TopTest.scala")
os.remove("./src/main/scala/ProgramROM.scala.backup")
os.remove("./src/test/scala/TopTest.scala.backup")
36 changes: 36 additions & 0 deletions scripts/integration_test/rom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import sys
from pathlib import Path

from jinja2 import Environment, PackageLoader, select_autoescape


def render_program_rom(address_table, code_table):
env = Environment(
loader=PackageLoader("rom", "templates"),
autoescape=select_autoescape()
)
template = env.get_template("ProgramROM.scala.template")
content = template.render(
address_table=address_table, code_table=code_table)
print(content)


def main():
lot_file = Path(sys.argv[1])
address_table = {}
with lot_file.open('r') as f:
for line in (l.strip() for l in f.readlines() if l.strip() != ""):
[section_name, section_address] = line.split(":")
section_address = int(section_address, 16)
address_table[section_name] = section_address
code_table = {}
for section in address_table.keys():
path = lot_file.parent.joinpath('{}.thex'.format(section))
with path.open('r') as f:
code_table[section] = [l.strip()
for l in f.readlines() if l.strip() != ""]
render_program_rom(address_table, code_table)


if __name__ == "__main__":
main()
44 changes: 44 additions & 0 deletions scripts/integration_test/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import glob
import os
from pathlib import Path
import shutil
import sys

shutil.copyfile("./src/main/scala/ProgramROM.scala",
"./src/main/scala/ProgramROM.scala.backup")
shutil.copyfile("./src/test/scala/TopTest.scala", "./src/test/scala/TopTest.scala.backup")
fail = False
for filenamestr in glob.glob('test_cases/*'):
filename = Path(filenamestr)
for asmstr in glob.glob(filenamestr + '/*.asm'):
asm = Path(asmstr)
os.system(
"docker run -i shuosc/shuasm < {} > {}"
.format(asmstr, asm.parent.joinpath("{}.thex".format(asm.stem)))
)
rom = os.popen(
"python3 ./scripts/integration_test/rom.py ./{}/layout.lot"
.format(filename)
).read()
expected = os.popen(
"python3 ./scripts/integration_test/checker.py ./{}/expected.exp"
.format(filename)
).read()
with open('./src/main/scala/ProgramROM.scala', 'w') as f:
f.write(rom)
with open('./src/test/scala/TopTest.scala', 'w') as f:
f.write(expected)
return_value = os.system('sbt "testOnly TopSpec" > /dev/null')
if return_value & 0xff00 != 0:
print("Test failed: {}".format(filename))
fail = True
break

shutil.copyfile("./src/main/scala/ProgramROM.scala.backup",
"./src/main/scala/ProgramROM.scala")
shutil.copyfile("./src/test/scala/TopTest.scala.backup",
"./src/test/scala/TopTest.scala")
os.remove("./src/main/scala/ProgramROM.scala.backup")
os.remove("./src/test/scala/TopTest.scala.backup")
if fail:
sys.exit(1)
23 changes: 23 additions & 0 deletions scripts/integration_test/templates/ProgramROM.scala.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import chisel3._

class ProgramROMBundle extends Bundle {
val address = Input(UInt(32.W))
val value = Output(UInt(32.W))
}

// A temporary program ROM
// Just for testing, use a real flash rom in real world
class ProgramROM extends Module {
val io = IO(new ProgramROMBundle)
{% for section_name, section_content in code_table.items() %}
val {{section_name}}Content = VecInit(Array(
{% for instruction in section_content %}"h{{instruction}}".U(32.W),
{% endfor %}
)){% endfor %}
{% for section_name, section_start in address_table.items() | sort(attribute='1')%}
{% if loop.first %}when(io.address < "h{{"{:x}".format(section_start + 4*(code_table[section_name] | length))}}".U){% else %}.elsewhen(io.address < "h{{"{}".format(section_start + 4*(code_table[section_name] | length))}}".U){% endif %} {
io.value := {{section_name}}Content((io.address - "h80000000".U) (31, 2))
}{% endfor %}.otherwise {
io.value := 0xdead.U
}
}
38 changes: 38 additions & 0 deletions scripts/integration_test/templates/TopTest.scala.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import chisel3._
import chisel3.iotesters._
import org.scalatest.{FlatSpec, Matchers}

class TopTest(top: Top) extends PeekPokeTester(top) {
var expected = List(
{% for e in expected %}BigInt("{{e}}"),
{% endfor %}
)
var last = BigInt(0)
var countDown = 65536
while (!expected.isEmpty) {
val next = peek(top.io.gpio)
if (next != last) {
expect(top.io.gpio, expected.head)
expected = expected.tail
countDown = 65536
} else {
countDown -= 1
if (countDown <= 0) {
fail
expected = List()
}
}
last = next
step(1)
}
}

class TopSpec extends FlatSpec with Matchers {
behavior of "TopSpec"

it should "compute successfully" in {
chisel3.iotesters.Driver.execute(Array("--generate-vcd-output", "on"), () => new Top) { top =>
new TopTest(top)
} should be(true)
}
}
53 changes: 11 additions & 42 deletions src/main/scala/ProgramROM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,16 @@ class ProgramROMBundle extends Bundle {
// Just for testing, use a real flash rom in real world
class ProgramROM extends Module {
val io = IO(new ProgramROMBundle)
val content = VecInit(Array(
"h00004fb7".U(32.W), // li t6, 0x4000
"h000f8f93".U(32.W),
"h01200513".U(32.W), // li a0, 0x12
"h00afa023".U(32.W), // sw a0, 0(t6)
"h00000513".U(32.W), // li a0, 0x0
"h00afa223".U(32.W), // sw a0, 4(t6)

"h80020f37".U(32.W), // li t5, 0x80020000
"h00100293".U(32.W), // li t0, 1
"h00200313".U(32.W), // li t1, 2
"h006303b3".U(32.W), // add t2, t1, t1
"h005383b3".U(32.W), // add t2, t2, t0
"h10012e37".U(32.W), // li t3, 0x10012000
"h000e0e13".U(32.W),
"h007e2023".U(32.W), // sw t2, 0(t3)
// label
"h007f2223".U(32.W), // sw t2, 4(t5)
"h004f2e03".U(32.W), // lw t3, 4(t5)
"h01c38eb3".U(32.W), // add t4, t2, t3
"h007eeeb3".U(32.W), // or t4, t4, t2
"h10012e37".U(32.W), // li t3, 0x10012000
"h000e0e13".U(32.W),
"h01de2023".U(32.W), // sw t4, 0(t3)
"h00138393".U(32.W), // addi t2, t2, 1
// j label
"hfe1ff06f".U(32.W)))

val interruptContent = VecInit(Array(
"h000fa503".U(32.W), // lw a0, 0(t6)
"h00a50533".U(32.W), // add a0, a0, a0
"h00afa023".U(32.W), // sw a0, 0(t6)
"h100125b7".U(32.W), // li a1, 0x10012000
"h00058593".U(32.W),
"h00a5a023".U(32.W), // sw a0, 0(a1)
// mret
"h30200073".U(32.W)))

when(io.address < "h80010000".U) {
io.value := content((io.address - "h80000000".U) (31, 2))

val codeContent = VecInit(Array(
"h10012537".U(32.W),
"h00100593".U(32.W),
"h00b52023".U(32.W)
))

when(io.address < "h8000000c".U) {
io.value := codeContent((io.address - "h80000000".U) (31, 2))
}.otherwise {
io.value := interruptContent((io.address - "h80010000".U) (31, 2))
io.value := 0xdead.U
}
}
}
4 changes: 4 additions & 0 deletions src/main/scala/Top.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ class Top extends Module {
cpu.io.programROMBundle <> programROM.io
cpu.io.timerInterruptPending := dataBus.io.timerInterruptPending
}

object TopDriver extends App {
(new chisel3.stage.ChiselStage).emitVerilog(new Top, args)
}
37 changes: 19 additions & 18 deletions src/test/scala/TopTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ import chisel3.iotesters._
import org.scalatest.{FlatSpec, Matchers}

class TopTest(top: Top) extends PeekPokeTester(top) {
for (i <- 0 to 15) {
var expected = List(
BigInt("1")
)
var last = BigInt(0)
var countDown = 65536
while (!expected.isEmpty) {
val next = peek(top.io.gpio)
if (next != last) {
expect(top.io.gpio, expected.head)
expected = expected.tail
countDown = 65536
} else {
countDown -= 1
if (countDown <= 0) {
fail
expected = List()
}
}
last = next
step(1)
}
expect(top.io.gpio, 5.U(32.W))
for (i <- 0 to 10) {
step(1)
}
expect(top.io.gpio, 0x24.U(32.W))
for (i <- 0 to 5) {
step(1)
}
expect(top.io.gpio, 0xa.U(32.W))
for (i <- 0 to 12) {
step(1)
}
expect(top.io.gpio, 0x48.U(32.W))
for (i <- 0 to 5) {
step(1)
}
expect(top.io.gpio, 0xc.U(32.W))
}

class TopSpec extends FlatSpec with Matchers {
Expand Down
24 changes: 24 additions & 0 deletions test_cases/basic_math/code.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
main:
li a0, 0x10012000
sw zero, 0(a0)
li a2, 1
li a3, 2
add a2, a2, a3
sw a2, 0(a0)
li a2, 3
li a3, 2
sub a2, a2, a3
sw a2, 0(a0)
sub a2, a2, a3
sw a2, 0(a0)
sll a2, a2, a3
sw a2, 0(a0)
sra a2, a2, a3
addi a2, a2, 1
sw a2, 0(a0)
addi a2, a2, -2
sw a2, 0(a0)
slli a2, a2, 3
sw a2, 0(a0)
srai a2, a2, 2
sw a2, 0(a0)
8 changes: 8 additions & 0 deletions test_cases/basic_math/expected.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
3
1
-1
-4
0
-2
-16
-4
1 change: 1 addition & 0 deletions test_cases/basic_math/layout.lot
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
code: 0x80001000

0 comments on commit debfd43

Please sign in to comment.