summaryrefslogtreecommitdiff
path: root/frontend/decode.h
blob: 717d0f612de1868593bf9d7ad6a8a27964a0c526 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#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<restart> restartp;
        infra::port<fetch::restart> *fetch_restartp = nullptr;

        infra::port<bundle> bundlep;
        infra::port<inst> *instp = nullptr;

        inst next_inst;
        unsigned int generation_up = 0;
        unsigned int generation_down = 0;
        std::uint64_t pc = 0;

        static constexpr unsigned int MAX_INST_SIZE = 64;
        static constexpr unsigned int BYTES_PER_CYCLE = 4;

        void clock() {
            if (restartp.can_read()) {
                auto r = restartp.read();
                generation_down = r.new_generation;
                pc = r.new_pc;
                next_inst.size = 0;
                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<std::uint8_t *>(&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<std::uint64_t> target;
                            std::optional<bool> 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<std::uint64_t> 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()) {
                                    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.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();
            }
        }
    };
}