Livt packages
Eccelerators.RiscV
Version 0.3.0
Composable RV32I and Zmmul processors, machine-mode support, and reusable FPGA system components written in Livt.
Published files (120)
src/Pipeline/Rv32PipelineEngine.lvt
namespace Eccelerators.RiscV.Pipeline
using Eccelerators.RiscV.Sequential
using Eccelerators.RiscV.Storage
using Eccelerators.RiscV.Contracts
using Eccelerators.RiscV.Machine
/**
* Four-stage, single-issue RV32I engine with machine-capable optional state.
* Diagnostic compositions stop on faults; machine compositions enter direct mtvec.
* CSR effects, multiply completion and traps are ordered by the commit owner.
* Borrows exclusive same-clock providers. Instruction reads must be side-effect
* free; only commit issues data requests. Requests cannot be cancelled. Pause and
* restart drain admitted work; idle includes the memory release edges. A paused
* step admits exactly one instruction. Reset must also reset the bus providers.
* Owns all stage validity and architectural ordering in one clocked controller.
* Interrupt delivery samples the CSR provider at a drained boundary (a NoCsrs
* provider ties interruptEligible low, making delivery inert).
*/
component Rv32PipelineEngine<RESET_PC: uint = 0, MACHINE_TRAPS: bool = false> : IRv32ControlPort
{
static assert (RESET_PC & 3) == 0, "Pipeline reset PC must be word aligned"
// Semantic families implemented by the controller, excluding the borrowed ALU.
public const CAPABILITIES: uint = Rv32iOperations.MEMORY_CAPABILITY | Rv32iOperations.CONTROL_CAPABILITY
// Decode response lifecycle; a stalled captured pair must not be read again.
const READ_WAIT: uint = 0
const READ_PENDING: uint = 1
const READ_CAPTURED: uint = 2
// Exclusive borrowed providers; the application owns their lifetime.
registers: IRv32DualReadRegisterPort
fetch: IRv32FetchPort
loadStore: IRv32LoadStorePort
decoder: IRv32DecodePort
integer: Rv32iIntegerUnit
multiplier: IRv32MultiplyPort
csrs: IRv32CsrPort
// Architectural state, initialization and single-pending control toggles.
initialized: bool
clearIndex: logic[5]
programCounter: uint
stopped: bool
stopCause: logic[4]
stopValue: uint
stepActive: bool
activeStep: logic
stepCompleted: logic
restartCompleted: logic
// Fetch owns one slot, including the uncancellable outstanding transaction.
fetchPc: uint
fBusy: bool
fValid: bool
fGap: bool
fKilled: bool
fPc: uint
fWord: uint
fError: bool
// Decode holds identity and, when execute stalls, its synchronous operand pair.
dValid: bool
dPhase: uint
dPc: uint
dWord: uint
dError: bool
dLeft: uint
dRight: uint
// Execute holds the complete decoded instruction independently of decode.
xValid: bool
xPc: uint
xWord: uint
xControl: uint
xImmediate: uint
xLeft: uint
xRight: uint
xError: bool
// Commit owns effects. cValue holds an ALU result, effective address or fault value.
cValid: bool
cPc: uint
cWord: uint
cNext: uint
cValue: uint
cStoreValue: uint
cRd: logic[5]
cWrites: bool
cKind: logic[4]
cWidth: logic[3]
cFault: bool
cCause: logic[4]
cRedirect: bool
dataGap: bool
cOperand: uint
multiplyGap: bool
// Trap event state survives fetch draining; CSR enable changes get a boundary edge.
boundaryCheck: bool
trapPending: bool
entryPc: logic[32]
entryCause: logic[32]
entryValue: logic[32]
trapPulse: logic
// Registered observation pulses and payload; stable across younger stage changes.
retirementPulse: logic
commitPc: logic[32]
commitWord: logic[32]
commitRd: logic[5]
commitValue: logic[32]
// Pure edge decisions derived from the old stage state, never scheduled helpers.
dControl: uint
xKind: uint
xWidth: uint
xResult: uint
xNext: uint
xFault: bool
xCause: logic[4]
xRedirect: bool
cMemory: bool
cMultiply: bool
cCsr: bool
cReturn: bool
cSerialize: bool
redirectPc: uint
takeInterrupt: bool
enterTrap: bool
cComplete: bool
cFailed: bool
cWrite: bool
cResult: uint
flush: bool
quiescent: bool
launch: bool
readOperands: bool
fTransfer: bool
dTransfer: bool
xTransfer: bool
/**
* Binds providers without scheduled wrappers. Startup clears x1..x31 before idle.
*/
new(registers: flip IRv32DualReadRegisterPort, fetch: flip IRv32FetchPort,
loadStore: flip IRv32LoadStorePort, decoder: flip IRv32DecodePort, integer: Rv32iIntegerUnit,
multiplier: flip IRv32MultiplyPort, csrs: flip IRv32CsrPort)
{
this.registers = registers
this.fetch = fetch
this.loadStore = loadStore
this.decoder = decoder
this.integer = integer
this.multiplier = multiplier
this.csrs = csrs
}
/**
* Evaluates execute without architectural effects. Faults travel to commit in order.
*/
process Execute[]()
{
var control = xControl
var operand1 = xLeft
var operand2 = xRight
xKind = RvDecoded.Operation(control) >> Rv32iOperations.KIND_SHIFT
xWidth = (xWord >> 12) & 7
integer.operation = (RvDecoded.Operation(control) & Rv32iOperations.ALU_MASK) as logic[4]
integer.left = xLeft as logic[32]
integer.right = xImmediate as logic[32]
if (xKind == Rv32iOperations.REGISTER) {
integer.right = xRight as logic[32]
}
if (xKind == Rv32iOperations.LUI) {
integer.left = 0
} elif (xKind == Rv32iOperations.AUIPC || xKind == Rv32iOperations.JAL || xKind == Rv32iOperations.BRANCH) {
integer.left = xPc as logic[32]
}
var arithmetic = integer.result as uint
xResult = arithmetic
xNext = xPc + 4
xFault = false
xCause = 0
xRedirect = false
if (xError) {
xFault = true
xCause = Rv32iOperations.INSTRUCTION_ACCESS as logic[4]
xResult = xPc
} elif (!RvDecoded.IsLegal(control)
|| (xKind == Rv32TrapOperations.RETURN_KIND && !MACHINE_TRAPS)) {
xFault = true
xCause = Rv32iOperations.ILLEGAL_INSTRUCTION as logic[4]
xResult = xWord
} else {
match (xKind)
{
Rv32iOperations.JAL, Rv32iOperations.JALR, Rv32iOperations.BRANCH =>
{
var target = arithmetic
if (xKind == Rv32iOperations.JALR) {
target = target & 0xfffffffe
}
xRedirect = xKind != Rv32iOperations.BRANCH || Rv32iSemantics.Branch(xWidth, operand1, operand2)
if (xRedirect) {
xNext = target
xResult = xPc + 4
if ((target & 3) != 0) {
xFault = true
xCause = Rv32iOperations.INSTRUCTION_MISALIGNED as logic[4]
xResult = target
}
}
}
Rv32iOperations.LOAD, Rv32iOperations.STORE =>
{
if (Rv32iSemantics.Misaligned(arithmetic, xWidth)) {
xFault = true
xCause = Rv32iOperations.LOAD_MISALIGNED as logic[4]
if (xKind == Rv32iOperations.STORE) {
xCause = Rv32iOperations.STORE_MISALIGNED as logic[4]
}
}
}
Rv32iOperations.ECALL =>
{
xFault = true
xCause = Rv32iOperations.ENVIRONMENT_CALL as logic[4]
xResult = 0
}
Rv32iOperations.EBREAK =>
{
xFault = true
xCause = Rv32iOperations.BREAKPOINT as logic[4]
xResult = xPc
}
_ => {
// Legal integer operations and FENCE use common ordered commit.
}
}
}
}
/**
* Drives stable buses and computes transfers from sampled stage state. Ready
* propagates only from commit toward fetch; no combinational ready cycle exists.
*/
process Drive[]()
{
decoder.instruction = dWord as logic[32]
dControl = decoder.control as uint
var control = dControl
var executeControl = xControl
var addressValue = cValue
var width = cWidth as uint
var csrInstruction = cWord
var csrOperand = cOperand
var csrDestination = cRd as uint
cMemory = cKind as uint == Rv32iOperations.LOAD || cKind as uint == Rv32iOperations.STORE
cMultiply = cKind as uint == Rv32MultiplyOperations.KIND
cCsr = cKind as uint == Rv32CsrOperations.KIND
cReturn = MACHINE_TRAPS && cKind as uint == Rv32TrapOperations.RETURN_KIND
cComplete = cValid && (cFault || (cMemory && !dataGap && loadStore.ready == 0b1)
|| (cMultiply && !multiplyGap && multiplier.ready == 0b1) || (!cMemory && !cMultiply))
cFailed = cFault || (cMemory && loadStore.error == 0b1) || (cCsr && csrs.illegal == 0b1)
cWrite = cComplete && !cFailed && cWrites && cRd != 0
cResult = cValue
if (cKind as uint == Rv32iOperations.LOAD) {
cResult = Rv32iSemantics.Load(loadStore.readData as uint, addressValue, width)
} elif (cMultiply) {
cResult = multiplier.result as uint
} elif (cCsr) {
cResult = csrs.readData as uint
}
redirectPc = cNext
if (cReturn) {
redirectPc = csrs.returnPc as uint
}
cSerialize = cComplete && !cFailed && (cCsr || cReturn)
quiescent = !fBusy && !fValid && !fGap && !dValid && !xValid && !cValid
&& !dataGap && !multiplyGap && !trapPending && !boundaryCheck
takeInterrupt = initialized && !stopped && !trapPending && !stepActive
&& restartRequest == restartCompleted && csrs.interruptEligible == 0b1
&& (!cValid || (cComplete && !cFailed)) && !cSerialize
&& (run == 0b1 || (quiescent && stepRequest != stepCompleted))
enterTrap = MACHINE_TRAPS && trapPending && !fBusy && !fGap && !dataGap && !multiplyGap
flush = (cComplete && (cFailed || cRedirect || cReturn)) || takeInterrupt
xTransfer = xValid && (!cValid || cComplete) && !flush && !cSerialize && !boundaryCheck && !trapPending
dTransfer = dValid && dPhase != READ_WAIT && (!xValid || xTransfer) && !flush
fTransfer = fValid && (!dValid || dTransfer) && !flush
launch = initialized && !stopped && !stepActive && !trapPending && restartRequest == restartCompleted
&& !fBusy && (!fValid || fTransfer) && !flush
&& (run == 0b1 || (quiescent && stepRequest != stepCompleted))
var rs1 = RvDecoded.Rs1(control)
var rs2 = RvDecoded.Rs2(control)
var xRd = RvDecoded.Rd(executeControl)
var hazard1 = RvDecoded.HasSource1(control) && rs1 != 0
&& ((xValid && RvDecoded.HasDestinationWrite(executeControl) && xRd == rs1)
|| (cValid && cWrites && cRd as uint == rs1 && !cWrite))
var hazard2 = RvDecoded.HasSource2(control) && rs2 != 0
&& ((xValid && RvDecoded.HasDestinationWrite(executeControl) && xRd == rs2)
|| (cValid && cWrites && cRd as uint == rs2 && !cWrite))
readOperands = dValid && dPhase == READ_WAIT && !hazard1 && !hazard2 && !flush
registers.readEnable = 0b0
registers.leftIndex = rs1 as logic[5]
registers.rightIndex = rs2 as logic[5]
registers.writeEnable = 0b0
registers.writeIndex = cRd
registers.writeData = cResult as logic[32]
if (readOperands) {
registers.readEnable = 0b1
}
if (!initialized) {
registers.writeEnable = 0b1
registers.writeIndex = clearIndex
registers.writeData = 0
} elif (cWrite) {
registers.writeEnable = 0b1
}
fetch.request = 0b0
fetch.address = fPc as logic[32]
if (fBusy) {
fetch.request = 0b1
}
loadStore.request = 0b0
loadStore.address = (cValue & 0xfffffffc) as logic[32]
loadStore.write = 0b0
loadStore.byteEnable = Rv32iSemantics.StoreMask(addressValue, width) as logic[4]
loadStore.writeData = (cStoreValue << (((cValue & 3) * 8) as int)) as logic[32]
if (cValid && cMemory && !cFault && !dataGap) {
loadStore.request = 0b1
}
if (cKind as uint == Rv32iOperations.STORE) {
loadStore.write = 0b1
}
multiplier.request = 0b0
multiplier.operation = cWidth as logic[2]
multiplier.left = cOperand as logic[32]
multiplier.right = cStoreValue as logic[32]
if (cValid && cMultiply && !cFault && !multiplyGap) {
multiplier.request = 0b1
}
csrs.address = (cWord >> 20) as logic[12]
csrs.operation = (width & 3) as logic[2]
csrs.operand = cOperand as logic[32]
csrs.readEnable = 0b0
csrs.writeEnable = 0b0
csrs.commit = 0b0
csrs.retire = 0b0
csrs.clear = 0b0
csrs.enterTrap = 0b0
csrs.returnTrap = 0b0
csrs.entryPc = entryPc
csrs.entryCause = entryCause
csrs.entryValue = entryValue
if (!initialized) {
csrs.clear = 0b1
}
if (cValid && cCsr) {
csrs.operand = Rv32ZicsrAccess.SelectOperand(width, csrInstruction, csrOperand) as logic[32]
csrs.readEnable = Rv32ZicsrAccess.RequiresRead(width, csrDestination) as logic
csrs.writeEnable = Rv32ZicsrAccess.RequiresWrite(width, csrInstruction) as logic
}
if (cComplete && !cFailed) {
csrs.retire = 0b1
if (cCsr) {
csrs.commit = 0b1
}
if (cReturn) {
csrs.returnTrap = 0b1
}
}
if (enterTrap) {
csrs.enterTrap = 0b1
}
idle = 0b0
halted = 0b0
if (initialized && quiescent && !stepActive && restartRequest == restartCompleted) {
idle = 0b1
}
if (stopped) {
halted = 0b1
}
pc = programCounter as logic[32]
faultCause = stopCause as logic[32]
faultValue = stopValue as logic[32]
stepAck = stepCompleted
restartAck = restartCompleted
trapped = trapPulse
trapPc = entryPc
trapCause = entryCause
trapValue = entryValue
if (MACHINE_TRAPS) {
faultCause = 0
faultValue = 0
}
retired = retirementPulse
retiredPc = commitPc
retiredInstruction = commitWord
retiredRd = commitRd
retiredValue = commitValue
}
/**
* Owns all pipeline state. Transfer enables above refer to the pre-edge state;
* downstream capture precedes upstream refill to preserve Livt sampling order.
* Flush overrides younger transfers while uncancellable fetch bookkeeping drains.
*/
process Advance()
{
state {
var control = dControl
var executeControl = xControl
retirementPulse = 0b0
trapPulse = 0b0
if (!initialized) {
programCounter = RESET_PC
fetchPc = RESET_PC
stopped = false
boundaryCheck = false
trapPending = false
entryPc = 0
entryCause = 0
entryValue = 0
stopCause = 0
stopValue = 0
if (clearIndex == 31) {
initialized = true
restartCompleted = restartRequest
stepCompleted = stepRequest
} else {
clearIndex = ((clearIndex as uint) + 1) as logic[5]
}
} else {
if (quiescent && restartRequest != restartCompleted) {
initialized = false
clearIndex = 0
stepActive = false
} elif (quiescent && stepActive) {
stepCompleted = activeStep
stepActive = false
}
// A launch on the fetch release edge raises request only AFTER that edge.
// A full response slot may transfer to decode while the next slot is reserved.
fGap = false
dataGap = false
multiplyGap = false
boundaryCheck = cSerialize
if (cComplete) {
cValid = false
if (cMemory && !cFault) {
dataGap = true
}
if (cMultiply && !cFault) {
multiplyGap = true
}
if (cFailed) {
programCounter = cPc
stopCause = cCause
stopValue = cValue
if (!cFault) {
stopCause = Rv32iOperations.LOAD_ACCESS as logic[4]
if (cCsr) {
stopCause = Rv32iOperations.ILLEGAL_INSTRUCTION as logic[4]
stopValue = cWord
} elif (cKind as uint == Rv32iOperations.STORE) {
stopCause = Rv32iOperations.STORE_ACCESS as logic[4]
}
}
if (MACHINE_TRAPS) {
trapPending = true
entryPc = cPc as logic[32]
entryCause = stopCause as logic[32]
entryValue = stopValue as logic[32]
} else {
stopped = true
}
} else {
retirementPulse = 0b1
commitPc = cPc as logic[32]
commitWord = cWord as logic[32]
commitRd = 0
commitValue = 0
if (cWrite) {
commitRd = cRd
commitValue = cResult as logic[32]
}
programCounter = redirectPc
}
}
if (flush) {
fetchPc = redirectPc
fValid = false
dValid = false
xValid = false
fKilled = fBusy
}
if (takeInterrupt) {
trapPending = true
entryPc = programCounter as logic[32]
entryCause = csrs.interruptCause
entryValue = 0
if (run == 0b0) {
stepActive = true
activeStep = stepRequest
}
}
if (enterTrap) {
trapPending = false
trapPulse = 0b1
programCounter = csrs.trapVector as uint
fetchPc = csrs.trapVector as uint
}
if (xTransfer) {
cValid = true
cPc = xPc
cWord = xWord
cNext = xNext
cValue = xResult
cStoreValue = xRight
cOperand = xLeft
cRd = RvDecoded.Rd(executeControl) as logic[5]
cWrites = RvDecoded.HasDestinationWrite(executeControl)
cKind = xKind as logic[4]
cWidth = xWidth as logic[3]
cFault = xFault
cCause = xCause
cRedirect = xRedirect
xValid = false
}
if (dTransfer) {
xValid = true
xPc = dPc
xWord = dWord
xControl = control
xImmediate = decoder.immediate as uint
xError = dError
xLeft = dLeft
xRight = dRight
if (dPhase == READ_PENDING) {
xLeft = registers.leftData as uint
xRight = registers.rightData as uint
}
// Unused encoded source fields must never contribute operands.
if (!RvDecoded.HasSource1(control)) {
xLeft = 0
}
if (!RvDecoded.HasSource2(control)) {
xRight = 0
}
dValid = false
} elif (dValid && dPhase == READ_PENDING) {
dLeft = registers.leftData as uint
dRight = registers.rightData as uint
dPhase = READ_CAPTURED
}
if (readOperands) {
dPhase = READ_PENDING
}
if (fTransfer) {
dValid = true
dPc = fPc
dWord = fWord
dError = fError
dPhase = READ_WAIT
fValid = false
}
if (fBusy && fetch.ready == 0b1) {
fBusy = false
fGap = true
fValid = !fKilled && !flush
fWord = fetch.instruction as uint
fError = fetch.error == 0b1
fKilled = false
}
if (launch) {
fBusy = true
fPc = fetchPc
fetchPc = fetchPc + 4
if (run == 0b0) {
stepActive = true
activeStep = stepRequest
}
}
}
}
}
}