blob: cfc4d7e8f99901291bd990e42f1b19b37829ea0c [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
from typing import Iterator, List, Optional, Tuple
from .isa import OTBNInsn
from .state import OTBNState
from .stats import ExecutionStats
from .trace import Trace
_TEST_RND_DATA = \
0x99999999_99999999_99999999_99999999_99999999_99999999_99999999_99999999
class OTBNSim:
def __init__(self) -> None:
self.state = OTBNState()
self.program = [] # type: List[OTBNInsn]
self.stats = None # type: Optional[ExecutionStats]
self._execute_generator = None # type: Optional[Iterator[None]]
def load_program(self, program: List[OTBNInsn]) -> None:
self.program = program.copy()
def load_data(self, data: bytes) -> None:
self.state.dmem.load_le_words(data)
def start(self) -> None:
'''Prepare to start the execution.
Use run() or step() to actually execute the program.
'''
self.stats = ExecutionStats(self.program)
self._execute_generator = None
self.state.start()
def run(self, verbose: bool, collect_stats: bool) -> int:
'''Run until ECALL.
Return the number of cycles taken.
'''
insn_count = 0
# ISS will stall at start until URND data is valid, immediately set it
# valid when in free running mode as nothing else will.
self.state.set_urnd_reseed_complete()
while self.state.running:
self.step(verbose, collect_stats)
insn_count += 1
if self.state.wsrs.RND.pending_request:
# If an instruction requests RND data, make it available
# immediately.
self.state.wsrs.RND.set_unsigned(_TEST_RND_DATA)
return insn_count
def step(self,
verbose: bool,
collect_stats: bool) -> Tuple[Optional[OTBNInsn], List[Trace]]:
'''Run a single cycle.
Returns the instruction, together with a list of the architectural
changes that have happened. If the model isn't currently running,
returns no instruction and no changes.
'''
if not self.state.running:
return (None, [])
assert self.stats is not None
# Program counter before commit
pc_before = self.state.pc
word_pc = self.state.pc >> 2
if word_pc >= len(self.program):
raise RuntimeError('Trying to execute instruction at address '
'{:#x}, but the program is only {:#x} '
'bytes ({} instructions) long. Since there '
'are no architectural contents of the '
'memory here, we have to stop.'
.format(self.state.pc,
4 * len(self.program),
len(self.program)))
insn = self.program[word_pc]
sim_stalled = self.state.non_insn_stall
if not sim_stalled:
if self._execute_generator is None:
# This is the first cycle for an instruction. Run any setup for
# the state object and then start running the instruction
# itself.
self.state.pre_insn(insn.affects_control)
# Either execute the instruction directly (if it is a
# single-cycle instruction without a `yield` in execute()), or
# return a generator for multi-cycle instructions. Note that
# this doesn't consume the first yielded value.
self._execute_generator = insn.execute(self.state)
if self._execute_generator is not None:
# This is a cycle for a multi-cycle instruction (which possibly
# started just above)
try:
next(self._execute_generator)
except StopIteration:
self._execute_generator = None
sim_stalled = (self._execute_generator is not None)
if sim_stalled:
self.state.commit(sim_stalled=True)
disasm = '(stall)'
changes = []
if collect_stats:
self.stats.record_stall()
else:
assert self._execute_generator is None
self.state.post_insn()
if collect_stats:
self.stats.record_insn(insn, self.state)
if self.state.pending_halt:
# We've reached the end of the run (either because of an ECALL
# instruction or an error).
self.state.stop()
changes = self.state.changes()
# Only commit() may change the program counter.
assert pc_before == self.state.pc
self.state.commit(sim_stalled=False)
disasm = insn.disassemble(pc_before)
if verbose:
self._print_trace(pc_before, disasm, changes)
return (None if sim_stalled else insn, changes)
def dump_data(self) -> bytes:
return self.state.dmem.dump_le_words()
def _print_trace(self, pc: int, disasm: str, changes: List[Trace]) -> None:
'''Print a trace of the current instruction'''
changes_str = ', '.join([t.trace() for t in changes])
print('{:08x} | {:45} | [{}]'.format(pc, disasm, changes_str))