11.1. Decoder
This decoder implementation assumes there is no branch predictor or return address stack (return_stack_size_p and bpred_size_p both zero).
Reference Python implementations of both the encoder and decoder can be found at https://github.com/riscv-non-isa/riscv-trace-spec.
11.1.1. Decoder pseudo code
# global variables
global pc # Reconstructed program counter
global last_pc # PC of previous instruction
global branches = 0 # Number of branches to process
global branch_map = 0 # Bit vector of not taken/taken (1/0) status
# for branches
global bool stop_at_last_branch = FALSE # Flag to indicate reconstruction is to end at
# the final branch
global bool inferred_address = FALSE # Flag to indicate that reported address from
# format 0/1/2 was not following an uninferable
# jump (and is therefore inferred)
global bool start_of_trace = TRUE # Flag indicating 1st trace packet still
# to be processed
global address # Reconstructed address from te_inst messages
global privilege # Privilege from te_inst messages
global options # Operating mode flags
global array return_stack # Array holding return address stack
global irstack_depth = 0 # Depth of the return address stack
# Process te_inst packet. Call each time a te_inst packet is received #
function process_te_inst (te_inst)
if (te_inst.format == 3)
if (te_inst.subformat == 3) # Support packet
process_support(te_inst)
return
if (te_inst.subformat == 2) # Context packet
return
if (te_inst.subformat == 1) # Trap packet
report_trap(te_inst)
if (!te_inst.interrupt) # Exception
report_epc(exception_address(te_inst))
if (!te_inst.thaddr) # Trap only - nothing retired
return
inferred_address = FALSE
address = (te_inst.address << discovery_response.iaddress_lsb)
if (te_inst.subformat == 1 or start_of_trace)
branches = 0
branch_map = 0
if (is_branch(get_instr(address))) # 1 unprocessed branch if this instruction is a branch
branch_map = branch_map | (te_inst.branch << branches)
branches++
if (te_inst.subformat == 0 and !start_of_trace)
follow_execution_path(address, te_inst)
else
pc = address
report_pc(pc)
last_pc = pc # previous pc not known but ensures correct
# operation for is_sequential_jump()
privilege = te_inst.privilege
start_of_trace = FALSE
irstack_depth = 0
else # Duplicated at top of next page to show continuity
else # Duplicate of last line from previous page to show continuity
if (start_of_trace) # This should not be possible!
ERROR: Expecting trace to start with format 3
return
if (te_inst.format == 2 or te_inst.branches != 0)
stop_at_last_branch = FALSE
if (options.full_address)
address = (te_inst.address << discovery_response.iaddress_lsb)
else
address += (te_inst.address << discovery_response.iaddress_lsb)
if (te_inst.format == 1)
stop_at_last_branch = (te_inst.branches == 0)
# Branch map will contain <= 1 branch (1 if last reported instruction was a branch)
branch_map = branch_map | (te_inst.branch_map << branches)
if (te_inst.branches == 0)
branches += 31
else
branches += te_inst.branches
follow_execution_path(address, te_inst)
# Follow execution path to reported address #
function follow_execution_path(address, te_inst)
local previous_address = pc
local stop_here = FALSE
while (TRUE)
if (inferred_address) # iterate again from previously reported address to
# find second occurrence
stop_here = next_pc(previous_address)
report_pc(pc)
if (stop_here)
inferred_address = FALSE
else
stop_here = next_pc(address)
report_pc(pc)
if (branches == 1 and is_branch(get_instr(pc)) and stop_at_last_branch)
# Reached final branch - stop here (do not follow to next instruction as
# we do not yet know whether it retires)
stop_at_last_branch = FALSE
return
if (stop_here)
# Reached reported address following an uninferable discontinuity - stop here
if (unprocessed_branches(pc))
ERROR: unprocessed branches
return
if (te_inst.format != 3 and pc == address and !stop_at_last_branch and
(te_inst.notify != get_preceding_bit(te_inst, "notify")) and
!unprocessed_branches(pc))
# All branches processed, and reached reported address due to notification,
# not as an uninferable jump target
return
if (te_inst.format != 3 and pc == address and !stop_at_last_branch and
!is_uninferable_discon(get_instr(last_pc)) and
(te_inst.updiscon == get_preceding_bit(te_inst, "updiscon")) and
!unprocessed_branches()) and
((te_inst.irreport == get_previous_bit(te_inst, "irreport")) or
te_inst.irdepth == irstack_depth))
# All branches processed, and reached reported address, but not as an
# uninferable jump target
# Stop here for now, though flag indicates this may not be
# final retired instruction
inferred_address = TRUE
return
if (te_inst.format == 3 and pc == address and !unprocessed_branches(pc) and
(te_inst.privilege == privilege or is_return_from_trap(get_instr(last_pc))))
# All branches processed, and reached reported address
return
# Compute next PC #
function next_pc (address)
local instr = get_instr(pc)
local this_pc = pc
local stop_here = FALSE
if (is_inferable_jump(instr))
pc += instr.imm
else if (is_sequential_jump(instr, last_pc)) # lui/auipc followed by
# jump using same register
pc = sequential_jump_target(pc, last_pc)
else if (is_implicit_return(instr))
pc = pop_return_stack()
else if (is_uninferable_discon(instr))
if (stop_at_last_branch)
ERROR: unexpected uninferable discontinuity
else
pc = address
stop_here = TRUE
else if (is_taken_branch(instr))
pc += instr.imm
else
pc += instruction_size(instr)
if (is_call(instr))
push_return_stack(this_pc)
last_pc = this_pc
return stop_here
# Process support packet #
function process_support (te_inst)
local stop_here = FALSE
options = te_inst.options
if (te_inst.qual_status != no_change)
start_of_trace = TRUE # Trace ended, so get ready to start again
if (te_inst.qual_status == ended_ntr and inferred_address)
local previous_address = pc
inferred_address = FALSE
while (TRUE)
stop_here = next_pc(previous_address)
report_pc(pc)
if (stop_here)
return
return
# Determine if instruction is a branch, adjust branch count/map,
# and return taken status #
function is_taken_branch (instr)
local bool taken = FALSE
if (!is_branch(instr))
return FALSE
if (branches == 0)
ERROR: cannot resolve branch
else
taken = !branch_map[0]
branches--
branch_map >> 1
return taken
# Determine if instruction is a branch #
function is_branch (instr)
if ((instr.opcode == BEQ) or
(instr.opcode == BNE) or
(instr.opcode == BLT) or
(instr.opcode == BGE) or
(instr.opcode == BLTU) or
(instr.opcode == BGEU) or
(instr.opcode == C.BEQZ) or
(instr.opcode == C.BNEZ))
return TRUE
return FALSE
# Determine if instruction is an inferable jump #
function is_inferable_jump (instr)
if ((instr.opcode == JAL) or
(instr.opcode == C.JAL) or
(instr.opcode == C.J))
return TRUE
return FALSE
# Determine if instruction is an uninferable jump #
function is_uninferable_jump (instr)
if ((instr.opcode == JALR and instr.rs1 != 0) or
(instr.opcode == C.JALR) or
(instr.opcode == C.JR))
return TRUE
return FALSE
# Determine if instruction is a return from trap #
function is_return_from_trap (instr)
if ((instr.opcode == URET) or
(instr.opcode == SRET) or
(instr.opcode == MRET) or
(instr.opcode == DRET))
return TRUE
return false
# Determine if instruction is an uninferrable discontinuity #
function is_uninferrable_discon (instr)
if (is_uninferrable_jump(instr) or
is_return_from_trap (instr))
return TRUE
return FALSE
# Determine if instruction is a sequentially inferable jump #
function is_sequential_jump (instr, prev_addr)
if (not (is_uninferable_jump(instr) and options.sijump))
return FALSE
local prev_instr = get_instr(prev_addr)
if((prev_instr.opcode == AUIPC) or
(prev_instr.opcode == LUI) or
(prev_instr.opcode == C.LUI))
return (instr.rs1 == prev_instr.rd)
return FALSE
# Find the target of a sequentially inferable jump #
function sequential_jump_target (addr, prev_addr)
local instr = get_instr(addr)
local prev_instr = get_instr(prev_addr)
local target = 0
if (prev_instr.opcode == AUIPC)
target = prev_addr
target += prev_instr.imm
if (instr.opcode == JALR)
target += instr.imm
return target
# Determine if instruction is a call #
# - excludes tail calls as they do not push an address onto the return stack
function is_call (instr)
if ((instr.opcode == JALR and instr.rd == 1) or
(instr.opcode == C.JALR) or
(instr.opcode == JAL and instr.rd == 1) or
(instr.opcode == C.JAL))
return TRUE
return FALSE
# Determine if instruction return address can be implicitly inferred #
function is_implicit_return (instr)
if (options.implicit_return == 0) # Implicit return mode disabled
return FALSE
if ((instr.opcode == JALR and instr.rs1 == 1 and instr.rd == 0) or
(instr.opcode == C.JR and instr.rs1 == 1))
if ((te_inst.irreport != get_preceding_bit(te_inst, "irreport")) and
te_inst.irdepth == irstack_depth)
return FALSE
return (irstack_depth > 0)
return FALSE
#Check for unprocessed branches #
function unprocessed_branches (address)
# Check all branches processed (except 1 if this instruction is a branch)
return (branches != (is_branch(get_instr(address)) ? 1 : 0))
# Push address onto return stack #
function push_return_stack (address)
if (options.implicit_return == 0) # Implicit return mode disabled
return
local irstack_depth_max = discovery_response.return_stack_size ?
2**discovery_response.return_stack_size :
2**discovery_response.call_counter_size
local instr = get_instr(address)
local link = address
if (irstack_depth == irstack_depth_max)
# Delete oldest entry from stack to make room for new entry added below
irstack_depth--
for (i = 0; i < irstack_depth; i++)
return_stack[i] = return_stack[i+1]
link += instruction_size(instr)
return_stack[irstack_depth] = link
irstack_depth++
return
# Pop address from return stack #
function pop_return_stack ()
irstack_depth-- # function not called if irstack_depth is 0, so no need
# to check for underflow
local link = return_stack[irstack_depth]
return link
# Return the address of an exception #
function exception_address(te_inst)
local instr = get_instr(pc)
if (is_uninferable_discon(instr) and !te_inst.thaddr)
return te_inst.address
if (instr.opcode == ECALL) or (instr.opcode == EBREAK) or (instr.opcode == C.EBREAK))
return pc
return next_pc(pc)
# Report ecause and tval (user to populate if desired) #
function report_trap(te_inst)
return
# Report program counter value (user to populate if desired) #
function report_pc(address)
return
# Report exception program counter value (user to populate if desired) #
function report_epc(address)
return