blob: 2a6de5b272363fd7c73c51a2c6ffcf50423ab304 [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 List, Optional
from shared.mem_layout import get_memory_layout
from .csr import CSRFile
from .dmem import Dmem
from .err_bits import BAD_INSN_ADDR
from .ext_regs import OTBNExtRegs
from .flags import FlagReg
from .gpr import GPRs
from .loop import LoopStack
from .reg import RegFile
from .trace import Trace, TracePC
from .wsr import WSRFile
class OTBNState:
def __init__(self) -> None:
self.gprs = GPRs()
self.wdrs = RegFile('w', 256, 32)
self.wsrs = WSRFile()
self.csrs = CSRFile()
self.pc = 0
self._pc_next_override = None # type: Optional[int]
_, imem_size = get_memory_layout()['IMEM']
self.imem_size = imem_size
self.dmem = Dmem()
# Stalling support: Instructions can indicate they should stall by
# returning false from OTBNInsn.pre_execute. For non instruction related
# stalls setting self.non_insn_stall will produce a stall.
#
# As a special case, we stall until the URND reseed is completed then
# stall for one more cycle before fetching the first instruction (to
# match the behaviour of the RTL). This is modelled by setting
# self._start_stall, self._urnd_stall and self.non_insn_stall
self.non_insn_stall = False
self._start_stall = False
self._urnd_stall = False
self.loop_stack = LoopStack()
self.ext_regs = OTBNExtRegs()
self.running = False
self._err_bits = 0
self.pending_halt = False
self._new_rnd_data = None # type: Optional[int]
self._urnd_reseed_complete = False
def get_next_pc(self) -> int:
if self._pc_next_override is not None:
return self._pc_next_override
return self.pc + 4
def set_next_pc(self, next_pc: int) -> None:
'''Overwrite the next program counter, e.g. as result of a jump.'''
self._pc_next_override = next_pc
def set_rnd_data(self, rnd_data: int) -> None:
self._new_rnd_data = rnd_data
def set_urnd_reseed_complete(self) -> None:
self._urnd_reseed_complete = True
def loop_start(self, iterations: int, bodysize: int) -> None:
self.loop_stack.start_loop(self.pc + 4, iterations, bodysize)
def loop_step(self) -> None:
back_pc = self.loop_stack.step(self.pc)
if back_pc is not None:
self.set_next_pc(back_pc)
def in_loop(self) -> bool:
'''The processor is currently executing a loop.'''
# A loop is executed if the loop stack is not empty.
return bool(self.loop_stack.stack)
def changes(self) -> List[Trace]:
c = [] # type: List[Trace]
c += self.gprs.changes()
if self._pc_next_override is not None:
# Only append the next program counter to the trace if it is special
c.append(TracePC(self.get_next_pc()))
c += self.dmem.changes()
c += self.loop_stack.changes()
c += self.ext_regs.changes()
c += self.wsrs.changes()
c += self.csrs.flags.changes()
c += self.wdrs.changes()
return c
def commit(self, sim_stalled: bool) -> None:
# In case of a pending halt only commit the external registers, which
# contain e.g. the ERR_BITS field, but nothing else.
if self.pending_halt:
self.ext_regs.commit()
return
# If error bits are set, pending_halt should have been set as well.
assert self._err_bits == 0
if self._new_rnd_data:
self.wsrs.RND.set_unsigned(self._new_rnd_data)
self._new_rnd_data = None
if self._urnd_stall:
if self._urnd_reseed_complete:
self._urnd_stall = False
return
# If self._start_stall, this is the end of the stall cycle at the start
# of a run. Clear self.non_insn_stall and self._start_stall
# and commit self.ext_regs (so the start flag becomes visible).
if self._start_stall:
self._start_stall = False
self.non_insn_stall = False
self.ext_regs.commit()
self.dmem.commit(sim_stalled)
# If we're stalled, there's nothing more to do: we only commit when we
# finish our stall cycles.
if sim_stalled:
return
self.gprs.commit()
self.pc = self.get_next_pc()
self._pc_next_override = None
self.loop_stack.commit()
self.ext_regs.commit()
self.wsrs.commit()
self.csrs.flags.commit()
self.wdrs.commit()
def _abort(self) -> None:
'''Abort any pending state changes'''
self.gprs.abort()
self._pc_next_override = None
self.dmem.abort()
self.loop_stack.abort()
self.ext_regs.abort()
self.wsrs.abort()
self.csrs.flags.abort()
self.wdrs.abort()
def start(self) -> None:
'''Set the running flag and the ext_reg busy flag; perform state init'''
self.ext_regs.set_bits('STATUS', 1 << 0)
self.ext_regs.write('INSN_CNT', 0, True)
self.running = True
self._start_stall = True
self._urnd_stall = True
self.non_insn_stall = True
self.pending_halt = False
self._err_bits = 0
self._urnd_reseed_complete = False
self.pc = self.get_start_addr()
# Reset CSRs, WSRs and loop stack
self.csrs = CSRFile()
self.wsrs = WSRFile()
self.loop_stack = LoopStack()
def get_start_addr(self) -> int:
'''Get the start address of the processor.
Set the start address by writing the external register START_ADDR before
calling start(), and commiting the external register changes.
'''
return self.ext_regs.read('START_ADDR', True)
def stop(self) -> None:
'''Set flags to stop the processor and maybe abort the instruction.
If the current instruction has caused an error (so self._err_bits is
nonzero), abort all its pending changes, including changes to external
registers.
If not, we've just executed an ECALL. The only pending change will be
the increment of INSN_CNT that we want to keep.
Either way, set the appropriate bits in the external ERR_CODE register,
clear the busy flag and write STOP_PC.
'''
if self._err_bits:
# Abort all pending changes, including changes to external registers.
self._abort()
# INTR_STATE is the interrupt state register. Bit 0 (which is being
# set) is the 'done' flag.
self.ext_regs.set_bits('INTR_STATE', 1 << 0)
# STATUS is a status register. Bit 0 (being cleared) is the 'busy' flag
self.ext_regs.clear_bits('STATUS', 1 << 0)
# Make any error bits visible
self.ext_regs.write('ERR_BITS', self._err_bits, True)
# Make the final PC visible. This isn't currently in the RTL, but is
# useful in simulations that want to track whether we stopped where we
# expected to stop.
self.ext_regs.write('STOP_PC', self.pc, True)
self.running = False
def set_flags(self, fg: int, flags: FlagReg) -> None:
'''Update flags for a flag group'''
self.csrs.flags[fg] = flags
def set_mlz_flags(self, fg: int, result: int) -> None:
'''Update M, L, Z flags for a flag group using the given result'''
self.csrs.flags[fg] = \
FlagReg.mlz_for_result(self.csrs.flags[fg].C, result)
def pre_insn(self, insn_affects_control: bool) -> None:
'''Run before running an instruction'''
self.loop_stack.check_insn(self.pc, insn_affects_control)
def check_next_pc(self) -> None:
'''Check whether the next program counter is valid.
If not, generates a BadAddrError.
'''
next_pc = self.get_next_pc()
# The PC should always be non-negative (it's an error in the simulator
# if that's come unstuck)
assert 0 <= next_pc
# Check the new PC is word-aligned
if next_pc & 3:
self._err_bits |= BAD_INSN_ADDR
# Check the new PC lies in instruction memory
if next_pc >= self.imem_size:
self._err_bits |= BAD_INSN_ADDR
def post_insn(self) -> None:
'''Update state after running an instruction but before commit'''
self.ext_regs.increment_insn_cnt()
self.check_next_pc()
self.loop_step()
self.gprs.post_insn()
self._err_bits |= (self.gprs.err_bits() |
self.dmem.err_bits() |
self.loop_stack.err_bits())
if self._err_bits:
self.pending_halt = True
def read_csr(self, idx: int) -> int:
'''Read the CSR with index idx as an unsigned 32-bit number'''
return self.csrs.read_unsigned(self.wsrs, idx)
def write_csr(self, idx: int, value: int) -> None:
'''Write value (an unsigned 32-bit number) to the CSR with index idx'''
self.csrs.write_unsigned(self.wsrs, idx, value)
def peek_call_stack(self) -> List[int]:
'''Return the current call stack, bottom-first'''
return self.gprs.peek_call_stack()
def stop_at_end_of_cycle(self, err_bits: int) -> None:
'''Tell the simulation to stop at the end of the cycle
Any bits set in err_bits will be set in the ERR_BITS register when
we're done.
'''
self._err_bits |= err_bits
self.pending_halt = True