<reponame>rprinz08/hBPF<gh_stars>10-100
#!/usr/bin/env python3

import sys
import re
from collections import namedtuple
# add search paths seen from project root folder
# (used for vscode test integration)
sys.path.insert(0, 'source')
sys.path.insert(0, 'tools/ubpf')
# add search paths when run from inside test folder
# (used when running test directly)
sys.path.insert(0, '../source')
sys.path.insert(0, '../tools/ubpf')

import unittest
import colour_runner.runner
import os
import gc
import tempfile
import struct
import re
import ntpath
import ubpf.assembler
import tools.testdata
from datetime import datetime
from migen import *
from litedram.frontend.bist import LFSR
from fpga.ram import *
from fpga.ram64 import *
from fpga.cpu import *

"""
This script determines the cycles for each opcode including opcode fetch.
It creates a CSV file for further processing. If OUPUT_MD_TABLE is set to
True, a markdown file will be created instead.
"""

OUPUT_MD_TABLE = False

stats = {}

def cpu_test(fd, opcode, cpu):
    ticks = 0
    done = False
    op = None
    ip = 0

    print(f"Testing ({opcode.name:15s}), ", end="")

    # Start CPU in reset an set input registers
    yield cpu.reset_n.eq(0)
    yield cpu.csr_r1.storage.eq(opcode.regs_in.r1)
    yield cpu.csr_r2.storage.eq(opcode.regs_in.r2)
    yield cpu.csr_r3.storage.eq(opcode.regs_in.r3)
    yield cpu.csr_r4.storage.eq(opcode.regs_in.r4)
    yield cpu.csr_r5.storage.eq(opcode.regs_in.r5)
    yield

    # Bring CPU out of reset
    yield cpu.reset_n.eq(1)
    yield

    # Process opcodes until CPU halts but no longer then 200 cycles.
    while ticks < 200 and not done:
        yield

        # Get current CPU state.
        halt = (yield cpu.halt)
        error = (yield cpu.error)
        state = (yield cpu.state)
        ip = (yield cpu.ip)
        if op is None:
            op = (yield cpu.opcode)
        r0 = (yield cpu.r0)
        r1 = (yield cpu.r1)
        r2 = (yield cpu.r2)
        r3 = (yield cpu.r3)
        r4 = (yield cpu.r4)
        r5 = (yield cpu.r5)

        if halt != 0:
            done = True

        ticks += 1

    # Subtract initial reset tick and final added exit op-code tick
    # (except for the 'exit' opcode itself)
    ticks -= (2 if opcode.name != "exit" else 1)

    # Write output
    if OUPUT_MD_TABLE:
        fd.write(f"|{opcode.name}|0x{op:02x}|{ticks}|\n")
    else:
        fd.write(f"\"{opcode.name}\",{ticks},{state},{halt},{error},"
            f"{r0},{r1},{r2},{r3},{r4},{r5}\n")

    failed = (opcode.regs_cmp.r1 != r1 or opcode.regs_cmp.r2 != r2 or
              opcode.regs_cmp.r3 != r3 or opcode.regs_cmp.r4 != r4 or
              opcode.regs_cmp.r5 != r5)

    if not failed and not opcode.ip is None:
        if opcode.ip != ip:
            failed = True

    print(f"opcode ({op:02x}) ... ", end="")
    if failed:
        print(f"\033[31mFAIL\033[0m, cycles ({ticks:4d})")
        if not opcode.ip is None:
            print(f"        IP {ip:016x} ({opcode.ip:016x})")
        print(f"        R1 {r1:016x} ({opcode.regs_cmp.r1:016x})")
        print(f"        R2 {r2:016x} ({opcode.regs_cmp.r2:016x})")
        print(f"        R3 {r3:016x} ({opcode.regs_cmp.r3:016x})")
        print(f"        R4 {r4:016x} ({opcode.regs_cmp.r4:016x})")
        print(f"        R5 {r5:016x} ({opcode.regs_cmp.r5:016x})")

        print(f"state: {state}, ip: {ip}, op: {op:02x}, "
            f"halt: {halt}, error: {error}")
    else:
        print(f"\033[32mOK  \033[0m, cycles ({ticks:4d})")

    b = stats.get(ticks, 0)
    stats[ticks] = b+1

    return


