From b0f8702877121832dfdee7d921af417237673284 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sun, 19 Nov 2023 14:57:52 +0100 Subject: [PATCH] docs: add basic documentation --- README.md | 78 ++++++++++++++++++++++++++++++++++ tests/README.md | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/run.py | 14 +++--- 3 files changed, 198 insertions(+), 5 deletions(-) create mode 100755 README.md create mode 100644 tests/README.md diff --git a/README.md b/README.md new file mode 100755 index 0000000..4d232da --- /dev/null +++ b/README.md @@ -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) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..f51fe86 --- /dev/null +++ b/tests/README.md @@ -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 diff --git a/tests/run.py b/tests/run.py index 3647e19..7e0efaa 100755 --- a/tests/run.py +++ b/tests/run.py @@ -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", -- 2.48.1