The Legacy ChampSim Module System

Previous versions of ChampSim used the following module system. Many modules exist in published artifacts, so documentation is included here. New work should use the updated module system.

Legacy modules can be enabled by adding an empty file named “__legacy__” in the same directory as the module sources. This is the preferred method. Alternatively, ChampSim can be configured with (for example):

{
    "L2C": {
        "prefetcher" {
            "path": "../path/to/module",
            "legacy": true
        }
    }
}

ChampSim uses four kinds of modules:

  • Branch Direction Predictors

  • Branch Target Predictors

  • Memory Prefetchers

  • Cache Replacement Policies

Each of these is implemented as a set of hook functions. Each hook must be implemented, or compilation will fail.

Branch Predictors

A branch predictor module must implement three functions.

void O3_CPU::initialize_branch_predictor()

This function is called when the core is initialized. You can use it to initialize elements of dynamic structures, such as std::vector or std::map.

uint8_t O3_CPU::predict_branch(uint64_t ip, uint64_t predicted_target, uint8_t always_taken, uint8_t branch_type)

This function is called when a prediction is needed. The parameters passed are:

  • ip: The instruction pointer of the branch

  • predicted_target: The predicted target of the branch. This is passed directly from the branch target predictor module and may be incorrect.

  • always_taken: A boolean value. This parameter will be nonzero if the branch target predictor determines that the branch is always taken.

  • branch_type: One of the following

    • BRANCH_DIRECT_JUMP: Direct non-call, unconditional branches, whose target is encoded in the instruction

    • BRANCH_INDIRECT: Indirect non-call, unconditional branches, whose target is stored in a register

    • BRANCH_CONDITIONAL: A direct conditional branch

    • BRANCH_DIRECT_CALL: A call to a procedure whose target is encoded in the instruction

    • BRANCH_INDIRECT_CALL: A call to a procedure whose target is stored in a register

    • BRANCH_RETURN: A return to a calling procedure

    • BRANCH_OTHER: If the branch type cannot be determined

void O3_CPU::last_branch_result(uint64_t ip, uint64_t branch_target, uint8_t taken, uint8_t branch_type)

This function is called when a branch is resolved. The parameters are the same as in the previous hook, except that the last three are guaranteed to be correct.

Branch Target Buffers

A BTB module must implement three functions.

void O3_CPU::initialize_btb()

This function is called when the core is initialized. You can use it to initialize elements of dynamic structures, such as std::vector or std::map.

std::pair<uint64_t, bool> O3_CPU::btb_prediction(uint64_t ip)

This function is called when a prediction is needed. The parameters passed are:

  • ip: The instruction pointer of the branch

The function should return a pair containing the predicted address and a boolean that describes if the branch is known to be always taken. If the prediction fails, the function should return a default-initialized address, e.g. uint64_t{}.

void O3_CPU::update_btb(uint64_t ip, uint64_t branch_target, uint8_t taken, uint8_t branch_type)

This function is called when a branch is resolved. The parameters are:

  • ip: The instruction pointer of the branch

  • branch_target: The correct target of the branch.

  • taken: A boolean value. This parameter will be nonzero if the branch was taken.

  • branch_type: One of the following

    • BRANCH_DIRECT_JUMP: Direct non-call, unconditional branches, whose target is encoded in the instruction

    • BRANCH_INDIRECT: Indirect non-call, unconditional branches, whose target is stored in a register

    • BRANCH_CONDITIONAL: A direct conditional branch

    • BRANCH_DIRECT_CALL: A call to a procedure whose target is encoded in the instruction

    • BRANCH_INDIRECT_CALL: A call to a procedure whose target is stored in a register

    • BRANCH_RETURN: A return to a calling procedure

    • BRANCH_OTHER: If the branch type cannot be determined

Memory Prefetchers

A prefetcher module must implement five or six functions.

void CACHE::prefetcher_initialize()

This function is called when the cache is initialized. You can use it to initialize elements of dynamic structures, such as std::vector or std::map.

uint32_t CACHE::prefetcher_cache_operate(uint64_t addr, uint64_t ip, uint8_t cache_hit, bool useful_prefetch, uint8_t type, uint32_t metadata_in);

This function is called when a tag is checked in the cache. The parameters passed are:

  • addr: the address of the packet. If this is the first-level cache, the offset bits are included. Otherwise, the offset bits are zero. If the cache was configured with “virtual_prefetch”: true, this address will be a virtual address. Otherwise, this is a physical address.

  • ip: the address of the instruction that initiated the demand. If the packet is a prefetch from another level, this value will be 0.

  • cache_hit: if this tag check is a hit, this value is nonzero. Otherwise, it is 0.

  • useful_prefetch: if this tag check hit a prior prefetch, this value is true.

  • type: the result of static_cast<std::underlying_type_t<access_type>>(v) for v in: * access_type::LOAD * access_type::RFO * access_type::PREFETCH * access_type::WRITE * access_type::TRANSLATION

  • metadata_in: the metadata carried along by the packet.

