Skip to content

Latest commit

 

History

History
389 lines (312 loc) · 12.3 KB

decoder.adoc

File metadata and controls

389 lines (312 loc) · 12.3 KB

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.

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)   or
      (instr.opcode == JALR and instr.rs1 == 0))
    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) or
      (instr.opcode == ECALL)     or
      (instr.opcode == EBREAK)    or
      (instr.opcode == C.EBREAK))
    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