Files
gem5/src/arch/riscv/faults.cc
Saúl 804f137325 arch-riscv: add unit-stride fault-only-first loads (i.e. vle*ff) (#794)
This patch provides unit-stride fault-only-first loads (i.e. vle*ff) for
the RISC-V architecture.

They are implemented within the regular unit-stride load (i.e. vle*). A
snippet named `fault_code` is inserted with templating to change their
behaviour to fault-only-first.

A part from this, a new micro based on the vset\*vl\* instructions
(VlFFTrimVlMicroOp) is inserted as the last micro in the macro
constructor to trim the VL to it's corresponding length based on the
faulting index.

This trimming micro waits for the load micros to finish (via data
dependency) and has a reference to the other micros to check whether
they faulted or not. The new VL is calculated with the VL of each micro,
stopping on the first faulting one (if there's such a fault).

I've tested this with VLEN=128,256,...,16384 and all the corresponding
SEW+LMUL configurations.


Change-Id: I7b937f6bcb396725461bba4912d2667f3b22f955
2024-02-08 09:15:58 -08:00

271 lines
8.8 KiB
C++

/*
* Copyright (c) 2016 RISC-V Foundation
* Copyright (c) 2016 The University of Virginia
* Copyright (c) 2018 TU Dresden
* Copyright (c) 2020 Barkhausen Institut
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "arch/riscv/faults.hh"
#include "arch/riscv/insts/static_inst.hh"
#include "arch/riscv/isa.hh"
#include "arch/riscv/mmu.hh"
#include "arch/riscv/pmp.hh"
#include "arch/riscv/regs/misc.hh"
#include "arch/riscv/utility.hh"
#include "cpu/base.hh"
#include "cpu/thread_context.hh"
#include "debug/Faults.hh"
#include "sim/debug.hh"
#include "sim/full_system.hh"
#include "sim/workload.hh"
namespace gem5
{
namespace RiscvISA
{
void
RiscvFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
panic("Fault %s encountered at pc %s.", name(), tc->pcState());
}
void
RiscvFault::invoke(ThreadContext *tc, const StaticInstPtr &inst)
{
auto pc_state = tc->pcState().as<PCState>();
DPRINTFS(Faults, tc->getCpuPtr(), "Fault (%s) at PC: %s\n",
name(), pc_state);
if (FullSystem) {
PrivilegeMode pp = (PrivilegeMode)tc->readMiscReg(MISCREG_PRV);
PrivilegeMode prv = PRV_M;
MISA misa = tc->readMiscRegNoEffect(MISCREG_ISA);
STATUS status = tc->readMiscReg(MISCREG_STATUS);
// According to riscv-privileged-v1.11, if a NMI occurs at the middle
// of a M-mode trap handler, the state (epc/cause) will be overwritten
// and is not necessary recoverable. There's nothing we can do here so
// we'll just warn our user that the CPU state might be broken.
warn_if(isNonMaskableInterrupt() && pp == PRV_M && status.mie == 0,
"NMI overwriting M-mode trap handler state");
// Set fault handler privilege mode
if (isNonMaskableInterrupt()) {
prv = PRV_M;
} else if (isInterrupt()) {
if (pp != PRV_M &&
bits(tc->readMiscReg(MISCREG_MIDELEG), _code) != 0) {
prv = (misa.rvs) ? PRV_S : ((misa.rvn) ? PRV_U : PRV_M);
}
if (pp == PRV_U && misa.rvs && misa.rvn &&
bits(tc->readMiscReg(MISCREG_SIDELEG), _code) != 0) {
prv = PRV_U;
}
} else {
if (pp != PRV_M &&
bits(tc->readMiscReg(MISCREG_MEDELEG), _code) != 0) {
prv = (misa.rvs) ? PRV_S : ((misa.rvn) ? PRV_U : PRV_M);
}
if (pp == PRV_U && misa.rvs && misa.rvn &&
bits(tc->readMiscReg(MISCREG_SEDELEG), _code) != 0) {
prv = PRV_U;
}
}
// Set fault registers and status
MiscRegIndex cause, epc, tvec, tval;
switch (prv) {
case PRV_U:
cause = MISCREG_UCAUSE;
epc = MISCREG_UEPC;
tvec = MISCREG_UTVEC;
tval = MISCREG_UTVAL;
status.upie = status.uie;
status.uie = 0;
break;
case PRV_S:
cause = MISCREG_SCAUSE;
epc = MISCREG_SEPC;
tvec = MISCREG_STVEC;
tval = MISCREG_STVAL;
status.spp = pp;
status.spie = status.sie;
status.sie = 0;
break;
case PRV_M:
cause = MISCREG_MCAUSE;
epc = MISCREG_MEPC;
tvec = isNonMaskableInterrupt() ? MISCREG_NMIVEC : MISCREG_MTVEC;
tval = MISCREG_MTVAL;
status.mpp = pp;
status.mpie = status.mie;
status.mie = 0;
break;
default:
panic("Unknown privilege mode %d.", prv);
break;
}
// Set fault cause, privilege, and return PC
uint64_t _cause = _code;
if (isInterrupt()) {
_cause |= CAUSE_INTERRUPT_MASKS[pc_state.rvType()];
}
tc->setMiscReg(cause, _cause);
tc->setMiscReg(epc, tc->pcState().instAddr());
tc->setMiscReg(tval, trap_value());
tc->setMiscReg(MISCREG_PRV, prv);
tc->setMiscReg(MISCREG_STATUS, status);
// Temporarily mask NMI while we're in NMI handler. Otherweise, the
// checkNonMaskableInterrupt will always return true and we'll be
// stucked in an infinite loop.
if (isNonMaskableInterrupt()) {
tc->setMiscReg(MISCREG_NMIE, 0);
}
// Clear load reservation address
auto isa = static_cast<RiscvISA::ISA*>(tc->getIsaPtr());
isa->clearLoadReservation(tc->contextId());
// Set PC to fault handler address
Addr addr = isa->getFaultHandlerAddr(tvec, _code, isInterrupt());
pc_state.set(addr);
tc->pcState(pc_state);
} else {
invokeSE(tc, inst);
}
}
void
Reset::invoke(ThreadContext *tc, const StaticInstPtr &inst)
{
tc->setMiscReg(MISCREG_PRV, PRV_M);
STATUS status = tc->readMiscReg(MISCREG_STATUS);
status.mie = 0;
status.mprv = 0;
tc->setMiscReg(MISCREG_STATUS, status);
tc->setMiscReg(MISCREG_MCAUSE, 0);
// Advance the PC to the implementation-defined reset vector
auto workload = dynamic_cast<Workload *>(tc->getSystemPtr()->workload);
std::unique_ptr<PCState> new_pc(dynamic_cast<PCState *>(
tc->getIsaPtr()->newPCState(workload->getEntry())));
panic_if(!new_pc, "Failed create new PCState from ISA pointer");
VTYPE vtype = 0;
vtype.vill = 1;
new_pc->vtype(vtype);
new_pc->vl(0);
tc->pcState(*new_pc);
// Reset PMP Cfg
auto* mmu = dynamic_cast<RiscvISA::MMU*>(tc->getMMUPtr());
if (mmu == nullptr) {
warn("MMU is not Riscv MMU instance, we can't reset PMP");
return;
}
mmu->getPMP()->pmpReset();
}
void
UnknownInstFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
auto *rsi = static_cast<RiscvStaticInst *>(inst.get());
panic("Unknown instruction 0x%08x at pc %s", rsi->machInst,
tc->pcState());
}
void
IllegalInstFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
if (! tc->getSystemPtr()->trapToGdb(GDBSignal::ILL, tc->contextId()) ) {
auto *rsi = static_cast<RiscvStaticInst *>(inst.get());
panic("Illegal instruction 0x%08x at pc %s: %s", rsi->machInst,
tc->pcState(), reason.c_str());
}
}
void
UnimplementedFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
panic("Unimplemented instruction %s at pc %s", instName, tc->pcState());
}
void
IllegalFrmFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
panic("Illegal floating-point rounding mode 0x%x at pc %s.",
frm, tc->pcState());
}
void
BreakpointFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
if (! tc->getSystemPtr()->trapToGdb(GDBSignal::TRAP, tc->contextId()) ) {
schedRelBreak(0);
}
}
void
SyscallFault::invokeSE(ThreadContext *tc, const StaticInstPtr &inst)
{
/* Advance the PC to next instruction so - once (simulated) syscall
is executed - execution continues. */
auto pc_state = tc->pcState().as<PCState>();
inst->advancePC(pc_state);
tc->pcState(pc_state);
tc->getSystemPtr()->workload->syscall(tc);
}
bool
getFaultVAddr(Fault fault, Addr &va)
{
auto addr_fault = dynamic_cast<AddressFault *>(fault.get());
if (addr_fault) {
va = addr_fault->trap_value();
return true;
}
auto pgt_fault = dynamic_cast<GenericPageTableFault *>(fault.get());
if (pgt_fault) {
va = pgt_fault->getFaultVAddr();
return true;
}
return false;
}
} // namespace RiscvISA
} // namespace gem5