~ruther/verilog-riscv-semestral-project

308a146292087449ecb82e4b7935f789ab21c64b — Rutherther 1 year, 5 months ago 38e8429
tests: add register dump, printing
M src/file_program_memory.sv => src/file_program_memory.sv +1 -1
@@ 4,7 4,7 @@ module file_program_memory
  output [31:0] instruction
);
  parameter string FILE_NAME;
  parameter WIDTH = 15;
  parameter WIDTH = 20;
  parameter MEM_SIZE = 1 << (WIDTH - 2) - 1;

  reg [31:0] imem[0:MEM_SIZE];

M src/ram.sv => src/ram.sv +2 -1
@@ 4,6 4,7 @@ module ram (
  input         clk, we,
  input [31:0]  a, wd,
  input [3:0]   write_byte_enable,
  input         dump,
  output [31:0] rd);

  reg [31:0]      mask;


@@ 33,7 34,7 @@ module ram (

  initial begin
    if (WRITE_FILE == 1) begin
      wait (a == {32{1'b1}});
      wait (dump == 1);
      #5
      $display("Writing memory to file %s.", WRITE_FILE_PATH);
      $writememh(WRITE_FILE_PATH, memory);

M testbench/tb_cpu_program.sv => testbench/tb_cpu_program.sv +17 -8
@@ 3,7 3,7 @@ import cpu_types::*;
module tb_cpu_program();
  reg clk, rst_n;

  wire [31:0] memory_address, cpu_memory_address, memory_write, memory_out;
  wire [31:0] memory_address, memory_write, memory_out;
  wire [3:0]  memory_write_byte_enable;
  wire        memory_we;



@@ 13,15 13,16 @@ module tb_cpu_program();
  wire        ebreak;

  parameter string  CPU_PROGRAM_PATH;
  parameter string  CPU_PROGRAM_NAME;

  parameter string  TRACE_FILE_PATH = "trace.vcd";

  parameter         MEMORY_LOAD_FILE = 0;
  parameter string  MEMORY_LOAD_FILE_PATH = "";
  parameter         MEMORY_WRITE_FILE = 0;
  parameter string  MEMORY_WRITE_FILE_PATH = "";

  // assign 0xFF... when ebreak. To save the memory to a file.
  assign memory_address = ebreak == 1'b1 ? {32{1'b1}} : cpu_memory_address;
  parameter         REGISTER_DUMP_FILE = 0;
  parameter string  REGISTER_DUMP_FILE_PATH = "";

  cpu uut(
    .clk(clk),


@@ 30,7 31,7 @@ module tb_cpu_program();
    .instruction(instruction),
    .pc(pc),

    .memory_address(cpu_memory_address),
    .memory_address(memory_address),
    .memory_out(memory_out),
    .memory_write(memory_write),
    .memory_byte_enable(memory_write_byte_enable),


@@ 50,18 51,26 @@ module tb_cpu_program();
    .write_byte_enable(memory_write_byte_enable),
    .we(memory_we),
    .wd(memory_write),
    .rd(memory_out)
    .rd(memory_out),
    .dump(ebreak)
  );

  file_program_memory #(
    .FILE_NAME(CPU_PROGRAM_PATH)
  ) prog_mem_inst(
    .addr(pc[14:0]),
    .addr(pc[19:0]),
    .instruction(instruction)
  );

  always_ff @ (posedge ebreak) begin
    $display("ebreak at %d", pc);

    if (REGISTER_DUMP_FILE == 1)
      $writememh(REGISTER_DUMP_FILE_PATH, uut.register_file_inst.gprs);

    for (int i = 1; i < 32; i++) begin
      $display("R%0d:%0d", i, uut.register_file_inst.gprs[i]);
    end
    #15 $finish;
  end



@@ 71,7 80,7 @@ module tb_cpu_program();
  end

  initial begin
    $dumpfile({"waves/cpu_program_", CPU_PROGRAM_NAME, ".vcd"});
    $dumpfile(TRACE_FILE_PATH);
    $dumpvars;

    rst_n = 0;

M tests/custom/custom_tests.py => tests/custom/custom_tests.py +4 -2
@@ 37,9 37,10 @@ def find_tests(groups_dir: Path, programs_dir: Path, out_dir: Path, group_name: 
                group_dir / f"{test_name}-input.dat",
                out_dir / f"{test_name}-output.dat",
                group_dir / f"{test_name}-expected.dat",
                out_dir / f"{test_name}-registers.dat",
            )

            if not test.input_file.exists() or not test.expected_file.exists():
            if not test.memory_in_file.exists() or not test.memory_exp_file.exists():
                continue

            tests.append(test)


@@ 49,7 50,8 @@ def find_tests(groups_dir: Path, programs_dir: Path, out_dir: Path, group_name: 

    return groups

def compile_program(make_dir: Path, group: TestGroup) -> bool:
def compile_program(make_dir: Path, test: Test) -> bool:
    group = test.group
    return subprocess.run(
        ["make", "-C", make_dir, group.dat_test_file.relative_to(make_dir)],
        stdout = subprocess.DEVNULL,

M tests/official/env/p/riscv_test.h => tests/official/env/p/riscv_test.h +7 -6
@@ 139,16 139,17 @@ _start:                                                                 \
//-----------------------------------------------------------------------

#define RVTEST_PASS                                                     \
        addi x0, zero, 0;                                               \
        addi x1, zero, 0xFF;                                            \
        sw x1, 0(x0);                                                   \
        addi x1, zero, 0xAA;                                            \
        sw x1, 0(zero);                                                 \
        nop;                                                            \
        ebreak;

#define TESTNUM gp
#define RVTEST_FAIL                                                     \
        addi x0, zero, 0;                                               \
        addi x1, zero, 0;                                               \
        sw x1, 0(x0);                                                   \
        addi x1, zero, 0xFF;                                            \
        sw x1, 0(zero);                                                 \
        sw x1, 4(TESTNUM);                                              \
        nop;                                                            \
        ebreak;

//-----------------------------------------------------------------------

M tests/official/official_tests.py => tests/official/official_tests.py +4 -3
@@ 29,9 29,10 @@ def find_tests(out_dir: Path) -> list[TestGroup]:
        tests.append(Test(
            group = group,
            name = test_name,
            input_file = here / "input.dat",
            output_file = out_dir / f"{group.name}-output.dat",
            expected_file = here / "expected.dat",
            memory_in_file = out_dir / f"rv32ui_{test_name}.dat",
            memory_out_file = out_dir / f"rv32ui_{test_name}_output.dat",
            memory_exp_file = here / "expected.dat",
            register_dump_file = out_dir / f"rv32ui_{test_name}_registers.dat"
        ))

        groups.append(group)

M tests/run.py => tests/run.py +94 -52
@@ 16,9 16,16 @@ sys.path.append('./official')
import custom_tests
import official_tests

PROGRAM_FILE = "program.dat"
MEMORY_WRITE_FILE = "memory_out.dat"
MEMORY_LOAD_FILE = "memory_in.dat"
REGISTER_FILE = "register_dump.dat"
SIMULATE_EXE = "simulate_cpu_program"
TRACE_FILE = "trace.vcd"

def validate_test(test: Test) -> Validation:
    expected = test.expected_file.read_text()
    actual = test.output_file.read_text()
    expected = test.memory_exp_file.read_text()
    actual = test.memory_out_file.read_text()

    expected_arr = list(filter(lambda word: word != "", re.split(r"[\n ]+", expected)))
    actual_arr = re.split(r"[\n ]+", actual)


@@ 32,14 39,26 @@ def validate_test(test: Test) -> Validation:
        matches = (actual_arr == expected_arr)
    )

def compile(project_dir: Path, comp_list: Path, out_dir: Path) -> bool:
    program_path = out_dir / "program.dat"
    memory_write_file = out_dir / "memory_out.dat"
    memory_load_file = out_dir / "memory_in.dat"
def print_registers(test: Test):
    reg_names = [ "ze", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"]
    values = ["00000000"] + re.split(r"[\n ]+", test.register_dump_file.read_text())

    print("  ", end = '')
    for i, (name, value) in enumerate(zip(reg_names, values)):
        print(f"{name}: 0x{value}", end = '\n  ' if i % 4 == 3 else ' ')

def compile(project_dir: Path, comp_list: Path, out_dir: Path, trace: bool) -> bool:
    program_path = out_dir / PROGRAM_FILE
    memory_load_file = out_dir / MEMORY_LOAD_FILE
    memory_write_file = out_dir / MEMORY_WRITE_FILE
    register_file = out_dir / REGISTER_FILE
    trace_file = out_dir / TRACE_FILE

    generics = {
        'CPU_PROGRAM_PATH': f"\\\"{program_path}\\\"",
        'CPU_PROGRAM_NAME': f"\\\"testcase\\\"",
        'TRACE_FILE_PATH': f"\\\"{trace_file}\\\"",
        'REGISTER_DUMP_FILE': 1,
        'REGISTER_DUMP_FILE_PATH': f"\\\"{register_file}\\\"",
        'MEMORY_LOAD_FILE': 1,
        'MEMORY_LOAD_FILE_PATH': f"\\\"{memory_load_file}\\\"",
        'MEMORY_WRITE_FILE': 1,


@@ 54,10 73,14 @@ def compile(project_dir: Path, comp_list: Path, out_dir: Path) -> bool:
    params.append("--Mdir")
    params.append(f"{out_dir}")
    params.append("-o")
    params.append(f"simulate_cpu_program")
    params.append(SIMULATE_EXE)
    params.append("--top")
    params.append("tb_cpu_program")

    if trace:
        params.append("--trace")
        params.append("--trace-max-array 4096")

    for line in comp_list.read_text().split('\n'):
        if line != "":
            params.append(f"{project_dir / line}")


@@ 70,24 93,36 @@ def compile(project_dir: Path, comp_list: Path, out_dir: Path) -> bool:
    ).returncode == 0

def run_test(out_dir: Path, test: Test) -> bool:
    program_path = out_dir / "program.dat"
    memory_write_file = out_dir / "memory_out.dat"
    memory_load_file = out_dir / "memory_in.dat"
    program_path = out_dir / PROGRAM_FILE
    memory_load_file = out_dir / MEMORY_LOAD_FILE
    memory_write_file = out_dir / MEMORY_WRITE_FILE
    register_file = out_dir / REGISTER_FILE

    shutil.copy(test.input_file, memory_load_file)
    shutil.copy(test.memory_in_file, memory_load_file)
    shutil.copy(test.group.dat_test_file, program_path)

    subprocess.run(
        [out_dir / f"simulate_cpu_program"],
        [out_dir / SIMULATE_EXE],
        stdout = subprocess.DEVNULL,
        shell = True,
        check = True,
    )

    shutil.copy(memory_write_file, test.output_file)
    shutil.copy(memory_write_file, test.memory_out_file)
    shutil.copy(register_file, test.register_dump_file)

    return True

def filter_tests(groups: list[TestGroup], group_name: str|None, test_name: str|None) -> list[TestGroup]:
    if group_name is not None:
        groups = list(filter(lambda g: g.name == group_name, groups))

    if test_name is not None:
        for group in groups:
            group.tests = list(filter(lambda t: t.name == test_name, group.tests))

    return groups

# Program
parser = argparse.ArgumentParser("Test simple RISC-V processor written in Verilog.")
parser.add_argument(


@@ 109,6 144,21 @@ parser.add_argument(
    default = "custom",
    help = "Type of the testcases, either custom testcases or official riscv selftests.",
)
parser.add_argument(
    "--trace",
    action = "store_true",
    help = "Trace, produce vcd file",
)
parser.add_argument(
    "--print-registers",
    action = "store_true",
    help = "Trace, produce vcd file",
)
# parser.add_argument(
#     "--print-memory",
#     type = int,
#     help = "Trace, produce vcd file",
# )

args = parser.parse_args()



@@ 118,55 168,47 @@ programs_dir = project_dir / "programs"
out_dir = here / "out"
groups_dir = here / "custom"

# TODO support multiple tests
group_name, test_name = args.filter[0].split('.') if args.filter is not None else (None, None)
# TODO support multiple filters
filt = args.filter[0].split('.') if args.filter is not None and len(args.filter) > 0 else [None, None]

group_name = filt[0]
test_name = None
if len(filt) >= 2:
    test_name = filt[1]

compile(project_dir, here / "comp_list.lst", out_dir)
compile(project_dir, here / "comp_list.lst", out_dir, args.trace)

if args.type == "custom":
    test_groups: list[TestGroup] = custom_tests.find_tests(
        groups_dir, programs_dir, out_dir, group_name, test_name
    )
    if args.command == "list":
        print("Found these tests:")
        for group in test_groups:
            for test in group.tests:
                print(f"  {test}")
        sys.exit(0)

    for group in test_groups:
        custom_tests.compile_program(project_dir, group)
        for test in group.tests:
            run_test(out_dir, test)

            validation = validate_test(test)

            if validation.matches:
                print(f"{test.group.name}.{test.name} {bcolors.OKGREEN}passed{bcolors.ENDC}")
            else:
                print(f"{test.group.name}.{test.name} {bcolors.FAIL}failed{bcolors.ENDC}")
                print(f"  Got {validation.actual}. Expected {validation.expected}")
    compile_program = custom_tests.compile_program
else: # official
    test_groups: list[TestGroup] = official_tests.find_tests(
        here / "official" / "out"
        here / "official" / "out",
    )
    test_groups = filter_tests(test_groups, group_name, test_name)
    compile_program = official_tests.compile_program

    if args.command == "list":
        print("Found these tests:")
        for group in test_groups:
            for test in group.tests:
                print(f"  {test}")
        sys.exit(0)

if args.command == "list":
    print("Found these tests:")
    for group in test_groups:
        for test in group.tests:
            official_tests.compile_program(project_dir, test)
            run_test(out_dir, test)
            print(f"  {test}")
    sys.exit(0)

for group in test_groups:
    for test in group.tests:
        compile_program(project_dir, test)
        run_test(out_dir, test)

        validation = validate_test(test)

            validation = validate_test(test)
        if validation.matches:
            print(f"{test.group.name}.{test.name} {bcolors.OKGREEN}passed{bcolors.ENDC}")
        else:
            print(f"{test.group.name}.{test.name} {bcolors.FAIL}failed{bcolors.ENDC}")
            print(f"  Got {validation.actual}. Expected {validation.expected}")

            if validation.matches:
                print(f"{test.group.name}.{test.name} {bcolors.OKGREEN}passed{bcolors.ENDC}")
            else:
                print(f"{test.group.name}.{test.name} {bcolors.FAIL}failed{bcolors.ENDC}")
                print(f"  Got {validation.actual}. Expected {validation.expected}")
        if args.print_registers:
            print_registers(test)

M tests/test_types.py => tests/test_types.py +4 -3
@@ 31,9 31,10 @@ class Test:
    group: TestGroup
    name: str

    input_file: Path
    output_file: Path
    expected_file: Path
    memory_in_file: Path
    memory_out_file: Path
    memory_exp_file: Path
    register_dump_file: Path

    def __str__(self):
        return f"{self.group.name}.{self.name}"

Do not follow this link