From b1d67364b4b4832b8efed112f7b0ead1a0b3cd3b Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 22 Sep 2024 22:45:18 +0300 Subject: start experimenting with processor grids --- Makefile | 2 +- README.md | 50 ++- include/gran/bus/simple_bus.h | 2 +- include/gran/cpu/riscv/simple_riscv32.h | 13 - include/gran/cpu/riscv/simple_riscv64.h | 15 + include/gran/grid/node.h | 14 + include/gran/grid/router.h | 10 + include/gran/packet.h | 2 +- include/gran/uart/simple_uart.h | 8 + include/gran/vec.h | 46 +++ src/clock_domain.c | 27 +- src/components/bus/simple_bus.c | 2 +- src/components/cpu/riscv/simple_riscv32.c | 566 --------------------------- src/components/cpu/riscv/simple_riscv64.c | 626 ++++++++++++++++++++++++++++++ src/components/cpu/riscv/source.mk | 2 +- src/components/grid/node.c | 106 +++++ src/components/grid/router.c | 114 ++++++ src/components/grid/source.mk | 1 + src/components/uart/simple_uart.c | 28 ++ src/components/uart/source.mk | 1 + src/vec.c | 67 ++++ tests/simple_grid/sim.c | 114 ++++++ tests/simple_grid/source.mk | 6 + tests/simple_grid/test.c | 43 ++ tests/simple_riscv32/s.asm | 16 - tests/simple_riscv32/sim.c | 38 -- tests/simple_riscv32/source.mk | 6 - tests/simple_riscv64/s.asm | 16 + tests/simple_riscv64/sim.c | 38 ++ tests/simple_riscv64/source.mk | 6 + tests/simple_uart/hello.c | 18 + tests/simple_uart/sim.c | 45 +++ tests/simple_uart/source.mk | 6 + 33 files changed, 1387 insertions(+), 667 deletions(-) delete mode 100644 include/gran/cpu/riscv/simple_riscv32.h create mode 100644 include/gran/cpu/riscv/simple_riscv64.h create mode 100644 include/gran/grid/node.h create mode 100644 include/gran/grid/router.h create mode 100644 include/gran/uart/simple_uart.h create mode 100644 include/gran/vec.h delete mode 100644 src/components/cpu/riscv/simple_riscv32.c create mode 100644 src/components/cpu/riscv/simple_riscv64.c create mode 100644 src/components/grid/node.c create mode 100644 src/components/grid/router.c create mode 100644 src/components/grid/source.mk create mode 100644 src/components/uart/simple_uart.c create mode 100644 src/components/uart/source.mk create mode 100644 src/vec.c create mode 100644 tests/simple_grid/sim.c create mode 100644 tests/simple_grid/source.mk create mode 100644 tests/simple_grid/test.c delete mode 100644 tests/simple_riscv32/s.asm delete mode 100644 tests/simple_riscv32/sim.c delete mode 100644 tests/simple_riscv32/source.mk create mode 100644 tests/simple_riscv64/s.asm create mode 100644 tests/simple_riscv64/sim.c create mode 100644 tests/simple_riscv64/source.mk create mode 100644 tests/simple_uart/hello.c create mode 100644 tests/simple_uart/sim.c create mode 100644 tests/simple_uart/source.mk diff --git a/Makefile b/Makefile index 88855fa..108c182 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ DO != echo -n > deps.mk DEBUGFLAGS != [ $(RELEASE) ] && echo "-flto=auto -O2 -DNODEBUG" || echo "-O0 -g -DDEBUG" -CFLAGS = -Wall -Wextra -fopenmp -g +CFLAGS = -Wall -Wextra -g DEPFLAGS = -MT $@ -MMD -MP -MF $@.d LINTFLAGS = -fsyntax-only INCLUDEFLAGS = -Iinclude diff --git a/README.md b/README.md index 6bf8707..a50938b 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,53 @@ # gran -Gran is a toy granular full system simulator, -taking heavy inspiration from [gem5](https://www.gem5.org/). +Gran is a toy granular full system simulator, taking heavy inspiration from +[gem5](https://www.gem5.org/). -Currently there are a couple example components, including -`simple_mem` for atomic memory accesses, `simple_bus` for non-coherent -atomic transfers between components and `simple_riscv32`, a simple -rv32i processor. +Currently there are a couple example components, including `simple_mem` for +atomic memory accesses, `simple_bus` for non-coherent atomic transfers between +components and `simple_riscv32`, a simple rv32i processor. # Building + `make` Add `RELEASE=1` to enable optimizations. + +# manycore + +Idea: Have a grid of x/y width, where each node has three 'axes', i.e. if the +address matches the node, pass the message to whatever is at that node (think of +it like a miniature system I suppose?), otherwise check x, send it in that +direction, otherwise check y, send it in that direction, otherwise check if +message should be transcended, i.e. sent to some other cluster. + +Each address is 64 bits, split in half. The lower 32 bits is the offset within +the node, and the upper 32 bits are split (again) in twine, something like x1 y1 +x2 y2 where each x or y is eight bits, specifying a node in an x/y grid. x2/y2 +specifies the lower cluster, with x1/y1 upper cluster. The idea is that a +complete system would consist of cards that contain one cluster, clustered +together. So a 'supercomputer' could be up to 65536 cards, with each card having +a cluster of 65536 nodes. Each node is a compute unit, probably a single core, +possibly with associated memory or devices like UART, whatever. + +Currently I'm imagining that each node also has a control region that can be +written to with commands like 'drop all cache entries that containt address xyz' +or 'wake up' (to enable userspace mutexes, that would be massively cool) + +The current focus would be to enable lightweight userspaces, full-blown +hardware-assisted virtual memory is costly to implement (as in takes up too much +space in hardware), but I'd still want to be able to run a command line and +generic programs on the system. One idea would be a capability-based system, +sort of like CHERIoT, though it has some drawbacks like having to keep track of +the valid capabilities in separate memory or as part of ECC. + +Another approach that I'm currently thinking of is having a *very* simple +region-based memory protection scheme, where instead of paging we have a TLB of, +dunno, 64 regions that the core is allowed to access. If the core tries to +access a region outside of those regions, the kernel is trapped to and can then +insert the requested area into the regions or kill the process or whatever. This +is *similar* to virtual memory in other systems (particularly MIPS), but would +be range-based rather than page-based. If I wanted to use OpenASIP, I probably +wouldn't be able to use interrupts, and we would either need a separate +coprocessor to handle these kinds of exceptions or implement a tree walker in +hardware (unsure how much extra area it would take) diff --git a/include/gran/bus/simple_bus.h b/include/gran/bus/simple_bus.h index 41a8d2e..62dd7d2 100644 --- a/include/gran/bus/simple_bus.h +++ b/include/gran/bus/simple_bus.h @@ -8,6 +8,6 @@ struct component *create_simple_bus(); stat simple_bus_add(struct component *bus, struct component *component, - uintptr_t addr, size_t size); + uint64_t addr, uint64_t size); #endif /* GRAN_SIMPLE_BUS_H */ diff --git a/include/gran/cpu/riscv/simple_riscv32.h b/include/gran/cpu/riscv/simple_riscv32.h deleted file mode 100644 index 3cbd842..0000000 --- a/include/gran/cpu/riscv/simple_riscv32.h +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#ifndef GRAN_SIMPLE_RISCV32 -#define GRAN_SIMPLE_RISCV32 - -#include - -struct component *create_simple_riscv32(uint32_t start_pc, - struct component *imem, - struct component *dmem); - -#endif /* GRAN_SIMPLE_RISCV */ diff --git a/include/gran/cpu/riscv/simple_riscv64.h b/include/gran/cpu/riscv/simple_riscv64.h new file mode 100644 index 0000000..8f66013 --- /dev/null +++ b/include/gran/cpu/riscv/simple_riscv64.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#ifndef GRAN_SIMPLE_RISCV64 +#define GRAN_SIMPLE_RISCV64 + +#include + +struct component *create_simple_riscv64(uint32_t start_pc, + struct component *imem, + struct component *dmem); + +void simple_riscv64_set_reg(struct component *cpu, size_t reg, uint64_t val); + +#endif /* GRAN_SIMPLE_RISCV */ diff --git a/include/gran/grid/node.h b/include/gran/grid/node.h new file mode 100644 index 0000000..637ac91 --- /dev/null +++ b/include/gran/grid/node.h @@ -0,0 +1,14 @@ +#ifndef GRAN_GRID_NODE_H +#define GRAN_GRID_NODE_H + +#include +#include + +struct component *create_grid_node(uint8_t u, uint8_t v, uint8_t x, uint8_t y); + +stat grid_node_connect(struct component *node, + struct component *left, struct component *right, + struct component *up, struct component *down, + struct component *lower, struct component *ascend); + +#endif /* GRAN_GRID_NODE_H */ diff --git a/include/gran/grid/router.h b/include/gran/grid/router.h new file mode 100644 index 0000000..cff0fa1 --- /dev/null +++ b/include/gran/grid/router.h @@ -0,0 +1,10 @@ +#ifndef GRAN_NODE_ROUTER_H +#define GRAN_NODE_ROUTER_H + +#include + +struct component *create_node_router(uint8_t u, uint8_t v, uint8_t x, uint8_t y); +stat node_router_add(struct component *router, struct component *component, uint32_t addr, uint32_t size); +stat node_router_ascend(struct component *router, struct component *node); + +#endif /* GRAN_NODE_ROUTER_H */ diff --git a/include/gran/packet.h b/include/gran/packet.h index d18dfd6..ab07b56 100644 --- a/include/gran/packet.h +++ b/include/gran/packet.h @@ -39,7 +39,7 @@ enum packet_state packet_state(struct packet *pkt); void packet_set_state(struct packet *pkt, enum packet_state state); size_t packet_size(struct packet *pkt); -uintptr_t packet_addr(struct packet *pkt); +uint64_t packet_addr(struct packet *pkt); void *packet_data(struct packet *pkt); void destroy_packet(struct packet *pkt); diff --git a/include/gran/uart/simple_uart.h b/include/gran/uart/simple_uart.h new file mode 100644 index 0000000..75906fa --- /dev/null +++ b/include/gran/uart/simple_uart.h @@ -0,0 +1,8 @@ +#ifndef GRAN_SIMPLE_UART_H +#define GRAN_SIMPLE_UART_H + +#include + +struct component *create_simple_uart(); + +#endif /* GRAN_SIMPLE_UART_H */ diff --git a/include/gran/vec.h b/include/gran/vec.h new file mode 100644 index 0000000..08a608a --- /dev/null +++ b/include/gran/vec.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ + +#ifndef VEC_H +#define VEC_H + +#include + +struct vec { + size_t n; + size_t s; + size_t ns; + void *buf; +}; + +struct vec vec_create(size_t s); +void vec_destroy(struct vec *v); +void vec_reset(struct vec *v); + +size_t vec_len(struct vec *v); +void *vec_at(struct vec *v, size_t i); +void *vec_back(struct vec *v); +void *vec_pop(struct vec *v); +void vec_append(struct vec *v, void *n); + +typedef int (*vec_comp_t)(const void *, const void *); +void vec_sort(struct vec *v, vec_comp_t comp); + +#define foreach_vec(iter, v) \ + for (size_t iter = 0; iter < vec_len(&v); ++iter) + +#define vect_at(type, v, i) \ + *(type *)vec_at(&v, i) + +#define vect_append(type, v, e) \ + vec_append(&v, (type *)(e)) + +#define vect_back(type, v) \ + *(type *)vec_back(&v) + +#define vect_pop(type, v) \ + *(type *)vec_pop(&v) + +#define vec_uninit(v) \ + (v.buf == NULL) + +#endif /* VEC_H */ diff --git a/src/clock_domain.c b/src/clock_domain.c index 65dc72a..6610d8f 100644 --- a/src/clock_domain.c +++ b/src/clock_domain.c @@ -12,12 +12,10 @@ #include #include - -#define MAX_COMPONENTS 512 +#include struct clock_domain { - size_t num_components; - struct component *components[MAX_COMPONENTS]; + struct vec components; struct clock_time time; tick period; @@ -44,21 +42,23 @@ struct clock_domain *create_clock_domain(tick period) clk->period = period; mtx_init(&clk->mtx, mtx_plain); + clk->components = vec_create(sizeof(struct component *)); return clk; } void destroy_clock_domain(struct clock_domain *clk) { - for (size_t i = 0; i < clk->num_components; ++i) - destroy(clk->components[i]); + for (size_t i = 0; i < vec_len(&clk->components); ++i) + destroy(vect_at(struct component *, clk->components, i)); + vec_destroy(&clk->components); free(clk); } stat clock_domain_add(struct clock_domain *clk, struct component *component) { - clk->components[clk->num_components++] = component; + vect_append(struct component *, clk->components, &component); return OK; } @@ -80,18 +80,11 @@ static void clocked_component_tick(struct clock_domain *clk, stat clock_domain_tick(struct clock_domain *clk) { - /* openmp on gcc apparently doesn't really handle if conditionals - * particularly efficiently, so it's faster to do it manually */ - if (clk->num_components == 1) { - clocked_component_tick(clk, clk->components[0]); - advance_clock(clk); - return clk->ret; + for (size_t i = 0; i < vec_len(&clk->components); ++i) { + clocked_component_tick(clk, + vect_at(struct component *, clk->components, i)); } -#pragma omp parallel for - for (size_t i = 0; i < clk->num_components; ++i) - clocked_component_tick(clk, clk->components[i]); - advance_clock(clk); return clk->ret; } diff --git a/src/components/bus/simple_bus.c b/src/components/bus/simple_bus.c index 440ff8a..218e930 100644 --- a/src/components/bus/simple_bus.c +++ b/src/components/bus/simple_bus.c @@ -172,7 +172,7 @@ struct component *create_simple_bus() } stat simple_bus_add(struct component *bus, struct component *component, - uintptr_t addr, size_t size) + uint64_t addr, uint64_t size) { struct mem_region *new = calloc(1, sizeof(struct mem_region)); if (!new) diff --git a/src/components/cpu/riscv/simple_riscv32.c b/src/components/cpu/riscv/simple_riscv32.c deleted file mode 100644 index 51b2a42..0000000 --- a/src/components/cpu/riscv/simple_riscv32.c +++ /dev/null @@ -1,566 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#include -#include -#include -#include - -#include - -struct simple_rv32_ldst { - struct packet *pkt; - uint32_t reg; - bool u; -}; - -struct simple_riscv32 { - struct component component; - - struct component *imem; - struct component *dmem; - - struct simple_rv32_ldst dls; - struct simple_rv32_ldst ils; - - /* have to be careful with x0 */ - uint32_t regs[32]; - uint32_t pc; -}; - -/* big endian format, going from smallest to highest address. - * Easy to get confused */ -struct rtype { - uint32_t op : 7; - uint32_t rd : 5; - uint32_t funct3 : 3; - uint32_t rs1 : 5; - uint32_t rs2 : 5; - uint32_t funct7 : 7; -}; - -struct itype { - uint32_t op : 7; - uint32_t rd : 5; - uint32_t funct3 : 3; - uint32_t rs1 : 5; - uint32_t imm : 12; -}; - -struct stype { - uint32_t op : 7; - uint32_t imm0 : 5; - uint32_t funct3 : 3; - uint32_t rs1 : 5; - uint32_t rs2 : 5; - uint32_t imm1 : 7; -}; - -struct btype { - uint32_t op : 7; - uint32_t imm0 : 1; - uint32_t imm1 : 4; - uint32_t funct3 : 3; - uint32_t rs1 : 5; - uint32_t rs2 : 5; - uint32_t imm2 : 6; - uint32_t imm3 : 1; -}; - -struct utype { - uint32_t op : 7; - uint32_t rd : 5; - uint32_t imm : 20; -}; - -struct jtype { - uint32_t op : 7; - uint32_t rd : 5; - uint32_t imm0 : 8; - uint32_t imm1 : 1; - uint32_t imm2 : 10; - uint32_t imm3 : 1; -}; - -enum opcode { - LOAD = 0b0000011, - LOAD_FP = 0b0000111, - MISC_MEM = 0b0001111, - OP_IMM = 0b0010011, - AUIPC = 0b0010111, - OP_IMM_32 = 0b0011011, - - STORE = 0b0100011, - STORE_FP = 0b0100111, - AMO = 0b0101111, - OP = 0b0110011, - LUI = 0b0110111, - OP_32 = 0b0111011, - - MADD = 0b1000011, - MSUB = 0b1000111, - NMSUB = 0b1001011, - NMADD = 0b1001111, - OP_FP = 0b1010011, - - BRANCH = 0b1100011, - JALR = 0b1100111, - JAL = 0b1101111, - SYSTEM = 0b1110011, -}; - -union rv_insn { - struct rtype rtype; - struct itype itype; - struct stype stype; - struct btype btype; - struct utype utype; - struct jtype jtype; - uint32_t val; -}; - -static uint32_t get_reg(struct simple_riscv32 *cpu, size_t i) -{ - assert(i < 32); - - if (i == 0) - return 0; - - return cpu->regs[i]; -} - -static void set_reg(struct simple_riscv32 *cpu, size_t i, uint32_t v) -{ - assert(i < 32); - - if (i == 0) - return; - - cpu->regs[i] = v; -} - -#define EXTEND_IMM12(x) ((int32_t)((x) << 20) >> 20) -#define EXTEND_IMM20(x) ((int32_t)((x) << 12) >> 12) -#define SHAMT(x) ((x) & 0b11111) - -static stat op_imm(struct simple_riscv32 *cpu, union rv_insn insn) -{ - uint32_t dst = 0; - uint32_t src = get_reg(cpu, insn.itype.rs1); - - uint32_t imm = EXTEND_IMM12(insn.itype.imm); - - switch (insn.rtype.funct3) { - /* ADDI */ - case 0b000: dst = src + imm; break; - /* SLTI */ - case 0b010: dst = (int32_t)src < (int32_t)imm; break; - /* SLTIU */ - case 0b011: dst = src < imm; break; - /* ANDI */ - case 0b111: dst = src & imm; break; - /* ORI */ - case 0b110: dst = src | imm; break; - /* XORI */ - case 0b100: dst = src ^ imm; break; - /* SLLI */ - case 0b001: dst = src << SHAMT(imm); break; - - /* SRLI / SRAI */ - case 0b101: - if (imm & ~0b11111) /* SRLI */ - dst = src >> SHAMT(imm); - else /* SRAI */ - dst = (int32_t)src >> SHAMT(imm); - break; - - default: - error("unknown OP-IMM instruction: %x", insn.itype.funct3); - return ENOSUCH; - } - - set_reg(cpu, insn.itype.rd, dst); - cpu->pc += 4; - return OK; -} - -static stat lui(struct simple_riscv32 *cpu, union rv_insn insn) -{ - set_reg(cpu, insn.utype.rd, insn.utype.imm << 12); - cpu->pc += 4; - return OK; -} - -static stat auipc(struct simple_riscv32 *cpu, union rv_insn insn) -{ - uint32_t res = cpu->pc + (insn.utype.imm << 12); - set_reg(cpu, insn.utype.rd, res); - cpu->pc += 4; - return OK; -} - -static stat op(struct simple_riscv32 *cpu, union rv_insn insn) -{ - uint32_t dst = 0; - uint32_t src1 = get_reg(cpu, insn.rtype.rs1); - uint32_t src2 = get_reg(cpu, insn.rtype.rs2); - - switch (insn.rtype.funct3) { - /* ADD/SUB */ - case 0b000: - if (insn.rtype.funct7) /* SUB */ - dst = src1 - src2; - else /* ADD */ - dst = src1 + src2; - break; - - /* SLT */ - case 0b010: dst = (int32_t)src1 < (int32_t)src2; break; - /* SLTU */ - case 0b011: dst = src1 < src2; break; - /* AND */ - case 0b111: dst = src1 & src2; break; - /* OR */ - case 0b110: dst = src1 | src2; break; - /* XOR */ - case 0b100: dst = src1 ^ src2; break; - /* SLL */ - case 0b001: dst = src1 << src2; break; - /* SRL/SRA */ - case 0b101: - if (insn.rtype.funct7) /* SRL */ - dst = src1 >> src2; - else /* SRA */ - dst = (int32_t)src1 >> src2; - break; - - default: - error("unknown OP instruction: %x", insn.rtype.funct3); - return ENOSUCH; - } - - set_reg(cpu, insn.rtype.rd, dst); - cpu->pc += 4; - return OK; -} - -#define JTYPE_IMM(insn) \ - EXTEND_IMM20((insn.jtype.imm3 << 20) \ - | (insn.jtype.imm2 << 1) \ - | (insn.jtype.imm1 << 11) \ - | (insn.jtype.imm0 << 12)) - -static stat jal(struct simple_riscv32 *cpu, union rv_insn insn) -{ - /** @todo generate exception on unaligned jumps */ - int32_t imm = JTYPE_IMM(insn); - set_reg(cpu, insn.jtype.rd, cpu->pc + 4); - cpu->pc += imm; - return OK; -} - -static stat jalr(struct simple_riscv32 *cpu, union rv_insn insn) -{ - int32_t src = get_reg(cpu, insn.itype.rs1); - set_reg(cpu, insn.itype.rd, cpu->pc + 4); - cpu->pc += src + EXTEND_IMM12(insn.itype.imm); - return OK; -} - -#define BTYPE_IMM(insn) \ - EXTEND_IMM12((insn.btype.imm3 << 12) \ - | (insn.btype.imm2 << 5) \ - | (insn.btype.imm1 << 1) \ - | (insn.btype.imm0 << 11)) - -static stat branch(struct simple_riscv32 *cpu, union rv_insn insn) -{ - uint32_t src1 = get_reg(cpu, insn.btype.rs1); - uint32_t src2 = get_reg(cpu, insn.btype.rs2); - int32_t offset = BTYPE_IMM(insn); - - switch (insn.btype.funct3) { - /* BEQ */ - case 0b000: - if (src1 == src2) { - cpu->pc += offset; - return OK; - } - break; - - /* BNE */ - case 0b001: - if (src1 != src2) { - cpu->pc += offset; - return OK; - } - break; - - /* BLT */ - case 0b100: - if ((int32_t)src1 < (int32_t)src2) { - cpu->pc += offset; - return OK; - } - break; - - /* BLTU */ - case 0b110: - if (src1 < src2) { - cpu->pc += offset; - return OK; - } - break; - - /* BGE */ - case 0b101: - if ((int32_t)src1 >= (int32_t)src2) { - cpu->pc += offset; - return OK; - } - break; - - /* BGEU */ - case 0b111: - if (src1 >= src2) { - cpu->pc += offset; - return OK; - } - break; - - default: - error("unknown BRANCH instruction %x", insn.btype.funct3); - return ENOSUCH; - } - - cpu->pc += 4; - return OK; -} - -static stat load(struct simple_riscv32 *cpu, union rv_insn insn) -{ - int32_t imm = EXTEND_IMM12(insn.itype.imm); - int32_t base = get_reg(cpu, insn.itype.rs1); - - size_t addr = base + imm; - size_t size = 0; - bool u = false; - - // assume little endian for now - switch (insn.itype.funct3) { - /* LB/LBU */ - case 0b100: u = true; /* fallthrough */ - case 0b000: size = 1; break; - /* LH/LHU */ - case 0b101: u = true; /* fallthrough */ - case 0b001: size = 2; break; - /* LW */ - case 0b010: size = 4; break; - default: - error("unknown LOAD width %x", insn.btype.funct3); - return ENOSUCH; - } - - struct packet *pkt = create_packet(PACKET_READ, addr, size); - if (!pkt) - return EMEM; - - cpu->dls = (struct simple_rv32_ldst){pkt, insn.itype.rd, u}; - stat ret = read(cpu->dmem, pkt); - if (ret) - return ret; - - cpu->pc += 4; - return OK; -} - -#define STYPE_IMM(insn) \ - EXTEND_IMM12((insn.stype.imm1 << 5) \ - | (insn.stype.imm0)) - -static stat store(struct simple_riscv32 *cpu, union rv_insn insn) -{ - int32_t imm = STYPE_IMM(insn); - int32_t base = get_reg(cpu, insn.stype.rs1); - size_t addr = base + imm; - - uint32_t src = get_reg(cpu, insn.stype.rs2); - - switch (insn.stype.funct3) { - /* SB */ - case 0b000: { - cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 1); - *(uint8_t *)packet_data(cpu->dls.pkt) = src; - break; - } - /* SH */ - case 0b001: { - cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 2); - *(uint16_t *)packet_data(cpu->dls.pkt) = src; - break; - } - /* SW */ - case 0b010: { - cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 4); - *(uint32_t *)packet_data(cpu->dls.pkt) = src; - break; - } - default: - error("unknown width of STORE %x", insn.stype.funct3); - return ENOSUCH; - } - - cpu->pc += 4; - return write(cpu->dmem, cpu->dls.pkt); -} - -static void finalize_ld(struct simple_riscv32 *cpu) -{ - struct simple_rv32_ldst ld = cpu->dls; - - uint32_t val = 0; - void *data = packet_data(ld.pkt); - switch (packet_size(ld.pkt)) { - case 1: - if (ld.u) val = *(uint8_t *)data; - else val = *(int8_t *)data; - break; - - case 2: if (ld.u) val = *(uint16_t *)data; - else val = *(int16_t *)data; - break; - - case 4: val = *(uint32_t *)data; - break; - - default: - error("unknown load size %zu", packet_size(ld.pkt)); - } - - set_reg(cpu, ld.reg, val); -} - -static void finalize_st(struct simple_riscv32 *cpu) -{ - (void)cpu; - /* nothing really to do, this is here mostly for vibe */ -} - -static void finalize_dls(struct simple_riscv32 *cpu) -{ - struct packet *pkt = cpu->dls.pkt; - - if (packet_type(pkt) == PACKET_READ) - finalize_ld(cpu); - else if (packet_type(pkt) == PACKET_WRITE) - finalize_st(cpu); - else - error("unsupported packet type for simple_riscv32"); - - destroy_packet(pkt); - cpu->dls.pkt = NULL; -} - -static uint32_t finalize_ils(struct simple_riscv32 *cpu) -{ - uint32_t insn = *(uint32_t *)packet_data(cpu->ils.pkt); - destroy_packet(cpu->ils.pkt); - cpu->ils.pkt = NULL; - return insn; -} - -static stat simple_riscv32_clock(struct simple_riscv32 *cpu) -{ - /* there's an active data transfer we should handle */ - if (cpu->dls.pkt) { - assert(packet_state(cpu->dls.pkt) != PACKET_FAILED); - - if (packet_state(cpu->dls.pkt) == PACKET_DONE) - finalize_dls(cpu); - else /* wait for data */ - return OK; - } - - if (!cpu->ils.pkt) { - cpu->ils.pkt = - create_packet(PACKET_READ, cpu->pc, sizeof(uint32_t)); - if (!cpu->ils.pkt) - return EMEM; - - stat ret = read(cpu->imem, cpu->ils.pkt); - if (ret) - return ret; - } - - uint32_t insn = 0; - if (cpu->ils.pkt) { - assert(packet_state(cpu->ils.pkt) != PACKET_FAILED); - - if (packet_state(cpu->ils.pkt) == PACKET_DONE) - insn = finalize_ils(cpu); - else /* wait for instruction */ - return OK; - } - - // for now assume little endian emulated and host cpu - union rv_insn i = {.val = insn}; - - stat ret = OK; - // all formats have identical opcodes, use whatever - switch (i.rtype.op) { - case OP_IMM: ret = op_imm(cpu, i); break; - case LUI: ret = lui(cpu, i); break; - case AUIPC: ret = auipc(cpu, i); break; - case OP: ret = op(cpu, i); break; - case JAL: ret = jal(cpu, i); break; - case JALR: ret = jalr(cpu, i); break; - case BRANCH: ret = branch(cpu, i); break; - case LOAD: ret = load(cpu, i); break; - case STORE: ret = store(cpu, i); break; - case MISC_MEM: /* nop in this case */ break; - case SYSTEM: - /* we don't support these yet, but we can use them - * to stop the simulation */ - return DONE; - default: - error("unknown opcode %x", i.rtype.op); - return ENOSUCH; - } - - return ret; -} - -static void simple_riscv32_destroy(struct simple_riscv32 *cpu) -{ - /* oh yeah, will have to think about the name stuff, - * i.e. how and where to free it, and where to assign it */ - destroy(cpu->imem); - - if (cpu->imem != cpu->dmem) - destroy(cpu->dmem); - - if (cpu->ils.pkt) - destroy_packet(cpu->ils.pkt); - - if (cpu->dls.pkt) - destroy_packet(cpu->dls.pkt); - - free(cpu); -} - -struct component *create_simple_riscv32(uint32_t start_pc, - struct component *imem, - struct component *dmem) -{ - struct simple_riscv32 *new = calloc(1, sizeof(struct simple_riscv32)); - if (!new) - return NULL; - - new->component.clock = (clock_callback)simple_riscv32_clock; - new->component.destroy = (destroy_callback)simple_riscv32_destroy; - - new->pc = start_pc; - new->imem = imem; - new->dmem = dmem; - return (struct component *)new; -} diff --git a/src/components/cpu/riscv/simple_riscv64.c b/src/components/cpu/riscv/simple_riscv64.c new file mode 100644 index 0000000..be96e60 --- /dev/null +++ b/src/components/cpu/riscv/simple_riscv64.c @@ -0,0 +1,626 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include +#include +#include +#include +#include + +#include + +struct simple_rv64_ldst { + struct packet *pkt; + uint32_t reg; + bool u; +}; + +struct simple_riscv64 { + struct component component; + + struct component *imem; + struct component *dmem; + + struct simple_rv64_ldst dls; + struct simple_rv64_ldst ils; + + /* have to be careful with x0 */ + uint64_t regs[32]; + uint64_t pc; +}; + +/* big endian format, going from smallest to highest address. + * Easy to get confused */ +struct rtype { + uint32_t op : 7; + uint32_t rd : 5; + uint32_t funct3 : 3; + uint32_t rs1 : 5; + uint32_t rs2 : 5; + uint32_t funct7 : 7; +}; + +struct itype { + uint32_t op : 7; + uint32_t rd : 5; + uint32_t funct3 : 3; + uint32_t rs1 : 5; + uint32_t imm : 12; +}; + +struct stype { + uint32_t op : 7; + uint32_t imm0 : 5; + uint32_t funct3 : 3; + uint32_t rs1 : 5; + uint32_t rs2 : 5; + uint32_t imm1 : 7; +}; + +struct btype { + uint32_t op : 7; + uint32_t imm0 : 1; + uint32_t imm1 : 4; + uint32_t funct3 : 3; + uint32_t rs1 : 5; + uint32_t rs2 : 5; + uint32_t imm2 : 6; + uint32_t imm3 : 1; +}; + +struct utype { + uint32_t op : 7; + uint32_t rd : 5; + uint32_t imm : 20; +}; + +struct jtype { + uint32_t op : 7; + uint32_t rd : 5; + uint32_t imm0 : 8; + uint32_t imm1 : 1; + uint32_t imm2 : 10; + uint32_t imm3 : 1; +}; + +enum opcode { + LOAD = 0b0000011, + LOAD_FP = 0b0000111, + MISC_MEM = 0b0001111, + OP_IMM = 0b0010011, + AUIPC = 0b0010111, + OP_IMM_32 = 0b0011011, + + STORE = 0b0100011, + STORE_FP = 0b0100111, + AMO = 0b0101111, + OP = 0b0110011, + LUI = 0b0110111, + OP_32 = 0b0111011, + + MADD = 0b1000011, + MSUB = 0b1000111, + NMSUB = 0b1001011, + NMADD = 0b1001111, + OP_FP = 0b1010011, + + BRANCH = 0b1100011, + JALR = 0b1100111, + JAL = 0b1101111, + SYSTEM = 0b1110011, +}; + +union rv_insn { + struct rtype rtype; + struct itype itype; + struct stype stype; + struct btype btype; + struct utype utype; + struct jtype jtype; + uint32_t val; +}; + +static uint64_t get_reg(struct simple_riscv64 *cpu, size_t i) +{ + assert(i < 32); + + if (i == 0) + return 0; + + return cpu->regs[i]; +} + +static void set_reg(struct simple_riscv64 *cpu, size_t i, uint64_t v) +{ + assert(i < 32); + + if (i == 0) + return; + + cpu->regs[i] = v; +} + +#define EXTEND_IMM12(x) ((int32_t)((x) << 20) >> 20) +#define EXTEND_IMM20(x) ((int32_t)((x) << 12) >> 12) +#define SHAMT(x) ((x) & 0b11111) + +static stat op_imm(struct simple_riscv64 *cpu, union rv_insn insn) +{ + uint64_t dst = 0; + uint64_t src = get_reg(cpu, insn.itype.rs1); + + uint64_t imm = EXTEND_IMM12(insn.itype.imm); + + switch (insn.rtype.funct3) { + /* ADDI */ + case 0b000: dst = src + imm; break; + /* SLTI */ + case 0b010: dst = (int64_t)src < (int64_t)imm; break; + /* SLTIU */ + case 0b011: dst = src < imm; break; + /* ANDI */ + case 0b111: dst = src & imm; break; + /* ORI */ + case 0b110: dst = src | imm; break; + /* XORI */ + case 0b100: dst = src ^ imm; break; + /* SLLI */ + case 0b001: dst = src << SHAMT(imm); break; + + /* SRLI / SRAI */ + case 0b101: + if (imm & ~0b11111) /* SRLI */ + dst = src >> SHAMT(imm); + else /* SRAI */ + dst = (int64_t)src >> SHAMT(imm); + break; + + default: + error("unknown OP-IMM instruction: %x", insn.itype.funct3); + return ENOSUCH; + } + + set_reg(cpu, insn.itype.rd, dst); + cpu->pc += 4; + return OK; +} + +static stat op_imm_32(struct simple_riscv64 *cpu, union rv_insn insn) +{ + uint64_t dst = 0; + uint64_t src = get_reg(cpu, insn.itype.rs1); + + uint64_t imm = EXTEND_IMM12(insn.itype.imm); + + switch (insn.rtype.funct3) { + /* ADDIW */ + case 0b000: dst = src + imm; break; + /* SLTI */ + case 0b010: dst = (int64_t)src < (int64_t)imm; break; + /* SLTIU */ + case 0b011: dst = src < imm; break; + /* ANDI */ + case 0b111: dst = src & imm; break; + /* ORI */ + case 0b110: dst = src | imm; break; + /* XORI */ + case 0b100: dst = src ^ imm; break; + /* SLLIW */ + case 0b001: dst = src << SHAMT(imm); break; + + /* SRLIW / SRAIW */ + case 0b101: + if (imm & ~0b11111) /* SRLI */ + dst = src >> SHAMT(imm); + else /* SRAI */ + dst = (int64_t)src >> SHAMT(imm); + break; + + default: + error("unknown OP-IMM instruction: %x", insn.itype.funct3); + return ENOSUCH; + } + + set_reg(cpu, insn.itype.rd, dst); + cpu->pc += 4; + return OK; +} + +static stat lui(struct simple_riscv64 *cpu, union rv_insn insn) +{ + set_reg(cpu, insn.utype.rd, insn.utype.imm << 12); + cpu->pc += 4; + return OK; +} + +static stat auipc(struct simple_riscv64 *cpu, union rv_insn insn) +{ + uint64_t res = cpu->pc + (insn.utype.imm << 12); + set_reg(cpu, insn.utype.rd, res); + cpu->pc += 4; + return OK; +} + +static stat op(struct simple_riscv64 *cpu, union rv_insn insn) +{ + uint64_t dst = 0; + uint64_t src1 = get_reg(cpu, insn.rtype.rs1); + uint64_t src2 = get_reg(cpu, insn.rtype.rs2); + + switch (insn.rtype.funct3) { + /* ADD/SUB */ + case 0b000: + if (insn.rtype.funct7) /* SUB */ + dst = src1 - src2; + else /* ADD */ + dst = src1 + src2; + break; + + /* SLT */ + case 0b010: dst = (int64_t)src1 < (int64_t)src2; break; + /* SLTU */ + case 0b011: dst = src1 < src2; break; + /* AND */ + case 0b111: dst = src1 & src2; break; + /* OR */ + case 0b110: dst = src1 | src2; break; + /* XOR */ + case 0b100: dst = src1 ^ src2; break; + /* SLL */ + case 0b001: dst = src1 << src2; break; + /* SRL/SRA */ + case 0b101: + if (insn.rtype.funct7) /* SRL */ + dst = src1 >> src2; + else /* SRA */ + dst = (int64_t)src1 >> src2; + break; + + default: + error("unknown OP instruction: %x", insn.rtype.funct3); + return ENOSUCH; + } + + set_reg(cpu, insn.rtype.rd, dst); + cpu->pc += 4; + return OK; +} + +#define JTYPE_IMM(insn) \ + EXTEND_IMM20((insn.jtype.imm3 << 20) \ + | (insn.jtype.imm2 << 1) \ + | (insn.jtype.imm1 << 11) \ + | (insn.jtype.imm0 << 12)) + +static stat jal(struct simple_riscv64 *cpu, union rv_insn insn) +{ + /** @todo generate exception on unaligned jumps */ + int64_t imm = JTYPE_IMM(insn); + set_reg(cpu, insn.jtype.rd, cpu->pc + 4); + cpu->pc += imm; + return OK; +} + +static stat jalr(struct simple_riscv64 *cpu, union rv_insn insn) +{ + int64_t src = get_reg(cpu, insn.itype.rs1); + set_reg(cpu, insn.itype.rd, cpu->pc + 4); + cpu->pc += src + EXTEND_IMM12(insn.itype.imm); + return OK; +} + +#define BTYPE_IMM(insn) \ + EXTEND_IMM12((insn.btype.imm3 << 12) \ + | (insn.btype.imm2 << 5) \ + | (insn.btype.imm1 << 1) \ + | (insn.btype.imm0 << 11)) + +static stat branch(struct simple_riscv64 *cpu, union rv_insn insn) +{ + uint64_t src1 = get_reg(cpu, insn.btype.rs1); + uint64_t src2 = get_reg(cpu, insn.btype.rs2); + int64_t offset = BTYPE_IMM(insn); + + switch (insn.btype.funct3) { + /* BEQ */ + case 0b000: + if (src1 == src2) { + cpu->pc += offset; + return OK; + } + break; + + /* BNE */ + case 0b001: + if (src1 != src2) { + cpu->pc += offset; + return OK; + } + break; + + /* BLT */ + case 0b100: + if ((int64_t)src1 < (int64_t)src2) { + cpu->pc += offset; + return OK; + } + break; + + /* BLTU */ + case 0b110: + if (src1 < src2) { + cpu->pc += offset; + return OK; + } + break; + + /* BGE */ + case 0b101: + if ((int64_t)src1 >= (int64_t)src2) { + cpu->pc += offset; + return OK; + } + break; + + /* BGEU */ + case 0b111: + if (src1 >= src2) { + cpu->pc += offset; + return OK; + } + break; + + default: + error("unknown BRANCH instruction %x", insn.btype.funct3); + return ENOSUCH; + } + + cpu->pc += 4; + return OK; +} + +static stat load(struct simple_riscv64 *cpu, union rv_insn insn) +{ + int64_t imm = EXTEND_IMM12(insn.itype.imm); + int64_t base = get_reg(cpu, insn.itype.rs1); + + int64_t addr = base + imm; + int64_t size = 0; + bool u = false; + + // assume little endian for now + switch (insn.itype.funct3) { + /* LB/LBU */ + case 0b100: u = true; /* fallthrough */ + case 0b000: size = 1; break; + /* LH/LHU */ + case 0b101: u = true; /* fallthrough */ + case 0b001: size = 2; break; + /* LW */ + case 0b010: size = 4; break; + case 0b011: size = 8; break; + default: + error("unknown LOAD width %x", insn.btype.funct3); + return ENOSUCH; + } + + struct packet *pkt = create_packet(PACKET_READ, addr, size); + if (!pkt) + return EMEM; + + cpu->dls = (struct simple_rv64_ldst){pkt, insn.itype.rd, u}; + stat ret = read(cpu->dmem, pkt); + if (ret) + return ret; + + cpu->pc += 4; + return OK; +} + +#define STYPE_IMM(insn) \ + EXTEND_IMM12((insn.stype.imm1 << 5) \ + | (insn.stype.imm0)) + +static stat store(struct simple_riscv64 *cpu, union rv_insn insn) +{ + int64_t imm = STYPE_IMM(insn); + int64_t base = get_reg(cpu, insn.stype.rs1); + int64_t addr = base + imm; + + uint64_t src = get_reg(cpu, insn.stype.rs2); + + switch (insn.stype.funct3) { + /* SB */ + case 0b000: { + cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 1); + *(uint8_t *)packet_data(cpu->dls.pkt) = src; + break; + } + /* SH */ + case 0b001: { + cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 2); + *(uint16_t *)packet_data(cpu->dls.pkt) = src; + break; + } + /* SW */ + case 0b010: { + cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 4); + *(uint32_t *)packet_data(cpu->dls.pkt) = src; + break; + } + /* SD */ + case 0b011: { + cpu->dls.pkt = create_packet(PACKET_WRITE, addr, 8); + *(uint64_t *)packet_data(cpu->dls.pkt) = src; + break; + } + default: + error("unknown width of STORE %x", insn.stype.funct3); + return ENOSUCH; + } + + cpu->pc += 4; + return write(cpu->dmem, cpu->dls.pkt); +} + +static void finalize_ld(struct simple_riscv64 *cpu) +{ + struct simple_rv64_ldst ld = cpu->dls; + + uint64_t val = 0; + void *data = packet_data(ld.pkt); + switch (packet_size(ld.pkt)) { + case 1: + if (ld.u) val = *(uint8_t *)data; + else val = *(int8_t *)data; + break; + + case 2: if (ld.u) val = *(uint16_t *)data; + else val = *(int16_t *)data; + break; + + case 4: if (ld.u) val = *(uint32_t *)data; + else val = *(int32_t *)data; + break; + + case 8: val = *(uint64_t *)data; + break; + + default: + error("unknown load size %zu", packet_size(ld.pkt)); + } + + set_reg(cpu, ld.reg, val); +} + +static void finalize_st(struct simple_riscv64 *cpu) +{ + (void)cpu; + /* nothing really to do, this is here mostly for vibe */ +} + +static void finalize_dls(struct simple_riscv64 *cpu) +{ + struct packet *pkt = cpu->dls.pkt; + + if (packet_type(pkt) == PACKET_READ) + finalize_ld(cpu); + else if (packet_type(pkt) == PACKET_WRITE) + finalize_st(cpu); + else + error("unsupported packet type for simple_riscv64"); + + destroy_packet(pkt); + cpu->dls.pkt = NULL; +} + +static uint32_t finalize_ils(struct simple_riscv64 *cpu) +{ + uint32_t insn = *(uint32_t *)packet_data(cpu->ils.pkt); + destroy_packet(cpu->ils.pkt); + cpu->ils.pkt = NULL; + return insn; +} + +static stat simple_riscv64_clock(struct simple_riscv64 *cpu) +{ + /* there's an active data transfer we should handle */ + if (cpu->dls.pkt) { + assert(packet_state(cpu->dls.pkt) != PACKET_FAILED); + + if (packet_state(cpu->dls.pkt) == PACKET_DONE) + finalize_dls(cpu); + else /* wait for data */ + return OK; + } + + if (!cpu->ils.pkt) { + cpu->ils.pkt = + create_packet(PACKET_READ, cpu->pc, sizeof(uint32_t)); + if (!cpu->ils.pkt) + return EMEM; + + stat ret = read(cpu->imem, cpu->ils.pkt); + if (ret) + return ret; + } + + uint32_t insn = 0; + if (cpu->ils.pkt) { + assert(packet_state(cpu->ils.pkt) != PACKET_FAILED); + + if (packet_state(cpu->ils.pkt) == PACKET_DONE) + insn = finalize_ils(cpu); + else /* wait for instruction */ + return OK; + } + + // for now assume little endian emulated and host cpu + union rv_insn i = {.val = insn}; + + stat ret = OK; + // all formats have identical opcodes, use whatever + switch (i.rtype.op) { + case OP_IMM: ret = op_imm(cpu, i); break; + case OP_IMM_32: ret = op_imm_32(cpu, i); break; + case LUI: ret = lui(cpu, i); break; + case AUIPC: ret = auipc(cpu, i); break; + case OP: ret = op(cpu, i); break; + case JAL: ret = jal(cpu, i); break; + case JALR: ret = jalr(cpu, i); break; + case BRANCH: ret = branch(cpu, i); break; + case LOAD: ret = load(cpu, i); break; + case STORE: ret = store(cpu, i); break; + case MISC_MEM: /* nop in this case */ break; + case SYSTEM: + /* we don't support these yet, but we can use them + * to stop the simulation */ + return DONE; + default: + error("unknown opcode %x", i.rtype.op); + return ENOSUCH; + } + + return ret; +} + +static void simple_riscv64_destroy(struct simple_riscv64 *cpu) +{ + /* oh yeah, will have to think about the name stuff, + * i.e. how and where to free it, and where to assign it */ + destroy(cpu->imem); + + if (cpu->imem != cpu->dmem) + destroy(cpu->dmem); + + if (cpu->ils.pkt) + destroy_packet(cpu->ils.pkt); + + if (cpu->dls.pkt) + destroy_packet(cpu->dls.pkt); + + free(cpu); +} + +struct component *create_simple_riscv64(uint32_t start_pc, + struct component *imem, + struct component *dmem) +{ + struct simple_riscv64 *new = calloc(1, sizeof(struct simple_riscv64)); + if (!new) + return NULL; + + new->component.clock = (clock_callback)simple_riscv64_clock; + new->component.destroy = (destroy_callback)simple_riscv64_destroy; + + new->pc = start_pc; + new->imem = imem; + new->dmem = dmem; + return (struct component *)new; +} + +void simple_riscv64_set_reg(struct component *cpu, size_t reg, uint64_t val) +{ + struct simple_riscv64 *rv64 = (struct simple_riscv64 *)cpu; + set_reg(rv64, reg, val); +} diff --git a/src/components/cpu/riscv/source.mk b/src/components/cpu/riscv/source.mk index ea41f62..da1cef6 100644 --- a/src/components/cpu/riscv/source.mk +++ b/src/components/cpu/riscv/source.mk @@ -1 +1 @@ -SOURCES += src/components/cpu/riscv/simple_riscv32.c +SOURCES += src/components/cpu/riscv/simple_riscv64.c diff --git a/src/components/grid/node.c b/src/components/grid/node.c new file mode 100644 index 0000000..0aed768 --- /dev/null +++ b/src/components/grid/node.c @@ -0,0 +1,106 @@ +/* very simple grid node with 32bit private region, does not currently signal + * being busy or anything. I think I might have to refine the message passing + * interface I have, but this is good enough. + * + * Each node should have a router beneath it, just to simplify my life. A router + * is basically a bus with a fallback ascension path. + */ +#include + +struct grid_node { + struct component component; + uint8_t u, v, x, y; + struct component *left, *right, *up, *down, *ascend, *lower; +}; + +typedef read_callback callback; + +static stat grid_route(struct grid_node *grid, struct packet *pkt, callback op) +{ + uint64_t addr = packet_addr(pkt); + uint8_t u = (addr >> 56) & 0xff; + uint8_t v = (addr >> 48) & 0xff; + uint8_t x = (addr >> 40) & 0xff; + uint8_t y = (addr >> 32) & 0xff; + + if (grid->u == u && grid->v == v && grid->x == x && grid->y == y) + return op(grid->lower, pkt); + + if (grid->u != u || grid->v != v) { + if (!grid->ascend) + return EBUS; + + return op(grid->ascend, pkt); + } + + if (y < grid->y) { + if (!grid->down) + return EBUS; + + return op(grid->down, pkt); + } + + if (y > grid->y) { + if (!grid->up) + return EBUS; + + return op(grid->up, pkt); + } + + if (x < grid->x) { + if (!grid->left) + return EBUS; + + return op(grid->left, pkt); + } + + if (x > grid->x) { + if (!grid->right) + return EBUS; + + return op(grid->right, pkt); + } + + return EBUS; +} + +static stat grid_write(struct grid_node *node, struct packet *pkt) +{ + return grid_route(node, pkt, write); +} + +static stat grid_read(struct grid_node *node, struct packet *pkt) +{ + return grid_route(node, pkt, read); +} + +struct component *create_grid_node(uint8_t u, uint8_t v, uint8_t x, uint8_t y) +{ + struct grid_node *node = calloc(1, sizeof(struct grid_node)); + if (!node) + return NULL; + + node->u = u; + node->v = v; + node->x = x; + node->y = y; + node->component.write = (write_callback)grid_write; + node->component.read = (read_callback)grid_read; + + return (struct component *)node; +} + +stat grid_node_connect(struct component *node, + struct component *left, struct component *right, + struct component *up, struct component *down, + struct component *lower, struct component *ascend) +{ + struct grid_node *n = (struct grid_node *)node; + n->left = left; + n->right = right; + n->up = up; + n->down = down; + n->lower = lower; + n->ascend = ascend; + return OK; +} diff --git a/src/components/grid/router.c b/src/components/grid/router.c new file mode 100644 index 0000000..bdb05c8 --- /dev/null +++ b/src/components/grid/router.c @@ -0,0 +1,114 @@ +#include +#include + +struct router_region { + uint32_t addr; + uint32_t size; + struct component *component; +}; + +struct node_router { + struct component component; + struct component *ascend; + struct vec regions; + uint8_t u, v, x, y; +}; + +static struct router_region *find_region(struct node_router *router, uint32_t addr) +{ + for (size_t i = 0; i < vec_len(&router->regions); ++i) { + struct router_region *region = vec_at(&router->regions, i); + + if (addr >= region->addr && addr < region->addr + region->size) + return region; + } + + return NULL; +} + +static stat router_write(struct node_router *router, struct packet *pkt) +{ + uint64_t addr = packet_addr(pkt); + uint8_t u = (addr >> 56) & 0xff; + uint8_t v = (addr >> 48) & 0xff; + uint8_t x = (addr >> 40) & 0xff; + uint8_t y = (addr >> 32) & 0xff; + + if (router->u != u || router->v != v || router->x != x || router->y != y) + return write(router->ascend, pkt); + + struct router_region *region = find_region(router, addr); + if (!region) + return write(router->ascend, pkt); + + return write(region->component, pkt); +} + +static stat router_read(struct node_router *router, struct packet *pkt) +{ + uint64_t addr = packet_addr(pkt); + uint8_t u = (addr >> 56) & 0xff; + uint8_t v = (addr >> 48) & 0xff; + uint8_t x = (addr >> 40) & 0xff; + uint8_t y = (addr >> 32) & 0xff; + + if (router->u != u || router->v != v || router->x != x || router->y != y) + return read(router->ascend, pkt); + + struct router_region *region = find_region(router, addr); + if (!region) + return read(router->ascend, pkt); + + return read(region->component, pkt); +} + +struct component *create_node_router(uint8_t u, uint8_t v, uint8_t x, uint8_t y) +{ + struct node_router *router = calloc(1, sizeof(struct node_router)); + if (!router) + return NULL; + + router->u = u; + router->v = v; + router->x = x; + router->y = y; + + router->component.write = (write_callback)router_write; + router->component.read = (read_callback)router_read; + router->regions = vec_create(sizeof(struct router_region)); + + return (struct component *)router; +} + +stat node_router_add(struct component *router, struct component *component, uint32_t addr, uint32_t size) +{ + struct node_router *nr = (struct node_router *)router; + struct router_region *found = find_region(nr, addr); + if (!found) found = find_region(nr, addr + size); + + if (found) { + error("%s overlaps with %s at %x", + found->component->name, + component->name, + found->addr + ); + + return EEXISTS; + } + + struct router_region r = (struct router_region){ + .addr = addr, + .size = size, + .component = component + }; + vect_append(struct node_region, nr->regions, &r); + + return OK; +} + +stat node_router_ascend(struct component *router, struct component *node) +{ + struct node_router *nr = (struct node_router *)router; + nr->ascend = node; + return OK; +} diff --git a/src/components/grid/source.mk b/src/components/grid/source.mk new file mode 100644 index 0000000..4fb44ec --- /dev/null +++ b/src/components/grid/source.mk @@ -0,0 +1 @@ +SOURCES += src/components/grid/node.c src/components/grid/router.c diff --git a/src/components/uart/simple_uart.c b/src/components/uart/simple_uart.c new file mode 100644 index 0000000..5c97ccd --- /dev/null +++ b/src/components/uart/simple_uart.c @@ -0,0 +1,28 @@ +#include +#include + +struct simple_uart { + struct component component; +}; + +static stat simple_uart_write(struct simple_uart *uart, struct packet *pkt) +{ + (void)uart; + size_t size = packet_size(pkt); + if (size != 1) + return EBUS; + + putchar(*(uint8_t *)packet_data(pkt)); + packet_set_state(pkt, PACKET_DONE); + return OK; +} + +struct component *create_simple_uart() +{ + struct simple_uart *uart = calloc(1, sizeof(struct simple_uart)); + if (!uart) + return NULL; + + uart->component.write = (write_callback)simple_uart_write; + return (struct component *)uart; +} diff --git a/src/components/uart/source.mk b/src/components/uart/source.mk new file mode 100644 index 0000000..af26289 --- /dev/null +++ b/src/components/uart/source.mk @@ -0,0 +1 @@ +SOURCES += src/components/uart/simple_uart.c diff --git a/src/vec.c b/src/vec.c new file mode 100644 index 0000000..e56ac41 --- /dev/null +++ b/src/vec.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ + +#include +#include +#include + +#include + +struct vec vec_create(size_t ns) +{ + return (struct vec) { + .n = 0, + .s = 1, + .ns = ns, + .buf = malloc(ns), + }; +} + +size_t vec_len(struct vec *v) +{ + return v->n; +} + +void *vec_at(struct vec *v, size_t i) +{ + assert(i < v->n && "out of vector bounds"); + return v->buf + i * v->ns; +} + +void *vec_back(struct vec *v) +{ + assert(v->n); + return v->buf + (v->n - 1) * v->ns; +} + +void *vec_pop(struct vec *v) +{ + assert(v->n && "attempting to pop empty vector"); + v->n--; + return v->buf + v->n * v->ns; +} + +void vec_append(struct vec *v, void *n) +{ + v->n++; + if (v->n >= v->s) { + v->s *= 2; + v->buf = realloc(v->buf, v->s * v->ns); + } + + void *p = vec_at(v, v->n - 1); + memcpy(p, n, v->ns); +} + +void vec_reset(struct vec *v) +{ + v->n = 0; +} + +void vec_destroy(struct vec *v) { + free(v->buf); +} + +void vec_sort(struct vec *v, vec_comp_t comp) +{ + qsort(v->buf, v->n, v->ns, comp); +} diff --git a/tests/simple_grid/sim.c b/tests/simple_grid/sim.c new file mode 100644 index 0000000..e0c485b --- /dev/null +++ b/tests/simple_grid/sim.c @@ -0,0 +1,114 @@ +/* testcase for a 64x64 grid of processors, that all just spam the first memory + * region due to there being a 'lock' variable there, more or less the worst + * possible program for performance. */ +#include + +#include +#include +#include +#include +#include +#include +#include + +unsigned char test_grid[] = { + 0xb3, 0x67, 0xb5, 0x00, 0x63, 0x9a, 0x07, 0x0a, 0xb7, 0x17, 0x00, 0x00, + 0x23, 0xb0, 0x07, 0x00, 0x1b, 0x57, 0x35, 0x00, 0xb7, 0x27, 0x00, 0x00, + 0x93, 0x06, 0x80, 0x02, 0x13, 0x77, 0x77, 0x00, 0x23, 0x80, 0xd7, 0x00, + 0x13, 0x07, 0x07, 0x03, 0x93, 0x76, 0x75, 0x00, 0x23, 0x80, 0xe7, 0x00, + 0x13, 0x87, 0x06, 0x03, 0x23, 0x80, 0xe7, 0x00, 0x93, 0x06, 0xc0, 0x02, + 0x1b, 0xd7, 0x35, 0x00, 0x23, 0x80, 0xd7, 0x00, 0x13, 0x77, 0x77, 0x00, + 0x93, 0x06, 0x00, 0x02, 0x23, 0x80, 0xd7, 0x00, 0x13, 0x07, 0x07, 0x03, + 0x93, 0xf6, 0x75, 0x00, 0x23, 0x80, 0xe7, 0x00, 0x13, 0x87, 0x06, 0x03, + 0x23, 0x80, 0xe7, 0x00, 0x13, 0x07, 0x90, 0x02, 0x23, 0x80, 0xe7, 0x00, + 0x93, 0x06, 0xa0, 0x00, 0x23, 0x80, 0xd7, 0x00, 0x1b, 0x87, 0x15, 0x00, + 0x93, 0x06, 0xf0, 0x03, 0x93, 0x77, 0xf7, 0x0f, 0x63, 0x98, 0xd5, 0x00, + 0x1b, 0x05, 0x15, 0x00, 0x13, 0x75, 0xf5, 0x0f, 0x93, 0x07, 0x00, 0x00, + 0x13, 0x15, 0x85, 0x00, 0x33, 0x65, 0xf5, 0x00, 0xb7, 0x17, 0x00, 0x00, + 0x23, 0xb0, 0xa7, 0x00, 0xb7, 0x16, 0x00, 0x00, 0x37, 0x47, 0x00, 0x00, + 0x83, 0xb7, 0x06, 0x00, 0xe3, 0x9e, 0xe7, 0xfe, 0x73, 0x00, 0x10, 0x00, + 0x67, 0x80, 0x00, 0x00, 0x13, 0x17, 0x85, 0x00, 0x33, 0x67, 0xb7, 0x00, + 0xb7, 0x16, 0x00, 0x00, 0x83, 0xb7, 0x06, 0x00, 0xe3, 0x9e, 0xe7, 0xfe, + 0x6f, 0xf0, 0x5f, 0xf4 +}; +unsigned int test_grid_len = 208; + +static struct component *get_grid(struct component **grid, int i, int j, uint8_t x, uint8_t y) +{ + if (i < 0) + return NULL; + + if (j < 0) + return NULL; + + if (i >= x) + return NULL; + + if (j >= y) + return NULL; + + return grid[i * x + j]; +} + +static stat build_grid(struct clock_domain *clk, uint8_t x, uint8_t y) +{ + struct component **grid = calloc(x * y, sizeof(struct component *)); + assert(grid); + + struct component **routers = calloc(x * y, sizeof(struct component *)); + assert(routers); + + for (size_t i = 0; i < x; ++i) + for (size_t j = 0; j < y; ++j) { + struct component *imem = create_simple_mem(4096); + init_simple_mem(imem, 0, test_grid_len, test_grid); + + struct component *dmem = create_simple_mem(4096); + struct component *router = create_node_router(0, 0, i, j); + node_router_add(router, dmem, 4096, 4096); + + struct component *rv64 = create_simple_riscv64(0, imem, router); + simple_riscv64_set_reg(rv64, 10, i); /* a0 */ + simple_riscv64_set_reg(rv64, 11, j); /* a1 */ + + clock_domain_add(clk, rv64); + + routers[i * x + j] = router; + grid[i * x + j] = create_grid_node(0, 0, i, j); + } + + struct component *uart = create_simple_uart(); + node_router_add(routers[0], uart, 8192, 1); + + for (int i = 0; i < x; ++i) + for (int j = 0; j < y; ++j) { + struct component *node = grid[i * x + j]; + struct component *lower = routers[i * x + j]; + struct component *left = get_grid(grid, i - 1, j , x, y); + struct component *right = get_grid(grid, i + 1, j , x, y); + struct component *up = get_grid(grid, i , j + 1, x, y); + struct component *down = get_grid(grid, i , j - 1, x, y); + grid_node_connect(node, left, right, up, down, lower, NULL); + node_router_ascend(lower, node); + } + + free(routers); + free(grid); + return OK; +} + +int main() +{ + struct clock_domain *clk = create_clock_domain(NS(1)); + + stat r = build_grid(clk, 64, 64); + assert(r == OK); + + struct gran_root *root = create_root(); + root_add_clock(root, clk); + + r = root_run(root); + assert(r == OK); + + destroy_root(root); +} diff --git a/tests/simple_grid/source.mk b/tests/simple_grid/source.mk new file mode 100644 index 0000000..e2dd442 --- /dev/null +++ b/tests/simple_grid/source.mk @@ -0,0 +1,6 @@ +GRID_TEST_OBJ != ./scripts/gen-deps --sources "tests/simple_grid/sim.c" + +TEST_PROGS += build/tests/simple_grid/sim + +build/tests/simple_grid/sim: $(GRID_TEST_OBJ) $(OBJS) + $(COMPILE) $(GRID_TEST_OBJ) $(OBJS) -o $@ diff --git a/tests/simple_grid/test.c b/tests/simple_grid/test.c new file mode 100644 index 0000000..782062d --- /dev/null +++ b/tests/simple_grid/test.c @@ -0,0 +1,43 @@ +#define X 64 +#define Y 64 + +void _start(unsigned char x, unsigned char y) +{ + volatile unsigned long *counter = (unsigned long *)4096; + volatile char *uart = (char *)8192; + + /* very hacky, not recommended but good enough for testing */ + if (x == 0 && y == 0) + *counter = 0; + else + while (*counter != ((x << 8) | y)) + ; + + *uart = '('; + /* [0 - 63] as two octal numbers */ + *uart = ((x >> 3) & 0x7) + '0'; + *uart = ((x >> 0) & 0x7) + '0'; + + *uart = ','; + *uart = ' '; + + *uart = ((y >> 3) & 0x7) + '0'; + *uart = ((y >> 0) & 0x7) + '0'; + + *uart = ')'; + *uart = '\n'; + + if (y == 63) { + x++; + y = 0; + } + else + y++; + + *counter = (x << 8) | y; + + while (*counter != (64 << 8)) + ; + + asm ("ebreak\n"); +} diff --git a/tests/simple_riscv32/s.asm b/tests/simple_riscv32/s.asm deleted file mode 100644 index d423dd9..0000000 --- a/tests/simple_riscv32/s.asm +++ /dev/null @@ -1,16 +0,0 @@ -.global _start -_start: -/* sum from 0 to 1000000 */ - -li a0, 0 /* index */ -li a1, 1000000 /* top */ -li a2, 0 /* sum */ - -top: -beq a0, a1, done -add a2, a2, a0 -addi a0, a0, 1 -j top - -done: -ebreak diff --git a/tests/simple_riscv32/sim.c b/tests/simple_riscv32/sim.c deleted file mode 100644 index fcf19a1..0000000 --- a/tests/simple_riscv32/sim.c +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#include - -#include -#include -#include - -unsigned char simple_sum[] = { - 0x13, 0x05, 0x00, 0x00, 0xb7, 0x45, 0x0f, 0x00, 0x93, 0x85, 0x05, 0x24, - 0x13, 0x06, 0x00, 0x00, 0x63, 0x08, 0xb5, 0x00, 0x33, 0x06, 0xa6, 0x00, - 0x13, 0x05, 0x15, 0x00, 0x6f, 0xf0, 0x5f, 0xff, 0x73, 0x00, 0x10, 0x00 -}; - -unsigned int simple_sum_len = 36; - -int main() -{ - const size_t size = 100000; - struct component *imem = create_simple_mem(size); - - /* This works for simple memory, but feels kind of hacky */ - init_simple_mem(imem, 0, simple_sum_len, simple_sum); - - struct component *dmem = create_simple_mem(size); - struct component *rv32 = create_simple_riscv32(0, imem, dmem); - - struct clock_domain *clk = create_clock_domain(NS(1)); - clock_domain_add(clk, rv32); - - struct gran_root *root = create_root(); - root_add_clock(root, clk); - - assert(root_run(root) == OK); - - destroy_root(root); -} diff --git a/tests/simple_riscv32/source.mk b/tests/simple_riscv32/source.mk deleted file mode 100644 index 736c111..0000000 --- a/tests/simple_riscv32/source.mk +++ /dev/null @@ -1,6 +0,0 @@ -RV32_TEST_OBJ != ./scripts/gen-deps --sources "tests/simple_riscv32/sim.c" - -TEST_PROGS += build/tests/simple_riscv32/sim - -build/tests/simple_riscv32/sim: $(RV32_TEST_OBJ) $(TEST_OBJS) $(OBJS) - $(COMPILE) $(RV32_TEST_OBJ) $(TEST_OBJS) $(OBJS) -o $@ diff --git a/tests/simple_riscv64/s.asm b/tests/simple_riscv64/s.asm new file mode 100644 index 0000000..d423dd9 --- /dev/null +++ b/tests/simple_riscv64/s.asm @@ -0,0 +1,16 @@ +.global _start +_start: +/* sum from 0 to 1000000 */ + +li a0, 0 /* index */ +li a1, 1000000 /* top */ +li a2, 0 /* sum */ + +top: +beq a0, a1, done +add a2, a2, a0 +addi a0, a0, 1 +j top + +done: +ebreak diff --git a/tests/simple_riscv64/sim.c b/tests/simple_riscv64/sim.c new file mode 100644 index 0000000..ae3cad4 --- /dev/null +++ b/tests/simple_riscv64/sim.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include + +#include +#include +#include + +unsigned char simple_sum[] = { + 0x13, 0x05, 0x00, 0x00, 0xb7, 0x45, 0x0f, 0x00, 0x93, 0x85, 0x05, 0x24, + 0x13, 0x06, 0x00, 0x00, 0x63, 0x08, 0xb5, 0x00, 0x33, 0x06, 0xa6, 0x00, + 0x13, 0x05, 0x15, 0x00, 0x6f, 0xf0, 0x5f, 0xff, 0x73, 0x00, 0x10, 0x00 +}; + +unsigned int simple_sum_len = 36; + +int main() +{ + const size_t size = 100000; + struct component *imem = create_simple_mem(size); + + /* This works for simple memory, but feels kind of hacky */ + init_simple_mem(imem, 0, simple_sum_len, simple_sum); + + struct component *dmem = create_simple_mem(size); + struct component *rv64 = create_simple_riscv64(0, imem, dmem); + + struct clock_domain *clk = create_clock_domain(NS(1)); + clock_domain_add(clk, rv64); + + struct gran_root *root = create_root(); + root_add_clock(root, clk); + + assert(root_run(root) == OK); + + destroy_root(root); +} diff --git a/tests/simple_riscv64/source.mk b/tests/simple_riscv64/source.mk new file mode 100644 index 0000000..fbc541d --- /dev/null +++ b/tests/simple_riscv64/source.mk @@ -0,0 +1,6 @@ +RV64_TEST_OBJ != ./scripts/gen-deps --sources "tests/simple_riscv64/sim.c" + +TEST_PROGS += build/tests/simple_riscv64/sim + +build/tests/simple_riscv64/sim: $(RV64_TEST_OBJ) $(TEST_OBJS) $(OBJS) + $(COMPILE) $(RV64_TEST_OBJ) $(TEST_OBJS) $(OBJS) -o $@ diff --git a/tests/simple_uart/hello.c b/tests/simple_uart/hello.c new file mode 100644 index 0000000..30719e0 --- /dev/null +++ b/tests/simple_uart/hello.c @@ -0,0 +1,18 @@ +/* compile with + * riscv64-unknown-elf-gcc -ffreestanding -nostdlib -Wl,-Ttext=0 -march=rv64i -mabi=lp64 -O2 hello.c -o simple_hello + * xxd --include simple_hello + */ +void _start() +{ + /* relies on hard-coded system, good enough for this simple test */ + volatile char *uart = (char *)4096; + *uart = 'H'; + *uart = 'e'; + *uart = 'l'; + *uart = 'l'; + *uart = 'o'; + *uart = '!'; + *uart = '\n'; + + asm ("ebreak\n"); +} diff --git a/tests/simple_uart/sim.c b/tests/simple_uart/sim.c new file mode 100644 index 0000000..762b591 --- /dev/null +++ b/tests/simple_uart/sim.c @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include + +#include +#include +#include +#include +#include + +unsigned char simple_hello[] = { + 0xb7, 0x17, 0x00, 0x00, 0x13, 0x07, 0x80, 0x04, 0x23, 0x80, 0xe7, 0x00, + 0x13, 0x07, 0x50, 0x06, 0x23, 0x80, 0xe7, 0x00, 0x13, 0x07, 0xc0, 0x06, + 0x23, 0x80, 0xe7, 0x00, 0x23, 0x80, 0xe7, 0x00, 0x13, 0x07, 0xf0, 0x06, + 0x23, 0x80, 0xe7, 0x00, 0x13, 0x07, 0x10, 0x02, 0x23, 0x80, 0xe7, 0x00, + 0x13, 0x07, 0xa0, 0x00, 0x23, 0x80, 0xe7, 0x00, 0x73, 0x00, 0x10, 0x00, + 0x67, 0x80, 0x00, 0x00 +}; +unsigned int simple_hello_len = 64; + +int main() +{ + const size_t size = 4096; + struct component *imem = create_simple_mem(size); + init_simple_mem(imem, 0, simple_hello_len, simple_hello); + + struct component *dmem = create_simple_mem(size); + struct component *uart = create_simple_uart(); + struct component *bus = create_simple_bus(); + simple_bus_add(bus, dmem, 0, size); + simple_bus_add(bus, uart, size, 1); + + struct component *rv64 = create_simple_riscv64(0, imem, bus); + + struct clock_domain *clk = create_clock_domain(NS(1)); + clock_domain_add(clk, rv64); + + struct gran_root *root = create_root(); + root_add_clock(root, clk); + + assert(root_run(root) == OK); + + destroy_root(root); +} diff --git a/tests/simple_uart/source.mk b/tests/simple_uart/source.mk new file mode 100644 index 0000000..a42a447 --- /dev/null +++ b/tests/simple_uart/source.mk @@ -0,0 +1,6 @@ +UART_TEST_OBJ != ./scripts/gen-deps --sources "tests/simple_uart/sim.c" + +TEST_PROGS += build/tests/simple_uart/sim + +build/tests/simple_uart/sim: $(UART_TEST_OBJ) $(OBJS) + $(COMPILE) $(UART_TEST_OBJ) $(TEST_OBJS) $(OBJS) -o $@ -- cgit v1.3