The function should return metadata that will be stored alongside the block.

uint32_t CACHE::prefetcher_cache_fill(uint64_t addr, uint32_t set, uint32_way, uint8_t prefetch, uint32_t metadata_in);

This function is called when a miss is filled in the cache. The parameters passed are:

  • addr: the address of the packet. If this is the first-level cache, the offset bits are included. Otherwise, the offset bits are zero. If the cache was configured with “virtual_prefetch”: true, this address will be a virtual address. Otherwise, this is a physical address.

  • set: the set that the fill occurred in

  • way: the way that the fill occurred in, or this->NUM_WAY if a bypass occurred

  • prefetch: if this tag check hit a prior prefetch, this value is true.

  • metadata_in: the metadata carried along by the packet.

The function should return metadata that will be stored alongside the block.

void CACHE::prefetcher_cycle_operate();

This function is called each cycle, after all other operation has completed.

void CACHE::prefetcher_final_stats();

This function is called at the end of the simulation and can be used to print statistics.

void CACHE::prefetcher_branch_operate(uint64_t ip, uint8_t branch_type, uint64_t branch_target);

This function must be implemented by instruction prefetchers. The parameters passed are:

  • ip: The instruction pointer of the branch

  • branch_type: One of the following

    • BRANCH_DIRECT_JUMP: Direct non-call, unconditional branches, whose target is encoded in the instruction

    • BRANCH_INDIRECT: Indirect non-call, unconditional branches, whose target is stored in a register

    • BRANCH_CONDITIONAL: A direct conditional branch

    • BRANCH_DIRECT_CALL: A call to a procedure whose target is encoded in the instruction

    • BRANCH_INDIRECT_CALL: A call to a procedure whose target is stored in a register

    • BRANCH_RETURN: A return to a calling procedure

    • BRANCH_OTHER: If the branch type cannot be determined

  • branch_target: The instruction pointer of the target

Replacement Policies

A replacement policy module must implement four functions.

void CACHE::initialize_replacement()

This function is called when the cache is initialized. You can use it to initialize elements of dynamic structures, such as std::vector or std::map.

uint32_t CACHE::find_victim(uint32_t triggering_cpu, uint64_t instr_id, uint32_t set, const BLOCK* current_set, uint64_t ip, uint64_t addr, uint32_t type);

This function is called when a tag is checked in the cache. The parameters passed are:

  • triggering_cpu: the core index that initiated this fill

  • instr_id: an instruction count that can be used to examine the program order of requests.

  • set: the set that the fill occurred in.

  • current_set: a pointer to the beginning of the set being accessed.

  • ip: the address of the instruction that initiated the demand. If the packet is a prefetch from another level, this value will be 0.

  • addr: the address of the packet. If this is the first-level cache, the offset bits are included. Otherwise, the offset bits are zero. If the cache was configured with “virtual_prefetch”: true, this address will be a virtual address. Otherwise, this is a physical address.

  • type: the result of static_cast<std::underlying_type_t<access_type>>(v) for v in:

    • access_type::LOAD

    • access_type::RFO

    • access_type::PREFETCH

    • access_type::WRITE

    • access_type::TRANSLATION

The function should return the way index that should be evicted, or this->NUM_WAY to indicate that a bypass should occur.

void CACHE::update_replacement_state(uint32_t triggering_cpu, uint32_t set, uint32_t way, uint64_t addr, uint64_t ip, uint64_t victim_addr, uint8_t hit);

This function is called when a hit occurs or a miss is filled in the cache. The parameters passed are:

  • triggering_cpu: the core index that initiated this fill

  • set: the set that the fill occurred in.

  • way: the way that the fill occurred in.

  • addr: the address of the packet. If this is the first-level cache, the offset bits are included. Otherwise, the offset bits are zero. If the cache was configured with “virtual_prefetch”: true, this address will be a virtual address. Otherwise, this is a physical address.

  • ip: the address of the instruction that initiated the demand. If the packet is a prefetch from another level, this value will be 0.

  • victim_addr: the address of the evicted block, if this is a miss. If this is a hit, the value is 0.

  • type: the result of static_cast<std::underlying_type_t<access_type>>(v) for v in:

    • access_type::LOAD

    • access_type::RFO

    • access_type::PREFETCH

    • access_type::WRITE

    • access_type::TRANSLATION

The function should return metadata that will be stored alongside the block.

void CACHE::replacement_final_stats();

This function is called at the end of the simulation and can be used to print statistics.