#pragma once #include "frontend/bundle.h" #include "frontend/fetch.h" #include "infra/port.h" #include "inst.h" #include "memory/line.h" namespace frontend { struct decode : public infra::sim { struct restart { unsigned int new_generation; std::uint64_t new_pc; std::uint64_t from_pc; }; infra::port restartp; infra::port *fetch_restartp = nullptr; infra::port bundlep; infra::port *instp = nullptr; inst next_inst; unsigned int generation_up = 0; unsigned int generation_down = 0; std::uint64_t pc = 0; std::uint64_t indirect_jump_origin = ~(std::uint64_t)0; std::uint64_t indirect_jump_destination = ~(std::uint64_t)0; static constexpr unsigned int MAX_INST_SIZE = 64; static constexpr unsigned int BYTES_PER_CYCLE = 1; void clock() { if (restartp.can_read()) { auto r = restartp.read(); generation_down = r.new_generation; next_inst.init_pc = pc = r.new_pc; next_inst.size = 0; indirect_jump_origin = r.from_pc; indirect_jump_destination = r.new_pc; for (auto &f : next_inst.field) f = 0; fetch::restart fr; fr.new_generation = ++generation_up; fr.new_next_line_address = pc >> memory::LINE_BYTES_LOG2; fr.previous_line_address = r.from_pc >> memory::LINE_BYTES_LOG2; fetch_restartp->write(std::move(fr)); return; } if (next_inst.size >= MAX_INST_SIZE) return; if (bundlep.can_read()) { const auto &b = bundlep.peek(); for (unsigned int i = 0; i < BYTES_PER_CYCLE; ++i) { auto line = pc >> memory::LINE_BYTES_LOG2; auto offset = pc & memory::LINE_BYTE_OFFSET_MASK; if (b.generation == generation_up && b.line_address == line && instp->can_write()) { decodebyte byte; std::memcpy(&byte, b.data.data() + offset, sizeof(byte)); pte(b.transaction, "d", fmt::format("decode gen={} pc={:x} byte={:02x}", generation_up, pc, *reinterpret_cast(&byte))); ++next_inst.size; if (byte.invert) next_inst.field[byte.field] = ~next_inst.field[byte.field]; next_inst.field[byte.field] = next_inst.field[byte.field] << 4; next_inst.field[byte.field] |= byte.bits; ++pc; if (!byte.hold) { next_inst.transaction = infra::pt::child(b.transaction); next_inst.generation = generation_down; next_inst.linear_next_pc = pc; next_inst.predicted_next_pc = pc; pte(next_inst.transaction, "D", fmt::format("decode gen={}", generation_down)); bool jump = false; std::optional target; std::optional taken; switch (next_inst.field[OPCODE]) { case OP_JUMP_ABS_IF_ZERO: jump = true; if (next_inst.field[FLAGS_DST] & FLAG_IMM1) target = next_inst.field[SRC1]; if (next_inst.field[FLAGS_DST] & FLAG_IMM2) taken = next_inst.field[SRC2] == 0; break; case OP_JUMP_ABS_IF_NONZERO: jump = true; if (next_inst.field[FLAGS_DST] & FLAG_IMM1) target = next_inst.field[SRC1]; if (next_inst.field[FLAGS_DST] & FLAG_IMM2) taken = next_inst.field[SRC2] != 0; break; } std::optional redirect; bool unpredictable = false; if (jump) { if (target.has_value()) { if (taken.has_value()) { if (taken.value()) redirect = target; } else if (target.value() < pc) { redirect = target; } } else if (!taken.has_value() || taken.value()) { if (next_inst.init_pc == indirect_jump_origin) redirect = indirect_jump_destination; else unpredictable = true; } } if (redirect.has_value()) { pte(next_inst.transaction, "", fmt::format("fe predicts jump to {:x}", redirect.value())); next_inst.predicted_next_pc = pc = redirect.value(); } else if (unpredictable) { pte(next_inst.transaction, "", "frontend halt due to unpredictable jump"); next_inst.predicted_next_pc.reset(); } instp->write(std::move(next_inst)); next_inst.init_pc = pc; next_inst.size = unpredictable ? MAX_INST_SIZE : 0; for (auto &f : next_inst.field) f = 0; } } } auto line = pc >> memory::LINE_BYTES_LOG2; if (b.generation == generation_up && b.line_address != line && b.next_line_address != line) { fetch::restart fr; fr.new_generation = ++generation_up; fr.new_next_line_address = pc >> memory::LINE_BYTES_LOG2; fr.previous_line_address = b.line_address; fetch_restartp->write(std::move(fr)); } if (b.generation != generation_up || b.line_address != line) bundlep.discard(); } } }; }