From eb3fd68203fee7c63245c702914c2acd3332d65a Mon Sep 17 00:00:00 2001 From: Julian Blake Kongslie Date: Thu, 22 Sep 2022 11:29:07 -0700 Subject: Initial commit. --- .gitignore | 1 + Makefile | 50 ++++++++++++++++++ backend/exec.h | 72 ++++++++++++++++++++++++++ backend/regfile.h | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ cpu.h | 40 +++++++++++++++ frontend/bundle.h | 14 +++++ frontend/decode.h | 128 ++++++++++++++++++++++++++++++++++++++++++++++ frontend/fetch.h | 79 +++++++++++++++++++++++++++++ infra/arbiter.h | 42 +++++++++++++++ infra/pipetrace.cpp | 9 ++++ infra/pipetrace.h | 37 ++++++++++++++ infra/port.h | 45 +++++++++++++++++ infra/queue.h | 29 +++++++++++ infra/sim.cpp | 9 ++++ infra/sim.h | 38 ++++++++++++++ inst.h | 56 ++++++++++++++++++++ main.cpp | 22 ++++++++ memory/dram.h | 96 +++++++++++++++++++++++++++++++++++ memory/line.h | 15 ++++++ procmodel | 1 + pt | 95 ++++++++++++++++++++++++++++++++++ test | 58 +++++++++++++++++++++ tests/countdown.hex | 5 ++ tests/fib-mem.hex | 9 ++++ tests/fib-regs.hex | 6 +++ tests/loop-with-add.hex | 2 + tests/loop.hex | 1 + 27 files changed, 1091 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 backend/exec.h create mode 100644 backend/regfile.h create mode 100644 cpu.h create mode 100644 frontend/bundle.h create mode 100644 frontend/decode.h create mode 100644 frontend/fetch.h create mode 100644 infra/arbiter.h create mode 100644 infra/pipetrace.cpp create mode 100644 infra/pipetrace.h create mode 100644 infra/port.h create mode 100644 infra/queue.h create mode 100644 infra/sim.cpp create mode 100644 infra/sim.h create mode 100644 inst.h create mode 100644 main.cpp create mode 100644 memory/dram.h create mode 100644 memory/line.h create mode 120000 procmodel create mode 100755 pt create mode 100755 test create mode 100644 tests/countdown.hex create mode 100644 tests/fib-mem.hex create mode 100644 tests/fib-regs.hex create mode 100644 tests/loop-with-add.hex create mode 100644 tests/loop.hex 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 @@ +BUILD := build + +override PACKAGES := fmt + +WARNINGS := -Wall -Werror +OPTIMIZE := -O3 -flto +DEBUG := -g + +CXX := g++ +CXXFLAGS := $(WARNINGS) $(OPTIMIZE) $(DEBUG) + +XXD := xxd + +override CXXFLAGS += -std=c++20 + +override COMPILE_FLAGS := -MMD -MP -I. +override LINK_FLAGS := + +ifneq "$(strip $(PACKAGES))" "" + override COMPILE_FLAGS += $(shell pkg-config --cflags $(PACKAGES)) + override LINK_FLAGS += -Wl,--start-group $(shell pkg-config --libs $(PACKAGES)) -Wl,--end-group +endif + +default: $(BUILD)/procmodel +.PHONY: default + +clean: + rm -rf $(BUILD) +.PHONY: clean + +.SUFFIXES: + +override SOURCES := $(shell find -\( -name build -prune -\) -o -\( -name \*.cpp -print -\)) + +override OBJECTS := $(addprefix $(BUILD)/, $(addsuffix .o, $(basename $(SOURCES)))) +override DEPENDS := $(addprefix $(BUILD)/, $(addsuffix .d, $(basename $(SOURCES)))) + +-include $(DEPENDS) + +$(BUILD)/%.o: %.cpp + @mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) $(COMPILE_FLAGS) -c -o $@ $< + +$(BUILD)/procmodel: $(OBJECTS) + @mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) -o $@ -Wl,--start-group $+ -Wl,--end-group $(LINK_FLAGS) + +$(BUILD)/%.bin: %.hex + @mkdir -p $(dir $@) + $(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 @@ +#pragma once + +#include "infra/port.h" +#include "inst.h" +#include "memory/dram.h" + +namespace backend { + struct exec : public infra::sim { + infra::port execp; + infra::port *writebackp = nullptr; + infra::port *loadp = nullptr; + infra::port loadresultp; + + infra::port stallp; + + void clock() { + if (stallp.can_read() && writebackp->can_write()) { + const auto &i = stallp.peek(); + switch (i.field[OPCODE]) { + case OP_LOAD: + if (loadresultp.can_read()) { + auto i = stallp.read(); + auto addr = i.field[SRC1] + i.field[SRC2]; + auto offset = addr & memory::LINE_BYTE_OFFSET_MASK; + pte(i.transaction, "", fmt::format("addr={:x} offset={:x}", addr, offset)); + auto f = loadresultp.read(); + std::uint64_t r = 0; + for (unsigned int i = 0; i < sizeof(r); ++i) + r |= f.data[i + offset] << (8 * i); + i.result = r; + writebackp->write(std::move(i)); + } + break; + } + } else if (execp.can_read() && writebackp->can_write() && loadp->can_write()) { + auto i = execp.read(); + 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])); + switch (i.field[OPCODE]) { + case OP_JUMP_ABS_IF_ZERO: + if (i.field[SRC2] == 0) + i.result = i.field[SRC1]; + else + i.result = i.linear_next_pc; + break; + case OP_JUMP_ABS_IF_NONZERO: + if (i.field[SRC2] != 0) + i.result = i.field[SRC1]; + else + i.result = i.linear_next_pc; + break; + case OP_EMIT: + case OP_ADD: + i.result = i.field[SRC1] + i.field[SRC2]; + break; + case OP_LOAD: + { + memory::dram::command c; + c.transaction = i.transaction; + c.line_address = (i.field[SRC1] + i.field[SRC2]) >> memory::LINE_BYTES_LOG2; + c.write = false; + c.responsep = &loadresultp; + loadp->write(std::move(c)); + } + stallp.write(std::move(i)); + break; + } + if (stallp.can_write()) + writebackp->write(std::move(i)); + } + } + }; +} 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 @@ +#pragma once + +#include + +#include "frontend/decode.h" +#include "infra/port.h" +#include "inst.h" +#include "memory/dram.h" + +namespace backend { + struct regfile : public infra::sim { + infra::port *decode_restartp = nullptr; + + infra::port instp; + infra::port *execp = nullptr; + infra::port writebackp; + infra::port *storep = nullptr; + + unsigned int generation_up = 0; + unsigned int generation_down = 0; + + std::array regs; + std::array hazards; + std::uint64_t pc = 0; + + regfile() { + regs.fill(0); + hazards.fill(false); + } + + void clock() { + if (writebackp.can_read() && storep->can_write()) { + auto i = writebackp.read(); + if (i.generation == generation_down) { + pte(i.transaction, "W", fmt::format("writeback gen={} pc={:x}", generation_down, pc)); + auto old_pc = pc; + pc = i.linear_next_pc; + switch (i.field[OPCODE]) { + case OP_JUMP_ABS_IF_ZERO: + case OP_JUMP_ABS_IF_NONZERO: + pte(i.transaction, "", fmt::format("jump to {:x}", i.result.value())); + pc = i.result.value(); + break; + case OP_EMIT: + pte(i.transaction, "*", fmt::format("emit {}", i.result.value())); + break; + case OP_STORE: + { + memory::dram::command c; + c.transaction = i.transaction; + c.line_address = i.field[SRC1] >> memory::LINE_BYTES_LOG2; + c.write = true; + c.mask.fill(false); + auto offset = i.field[SRC1] & memory::LINE_BYTE_OFFSET_MASK; + pte(i.transaction, "", fmt::format("store [{:x}]={:x} offset={:x}", i.field[SRC1], i.field[SRC2], offset)); + for (unsigned int j = 0; j < sizeof(i.field[SRC2]); ++j) { + c.mask[offset + j] = true; + c.data[offset + j] = (i.field[SRC2] >> (8 * j)) & 0xff; + } + storep->write(std::move(c)); + } + break; + default: + pte(i.transaction, "", fmt::format("wb r{}={:x}", i.field[FLAGS_DST] % regs.size(), i.result.value())); + regs[i.field[FLAGS_DST] % regs.size()] = i.result.value(); + hazards[i.field[FLAGS_DST] % regs.size()] = false; + break; + } + if (!i.predicted_next_pc.has_value() || pc != i.predicted_next_pc.value()) { + pte(i.transaction, "", "restart due to pc misprediction"); + frontend::decode::restart dr; + dr.new_generation = ++generation_up; + dr.new_pc = pc; + dr.from_pc = old_pc; + decode_restartp->write(std::move(dr)); + hazards.fill(false); + ++generation_down; + } + } + } + + if (instp.can_read() && execp->can_write() && !writebackp.can_read()) { + auto i = instp.peek(); + if (i.generation == generation_up) { + bool hazard = false; + if (!(i.field[FLAGS_DST] & FLAG_IMM1)) + hazard |= hazards[i.field[SRC1] % regs.size()]; + if (!(i.field[FLAGS_DST] & FLAG_IMM2)) + hazard |= hazards[i.field[SRC2] % regs.size()]; + switch (i.field[OPCODE]) { + case OP_JUMP_ABS_IF_ZERO: + case OP_JUMP_ABS_IF_NONZERO: + case OP_EMIT: + case OP_STORE: + break; + default: + hazard |= hazards[i.field[FLAGS_DST] % regs.size()]; + break; + } + if (!hazard) { + auto i = instp.read(); + if (!(i.field[FLAGS_DST] & FLAG_IMM1)) { + auto x = regs[i.field[SRC1] % regs.size()]; + pte(i.transaction, "", fmt::format("rf1[{}]={:x}", i.field[SRC1] % regs.size(), x)); + i.field[SRC1] = x; + } + if (!(i.field[FLAGS_DST] & FLAG_IMM2)) { + auto x = regs[i.field[SRC2] % regs.size()]; + pte(i.transaction, "", fmt::format("rf2[{}]={:x}", i.field[SRC2] % regs.size(), x)); + i.field[SRC2] = x; + } + pte(i.transaction, "R", fmt::format("read gen={}", generation_up)); + i.generation = generation_down; + switch (i.field[OPCODE]) { + case OP_JUMP_ABS_IF_ZERO: + case OP_JUMP_ABS_IF_NONZERO: + case OP_EMIT: + case OP_STORE: + break; + default: + hazards[i.field[FLAGS_DST] % regs.size()] = true; + break; + } + execp->write(std::move(i)); + } + } else { + instp.discard(); + } + } + } + }; +} diff --git a/cpu.h b/cpu.h new file mode 100644 index 0000000..5ef45a0 --- /dev/null +++ b/cpu.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include "backend/exec.h" +#include "backend/regfile.h" +#include "frontend/decode.h" +#include "frontend/fetch.h" +#include "infra/arbiter.h" +#include "infra/pipetrace.h" +#include "infra/queue.h" +#include "infra/sim.h" +#include "inst.h" +#include "memory/dram.h" + +struct cpu { + backend::exec exec; + backend::regfile regfile; + frontend::decode decode; + frontend::fetch fetch; + infra::priority_arbiter dram_arbiter; + infra::queue decodeq; + memory::dram dram; + + cpu() { + decode.fetch_restartp = &fetch.restartp; + decode.instp = &decodeq.input; + decodeq.output = ®file.instp; + dram_arbiter.outp = &dram.commandp; + exec.loadp = &dram_arbiter.peerp[1]; + exec.writebackp = ®file.writebackp; + fetch.bundlep = &decode.bundlep; + fetch.commandp = &dram_arbiter.peerp[2]; + regfile.decode_restartp = &decode.restartp; + regfile.execp = &exec.execp; + regfile.storep = &dram_arbiter.peerp[0]; + } +}; 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 @@ +#pragma once + +#include "infra/pipetrace.h" +#include "memory/line.h" + +namespace frontend { + struct bundle { + infra::transaction transaction; + unsigned int generation; + std::uint64_t line_address; + std::uint64_t next_line_address; + memory::line data; + }; +} 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 @@ +#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; + + 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(&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()) { + 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(); + } + } + }; +} 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 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "frontend/bundle.h" +#include "infra/pipetrace.h" +#include "infra/sim.h" +#include "memory/dram.h" + +namespace frontend { + struct fetch : public infra::sim { + struct restart { + unsigned int new_generation; + std::uint64_t previous_line_address; + std::uint64_t new_next_line_address; + }; + infra::port restartp; + + infra::port *commandp = nullptr; + infra::port responsep; + + infra::port *bundlep = nullptr; + + unsigned int generation = 0; + std::uint64_t next_line_address = 0; + + // FIXME make prediction table finite + std::map predictor; + + bool fill_request_sent = false; + + void clock() { + if (restartp.can_read()) { + auto r = restartp.read(); + generation = r.new_generation; + next_line_address = r.new_next_line_address; + fill_request_sent = false; + if (r.new_next_line_address == r.previous_line_address || r.new_next_line_address == r.previous_line_address + 1) + predictor.erase(r.previous_line_address); + else + predictor[r.previous_line_address] = r.new_next_line_address; + } + if (fill_request_sent && responsep.can_read() && bundlep->can_write()) { + auto r = responsep.read(); + if (r.line_address == next_line_address) { + bundle b; + b.transaction = r.transaction; + b.generation = generation; + b.line_address = next_line_address; + if (auto p = predictor.find(next_line_address); p != predictor.end()) + b.next_line_address = p->second; + else + b.next_line_address = next_line_address + 1; + next_line_address = b.next_line_address; + pte(b.transaction, "", fmt::format("next fetch line {:x}", next_line_address)); + b.data = std::move(r.data); + bundlep->write(std::move(b)); + fill_request_sent = false; + } + } + if (!fill_request_sent && commandp->can_write()) { + memory::dram::command c; + c.transaction = infra::pt::toplevel(); + pte(c.transaction, "F", fmt::format("fetch gen={}", generation)); + c.line_address = next_line_address; + c.responsep = &responsep; + commandp->write(std::move(c)); + fill_request_sent = true; + } + if (!fill_request_sent) + responsep.discard(); + } + }; +} 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 @@ +#pragma once + +#include +#include +#include + +#include "infra/sim.h" + +namespace infra { + template struct priority_arbiter : public sim { + std::array, peers> peerp; + port *outp = nullptr; + + void clock() { + for (unsigned int i = 0; i < peers; ++i) { + if (outp->can_write() && peerp[i].can_read()) + outp->write(peerp[i].read()); + } + } + }; + + template struct round_robin_arbiter : public sim { + std::array, peers> peerp; + port *outp = nullptr; + unsigned int initial = 0; + + void clock() { + bool initially_empty = outp->can_write(); + for (unsigned int i = initial; i < peers; ++i) { + if (outp->can_write() && peerp[i].can_read()) + outp->write(peerp[i].read()); + } + for (unsigned int i = 0; i < initial; ++i) { + if (outp->can_write() && peerp[i].can_read()) + outp->write(peerp[i].read()); + } + if (initially_empty && !outp->can_write()) + if (++initial == peers) + initial = 0; + } + }; +} 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 @@ +#include +#include + +#include "infra/pipetrace.h" + +namespace infra { + std::ostream *pt::ptfile = nullptr; + std::uint64_t pt::next_record = 0; +} 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 @@ +#pragma once + +#include +#include +#include +#include + +namespace infra { + struct transaction { + std::uint64_t record = ~(std::uint64_t)0; + }; + + struct pt { + static std::ostream *ptfile; + + static std::uint64_t next_record; + + static transaction toplevel() { + transaction t; + t.record = next_record++; + return t; + } + + static transaction child(const transaction &p) { + transaction t; + t.record = next_record++; + if (ptfile) + *ptfile << fmt::format("{} parent {}\n", t.record, p.record); + return t; + } + + static void event(const transaction &t, const char *event, std::uint64_t time, const std::string &data) { + if (ptfile) + *ptfile << fmt::format("@{} {} {} {}\n", time, t.record, event, data); + } + }; +} 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 @@ +#pragma once + +#include +#include +#include + +#include "infra/sim.h" + +namespace infra { + template struct port : public sim { + std::optional consumer_side; + std::optional producer_side; + + bool can_read() { return consumer_side.has_value(); } + bool can_write() { return !producer_side.has_value(); } + + T read() { + assert(can_read()); + auto x = std::move(*consumer_side); + consumer_side.reset(); + return x; + } + + const T & peek() { + assert(can_read()); + return *consumer_side; + } + + void discard() { + consumer_side.reset(); + } + + void write(T &&x) { + assert(can_write()); + producer_side = std::move(x); + } + + void unclock() { + if (!consumer_side && producer_side) { + consumer_side = std::move(*producer_side); + producer_side.reset(); + } + } + }; +} 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 @@ +#pragma once + +#include +#include +#include +#include + +#include "infra/port.h" +#include "infra/sim.h" + +namespace infra { + template struct queue : public sim { + port input; + port *output = nullptr; + std::deque elements; + + void clock() { + if (input.can_read() && elements.size() < size) { + auto x = input.read(); + elements.emplace_back(std::move(x)); + } + if (output->can_write() && !elements.empty()) { + auto &x = elements.front(); + output->write(std::move(x)); + elements.pop_front(); + } + } + }; +} 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 @@ +#include +#include + +#include "infra/sim.h" + +namespace infra { + std::vector sim::sims; + std::uint64_t sim::now = 0; +} 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 @@ +#pragma once + +#include +#include +#include + +#include "infra/pipetrace.h" + +namespace infra { + struct sim { + virtual void clock() {} + virtual void unclock() {} + + static std::vector sims; + + static std::uint64_t now; + + sim() { + sims.emplace_back(this); + } + + virtual ~sim() { + std::erase(sims, this); + } + + static void advance() { + for (auto &s : sims) + s->clock(); + for (auto &s : sims) + s->unclock(); + ++now; + } + + void pte(const transaction &t, const char *event, const std::string &data) { + pt::event(t, event, now, data); + } + }; +} diff --git a/inst.h b/inst.h new file mode 100644 index 0000000..15ae9d7 --- /dev/null +++ b/inst.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include "infra/pipetrace.h" + +struct inst { + infra::transaction transaction; + unsigned int generation; + unsigned int size = 0; + std::uint64_t linear_next_pc; + std::optional predicted_next_pc; + std::uint64_t field[4] = {}; + std::optional result; +}; + +constexpr unsigned int OPCODE = 0; +constexpr unsigned int FLAGS_DST = 1; +constexpr unsigned int SRC1 = 2; +constexpr unsigned int SRC2 = 3; + +constexpr std::uint64_t FLAG_IMM1 = 1 << 4; +constexpr std::uint64_t FLAG_IMM2 = 1 << 5; + +enum { + OP_JUMP_ABS_IF_ZERO, + OP_JUMP_ABS_IF_NONZERO, + OP_EMIT, + OP_ADD, + OP_LOAD, + OP_STORE, +}; + +struct decodebyte { + std::uint8_t bits:4; + std::uint8_t field:2; + std::uint8_t invert:1; + std::uint8_t hold:1; +} __attribute__((packed)); + +// 0x - shift opcode and issue +// 1x - shift flagsdst and issue +// 2x - shift src1 and issue +// 3x - shift src2 and issue +// 4x - invert+shift opcode and issue +// 5x - invert+shift flagsdst and issue +// 6x - invert+shift src1 and issue +// 7x - invert+shift src2 and issue +// 8x - shift opcode +// 9x - shift flagsdst +// Ax - shift src1 +// Bx - shift src2 +// Cx - invert+shift opcode +// Dx - invert+shift flagsdst +// Ex - invert+shift src1 +// 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 @@ +#include +#include + +#include "cpu.h" +#include "infra/pipetrace.h" +#include "infra/sim.h" + +int main(int argc, const char *argv[]) { + infra::pt::ptfile = &std::cout; + + cpu cpu; + + for (int i = 1; i < argc; ++i) { + std::ifstream fh{argv[i]}; + cpu.dram.load(fh); + } + + for (unsigned int i = 0; i < 1000; ++i) + infra::sim::advance(); + + return 0; +} 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 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "memory/line.h" + +namespace memory { + struct dram : public infra::sim { + static constexpr std::uint64_t PAGE_LINES_LOG2 = 20 - LINE_BYTES_LOG2; + static constexpr std::uint64_t PAGE_LINES = 1 << PAGE_LINES_LOG2; + static constexpr std::uint64_t PAGE_LINE_OFFSET_MASK = PAGE_LINES - 1; + static constexpr std::uint64_t PAGE_BYTES_LOG2 = PAGE_LINES_LOG2 + LINE_BYTES_LOG2; + static constexpr std::uint64_t PAGE_BYTES = 1 << PAGE_BYTES_LOG2; + static constexpr std::uint64_t PAGE_BYTE_OFFSET_MASK = PAGE_BYTES - 1; + + typedef std::array page; + + std::map image; + + struct response { + infra::transaction transaction; + std::uint64_t line_address; + line data; + }; + + struct command { + infra::transaction transaction; + std::uint64_t line_address; + line data; + std::array mask; + bool write = false; + infra::port *responsep = nullptr; + }; + + infra::port commandp; + + void clock() { + if (commandp.can_read()) { + const auto &c = commandp.peek(); + if (!c.responsep || c.responsep->can_write()) { + auto page_address = c.line_address >> PAGE_LINES_LOG2; + auto page_line = c.line_address & PAGE_LINE_OFFSET_MASK; + if (c.write) { + pte(c.transaction, "s", fmt::format("store {:x}-{:x}", page_address, page_line)); + if (c.responsep) { + response r; + r.transaction = c.transaction; + r.line_address = c.line_address; + r.data = c.data; + c.responsep->write(std::move(r)); + } + auto [p, emplaced] = image.try_emplace(page_address); + if (emplaced) + for (unsigned int i = 0; i < PAGE_LINES; ++i) + p->second[i].fill(0); + auto &l = p->second[page_line]; + for (unsigned int i = 0; i < LINE_BYTES; ++i) + if (c.mask[i]) + l[i] = c.data[i]; + } else { + pte(c.transaction, "f", fmt::format("fill {:x}-{:x}", page_address, page_line)); + if (c.responsep) { + response r; + r.transaction = c.transaction; + r.line_address = c.line_address; + if (auto p = image.find(page_address); p != image.end()) + r.data = p->second[page_line]; + else + r.data.fill(0); + c.responsep->write(std::move(r)); + } + } + commandp.discard(); + } + } + } + + void load(std::istream &fh) { + for (unsigned int page = 0; ; ++page) { + auto [p, emplaced] = image.try_emplace(page); + if (emplaced) + for (unsigned int i = 0; i < PAGE_LINES; ++i) + p->second[i].fill(0); + for (unsigned int line = 0; line < PAGE_LINES; ++line) + if (!fh.read(reinterpret_cast(p->second[line].data()), LINE_BYTES)) + return; + } + } + }; +} 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 @@ +#pragma once + +#include +#include + +#include "infra/port.h" +#include "infra/sim.h" + +namespace memory { + constexpr std::uint64_t LINE_BYTES_LOG2 = 4; + constexpr std::uint64_t LINE_BYTES = 1 << LINE_BYTES_LOG2; + constexpr std::uint64_t LINE_BYTE_OFFSET_MASK = LINE_BYTES - 1; + + typedef std::array line; +} 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 @@ +#!/usr/bin/ruby + +$filter = ARGV + +$parents = {} +$events = {} +$has = {} +$data = {} +$horiz = {} +$maxtime = -1 + +$stdin.each_line do | line | + case line + + when /^(\d+) parent (\d+)$/ + child = $1.to_i + parent = $2.to_i + $parents[child] = parent + + when /^@(\d+) (\d+) (\S*) (.*)$/ + time = $1.to_i + rec = $2.to_i + event = $3 + data = $4 + if event.size > 0 + $events[rec] ||= {} + $events[rec][time] = event + $has[rec] ||= {} + $has[rec][event] = true + $horiz[event] ||= "" + $horiz[event] = $horiz[event].ljust(time) + $horiz[event][time] = event + end + if data.size > 0 + $data[rec] ||= "" + $data[rec] += " #{event}@#{time}:" if event.size > 0 + $data[rec] += " #{data}" + end + $maxtime = [$maxtime, time+1].max + + else + raise "Unexpected line: #{line}" + end +end + +$hier = {} +$hier_direct = {} + +$events.each_key do | rec | + subhier = {} + $hier_direct[rec] = subhier + if $parents.key?(rec) + $hier_direct[$parents[rec]][rec] = subhier + else + $hier[rec] = subhier + end +end + +$order = [] + +def flatten(hier) + hier.each do | rec, subhier | + $order << rec + flatten(subhier) + end +end +flatten($hier) + +rwidth = $order.map { | x | x.to_s.size }.max + +$horiz.keys.sort.each do | occ | + $stdout.write(" " * rwidth + " #{$horiz[occ].ljust($maxtime)}") + count = $horiz[occ].delete(" ").size + $stdout.write(" #{($maxtime.to_f / count.to_f).round(2).to_s.rjust(5)} cyc/evt\n") +end + +mwidth = 0 + +$order.each do | rec | + estr = "" + filter_match = $filter.empty? + $has[rec].each_key do | event | + filter_match ||= $filter.include?(event) + end + next unless filter_match + $events[rec].keys.sort.each do | time | + estr = estr.ljust(time + 1, estr.size == 0 ? " " : "-") + estr[time] = $events[rec][time] if $events[rec][time].size > 0 + end + estr += " " * 5 + estr = estr.ljust(mwidth - 1) + estr = estr.ljust(estr.size + 20 - estr.size % 20) + mwidth = [mwidth, estr.size].max + $stdout.write(rec.to_s.rjust(rwidth) + ": #{estr}#{$data[rec]}\n") +end diff --git a/test b/test new file mode 100755 index 0000000..d70c720 --- /dev/null +++ b/test @@ -0,0 +1,58 @@ +#!/bin/bash + +set -eu + +FILTER=() + +while [[ $# != 0 ]]; do + if [[ $1 == "-h" ]]; then + cat <