summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore1
-rw-r--r--Makefile50
-rw-r--r--backend/exec.h72
-rw-r--r--backend/regfile.h132
-rw-r--r--cpu.h40
-rw-r--r--frontend/bundle.h14
-rw-r--r--frontend/decode.h128
-rw-r--r--frontend/fetch.h79
-rw-r--r--infra/arbiter.h42
-rw-r--r--infra/pipetrace.cpp9
-rw-r--r--infra/pipetrace.h37
-rw-r--r--infra/port.h45
-rw-r--r--infra/queue.h29
-rw-r--r--infra/sim.cpp9
-rw-r--r--infra/sim.h38
-rw-r--r--inst.h56
-rw-r--r--main.cpp22
-rw-r--r--memory/dram.h96
-rw-r--r--memory/line.h15
l---------procmodel1
-rwxr-xr-xpt95
-rwxr-xr-xtest58
-rw-r--r--tests/countdown.hex5
-rw-r--r--tests/fib-mem.hex9
-rw-r--r--tests/fib-regs.hex6
-rw-r--r--tests/loop-with-add.hex2
-rw-r--r--tests/loop.hex1
27 files changed, 1091 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
/build
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..184f7c0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
1BUILD := build
2
3override PACKAGES := fmt
4
5WARNINGS := -Wall -Werror
6OPTIMIZE := -O3 -flto
7DEBUG := -g
8
9CXX := g++
10CXXFLAGS := $(WARNINGS) $(OPTIMIZE) $(DEBUG)
11
12XXD := xxd
13
14override CXXFLAGS += -std=c++20
15
16override COMPILE_FLAGS := -MMD -MP -I.
17override LINK_FLAGS :=
18
19ifneq "$(strip $(PACKAGES))" ""
20 override COMPILE_FLAGS += $(shell pkg-config --cflags $(PACKAGES))
21 override LINK_FLAGS += -Wl,--start-group $(shell pkg-config --libs $(PACKAGES)) -Wl,--end-group
22endif
23
24default: $(BUILD)/procmodel
25.PHONY: default
26
27clean:
28 rm -rf $(BUILD)
29.PHONY: clean
30
31.SUFFIXES:
32
33override SOURCES := $(shell find -\( -name build -prune -\) -o -\( -name \*.cpp -print -\))
34
35override OBJECTS := $(addprefix $(BUILD)/, $(addsuffix .o, $(basename $(SOURCES))))
36override DEPENDS := $(addprefix $(BUILD)/, $(addsuffix .d, $(basename $(SOURCES))))
37
38-include $(DEPENDS)
39
40$(BUILD)/%.o: %.cpp
41 @mkdir -p $(dir $@)
42 $(CXX) $(CXXFLAGS) $(COMPILE_FLAGS) -c -o $@ $<
43
44$(BUILD)/procmodel: $(OBJECTS)
45 @mkdir -p $(dir $@)
46 $(CXX) $(CXXFLAGS) -o $@ -Wl,--start-group $+ -Wl,--end-group $(LINK_FLAGS)
47
48$(BUILD)/%.bin: %.hex
49 @mkdir -p $(dir $@)
50 $(XXD) -r -p $< $@
diff --git a/backend/exec.h b/backend/exec.h
new file mode 100644
index 0000000..f1474b8
--- /dev/null
+++ b/backend/exec.h
@@ -0,0 +1,72 @@
1#pragma once
2
3#include "infra/port.h"
4#include "inst.h"
5#include "memory/dram.h"
6
7namespace backend {
8 struct exec : public infra::sim {
9 infra::port<inst> execp;
10 infra::port<inst> *writebackp = nullptr;
11 infra::port<memory::dram::command> *loadp = nullptr;
12 infra::port<memory::dram::response> loadresultp;
13
14 infra::port<inst> stallp;
15
16 void clock() {
17 if (stallp.can_read() && writebackp->can_write()) {
18 const auto &i = stallp.peek();
19 switch (i.field[OPCODE]) {
20 case OP_LOAD:
21 if (loadresultp.can_read()) {
22 auto i = stallp.read();
23 auto addr = i.field[SRC1] + i.field[SRC2];
24 auto offset = addr & memory::LINE_BYTE_OFFSET_MASK;
25 pte(i.transaction, "", fmt::format("addr={:x} offset={:x}", addr, offset));
26 auto f = loadresultp.read();
27 std::uint64_t r = 0;
28 for (unsigned int i = 0; i < sizeof(r); ++i)
29 r |= f.data[i + offset] << (8 * i);
30 i.result = r;
31 writebackp->write(std::move(i));
32 }
33 break;
34 }
35 } else if (execp.can_read() && writebackp->can_write() && loadp->can_write()) {
36 auto i = execp.read();
37 pte(i.transaction, "E", fmt::format("exec gen={} op={:x} a={:x} b={:x}", i.generation, i.field[OPCODE], i.field[SRC1], i.field[SRC2]));
38 switch (i.field[OPCODE]) {
39 case OP_JUMP_ABS_IF_ZERO:
40 if (i.field[SRC2] == 0)
41 i.result = i.field[SRC1];
42 else
43 i.result = i.linear_next_pc;
44 break;
45 case OP_JUMP_ABS_IF_NONZERO:
46 if (i.field[SRC2] != 0)
47 i.result = i.field[SRC1];
48 else
49 i.result = i.linear_next_pc;
50 break;
51 case OP_EMIT:
52 case OP_ADD:
53 i.result = i.field[SRC1] + i.field[SRC2];
54 break;
55 case OP_LOAD:
56 {
57 memory::dram::command c;
58 c.transaction = i.transaction;
59 c.line_address = (i.field[SRC1] + i.field[SRC2]) >> memory::LINE_BYTES_LOG2;
60 c.write = false;
61 c.responsep = &loadresultp;
62 loadp->write(std::move(c));
63 }
64 stallp.write(std::move(i));
65 break;
66 }
67 if (stallp.can_write())
68 writebackp->write(std::move(i));
69 }
70 }
71 };
72}
diff --git a/backend/regfile.h b/backend/regfile.h
new file mode 100644
index 0000000..276504c
--- /dev/null
+++ b/backend/regfile.h
@@ -0,0 +1,132 @@
1#pragma once
2
3#include <array>
4
5#include "frontend/decode.h"
6#include "infra/port.h"
7#include "inst.h"
8#include "memory/dram.h"
9
10namespace backend {
11 struct regfile : public infra::sim {
12 infra::port<frontend::decode::restart> *decode_restartp = nullptr;
13
14 infra::port<inst> instp;
15 infra::port<inst> *execp = nullptr;
16 infra::port<inst> writebackp;
17 infra::port<memory::dram::command> *storep = nullptr;
18
19 unsigned int generation_up = 0;
20 unsigned int generation_down = 0;
21
22 std::array<std::uint64_t, 16> regs;
23 std::array<bool, 16> hazards;
24 std::uint64_t pc = 0;
25
26 regfile() {
27 regs.fill(0);
28 hazards.fill(false);
29 }
30
31 void clock() {
32 if (writebackp.can_read() && storep->can_write()) {
33 auto i = writebackp.read();
34 if (i.generation == generation_down) {
35 pte(i.transaction, "W", fmt::format("writeback gen={} pc={:x}", generation_down, pc));
36 auto old_pc = pc;
37 pc = i.linear_next_pc;
38 switch (i.field[OPCODE]) {
39 case OP_JUMP_ABS_IF_ZERO:
40 case OP_JUMP_ABS_IF_NONZERO:
41 pte(i.transaction, "", fmt::format("jump to {:x}", i.result.value()));
42 pc = i.result.value();
43 break;
44 case OP_EMIT:
45 pte(i.transaction, "*", fmt::format("emit {}", i.result.value()));
46 break;
47 case OP_STORE:
48 {
49 memory::dram::command c;
50 c.transaction = i.transaction;
51 c.line_address = i.field[SRC1] >> memory::LINE_BYTES_LOG2;
52 c.write = true;
53 c.mask.fill(false);
54 auto offset = i.field[SRC1] & memory::LINE_BYTE_OFFSET_MASK;
55 pte(i.transaction, "", fmt::format("store [{:x}]={:x} offset={:x}", i.field[SRC1], i.field[SRC2], offset));
56 for (unsigned int j = 0; j < sizeof(i.field[SRC2]); ++j) {
57 c.mask[offset + j] = true;
58 c.data[offset + j] = (i.field[SRC2] >> (8 * j)) & 0xff;
59 }
60 storep->write(std::move(c));
61 }
62 break;
63 default:
64 pte(i.transaction, "", fmt::format("wb r{}={:x}", i.field[FLAGS_DST] % regs.size(), i.result.value()));
65 regs[i.field[FLAGS_DST] % regs.size()] = i.result.value();
66 hazards[i.field[FLAGS_DST] % regs.size()] = false;
67 break;
68 }
69 if (!i.predicted_next_pc.has_value() || pc != i.predicted_next_pc.value()) {
70 pte(i.transaction, "", "restart due to pc misprediction");
71 frontend::decode::restart dr;
72 dr.new_generation = ++generation_up;
73 dr.new_pc = pc;
74 dr.from_pc = old_pc;
75 decode_restartp->write(std::move(dr));
76 hazards.fill(false);
77 ++generation_down;
78 }
79 }
80 }
81
82 if (instp.can_read() && execp->can_write() && !writebackp.can_read()) {
83 auto i = instp.peek();
84 if (i.generation == generation_up) {
85 bool hazard = false;
86 if (!(i.field[FLAGS_DST] & FLAG_IMM1))
87 hazard |= hazards[i.field[SRC1] % regs.size()];
88 if (!(i.field[FLAGS_DST] & FLAG_IMM2))
89 hazard |= hazards[i.field[SRC2] % regs.size()];
90 switch (i.field[OPCODE]) {
91 case OP_JUMP_ABS_IF_ZERO:
92 case OP_JUMP_ABS_IF_NONZERO:
93 case OP_EMIT:
94 case OP_STORE:
95 break;
96 default:
97 hazard |= hazards[i.field[FLAGS_DST] % regs.size()];
98 break;
99 }
100 if (!hazard) {
101 auto i = instp.read();
102 if (!(i.field[FLAGS_DST] & FLAG_IMM1)) {
103 auto x = regs[i.field[SRC1] % regs.size()];
104 pte(i.transaction, "", fmt::format("rf1[{}]={:x}", i.field[SRC1] % regs.size(), x));
105 i.field[SRC1] = x;
106 }
107 if (!(i.field[FLAGS_DST] & FLAG_IMM2)) {
108 auto x = regs[i.field[SRC2] % regs.size()];
109 pte(i.transaction, "", fmt::format("rf2[{}]={:x}", i.field[SRC2] % regs.size(), x));
110 i.field[SRC2] = x;
111 }
112 pte(i.transaction, "R", fmt::format("read gen={}", generation_up));
113 i.generation = generation_down;
114 switch (i.field[OPCODE]) {
115 case OP_JUMP_ABS_IF_ZERO:
116 case OP_JUMP_ABS_IF_NONZERO:
117 case OP_EMIT:
118 case OP_STORE:
119 break;
120 default:
121 hazards[i.field[FLAGS_DST] % regs.size()] = true;
122 break;
123 }
124 execp->write(std::move(i));
125 }
126 } else {
127 instp.discard();
128 }
129 }
130 }
131 };
132}
diff --git a/cpu.h b/cpu.h
new file mode 100644
index 0000000..5ef45a0
--- /dev/null
+++ b/cpu.h
@@ -0,0 +1,40 @@
1#pragma once
2
3#include <cstdint>
4#include <cstring>
5#include <optional>
6
7#include "backend/exec.h"
8#include "backend/regfile.h"
9#include "frontend/decode.h"
10#include "frontend/fetch.h"
11#include "infra/arbiter.h"
12#include "infra/pipetrace.h"
13#include "infra/queue.h"
14#include "infra/sim.h"
15#include "inst.h"
16#include "memory/dram.h"
17
18struct cpu {
19 backend::exec exec;
20 backend::regfile regfile;
21 frontend::decode decode;
22 frontend::fetch fetch;
23 infra::priority_arbiter<memory::dram::command, 3> dram_arbiter;
24 infra::queue<inst, 4> decodeq;
25 memory::dram dram;
26
27 cpu() {
28 decode.fetch_restartp = &fetch.restartp;
29 decode.instp = &decodeq.input;
30 decodeq.output = &regfile.instp;
31 dram_arbiter.outp = &dram.commandp;
32 exec.loadp = &dram_arbiter.peerp[1];
33 exec.writebackp = &regfile.writebackp;
34 fetch.bundlep = &decode.bundlep;
35 fetch.commandp = &dram_arbiter.peerp[2];
36 regfile.decode_restartp = &decode.restartp;
37 regfile.execp = &exec.execp;
38 regfile.storep = &dram_arbiter.peerp[0];
39 }
40};
diff --git a/frontend/bundle.h b/frontend/bundle.h
new file mode 100644
index 0000000..cc6f441
--- /dev/null
+++ b/frontend/bundle.h
@@ -0,0 +1,14 @@
1#pragma once
2
3#include "infra/pipetrace.h"
4#include "memory/line.h"
5
6namespace frontend {
7 struct bundle {
8 infra::transaction transaction;
9 unsigned int generation;
10 std::uint64_t line_address;
11 std::uint64_t next_line_address;
12 memory::line data;
13 };
14}
diff --git a/frontend/decode.h b/frontend/decode.h
new file mode 100644
index 0000000..717d0f6
--- /dev/null
+++ b/frontend/decode.h
@@ -0,0 +1,128 @@
1#pragma once
2
3#include "frontend/bundle.h"
4#include "frontend/fetch.h"
5#include "infra/port.h"
6#include "inst.h"
7#include "memory/line.h"
8
9namespace frontend {
10 struct decode : public infra::sim {
11 struct restart {
12 unsigned int new_generation;
13 std::uint64_t new_pc;
14 std::uint64_t from_pc;
15 };
16 infra::port<restart> restartp;
17 infra::port<fetch::restart> *fetch_restartp = nullptr;
18
19 infra::port<bundle> bundlep;
20 infra::port<inst> *instp = nullptr;
21
22 inst next_inst;
23 unsigned int generation_up = 0;
24 unsigned int generation_down = 0;
25 std::uint64_t pc = 0;
26
27 static constexpr unsigned int MAX_INST_SIZE = 64;
28 static constexpr unsigned int BYTES_PER_CYCLE = 4;
29
30 void clock() {
31 if (restartp.can_read()) {
32 auto r = restartp.read();
33 generation_down = r.new_generation;
34 pc = r.new_pc;
35 next_inst.size = 0;
36 for (auto &f : next_inst.field)
37 f = 0;
38 fetch::restart fr;
39 fr.new_generation = ++generation_up;
40 fr.new_next_line_address = pc >> memory::LINE_BYTES_LOG2;
41 fr.previous_line_address = r.from_pc >> memory::LINE_BYTES_LOG2;
42 fetch_restartp->write(std::move(fr));
43 return;
44 }
45 if (next_inst.size >= MAX_INST_SIZE)
46 return;
47 if (bundlep.can_read()) {
48 const auto &b = bundlep.peek();
49 for (unsigned int i = 0; i < BYTES_PER_CYCLE; ++i) {
50 auto line = pc >> memory::LINE_BYTES_LOG2;
51 auto offset = pc & memory::LINE_BYTE_OFFSET_MASK;
52 if (b.generation == generation_up && b.line_address == line && instp->can_write()) {
53 decodebyte byte;
54 std::memcpy(&byte, b.data.data() + offset, sizeof(byte));
55 pte(b.transaction, "d", fmt::format("decode gen={} pc={:x} byte={:02x}", generation_up, pc, *reinterpret_cast<std::uint8_t *>(&byte)));
56 ++next_inst.size;
57 if (byte.invert)
58 next_inst.field[byte.field] = ~next_inst.field[byte.field];
59 next_inst.field[byte.field] = next_inst.field[byte.field] << 4;
60 next_inst.field[byte.field] |= byte.bits;
61 ++pc;
62 if (!byte.hold) {
63 next_inst.transaction = infra::pt::child(b.transaction);
64 next_inst.generation = generation_down;
65 next_inst.linear_next_pc = pc;
66 next_inst.predicted_next_pc = pc;
67 pte(next_inst.transaction, "D", fmt::format("decode gen={}", generation_down));
68 bool jump = false;
69 std::optional<std::uint64_t> target;
70 std::optional<bool> taken;
71 switch (next_inst.field[OPCODE]) {
72 case OP_JUMP_ABS_IF_ZERO:
73 jump = true;
74 if (next_inst.field[FLAGS_DST] & FLAG_IMM1)
75 target = next_inst.field[SRC1];
76 if (next_inst.field[FLAGS_DST] & FLAG_IMM2)
77 taken = next_inst.field[SRC2] == 0;
78 break;
79 case OP_JUMP_ABS_IF_NONZERO:
80 jump = true;
81 if (next_inst.field[FLAGS_DST] & FLAG_IMM1)
82 target = next_inst.field[SRC1];
83 if (next_inst.field[FLAGS_DST] & FLAG_IMM2)
84 taken = next_inst.field[SRC2] != 0;
85 break;
86 }
87 std::optional<std::uint64_t> redirect;
88 bool unpredictable = false;
89 if (jump) {
90 if (target.has_value()) {
91 if (taken.has_value()) {
92 if (taken.value())
93 redirect = target;
94 } else if (target.value() < pc) {
95 redirect = target;
96 }
97 } else if (!taken.has_value() || taken.value()) {
98 unpredictable = true;
99 }
100 }
101 if (redirect.has_value()) {
102 pte(next_inst.transaction, "", fmt::format("fe predicts jump to {:x}", redirect.value()));
103 next_inst.predicted_next_pc = pc = redirect.value();
104 } else if (unpredictable) {
105 pte(next_inst.transaction, "", "frontend halt due to unpredictable jump");
106 next_inst.predicted_next_pc.reset();
107 }
108 instp->write(std::move(next_inst));
109 next_inst.size = unpredictable ? MAX_INST_SIZE : 0;
110 for (auto &f : next_inst.field)
111 f = 0;
112 }
113 }
114 }
115 auto line = pc >> memory::LINE_BYTES_LOG2;
116 if (b.generation == generation_up && b.line_address != line && b.next_line_address != line) {
117 fetch::restart fr;
118 fr.new_generation = ++generation_up;
119 fr.new_next_line_address = pc >> memory::LINE_BYTES_LOG2;
120 fr.previous_line_address = b.line_address;
121 fetch_restartp->write(std::move(fr));
122 }
123 if (b.generation != generation_up || b.line_address != line)
124 bundlep.discard();
125 }
126 }
127 };
128}
diff --git a/frontend/fetch.h b/frontend/fetch.h
new file mode 100644
index 0000000..0eaebd5
--- /dev/null
+++ b/frontend/fetch.h
@@ -0,0 +1,79 @@
1#pragma once
2
3#include <cassert>
4#include <cstdint>
5#include <cstring>
6#include <map>
7#include <optional>
8#include <utility>
9
10#include "frontend/bundle.h"
11#include "infra/pipetrace.h"
12#include "infra/sim.h"
13#include "memory/dram.h"
14
15namespace frontend {
16 struct fetch : public infra::sim {
17 struct restart {
18 unsigned int new_generation;
19 std::uint64_t previous_line_address;
20 std::uint64_t new_next_line_address;
21 };
22 infra::port<restart> restartp;
23
24 infra::port<memory::dram::command> *commandp = nullptr;
25 infra::port<memory::dram::response> responsep;
26
27 infra::port<bundle> *bundlep = nullptr;
28
29 unsigned int generation = 0;
30 std::uint64_t next_line_address = 0;
31
32 // FIXME make prediction table finite
33 std::map<std::uint64_t, std::uint64_t> predictor;
34
35 bool fill_request_sent = false;
36
37 void clock() {
38 if (restartp.can_read()) {
39 auto r = restartp.read();
40 generation = r.new_generation;
41 next_line_address = r.new_next_line_address;
42 fill_request_sent = false;
43 if (r.new_next_line_address == r.previous_line_address || r.new_next_line_address == r.previous_line_address + 1)
44 predictor.erase(r.previous_line_address);
45 else
46 predictor[r.previous_line_address] = r.new_next_line_address;
47 }
48 if (fill_request_sent && responsep.can_read() && bundlep->can_write()) {
49 auto r = responsep.read();
50 if (r.line_address == next_line_address) {
51 bundle b;
52 b.transaction = r.transaction;
53 b.generation = generation;
54 b.line_address = next_line_address;
55 if (auto p = predictor.find(next_line_address); p != predictor.end())
56 b.next_line_address = p->second;
57 else
58 b.next_line_address = next_line_address + 1;
59 next_line_address = b.next_line_address;
60 pte(b.transaction, "", fmt::format("next fetch line {:x}", next_line_address));
61 b.data = std::move(r.data);
62 bundlep->write(std::move(b));
63 fill_request_sent = false;
64 }
65 }
66 if (!fill_request_sent && commandp->can_write()) {
67 memory::dram::command c;
68 c.transaction = infra::pt::toplevel();
69 pte(c.transaction, "F", fmt::format("fetch gen={}", generation));
70 c.line_address = next_line_address;
71 c.responsep = &responsep;
72 commandp->write(std::move(c));
73 fill_request_sent = true;
74 }
75 if (!fill_request_sent)
76 responsep.discard();
77 }
78 };
79}
diff --git a/infra/arbiter.h b/infra/arbiter.h
new file mode 100644
index 0000000..5dd1647
--- /dev/null
+++ b/infra/arbiter.h
@@ -0,0 +1,42 @@
1#pragma once
2
3#include <cassert>
4#include <optional>
5#include <utility>
6
7#include "infra/sim.h"
8
9namespace infra {
10 template<typename T, unsigned int peers> struct priority_arbiter : public sim {
11 std::array<port<T>, peers> peerp;
12 port<T> *outp = nullptr;
13
14 void clock() {
15 for (unsigned int i = 0; i < peers; ++i) {
16 if (outp->can_write() && peerp[i].can_read())
17 outp->write(peerp[i].read());
18 }
19 }
20 };
21
22 template<typename T, unsigned int peers> struct round_robin_arbiter : public sim {
23 std::array<port<T>, peers> peerp;
24 port<T> *outp = nullptr;
25 unsigned int initial = 0;
26
27 void clock() {
28 bool initially_empty = outp->can_write();
29 for (unsigned int i = initial; i < peers; ++i) {
30 if (outp->can_write() && peerp[i].can_read())
31 outp->write(peerp[i].read());
32 }
33 for (unsigned int i = 0; i < initial; ++i) {
34 if (outp->can_write() && peerp[i].can_read())
35 outp->write(peerp[i].read());
36 }
37 if (initially_empty && !outp->can_write())
38 if (++initial == peers)
39 initial = 0;
40 }
41 };
42}
diff --git a/infra/pipetrace.cpp b/infra/pipetrace.cpp
new file mode 100644
index 0000000..e6642ef
--- /dev/null
+++ b/infra/pipetrace.cpp
@@ -0,0 +1,9 @@
1#include <cstdint>
2#include <ostream>
3
4#include "infra/pipetrace.h"
5
6namespace infra {
7 std::ostream *pt::ptfile = nullptr;
8 std::uint64_t pt::next_record = 0;
9}
diff --git a/infra/pipetrace.h b/infra/pipetrace.h
new file mode 100644
index 0000000..656b9b9
--- /dev/null
+++ b/infra/pipetrace.h
@@ -0,0 +1,37 @@
1#pragma once
2
3#include <cstdint>
4#include <fmt/format.h>
5#include <ostream>
6#include <string>
7
8namespace infra {
9 struct transaction {
10 std::uint64_t record = ~(std::uint64_t)0;
11 };
12
13 struct pt {
14 static std::ostream *ptfile;
15
16 static std::uint64_t next_record;
17
18 static transaction toplevel() {
19 transaction t;
20 t.record = next_record++;
21 return t;
22 }
23
24 static transaction child(const transaction &p) {
25 transaction t;
26 t.record = next_record++;
27 if (ptfile)
28 *ptfile << fmt::format("{} parent {}\n", t.record, p.record);
29 return t;
30 }
31
32 static void event(const transaction &t, const char *event, std::uint64_t time, const std::string &data) {
33 if (ptfile)
34 *ptfile << fmt::format("@{} {} {} {}\n", time, t.record, event, data);
35 }
36 };
37}
diff --git a/infra/port.h b/infra/port.h
new file mode 100644
index 0000000..06a3aa5
--- /dev/null
+++ b/infra/port.h
@@ -0,0 +1,45 @@
1#pragma once
2
3#include <cassert>
4#include <optional>
5#include <utility>
6
7#include "infra/sim.h"
8
9namespace infra {
10 template<typename T> struct port : public sim {
11 std::optional<T> consumer_side;
12 std::optional<T> producer_side;
13
14 bool can_read() { return consumer_side.has_value(); }
15 bool can_write() { return !producer_side.has_value(); }
16
17 T read() {
18 assert(can_read());
19 auto x = std::move(*consumer_side);
20 consumer_side.reset();
21 return x;
22 }
23
24 const T & peek() {
25 assert(can_read());
26 return *consumer_side;
27 }
28
29 void discard() {
30 consumer_side.reset();
31 }
32
33 void write(T &&x) {
34 assert(can_write());
35 producer_side = std::move(x);
36 }
37
38 void unclock() {
39 if (!consumer_side && producer_side) {
40 consumer_side = std::move(*producer_side);
41 producer_side.reset();
42 }
43 }
44 };
45}
diff --git a/infra/queue.h b/infra/queue.h
new file mode 100644
index 0000000..1e490bc
--- /dev/null
+++ b/infra/queue.h
@@ -0,0 +1,29 @@
1#pragma once
2
3#include <cassert>
4#include <deque>
5#include <optional>
6#include <utility>
7
8#include "infra/port.h"
9#include "infra/sim.h"
10
11namespace infra {
12 template<typename T, unsigned int size> struct queue : public sim {
13 port<T> input;
14 port<T> *output = nullptr;
15 std::deque<T> elements;
16
17 void clock() {
18 if (input.can_read() && elements.size() < size) {
19 auto x = input.read();
20 elements.emplace_back(std::move(x));
21 }
22 if (output->can_write() && !elements.empty()) {
23 auto &x = elements.front();
24 output->write(std::move(x));
25 elements.pop_front();
26 }
27 }
28 };
29}
diff --git a/infra/sim.cpp b/infra/sim.cpp
new file mode 100644
index 0000000..21acc8c
--- /dev/null
+++ b/infra/sim.cpp
@@ -0,0 +1,9 @@
1#include <cstdint>
2#include <vector>
3
4#include "infra/sim.h"
5
6namespace infra {
7 std::vector<sim *> sim::sims;
8 std::uint64_t sim::now = 0;
9}
diff --git a/infra/sim.h b/infra/sim.h
new file mode 100644
index 0000000..185916a
--- /dev/null
+++ b/infra/sim.h
@@ -0,0 +1,38 @@
1#pragma once
2
3#include <cstdint>
4#include <string>
5#include <vector>
6
7#include "infra/pipetrace.h"
8
9namespace infra {
10 struct sim {
11 virtual void clock() {}
12 virtual void unclock() {}
13
14 static std::vector<sim *> sims;
15
16 static std::uint64_t now;
17
18 sim() {
19 sims.emplace_back(this);
20 }
21
22 virtual ~sim() {
23 std::erase(sims, this);
24 }
25
26 static void advance() {
27 for (auto &s : sims)
28 s->clock();
29 for (auto &s : sims)
30 s->unclock();
31 ++now;
32 }
33
34 void pte(const transaction &t, const char *event, const std::string &data) {
35 pt::event(t, event, now, data);
36 }
37 };
38}
diff --git a/inst.h b/inst.h
new file mode 100644
index 0000000..15ae9d7
--- /dev/null
+++ b/inst.h
@@ -0,0 +1,56 @@
1#pragma once
2
3#include <cstdint>
4
5#include "infra/pipetrace.h"
6
7struct inst {
8 infra::transaction transaction;
9 unsigned int generation;
10 unsigned int size = 0;
11 std::uint64_t linear_next_pc;
12 std::optional<std::uint64_t> predicted_next_pc;
13 std::uint64_t field[4] = {};
14 std::optional<std::uint64_t> result;
15};
16
17constexpr unsigned int OPCODE = 0;
18constexpr unsigned int FLAGS_DST = 1;
19constexpr unsigned int SRC1 = 2;
20constexpr unsigned int SRC2 = 3;
21
22constexpr std::uint64_t FLAG_IMM1 = 1 << 4;
23constexpr std::uint64_t FLAG_IMM2 = 1 << 5;
24
25enum {
26 OP_JUMP_ABS_IF_ZERO,
27 OP_JUMP_ABS_IF_NONZERO,
28 OP_EMIT,
29 OP_ADD,
30 OP_LOAD,
31 OP_STORE,
32};
33
34struct decodebyte {
35 std::uint8_t bits:4;
36 std::uint8_t field:2;
37 std::uint8_t invert:1;
38 std::uint8_t hold:1;
39} __attribute__((packed));
40
41// 0x - shift opcode and issue
42// 1x - shift flagsdst and issue
43// 2x - shift src1 and issue
44// 3x - shift src2 and issue
45// 4x - invert+shift opcode and issue
46// 5x - invert+shift flagsdst and issue
47// 6x - invert+shift src1 and issue
48// 7x - invert+shift src2 and issue
49// 8x - shift opcode
50// 9x - shift flagsdst
51// Ax - shift src1
52// Bx - shift src2
53// Cx - invert+shift opcode
54// Dx - invert+shift flagsdst
55// Ex - invert+shift src1
56// Fx - invert+shift src2
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..02796fd
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,22 @@
1#include <fstream>
2#include <iostream>
3
4#include "cpu.h"
5#include "infra/pipetrace.h"
6#include "infra/sim.h"
7
8int main(int argc, const char *argv[]) {
9 infra::pt::ptfile = &std::cout;
10
11 cpu cpu;
12
13 for (int i = 1; i < argc; ++i) {
14 std::ifstream fh{argv[i]};
15 cpu.dram.load(fh);
16 }
17
18 for (unsigned int i = 0; i < 1000; ++i)
19 infra::sim::advance();
20
21 return 0;
22}
diff --git a/memory/dram.h b/memory/dram.h
new file mode 100644
index 0000000..f59c7a6
--- /dev/null
+++ b/memory/dram.h
@@ -0,0 +1,96 @@
1#pragma once
2
3#include <array>
4#include <cstdint>
5#include <cstring>
6#include <istream>
7#include <map>
8#include <string>
9#include <utility>
10
11#include "memory/line.h"
12
13namespace memory {
14 struct dram : public infra::sim {
15 static constexpr std::uint64_t PAGE_LINES_LOG2 = 20 - LINE_BYTES_LOG2;
16 static constexpr std::uint64_t PAGE_LINES = 1 << PAGE_LINES_LOG2;
17 static constexpr std::uint64_t PAGE_LINE_OFFSET_MASK = PAGE_LINES - 1;
18 static constexpr std::uint64_t PAGE_BYTES_LOG2 = PAGE_LINES_LOG2 + LINE_BYTES_LOG2;
19 static constexpr std::uint64_t PAGE_BYTES = 1 << PAGE_BYTES_LOG2;
20 static constexpr std::uint64_t PAGE_BYTE_OFFSET_MASK = PAGE_BYTES - 1;
21
22 typedef std::array<line, PAGE_LINES> page;
23
24 std::map<std::uint64_t, page> image;
25
26 struct response {
27 infra::transaction transaction;
28 std::uint64_t line_address;
29 line data;
30 };
31
32 struct command {
33 infra::transaction transaction;
34 std::uint64_t line_address;
35 line data;
36 std::array<bool, LINE_BYTES> mask;
37 bool write = false;
38 infra::port<response> *responsep = nullptr;
39 };
40
41 infra::port<command> commandp;
42
43 void clock() {
44 if (commandp.can_read()) {
45 const auto &c = commandp.peek();
46 if (!c.responsep || c.responsep->can_write()) {
47 auto page_address = c.line_address >> PAGE_LINES_LOG2;
48 auto page_line = c.line_address & PAGE_LINE_OFFSET_MASK;
49 if (c.write) {
50 pte(c.transaction, "s", fmt::format("store {:x}-{:x}", page_address, page_line));
51 if (c.responsep) {
52 response r;
53 r.transaction = c.transaction;
54 r.line_address = c.line_address;
55 r.data = c.data;
56 c.responsep->write(std::move(r));
57 }
58 auto [p, emplaced] = image.try_emplace(page_address);
59 if (emplaced)
60 for (unsigned int i = 0; i < PAGE_LINES; ++i)
61 p->second[i].fill(0);
62 auto &l = p->second[page_line];
63 for (unsigned int i = 0; i < LINE_BYTES; ++i)
64 if (c.mask[i])
65 l[i] = c.data[i];
66 } else {
67 pte(c.transaction, "f", fmt::format("fill {:x}-{:x}", page_address, page_line));
68 if (c.responsep) {
69 response r;
70 r.transaction = c.transaction;
71 r.line_address = c.line_address;
72 if (auto p = image.find(page_address); p != image.end())
73 r.data = p->second[page_line];
74 else
75 r.data.fill(0);
76 c.responsep->write(std::move(r));
77 }
78 }
79 commandp.discard();
80 }
81 }
82 }
83
84 void load(std::istream &fh) {
85 for (unsigned int page = 0; ; ++page) {
86 auto [p, emplaced] = image.try_emplace(page);
87 if (emplaced)
88 for (unsigned int i = 0; i < PAGE_LINES; ++i)
89 p->second[i].fill(0);
90 for (unsigned int line = 0; line < PAGE_LINES; ++line)
91 if (!fh.read(reinterpret_cast<char *>(p->second[line].data()), LINE_BYTES))
92 return;
93 }
94 }
95 };
96}
diff --git a/memory/line.h b/memory/line.h
new file mode 100644
index 0000000..3377ec8
--- /dev/null
+++ b/memory/line.h
@@ -0,0 +1,15 @@
1#pragma once
2
3#include <array>
4#include <cstdint>
5
6#include "infra/port.h"
7#include "infra/sim.h"
8
9namespace memory {
10 constexpr std::uint64_t LINE_BYTES_LOG2 = 4;
11 constexpr std::uint64_t LINE_BYTES = 1 << LINE_BYTES_LOG2;
12 constexpr std::uint64_t LINE_BYTE_OFFSET_MASK = LINE_BYTES - 1;
13
14 typedef std::array<std::uint8_t, LINE_BYTES> line;
15}
diff --git a/procmodel b/procmodel
new file mode 120000
index 0000000..75ad7a3
--- /dev/null
+++ b/procmodel
@@ -0,0 +1 @@
build/procmodel \ No newline at end of file
diff --git a/pt b/pt
new file mode 100755
index 0000000..6797565
--- /dev/null
+++ b/pt
@@ -0,0 +1,95 @@
1#!/usr/bin/ruby
2
3$filter = ARGV
4
5$parents = {}
6$events = {}
7$has = {}
8$data = {}
9$horiz = {}
10$maxtime = -1
11
12$stdin.each_line do | line |
13 case line
14
15 when /^(\d+) parent (\d+)$/
16 child = $1.to_i
17 parent = $2.to_i
18 $parents[child] = parent
19
20 when /^@(\d+) (\d+) (\S*) (.*)$/
21 time = $1.to_i
22 rec = $2.to_i
23 event = $3
24 data = $4
25 if event.size > 0
26 $events[rec] ||= {}
27 $events[rec][time] = event
28 $has[rec] ||= {}
29 $has[rec][event] = true
30 $horiz[event] ||= ""
31 $horiz[event] = $horiz[event].ljust(time)
32 $horiz[event][time] = event
33 end
34 if data.size > 0
35 $data[rec] ||= ""
36 $data[rec] += " #{event}@#{time}:" if event.size > 0
37 $data[rec] += " #{data}"
38 end
39 $maxtime = [$maxtime, time+1].max
40
41 else
42 raise "Unexpected line: #{line}"
43 end
44end
45
46$hier = {}
47$hier_direct = {}
48
49$events.each_key do | rec |
50 subhier = {}
51 $hier_direct[rec] = subhier
52 if $parents.key?(rec)
53 $hier_direct[$parents[rec]][rec] = subhier
54 else
55 $hier[rec] = subhier
56 end
57end
58
59$order = []
60
61def flatten(hier)
62 hier.each do | rec, subhier |
63 $order << rec
64 flatten(subhier)
65 end
66end
67flatten($hier)
68
69rwidth = $order.map { | x | x.to_s.size }.max
70
71$horiz.keys.sort.each do | occ |
72 $stdout.write(" " * rwidth + " #{$horiz[occ].ljust($maxtime)}")
73 count = $horiz[occ].delete(" ").size
74 $stdout.write(" #{($maxtime.to_f / count.to_f).round(2).to_s.rjust(5)} cyc/evt\n")
75end
76
77mwidth = 0
78
79$order.each do | rec |
80 estr = ""
81 filter_match = $filter.empty?
82 $has[rec].each_key do | event |
83 filter_match ||= $filter.include?(event)
84 end
85 next unless filter_match
86 $events[rec].keys.sort.each do | time |
87 estr = estr.ljust(time + 1, estr.size == 0 ? " " : "-")
88 estr[time] = $events[rec][time] if $events[rec][time].size > 0
89 end
90 estr += " " * 5
91 estr = estr.ljust(mwidth - 1)
92 estr = estr.ljust(estr.size + 20 - estr.size % 20)
93 mwidth = [mwidth, estr.size].max
94 $stdout.write(rec.to_s.rjust(rwidth) + ": #{estr}#{$data[rec]}\n")
95end
diff --git a/test b/test
new file mode 100755
index 0000000..d70c720
--- /dev/null
+++ b/test
@@ -0,0 +1,58 @@
1#!/bin/bash
2
3set -eu
4
5FILTER=()
6
7while [[ $# != 0 ]]; do
8 if [[ $1 == "-h" ]]; then
9 cat <<END
10Usage: ./test [options ...] [tests ...]
11
12Options:
13 -h This help message
14 -e Show emit rows
15 -f Show fetch rows
16 -i Show instruction rows
17 -m Show rows with memory traffic
18 -r Show rows with writeback (retire)
19
20For the filtering options: if one or more filters are set, then rows which
21match any filter are shown. If no filter is set, then all rows are shown.
22END
23 exit
24 elif [[ $1 == "-e" ]]; then
25 shift
26 FILTER+=("*")
27 elif [[ $1 == "-f" ]]; then
28 shift
29 FILTER+=("F")
30 elif [[ $1 == "-i" ]]; then
31 shift
32 FILTER+=("D")
33 elif [[ $1 == "-m" ]]; then
34 shift
35 FILTER+=("f" "s")
36 elif [[ $1 == "-r" ]]; then
37 shift
38 FILTER+=("W")
39 else
40 break
41 fi
42done
43
44if [[ $# == 0 ]]; then
45 for TEST in $(ls -1 tests); do
46 set -- "$@" "${TEST%.hex}"
47 done
48fi
49
50make
51
52for TEST in "$@"; do
53 make "build/tests/$TEST.bin"
54 (
55 echo "$TEST"
56 ./procmodel "build/tests/$TEST.bin" | ./pt "${FILTER[@]}"
57 ) | less -S
58done
diff --git a/tests/countdown.hex b/tests/countdown.hex
new file mode 100644
index 0000000..1185774
--- /dev/null
+++ b/tests/countdown.hex
@@ -0,0 +1,5 @@
193 90 A3 03
292 90 02
392 90 FF 03
491 90 00
593 90 A4 00
diff --git a/tests/fib-mem.hex b/tests/fib-mem.hex
new file mode 100644
index 0000000..521301b
--- /dev/null
+++ b/tests/fib-mem.hex
@@ -0,0 +1,9 @@
193 90 A1 A0 A0 A8 B1 05
293 90 A1 A0 A1 A0 03
392 91 F0 04
492 90 A1 02
592 92 F8 04
691 A1 B2 03
7B1 05
892 90 B8 03
993 90 AF 00
diff --git a/tests/fib-regs.hex b/tests/fib-regs.hex
new file mode 100644
index 0000000..0f8c5f8
--- /dev/null
+++ b/tests/fib-regs.hex
@@ -0,0 +1,6 @@
193 91 A1 03
292 90 02
392 B1 03
492 90 A1 03
592 91 A2 03
693 90 A4 00
diff --git a/tests/loop-with-add.hex b/tests/loop-with-add.hex
new file mode 100644
index 0000000..7c93780
--- /dev/null
+++ b/tests/loop-with-add.hex
@@ -0,0 +1,2 @@
103
291 90 00
diff --git a/tests/loop.hex b/tests/loop.hex
new file mode 100644
index 0000000..29fe69d
--- /dev/null
+++ b/tests/loop.hex
@@ -0,0 +1 @@
93 90 00