diff --git a/run.py b/run.py index bae34b7..76df3c7 100644 --- a/run.py +++ b/run.py @@ -23,6 +23,7 @@ import sys import logging from scripts.lib import * +from scripts.verilator_log_to_trace_csv import * from scripts.spike_log_to_trace_csv import * from scripts.ovpsim_log_to_trace_csv import * from scripts.whisper_log_trace_csv import * @@ -414,7 +415,7 @@ def run_assembly(asm_test, iss_yaml, isa, mabi, gcc_opts, iss_opts, output_dir, base_cmd = parse_iss_yaml(iss, iss_yaml, isa, setting_dir, debug_cmd) logging.info("[%0s] Running ISS simulation: %s" % (iss, elf)) cmd = get_iss_cmd(base_cmd, elf, log) - run_cmd(cmd, 10, debug_cmd = debug_cmd) + run_cmd(cmd, 1500, debug_cmd = debug_cmd) logging.info("[%0s] Running ISS simulation: %s ...done" % (iss, elf)) if len(iss_list) == 2: compare_iss_log(iss_list, log_list, report) @@ -596,7 +597,6 @@ def iss_cmp(test_list, iss, output_dir, stop_on_first_error, exp, debug_cmd): if len(iss_list) != 2: return report = ("%s/iss_regr.log" % output_dir).rstrip() - run_cmd("rm -rf %s" % report) for test in test_list: for i in range(0, test['iterations']): elf = ("%s/asm_tests/%s_%d.o" % (output_dir, test['test'], i)) @@ -622,6 +622,8 @@ def compare_iss_log(iss_list, log_list, report, stop_on_first_error=0, exp=False csv_list.append(csv) if iss == "spike": process_spike_sim_log(log, csv) + elif "verilator" in iss or "vsim" in iss: + process_verilator_sim_log(log, csv) elif iss == "ovpsim": process_ovpsim_sim_log(log, csv, stop_on_first_error) elif iss == "sail": @@ -654,7 +656,7 @@ def setup_parser(): parser.add_argument("--target", type=str, default="rv32imc", help="Run the generator with pre-defined targets: \ - rv32imc, rv32i, rv64imc, rv64gc") + rv32imc, rv32i, rv32ima, rv64imc, rv64gc, rv64imac") parser.add_argument("-o", "--output", type=str, help="Output directory name", dest="o") parser.add_argument("-tl", "--testlist", type=str, default="", @@ -772,6 +774,15 @@ def load_config(args, cwd): if args.target == "rv32imc": args.mabi = "ilp32" args.isa = "rv32imc" + elif args.target == "rv32imac": + args.mabi = "ilp32" + args.isa = "rv32imac" + elif args.target == "rv32ima": + args.mabi = "ilp32" + args.isa = "rv32ima" + elif args.target == "rv32gc": + args.mabi = "ilp32" + args.isa = "rv32gc" elif args.target == "multi_harts": args.mabi = "ilp32" args.isa = "rv32gc" @@ -787,6 +798,9 @@ def load_config(args, cwd): elif args.target == "rv64gc": args.mabi = "lp64" args.isa = "rv64gc" + elif args.target == "rv64imac": + args.mabi = "lp64" + args.isa = "rv64imac" elif args.target == "rv64gcv": args.mabi = "lp64" args.isa = "rv64gcv" @@ -817,7 +831,7 @@ def main(): # Load configuration from the command line and the configuration file. cfg = load_config(args, cwd) # Create output directory - output_dir = create_output(args.o, args.noclean) + output_dir = create_output(args.o, args.noclean, cwd+"/out_") if args.verilog_style_check: logging.debug("Run style check") diff --git a/scripts/lib.py b/scripts/lib.py index b974637..90aef00 100644 --- a/scripts/lib.py +++ b/scripts/lib.py @@ -230,6 +230,8 @@ def process_regression_list(testlist, test, iterations, matched_list, riscv_dv_r if (iterations > 0 and entry['iterations'] > 0): entry['iterations'] = iterations if entry['iterations'] > 0: + entry['asm_tests'] = re.sub("\", get_env_var(entry['path_var']), entry['asm_tests']) + entry['gcc_opts'] = re.sub("\", get_env_var(entry['path_var']), entry['gcc_opts']) logging.info("Found matched tests: %s, iterations:%0d" % (entry['test'], entry['iterations'])) matched_list.append(entry) diff --git a/scripts/link.ld b/scripts/link.ld index df08889..ad50f56 100644 --- a/scripts/link.ld +++ b/scripts/link.ld @@ -19,15 +19,17 @@ ENTRY(_start) SECTIONS { . = 0x80000000; - .text : { *(.text) } + .text.init : { *(.text.init) } . = ALIGN(0x1000); .tohost : { *(.tohost) } . = ALIGN(0x1000); + .text : { *(.text) } + . = ALIGN(0x1000); .page_table : { *(.page_table) } - .data : { *(.data) } .user_stack : { *(.user_stack) } .kernel_data : { *(.kernel_data) } .kernel_stack : { *(.kernel_stack) } + .data : { *(.data) } .bss : { *(.bss) } _end = .; } diff --git a/scripts/verilator_log_to_trace_csv.py b/scripts/verilator_log_to_trace_csv.py new file mode 100644 index 0000000..2be83ae --- /dev/null +++ b/scripts/verilator_log_to_trace_csv.py @@ -0,0 +1,249 @@ +""" +Copyright 2019 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Convert verilator sim log to standard riscv instruction trace format +""" + +import argparse +import os +import re +import sys +import logging + +sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) + +from riscv_trace_csv import * +from lib import * + +RD_RE = re.compile(r"(?P\d) 0x(?P[a-f0-9]+?) " \ + "\((?P.*?)\) (?P[xf]\s*\d*?) 0x(?P[a-f0-9]+)") +CORE_RE = re.compile(r"core.*0x(?P[a-f0-9]+?) \(0x(?P.*?)\) (?P.*?)$") +ILLE_RE = re.compile(r"trap_illegal_instruction") + +LOGGER = logging.getLogger() + + +def process_instr(trace): + if trace.instr == "jal": + # Spike jal format jal rd, -0xf -> jal rd, -15 + idx = trace.operand.rfind(",") + imm = trace.operand[idx+1:] + if imm[0] == "-": + imm = "-" + str(int(imm[1:], 16)) + else: + imm = str(int(imm, 16)) + trace.operand = trace.operand[0:idx+1] + imm + trace.operand = trace.operand.replace("(", ",") + trace.operand = trace.operand.replace(")", "") + + +def read_verilator_instr(match, full_trace): + '''Unpack a regex match for CORE_RE to a RiscvInstructionTraceEntry + + If full_trace is true, extract operand data from the disassembled + instruction. + + ''' + + # Extract the disassembled instruction. + disasm = match.group('instr') + + # Spike's disassembler shows a relative jump as something like "j pc + + # 0x123" or "j pc - 0x123". We just want the relative offset. + disasm = disasm.replace('pc + ', '').replace('pc - ', '-') + + instr = RiscvInstructionTraceEntry() + instr.pc = match.group('addr') + instr.instr_str = disasm + instr.binary = match.group('bin') + + if full_trace: + opcode = disasm.split(' ')[0] + operand = disasm[len(opcode):].replace(' ', '') + instr.instr, instr.operand = \ + convert_pseudo_instr(opcode, operand, instr.binary) + + process_instr(instr) + + return instr + + +def read_verilator_trace(path, full_trace): + '''Read a Spike simulation log at , yielding executed instructions. + + This assumes that the log was generated with the -l and --log-commits options + to Spike. + + If full_trace is true, extract operands from the disassembled instructions. + + Since Spike has a strange trampoline that always runs at the start, we skip + instructions up to and including the one at PC 0x1010 (the end of the + trampoline). At the end of a DV program, there's an ECALL instruction, which + we take as a signal to stop checking, so we ditch everything that follows + that instruction. + + This function yields instructions as it parses them as tuples of the form + (entry, illegal). entry is a RiscvInstructionTraceEntry. illegal is a + boolean, which is true if the instruction caused an illegal instruction trap. + + ''' + + # This loop is a simple FSM with states TRAMPOLINE, INSTR, EFFECT. The idea + # is that we're in state TRAMPOLINE until we get to the end of Spike's + # trampoline, then we switch between INSTR (where we expect to read an + # instruction) and EFFECT (where we expect to read commit information). + # + # We yield a RiscvInstructionTraceEntry object each time we leave EFFECT + # (going back to INSTR), we loop back from INSTR to itself, or we get to the + # end of the file and have an instruction in hand. + # + # On entry to the loop body, we are in state TRAMPOLINE if in_trampoline is + # true. Otherwise, we are in state EFFECT if instr is not None, otherwise we + # are in state INSTR. + + end_trampoline_re = re.compile(r'core.*: 0x0000000080000000 ') + start_debug_it_re = re.compile(r'core.*: 0x0000000000000800 ') + stop_debug_it_re = re.compile(r'core.*: 0x0000000000000890 ') + + in_trampoline = True + in_debug = False + instr = None + + with open(path, 'r') as handle: + for line in handle: + if in_trampoline: + # The TRAMPOLINE state + if end_trampoline_re.match(line): + in_trampoline = False + continue + + if not in_trampoline: + if in_debug: + if stop_debug_it_re.match(line): + in_debug = False + continue + else: + if start_debug_it_re.match(line): + in_debug = True + continue + + if instr is None: + # The INSTR state. We expect to see a line matching CORE_RE. We'll + # discard any other lines. + instr_match = CORE_RE.match(line) + if not instr_match: + continue + + instr = read_verilator_instr(instr_match, full_trace) + + # If instr.instr_str is 'ecall', we should stop. + if instr.instr_str == 'ecall': + break + + continue + + # The EFFECT state. If the line matches CORE_RE, we should have been in + # state INSTR, so we yield the instruction we had, read the new + # instruction and continue. As above, if the new instruction is 'ecall', + # we need to stop immediately. + instr_match = CORE_RE.match(line) + if instr_match: + yield (instr, False) + instr = read_verilator_instr(instr_match, full_trace) + if instr.instr_str == 'ecall': + break + continue + + # The line doesn't match CORE_RE, so we are definitely on a follow-on + # line in the log. First, check for illegal instructions + if 'trap_illegal_instruction' in line: + yield (instr, True) + instr = None + continue + + # The instruction seems to have been fine. Do we have commit data (from + # the --log-commits Spike option)? + commit_match = RD_RE.match(line) + if commit_match: + instr.gpr.append(gpr_to_abi(commit_match.group('reg') + .replace(' ', '')) + + ':' + commit_match.group('val')) + instr.mode = commit_match.group('pri') + + # At EOF, we might have an instruction in hand. Yield it if so. + if instr is not None: + yield (instr, False) + + +def process_verilator_sim_log(verilator_log, csv, full_trace = 0): + """Process VERILATOR simulation log. + + Extract instruction and affected register information from verilator simulation + log and write the results to a CSV file at csv. Returns the number of + instructions written. + + """ + logging.info("Processing verilator log : %s" % verilator_log) + + instrs_in = 0 + instrs_out = 0 + + with open(csv, "w") as csv_fd: + trace_csv = RiscvInstructionTraceCsv(csv_fd) + trace_csv.start_new_trace() + + for (entry, illegal) in read_verilator_trace(verilator_log, full_trace): + instrs_in += 1 + + if illegal and full_trace: + logging.debug("Illegal instruction: {}, opcode:{}" + .format(entry.instr_str, entry.binary)) + + # Instructions that cause no architectural update (which includes illegal + # instructions) are ignored if full_trace is false. + # + # We say that an instruction caused an architectural update if either we + # saw a commit line (in which case, entry.gpr will contain a single + # entry) or the instruction was 'wfi' or 'ecall'. + if not (full_trace or entry.gpr or entry.instr_str in ['wfi', 'ecall']): + continue + + trace_csv.write_trace_entry(entry) + instrs_out += 1 + + logging.info("Processed instruction count : %d" % instrs_in) + logging.info("CSV saved to : %s" % csv) + return instrs_out + + +def main(): + # Parse input arguments + parser = argparse.ArgumentParser() + parser.add_argument("--log", type=str, help="Input verilator simulation log") + parser.add_argument("--csv", type=str, help="Output trace csv_buf file") + parser.add_argument("-f", "--full_trace", dest="full_trace", action="store_true", + help="Generate the full trace") + parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", + help="Verbose logging") + parser.set_defaults(full_trace=False) + parser.set_defaults(verbose=False) + args = parser.parse_args() + setup_logging(args.verbose) + # Process verilator log + process_verilator_sim_log(args.log, args.csv, args.full_trace) + + +if __name__ == "__main__": + main() diff --git a/yaml/iss.yaml b/yaml/iss.yaml index 344ef79..381cda6 100644 --- a/yaml/iss.yaml +++ b/yaml/iss.yaml @@ -17,6 +17,16 @@ cmd: > /spike --log-commits --isa= -l +- iss: verilator + path_var: RTL_PATH + cmd: > + make -C generate-trace-verilator elf-bin= + +- iss: vsim + path_var: RTL_PATH + cmd: > + make -C generate-trace-vsim preload= elf-bin=none + - iss: ovpsim path_var: OVPSIM_PATH cmd: >