A README.md => README.md +78 -0
@@ 0,0 1,78 @@
+# RISC-V single cycle processor in SystemVerilog
+Available at https://github.com/Rutherther/verilog-riscv-semestral-project
+
+This repository contains RISC-V processor written in SystemVerilog.
+
+## Architecture
+
+## Requirements
+- make
+- verilator
+- riscv32-unknown-elf toolchain (binutils, gcc)
+- python3
+
+There is a flake file for dev shell with all required dependencies
+including gtkwave for viewing vcd files.
+
+## Testing
+There are two separate testing mechanisms used,
+one is using Verilog testbenches. These testbenches
+are not automatically evaluated, but meant for observation
+of waves.
+
+Then there are ISA tests, official ones and few custom tests
+as well. These may be ran from Python script and validate correct
+functionality automatically.
+
+### Verilog testbenches
+All testbenches are located in `testbench/` folder.
+These testbenches do not assert anything by themselves.
+They pass simple arguments to the modules.
+
+#### Individual modules
+To run a module `$tb_module` (produce wave file),
+use `make ./waves/$tb_module.vcd`.
+To show this file, run
+`make show MODULE=$tb_module`
+
+#### Running programs
+There is a special testbench for running programs
+called `tb_cpu_program`.
+It is used in the ISA tests as well.
+
+To run a program, use `make run_program`.
+By default, program for finding gcd will be ran,
+with zeros in memory. (it expects
+the numbers in memory, so it won't do much)
+
+The target expects some arguments, namely
+- `PROGRAM` - name of the program to run
+- `MEMORY_LOAD` - path to the file to load memory from. Expected format is Verilog hex format. Do note that ram consists of 32 bit elements.
+- `MEMORY_WRITE` - path to the file to write memory to. By default `out/program_$PROGRAM_memory_out.dat`, so doesn't have to be changed
+
+For example, `make run_program PROGRAM=gcd MEMORY_LOAD=tests/custom/gcd/1071-462-21-input.dat`.
+This will run gcd program with 1071 and 462 as arguments. Expected output is 21.
+At the end of the program, registers are dumped to stdout. The value is in register 14 as well
+as in the memory on address zero.
+
+The program is expected to be at `programs/$PROGRAM.c`.
+It is expected that it has a main function, no arguments are passed to main.
+Main will be called from `programs/start.S`.
+
+### Compiling C programs
+Programs are compiled automatically when running
+programs or tests.
+
+To produce an object file for $program,
+use `make ./programs/bin/start-$program.o`.
+The file produced is the C program file linked with `./programs/start.S`
+This file might be used for `objdump`.
+There is make target for showing objdumps
+`make objdump PROGRAM=$program`.
+
+To produce elf file, use `make ./programs/bin/$program.bin`.
+To produce a file that may be loaded from verilog with `readmemh`,
+use `make ./programs/bin/$program.dat`.
+
+### ISA tests
+ISA tests are documented in [tests/README.md](tests/README.md)
A tests/README.md => tests/README.md +111 -0
@@ 0,0 1,111 @@
+# RISC-V tests
+Under the hood, the tests use `tb_cpu_program` Verilog testbench.
+
+Python script called `run.py` is used for testing.
+This will use makefiles under the hood,
+one from the project root, and other from `tests/official` subdirectory.
+
+## Test types
+There are two types of tests,
+official riscv tests that work by selftesting
+the processor. That means that if there is a problem
+with more instructions like branches, store words,
+it's possible that the tests will pass even though
+the processor is not working properly.
+
+The other type are custom tests. These do not depend
+on functionality of processor instructions.
+There is just a few of these. For each program,
+it's possible to specify different memory to load,
+so multiple parameters are passed to the same program.
+
+## Test success validation
+To validate test success, memory is dumped to a file
+at the end of the test - upon getting to ebreak instruction.
+This memory is then validated against file with expected memory
+values. The memory file format is Verilogs hex format.
+
+Selftests are checked the same way, but upon success, on address
+zero, there should be 0xAA. On fail, 0xFF.
+Here are the macros used for test pass and fail
+``` assembly
+#define RVTEST_PASS \
+ addi x1, zero, 0xAA; \
+ sw x1, 0(zero); \
+ nop; \
+ ebreak;
+
+#define TESTNUM gp
+#define RVTEST_FAIL \
+ addi x1, zero, 0xFF; \
+ sw x1, 0(zero); \
+ sw x1, 4(TESTNUM); \
+ nop; \
+ ebreak;
+```
+These macros are defined in `tests/official/env/p/riscv_test.h`.
+
+## Usage
+To run the tests, `run.py` can be used.
+By default, it will run the custom tests.
+
+It must be supplied one of run or list subcommands.
+To list all the tests, use `./run.py list`. To run the tests,
+supply `./run.py run`.
+
+Every test has a group. For custom tests the group specifies the program to run.
+Name of the test specifies the arguments.
+For official tests, there is only one group so far, `rv32ui`.
+
+It's possible to filter one or more of the tests,
+by using `--filter` flag. For example `./run.py run --filter gcd`
+to run only tests from the gcd group. For running only one test
+from the group, specify it after a dot. For example `./run.py --filter gcd.1071-462-21`.
+
+```
+usage: run.py [-h] [-f [FILTER ...]] [-t {custom,official}] [--trace] [--print-registers] {run,list}
+
+Test simple RISC-V processor written in Verilog.
+
+positional arguments:
+ {run,list} What to do. Either run the test, or run testcases based on filter.
+
+options:
+ -h, --help show this help message and exit
+ -f [FILTER ...], --filter [FILTER ...]
+ filter, should be in group.test format
+ -t {custom,official}, --type {custom,official}
+ type of the testcases, either custom testcases or official riscv selftests
+ --trace trace, produce vcd file
+ --print-registers dump registers on ebreak to stdout
+```
+
+## Creating custom tests
+To create a custom test, two things are needed.
+
+First, a C program has to be made. This program is expected to be located in
+`../programs/`. It should have a `main` function. This function will be called.
+It should ideally load parameters from the beginning of the memory, and save
+the result to the memory afterwards, again, at the beginning of the memory.
+
+Second, arguments have to be specified. To do this, first create a directory
+`custom/$program`. In this directory, files with parameters should be placed.
+For each test, two files are needed, input memory and expected memory.
+
+Name of the files should be `$test-input.dat` and `$test-expected.dat`.
+They should be in Verilog hex format (input will be read by readmemh, expected
+data will be compared against file written with writememh). Only first few
+words have to be specified, no need to put in full memory image.
+
+I have chosen to name the files by parameters passed in, every parameter
+separated by a dash. And at the end, the expected result is listed.
+For example, gcd.1071-462-21 will pass in 1071 and 462 as arguments
+to the gcd program. The expected result (gcd(1071, 462)) is 21.
+
+## Existing custom test programs (groups)
+- gcd
+ - Calculates gcd of two numbers
+- branches
+ - Tries all branch instructions
+- memory_bytes
+ - Tries to read and write bytes to memory
M tests/run.py => tests/run.py +9 -5
@@ 127,7 127,11 @@ def filter_tests(groups: list[TestGroup], group_name: str|None, test_name: str|N
return groups
# Program
-parser = argparse.ArgumentParser("Test simple RISC-V processor written in Verilog.")
+parser = argparse.ArgumentParser(
+ prog = "run.py",
+ description = "Test simple RISC-V processor written in Verilog."
+)
+
parser.add_argument(
"command",
choices = [ "run", "list"],
@@ 138,24 142,24 @@ parser.add_argument(
"--filter",
type = str,
nargs = "*",
- help = "Filter, should be in group.test format."
+ help = "filter, should be in group.test format"
)
parser.add_argument(
"-t",
"--type",
choices = ["custom", "official"],
default = "custom",
- help = "Type of the testcases, either custom testcases or official riscv selftests.",
+ help = "type of the testcases, either custom testcases or official riscv selftests",
)
parser.add_argument(
"--trace",
action = "store_true",
- help = "Trace, produce vcd file",
+ help = "trace, produce vcd file",
)
parser.add_argument(
"--print-registers",
action = "store_true",
- help = "Trace, produce vcd file",
+ help = "dump registers on ebreak to stdout",
)
# parser.add_argument(
# "--print-memory",