Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 53 additions & 8 deletions packages/river_hdl/lib/src/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1433,16 +1433,61 @@ class RiverCore extends BridgeModule {
? null
: addInput('prfSeedMode', prfSeedMode);

// Async interrupt take. Computes the highest-priority pending+enabled
// interrupt; the exec vectors it at an instruction boundary. M-interrupts
// (MSI/MTI/MEI = bits 3/7/11) are pending/enabled in mip/mie; S-interrupts
// (SSI/STI/SEI = 1/5/9) in the separate sip/sie (Weir writes sip.STIP for the
// SBI timer). Global enable per RISC-V: an M-interrupt is taken in S/U
// always and in M only if mstatus.MIE; an S-interrupt is taken in U always
// and in S only if sstatus.SIE, never in M. Priority MEI>MSI>MTI>SEI>SSI>STI.
// Select an asynchronous interrupt candidate. The executor accepts it at
// a clean instruction boundary and uses the ordinary trap-retirement path.
Logic? interruptTake;
Logic? interruptCause;
if (csrs != null) {
if (csrs != null &&
!config.hasHypervisor &&
config.executionMode != ExecutionMode.outOfOrder) {
// All sources share mip/mie (including hardware OR software SEIP).
// Source numbering does not determine the destination privilege.
final pending = (csrs.mip & csrs.mie).named('enabledInterrupts');
final delegated = config.hasSupervisor
? csrs.mideleg
: Const(0, width: config.mxlen.size);
final isM = mode.eq(Const(PrivilegeMode.machine.id, width: 3));
final isS = mode.eq(Const(PrivilegeMode.supervisor.id, width: 3));
final isU = mode.eq(Const(PrivilegeMode.user.id, width: 3));
final mGlobal = ~isM | csrs.mstatus[3];
final sGlobal = (isS & csrs.mstatus[1]) | isU;
// Lowest priority first within each target. M-target interrupts must
// outrank S-target interrupts, even if the M source has a lower cause.
const order = [5, 1, 9, 7, 3, 11];
Logic take = Const(0);
Logic cause = Const(0, width: 6);
for (final supervisor in [true, false]) {
for (final bit in order) {
final eligible =
pending[bit] &
(supervisor
? delegated[bit] & sGlobal
: ~delegated[bit] & mGlobal);
cause = mux(eligible, Const(bit, width: 6), cause);
take |= eligible;
}
}
// Keep the registered candidate. It is selection, not acceptance: exec
// accepts it only at a clean instruction boundary. Revalidate against
// current CSR/pending/privilege state, including priority, so a CSR write
// or xRET cannot deliver a stale candidate. An accepted trap completes
// through the existing exec/retirement path without further cancellation.
final candidateValid = Logic(name: 'interruptTakeReg');
final candidateCause = Logic(name: 'interruptCauseReg', width: 6);
Sequential(clk, [
If(
reset,
then: [candidateValid < 0, candidateCause < 0],
orElse: [candidateValid < take, candidateCause < cause],
),
]);
interruptTake = (candidateValid & take & candidateCause.eq(cause)).named(
'interruptTake',
);
interruptCause = candidateCause.named('interruptCause');
} else if (csrs != null) {
// Preserve the existing H/OoO delivery path; guest interrupt targeting
// and precise OoO acceptance require their own coordinated integration.
final xlen = config.mxlen.size;
final isM = mode.eq(Const(PrivilegeMode.machine.id, width: 3));
final isS = mode.eq(Const(PrivilegeMode.supervisor.id, width: 3));
Expand Down
118 changes: 118 additions & 0 deletions packages/river_hdl/test/csr/sstatus_trap_stack_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import 'dart:async';

import 'package:river/river.dart';
import 'package:river_hdl/river_hdl.dart';
import 'package:rohd/rohd.dart';
import 'package:test/test.dart';

void main() {
tearDown(Simulator.reset);
for (final xlen in [RiscVMxlen.rv32, RiscVMxlen.rv64]) {
for (final machine in [false, true]) {
test(
'RV${xlen.size} ${machine ? "M" : "S"} trap/return status stack',
() async {
final clk = SimpleClockGenerator(10).clk;
final reset = Logic()..inject(1);
final mode = Logic(width: 3)..inject(3);
final trap = Logic()..inject(0);
final returning = Logic()..inject(0);
final rd = DataPortInterface(xlen.size, 12);
final wr = DataPortInterface(xlen.size, 12);
final csr = RiscVCsrFile(
clk,
reset,
mode,
mxlen: xlen,
misa: xlen.misa,
hasSupervisor: true,
hasUser: true,
hasPaging: true,
hasSum: true,
hasMxr: true,
trapActive: trap,
trapTargetIsM: Const(machine ? 1 : 0),
trapPc: Const(0x40000100, width: xlen.size),
trapCauseVal: Const(13, width: xlen.size),
trapTval: Const(0x40204000, width: xlen.size),
returnActive: returning,
returnFromM: Const(machine ? 1 : 0),
csrRead: rd,
csrWrite: wr,
);
await csr.build();
rd.en.inject(0);
rd.addr.inject(0);
wr.en.inject(0);
wr.addr.inject(CsrAddress.mstatus.address);
wr.data.inject(0);
Simulator.setMaxSimTime(10000);
unawaited(Simulator.run());
try {
await clk.nextNegedge;
reset.inject(0);
await clk.nextNegedge;
const sumMxr = (1 << 18) | (1 << 19);
const mprv = 1 << 17;
const mStack = (3 << 11) | (1 << 7) | (1 << 3);
const sStack = (1 << 8) | (1 << 5) | (1 << 1);
const mask = sumMxr | mprv | mStack | sStack;
final ieBit = machine ? 3 : 1;
final pieBit = machine ? 7 : 5;
final ppBit = machine ? 11 : 8;
final preserved = sumMxr | (machine ? sStack : mStack);
for (final origin in [0, 1, if (machine) 3]) {
for (final ie in [0, 1]) {
mode.inject(3);
// Opposite initial PIE/PP values catch dropped single-field updates.
wr.data.inject(
preserved |
mprv |
((origin == 0 ? 1 : 0) << ppBit) |
((1 - ie) << pieBit) |
(ie << ieBit),
);
wr.en.inject(1);
await clk.nextNegedge;
wr.en.inject(0);
mode.inject(origin);
trap.inject(1);
await clk.nextNegedge;
trap.inject(0);
expect(
csr.mstatus.value.toInt() & mask,
preserved | mprv | (origin << ppBit) | (ie << pieBit),
reason: 'origin=$origin IE=$ie: push receiving stack only',
);
final epc = machine ? CsrAddress.mepc : CsrAddress.sepc;
final cause = machine ? CsrAddress.mcause : CsrAddress.scause;
final tval = machine ? CsrAddress.mtval : CsrAddress.stval;
int read(CsrAddress address) =>
csr.getData(LogicValue.ofInt(address.address, 12))!.toInt();
expect(read(epc), 0x40000100);
expect(read(cause), 13);
expect(read(tval), 0x40204000);
mode.inject(machine ? 3 : 1);
returning.inject(1);
await clk.nextNegedge;
returning.inject(0);
expect(
csr.mstatus.value.toInt() & mask,
preserved |
(1 << pieBit) |
(ie << ieBit) |
(machine && origin == 3 ? mprv : 0),
reason: 'pop stack; clear MPRV only on return below M',
);
await clk.nextNegedge;
}
}
} finally {
await Simulator.endSimulation();
await Simulator.simulationEnded;
}
},
);
}
}
}
Loading
Loading