def opcode_cycles(fd, opcode):
    # Add additional 'exit' op-code to test ...
    code = ubpf.assembler.assemble(opcode.code + "\nexit")
    half_words = len(code) // 4
    pgm_mem = list(struct.unpack('>{}L'.format(half_words), code))

    # Create some dummy data memory ...
    data = b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff"
    data_mem = list(data)

    cpu = CPU(pgm_init=pgm_mem, data_init=data_mem, simulation=True)
    run_simulation(cpu, cpu_test(fd, opcode, cpu), vcd_name="stats_op_cycles.vcd")


# Prepare named tuples to describe test
REGS = namedtuple('REGS', 'r1 r2 r3 r4 r5')
OPCODE = namedtuple('OPCODE', 'name code regs_in, regs_cmp,' +
    'ip, lddw')
OPCODE.__new__.__defaults__ = ("", "", REGS(0, 0, 0, 0, 0), REGS(0, 0, 0, 0, 0),
    None, False)

# Test definitions

#region ALU 64-Bit
opcodes_alu64 = [
    OPCODE("add imm",   "add r1, 1",
        REGS(1, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("add reg",   "add r1, r2",
        REGS(1, 1, 0, 0, 0),
        REGS(2, 1, 0, 0, 0)),
    OPCODE("sub imm",   "sub r1, 1",
        REGS(2, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("sub reg",   "sub r1, r2",
        REGS(2, 1, 0, 0, 0),
        REGS(1, 1, 0, 0, 0)),
    OPCODE("mul imm",   "mul r1, 2",
        REGS(2, 0, 0, 0, 0),
        REGS(4, 0, 0, 0, 0)),
    OPCODE("mul reg",   "mul r1, r2",
        REGS(2, 2, 0, 0, 0),
        REGS(4, 2, 0, 0, 0)),
    OPCODE("div imm",   "div r1, 2",
        REGS(5, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("div reg",   "div r1, r2",
        REGS(5, 2, 0, 0, 0),
        REGS(2, 2, 0, 0, 0)),
    OPCODE("or imm",    "or r1, 1",
        REGS(2, 0, 0, 0, 0),
        REGS(3, 0, 0, 0, 0)),
    OPCODE("or reg",    "or r1, r2",
        REGS(2, 1, 0, 0, 0),
        REGS(3, 1, 0, 0, 0)),
    OPCODE("and imm",   "and r1, 2",
        REGS(3, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("and reg",   "and r1, r2",
        REGS(3, 2, 0, 0, 0),
        REGS(2, 2, 0, 0, 0)),
    OPCODE("lsh imm",   "lsh r1, 4",
        REGS(1, 0, 0, 0, 0),
        REGS(16, 0, 0, 0, 0)),
    OPCODE("lsh reg",   "lsh r1, r2",
        REGS(1, 4, 0, 0, 0),
        REGS(16, 4, 0, 0, 0)),
    OPCODE("rsh imm",   "rsh r1, 3",
        REGS(16, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("rsh reg",   "rsh r1, r2",
        REGS(16, 3, 0, 0, 0),
        REGS(2, 3, 0, 0, 0)),
    OPCODE("neg",       "neg r1",
        REGS(3,                       0, 0, 0, 0),
        REGS(-3 & 0xffffffffffffffff, 0, 0, 0, 0)),
    OPCODE("mod imm",   "mod r1, 2",
        REGS(5, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("mod reg",   "mod r1, r2",
        REGS(5, 2, 0, 0, 0),
        REGS(1, 2, 0, 0, 0)),
    OPCODE("xor imm",   "xor r1, 2",
        REGS(7, 0, 0, 0, 0),
        REGS(5, 0, 0, 0, 0)),
    OPCODE("xor reg",   "xor r1, r2",
        REGS(7, 2, 0, 0, 0),
        REGS(5, 2, 0, 0, 0)),
    OPCODE("mov imm",   "mov r1, 2",
        REGS(4, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("arsh imm",   "arsh r1, 16",
        REGS(0x8000000000000000, 0, 0, 0, 0),
        REGS(0xffff800000000000, 0, 0, 0, 0)),
    OPCODE("arsh reg",   "arsh r1, r2",
        REGS(0x8000000000000000, 16, 0, 0, 0),
        REGS(0xffff800000000000, 16, 0, 0, 0)),
    OPCODE("le16",      "le16 r1",
        REGS(0xaabb, 0, 0, 0, 0),
        REGS(0xaabb, 0, 0, 0, 0)),
    OPCODE("le32",      "le32 r1",
        REGS(0xaabbccdd, 0, 0, 0, 0),
        REGS(0xaabbccdd, 0, 0, 0, 0)),
    OPCODE("le64",      "le64 r1",
        REGS(0xaabbccddeeff0011, 0, 0, 0, 0),
        REGS(0xaabbccddeeff0011, 0, 0, 0, 0)),
    OPCODE("be16",      "be16 r1",
        REGS(0xaabb, 0, 0, 0, 0),
        REGS(0xbbaa, 0, 0, 0, 0)),
    OPCODE("be32",      "be32 r1",
        REGS(0xaabbccdd, 0, 0, 0, 0),
        REGS(0xddccbbaa, 0, 0, 0, 0)),
    OPCODE("be64",      "be64 r1",
        REGS(0xaabbccddeeff0011, 0, 0, 0, 0),
        REGS(0x1100ffeeddccbbaa, 0, 0, 0, 0)),
]
#endregion

#region ALU 32-Bit
opcodes_alu32 = [
    OPCODE("add32 imm",   "add32 r1, 1",
        REGS(1, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("add32 reg",   "add32 r1, r2",
        REGS(1, 1, 0, 0, 0),
        REGS(2, 1, 0, 0, 0)),
    OPCODE("sub32 imm",   "sub32 r1, 1",
        REGS(2, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("sub32 reg",   "sub32 r1, r2",
        REGS(2, 1, 0, 0, 0),
        REGS(1, 1, 0, 0, 0)),
    OPCODE("mul32 imm",   "mul32 r1, 2",
        REGS(2, 0, 0, 0, 0),
        REGS(4, 0, 0, 0, 0)),
    OPCODE("mul32 reg",   "mul32 r1, r2",
        REGS(2, 2, 0, 0, 0),
        REGS(4, 2, 0, 0, 0)),
    OPCODE("div32 imm",   "div32 r1, 2",
        REGS(5, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("div32 reg",   "div32 r1, r2",
        REGS(5, 2, 0, 0, 0),
        REGS(2, 2, 0, 0, 0)),
    OPCODE("or32 imm",    "or32 r1, 1",
        REGS(2, 0, 0, 0, 0),
        REGS(3, 0, 0, 0, 0)),
    OPCODE("or32 reg",    "or32 r1, r2",
        REGS(2, 1, 0, 0, 0),
        REGS(3, 1, 0, 0, 0)),
    OPCODE("and32 imm",   "and32 r1, 2",
        REGS(3, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("and32 reg",   "and32 r1, r2",
        REGS(3, 2, 0, 0, 0),
        REGS(2, 2, 0, 0, 0)),
    OPCODE("lsh32 imm",   "lsh32 r1, 4",
        REGS(1, 0, 0, 0, 0),
        REGS(16, 0, 0, 0, 0)),
    OPCODE("lsh32 reg",   "lsh32 r1, r2",
        REGS(1, 4, 0, 0, 0),
        REGS(16, 4, 0, 0, 0)),
    OPCODE("rsh32 imm",   "rsh32 r1, 3",
        REGS(16, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("rsh32 reg",   "rsh32 r1, r2",
        REGS(16, 3, 0, 0, 0),
        REGS(2, 3, 0, 0, 0)),
    OPCODE("neg32",       "neg32 r1",
        REGS(3,                       0, 0, 0, 0),
        REGS(-3 & 0x00000000ffffffff, 0, 0, 0, 0)),
    OPCODE("mod32 imm",   "mod32 r1, 2",
        REGS(5, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("mod32 reg",   "mod32 r1, r2",
        REGS(5, 2, 0, 0, 0),
        REGS(1, 2, 0, 0, 0)),
    OPCODE("xor32 imm",   "xor32 r1, 2",
        REGS(7, 0, 0, 0, 0),
        REGS(5, 0, 0, 0, 0)),
    OPCODE("xor32 reg",   "xor32 r1, r2",
        REGS(7, 2, 0, 0, 0),
        REGS(5, 2, 0, 0, 0)),
    OPCODE("mov32 imm",   "mov32 r1, 2",
        REGS(4, 0, 0, 0, 0),
        REGS(2, 0, 0, 0, 0)),
    OPCODE("arsh32 imm",   "arsh32 r1, 16",
        REGS(0x0000000080000000, 0, 0, 0, 0),
        REGS(0x00000000ffff8000, 0, 0, 0, 0)),
    OPCODE("arsh32 reg",   "arsh32 r1, r2",
        REGS(0x0000000080000000, 16, 0, 0, 0),
        REGS(0x00000000ffff8000, 16, 0, 0, 0)),
]
#endregion

#region LD
opcodes_ld = [
    OPCODE("lddw",   "lddw r1, 0xbbaa998877665544",
        REGS(                 0, 0, 0, 0, 0),
        REGS(0xbbaa998877665544, 0, 0, 0, 0)),
]
#endregion

#region ST
opcodes_st = [
    OPCODE("stb",    "stb [r1+2], 0x40",
        REGS(1, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("sth",    "sth [r1+2], 0x5040",
        REGS(1, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("stw",    "stw [r1+2], 0x70605040",
        REGS(1, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
    OPCODE("stdw",   "stdw [r1+2], 0xb0a0908070605040",
        REGS(1, 0, 0, 0, 0),
        REGS(1, 0, 0, 0, 0)),
]
#endregion

#region LDX
opcodes_ldx = [
    OPCODE("ldxb",   "ldxb r2, [r1+2]",
        REGS(1,    0, 0, 0, 0),
        REGS(1, 0x44, 0, 0, 0)),
    OPCODE("ldxh",   "ldxh r2, [r1+2]",
        REGS(1,      0, 0, 0, 0),
        REGS(1, 0x5544, 0, 0, 0)),
    OPCODE("ldxw",   "ldxw r2, [r1+2]",
        REGS(1,          0, 0, 0, 0),
        REGS(1, 0x77665544, 0, 0, 0)),
    OPCODE("ldxdw",  "ldxdw r2, [r1+2]",
        REGS(1,                  0, 0, 0, 0),
        REGS(1, 0xbbaa998877665544, 0, 0, 0)),
]
#endregion

#region STX
opcodes_stx = [
    OPCODE("stxb",   "stxb [r1+2], r2",
        REGS(1, 0x40, 0, 0, 0),
        REGS(1, 0x40, 0, 0, 0)),
    OPCODE("stxh",   "stxh [r1+2], r2",
        REGS(1, 0x5040, 0, 0, 0),
        REGS(1, 0x5040, 0, 0, 0)),
    OPCODE("stxw",   "stxw [r1+2], r2",
        REGS(1, 0x70605040, 0, 0, 0),
        REGS(1, 0x70605040, 0, 0, 0)),
    OPCODE("stxdw",  "stxdw [r1+2], r2",
        REGS(1, 0xb0a0908070605040, 0, 0, 0),
        REGS(1, 0xb0a0908070605040, 0, 0, 0)),
]
#endregion

#region JMP
opcodes_jmp = [
    OPCODE("ja",        "ja +1234",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), 1234+1),
    OPCODE("jeq imm",   "jeq r1, 9, +4",
        REGS(9, 0, 0, 0, 0),
        REGS(9, 0, 0, 0, 0), 5),
    OPCODE("jeq reg",   "jeq r1, r2, +4",
        REGS(9, 9, 0, 0, 0),
        REGS(9, 9, 0, 0, 0), 5),
    OPCODE("jgt imm",   "jgt r1, 6, +2",
        REGS(7, 0, 0, 0, 0),
        REGS(7, 0, 0, 0, 0), 3),
    OPCODE("jgt reg",   "jgt r1, r2, +2",
        REGS(7, 6, 0, 0, 0),
        REGS(7, 6, 0, 0, 0), 3),
    OPCODE("jge imm",   "jge r1, 5, +4",
        REGS(5, 0, 0, 0, 0),
        REGS(5, 0, 0, 0, 0), 5),
    OPCODE("jge reg",   "jge r1, r2, +4",
        REGS(5, 4, 0, 0, 0),
        REGS(5, 4, 0, 0, 0), 5),
    OPCODE("jset imm",  "jset r1, 0x8, +4",
        REGS(9, 0, 0, 0, 0),
        REGS(9, 0, 0, 0, 0), None),
    OPCODE("jset reg",  "jset r1, r2, +4",
        REGS(9, 8, 0, 0, 0),
        REGS(9, 8, 0, 0, 0), None),
    OPCODE("jne imm",   "jne r1, 7, +543",
        REGS(6, 0, 0, 0, 0),
        REGS(6, 0, 0, 0, 0), 544),
    OPCODE("jne reg",   "jne r1, r2, +543",
        REGS(6, 7, 0, 0, 0),
        REGS(6, 7, 0, 0, 0), 544),
    OPCODE("jsgt imm",  "jsgt r1, 0xffffffff, +4",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jsgt reg",  "jsgt r1, r2, +4",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jsge imm",  "jsge r1, 0xffffffff, +5",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jsge reg",  "jsge r1, r2, +5",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    # OPCODE("call",       "call 0",
    #     REGS(0, 0, 0, 0, 0),
    #     REGS(0, 0, 0, 0, 0)),
    OPCODE("exit",      "exit",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jlt imm",   "jlt r1, 4, +2",
        REGS(3, 0, 0, 0, 0),
        REGS(3, 0, 0, 0, 0), 3),
    OPCODE("jlt reg",   "jlt r1, r2, +2",
        REGS(3, 4, 0, 0, 0),
        REGS(3, 4, 0, 0, 0), 3),
    OPCODE("jle imm",   "jle r1, 4, +99",
        REGS(4, 0, 0, 0, 0),
        REGS(4, 0, 0, 0, 0), 100),
    OPCODE("jle reg",   "jle r1, r2, +42",
        REGS(4, 5, 0, 0, 0),
        REGS(4, 5, 0, 0, 0), 43),
    OPCODE("jslt imm",  "jslt r1, 0xfffffffd, +2",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jslt reg",  "jslt r1, r1, +2",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jsle imm",  "jsle r1, 0xfffffffd, +1",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
    OPCODE("jsle reg",  "jsle r1, r2, +1",
        REGS(0, 0, 0, 0, 0),
        REGS(0, 0, 0, 0, 0), None),
]
#endregion

# Perform tests and produce output ...

# Open op-code statistics file.
date = datetime.now().strftime("%Y%m%d-%H%M%S")
output_file = f"./statistics/opcode_cycles_{date}.{'md' if OUPUT_MD_TABLE else 'csv'}"
with open(output_file, "w") as fd:

    if not OUPUT_MD_TABLE:
        fd.write("\"OpCode\",\"Cycles\",\"CPU State\",\"CPU Halt\",\"CPU Error\","
            "\"R1\",\"R1\",\"R2\",\"R3\",\"R4\",\"R5\"\n")

    print("\nALU 64-Bit")
    if OUPUT_MD_TABLE:
        fd.write("## ALU 64-Bit\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_alu64:
        opcode_cycles(fd, opcode)

    print("\nALU 32-Bit")
    if OUPUT_MD_TABLE:
        fd.write("\n## ALU 32-Bit\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_alu32:
        opcode_cycles(fd, opcode)

    print("\nLoad X")
    if OUPUT_MD_TABLE:
        fd.write("\n## Load X\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_ldx:
        opcode_cycles(fd, opcode)

    print("\nStore X")
    if OUPUT_MD_TABLE:
        fd.write("\n## Store X\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_stx:
        opcode_cycles(fd, opcode)

    print("\nLoad")
    if OUPUT_MD_TABLE:
        fd.write("\n## Load\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_ld:
        opcode_cycles(fd, opcode)

    print("\nStore")
    if OUPUT_MD_TABLE:
        fd.write("\n## Store\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_st:
        opcode_cycles(fd, opcode)

    print("\nJump")
    if OUPUT_MD_TABLE:
        fd.write("\n## Jump\n\n")
        fd.write("|Asm|OpCode|Cycles|\n")
        fd.write("|---|---|---|\n")
    for opcode in opcodes_jmp:
        opcode_cycles(fd, opcode)

    if OUPUT_MD_TABLE:
        fd.write("\n\n")

        t = {}
        s = sum(stats.values())
        for k, v in stats.items():
            pct = v * 100.0 / s
            t[k] = (v, pct)
        ts = sorted(t)
        fd.write("|Ticks|OpCodes|Pct%|\n")
        fd.write("|-----|-------|----|\n")
        for i in ts:
            fd.write(f"|{i}|{t[i][0]}|{t[i][1]}|\n")
