From f95a85de0082010e4af83e26e99299d601bb48d6 Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Mon, 13 May 2024 00:04:44 +0200 Subject: Some work on CSR --- emulator/bits.h | 15 ++ emulator/csr.c | 51 ++++++ emulator/csr.h | 36 ++++ emulator/csr_machine_information.c | 21 +++ emulator/csr_machine_trap_setup.c | 19 ++ emulator/csr_unpriv_counter_timer.c | 33 ++++ emulator/hart.c | 348 +++++++++++++----------------------- emulator/hart.h | 25 ++- emulator/instruction.h | 80 +++++++++ emulator/lib.c | 4 + emulator/main.c | 3 +- main.asm | 5 +- 12 files changed, 408 insertions(+), 232 deletions(-) create mode 100644 emulator/bits.h create mode 100644 emulator/csr.c create mode 100644 emulator/csr.h create mode 100644 emulator/csr_machine_information.c create mode 100644 emulator/csr_machine_trap_setup.c create mode 100644 emulator/csr_unpriv_counter_timer.c create mode 100644 emulator/instruction.h diff --git a/emulator/bits.h b/emulator/bits.h new file mode 100644 index 0000000..8d4bdff --- /dev/null +++ b/emulator/bits.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +static inline uint32_t sign_extend(uint32_t word, uint32_t size) +{ + const uint32_t mask = 1U << (size - 1); + return (word ^ mask) - mask; +} + +static inline uint64_t sign_extend_64(uint64_t word, uint32_t size) +{ + const uint64_t mask = 1ULL << (size - 1); + return (word ^ mask) - mask; +} diff --git a/emulator/csr.c b/emulator/csr.c new file mode 100644 index 0000000..d2527cf --- /dev/null +++ b/emulator/csr.c @@ -0,0 +1,51 @@ +#include "emulator/csr.h" + +#include "emulator/hart.h" + +#include + +static inline uint8_t csr_priv(uint16_t id) +{ + return (id & 0x300) >> 8; +} + +static inline bool csr_writable(uint16_t id) +{ + return (id & 0xC00) == 0xC00; +} + +uint32_t csr_action(Hart* hart, uint16_t id, uint32_t value, enum CsrAction action) +{ + // @Todo exceptions (bad access (rw, level), non existent, etc) + if (id > 4096 || hart->csrs[id].action) + { + // @Todo + return 0; + } + + if (action > CSR_R && csr_writable(id)) + { + // @Todo + return 0; + } + + if (csr_priv(id) > hart->priv) + { + // @Todo + return 0; + } + + return hart->csrs[id].action(hart, value, action); +} + +void csr_init_unpriv_counter_timer(Hart* hart); +void csr_init_machine_trap_setup(Hart* hart); +void csr_init_machine_information(Hart* hart); + +void csr_init(Hart* hart) +{ + csr_init_unpriv_counter_timer(hart); + csr_init_machine_trap_setup(hart); + csr_init_machine_information(hart); +} + diff --git a/emulator/csr.h b/emulator/csr.h new file mode 100644 index 0000000..89452b0 --- /dev/null +++ b/emulator/csr.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +typedef struct Hart Hart; + +#define CSR_CYCLE 0xC00 +#define CSR_CYCLEH 0xC80 +#define CSR_INSTRET 0xC02 +#define CSR_INSTRETH 0xC82 +#define CSR_TIME 0xC01 +#define CSR_TIMEH 0xC81 + +#define CSR_MVENDORID 0xF11 +#define CSR_MARCHID 0xF12 +#define CSR_MIMPID 0xF13 +#define CSR_MHARTID 0xF14 + +#define CSR_MISA 0x301 + +enum CsrAction +{ + CSR_R = 0, + CSR_W = 1, + CSR_RW = 3, + CSR_RC = 5, + CSR_RS = 7, +}; + +struct Csr +{ + uint32_t (*action)(Hart* hart, uint32_t value_mask, enum CsrAction action); +}; +typedef struct Csr Csr; + +uint32_t csr_action(Hart* hart, uint16_t id, uint32_t value, enum CsrAction action); diff --git a/emulator/csr_machine_information.c b/emulator/csr_machine_information.c new file mode 100644 index 0000000..38a33a1 --- /dev/null +++ b/emulator/csr_machine_information.c @@ -0,0 +1,21 @@ +#include "emulator/csr.h" + +#include "emulator/hart.h" + +static uint32_t csr_minfo_zero(Hart* hart, uint32_t v, enum CsrAction a) +{ + return 0; +} + +static uint32_t csr_mhartid(Hart* hart, uint32_t v, enum CsrAction a) +{ + return hart->hartid; +} + +void csr_init_machine_information(Hart* hart) +{ + hart->csrs[CSR_MVENDORID].action = csr_minfo_zero; + hart->csrs[CSR_MARCHID].action = csr_minfo_zero; + hart->csrs[CSR_MIMPID].action = csr_minfo_zero; + hart->csrs[CSR_MHARTID].action = csr_mhartid; +} diff --git a/emulator/csr_machine_trap_setup.c b/emulator/csr_machine_trap_setup.c new file mode 100644 index 0000000..385460f --- /dev/null +++ b/emulator/csr_machine_trap_setup.c @@ -0,0 +1,19 @@ +#include "emulator/csr.h" + +#include "emulator/hart.h" + +static uint32_t csr_misa(Hart* hart, uint32_t v, enum CsrAction a) +{ + uint32_t misa = 0; + misa |= 1 << 30; // 32-bit + misa |= 1 << 8; // Base ISA + misa |= 1 << 12; // M extension + misa |= 1 << 18; // Supervisor mode + misa |= 1 << 20; // User mode + return misa; +} + +void csr_init_machine_trap_setup(Hart* hart) +{ + hart->csrs[CSR_MISA].action = csr_misa; +} diff --git a/emulator/csr_unpriv_counter_timer.c b/emulator/csr_unpriv_counter_timer.c new file mode 100644 index 0000000..e474520 --- /dev/null +++ b/emulator/csr_unpriv_counter_timer.c @@ -0,0 +1,33 @@ +#include "emulator/csr.h" + +#include "emulator/hart.h" + +static uint32_t csr_cycle(Hart* hart, uint32_t v, enum CsrAction a) +{ + return hart->instret & 0xFFFFFFFF; +} + +static uint32_t csr_cycleh(Hart* hart, uint32_t v, enum CsrAction a) +{ + return hart->instret >> 32; +} + +static uint32_t csr_time(Hart* hart, uint32_t v, enum CsrAction a) +{ + return hart->time & 0xFFFFFFFF; +} + +static uint32_t csr_timeh(Hart* hart, uint32_t v, enum CsrAction a) +{ + return hart->time >> 32; +} + +void csr_init_unpriv_counter_timer(Hart* hart) +{ + hart->csrs[CSR_CYCLE].action = csr_cycle; + hart->csrs[CSR_CYCLEH].action = csr_cycleh; + hart->csrs[CSR_INSTRET].action = csr_cycle; + hart->csrs[CSR_INSTRETH].action = csr_cycleh; + hart->csrs[CSR_TIME].action = csr_time; + hart->csrs[CSR_TIMEH].action = csr_timeh; +} diff --git a/emulator/hart.c b/emulator/hart.c index 297148b..efa0556 100644 --- a/emulator/hart.c +++ b/emulator/hart.c @@ -1,129 +1,106 @@ #include "emulator/hart.h" +#include "emulator/bits.h" +#include "emulator/instruction.h" +#include "emulator/csr.h" + #include #include #include #include #include -#define REG_ZERO 0 -#define REG_RA 1 -#define REG_SP 2 -#define REG_GP 3 -#define REG_TP 4 -#define REG_T0 5 -#define REG_T1 6 -#define REG_T2 7 -#define REG_S0 8 -#define REG_S1 9 -#define REG_A0 10 -#define REG_A1 11 -#define REG_A2 12 -#define REG_A3 13 -#define REG_A4 14 -#define REG_A5 15 -#define REG_A6 16 -#define REG_A7 17 -#define REG_S2 18 -#define REG_S3 19 -#define REG_S4 20 -#define REG_S5 21 -#define REG_S6 22 -#define REG_S7 23 -#define REG_S8 24 -#define REG_S9 25 -#define REG_S10 26 -#define REG_S11 27 -#define REG_T3 28 -#define REG_T4 29 -#define REG_T5 30 -#define REG_T6 31 - -static inline uint32_t sign_extend(uint32_t word, uint32_t size) +#define REG_ZERO 0 +#define REG_RA 1 +#define REG_SP 2 +#define REG_GP 3 +#define REG_TP 4 +#define REG_T0 5 +#define REG_T1 6 +#define REG_T2 7 +#define REG_S0 8 +#define REG_S1 9 +#define REG_A0 10 +#define REG_A1 11 +#define REG_A2 12 +#define REG_A3 13 +#define REG_A4 14 +#define REG_A5 15 +#define REG_A6 16 +#define REG_A7 17 +#define REG_S2 18 +#define REG_S3 19 +#define REG_S4 20 +#define REG_S5 21 +#define REG_S6 22 +#define REG_S7 23 +#define REG_S8 24 +#define REG_S9 25 +#define REG_S10 26 +#define REG_S11 27 +#define REG_T3 28 +#define REG_T4 29 +#define REG_T5 30 +#define REG_T6 31 + +static void handle_ecall(Hart* hart) { - const uint32_t mask = 1U << (size - 1); - return (word ^ mask) - mask; } -static inline uint64_t sign_extend_64(uint64_t word, uint32_t size) +static inline uint32_t load_size(Hart* hart, uint32_t address, uint32_t size) { - const uint64_t mask = 1ULL << (size - 1); - return (word ^ mask) - mask; + if ((address & 0x80000000) == 0) + { + assert(address + size < hart->mem_size); + uint32_t value = 0; + memcpy(&value, hart->mem + address, size); + return value; + } + + return 0; } -struct Instruction +static uint32_t load_byte(Hart* hart, uint32_t address) { - uint8_t opcode; - uint8_t rs1; - uint8_t rs2; - uint8_t rd; - uint8_t funct3; - uint8_t funct7; - uint32_t imm; -}; -typedef struct Instruction Instruction; - -static Instruction decode_r_type(uint32_t word) + return load_size(hart, address, 1); +} + +static uint32_t load_half(Hart* hart, uint32_t address) { - Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.funct3 = (word >> 12) & 0x07; - instruction.rs1 = (word >> 15) & 0x1F; - instruction.rs2 = (word >> 20) & 0x1F; - instruction.funct7 = word >> 25; - return instruction; -}; - -static Instruction decode_i_type(uint32_t word) + return load_size(hart, address, 2); +} + +static uint32_t load_word(Hart* hart, uint32_t address) { - Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.funct3 = (word >> 12) & 0x07; - instruction.rs1 = (word >> 15) & 0x1F; - instruction.imm = sign_extend(word >> 20, 12); - return instruction; -}; - -static Instruction decode_s_type(uint32_t word) + return load_size(hart, address, 4); +} + +static inline void store_size(Hart* hart, uint32_t address, uint32_t value, uint32_t size) { - Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.funct3 = (word >> 12) & 0x07; - instruction.rs1 = (word >> 15) & 0x1F; - instruction.rs2 = (word >> 20) & 0x1F; - instruction.imm = sign_extend(((word >> 7) & 0x1F) | (word >> 25), 12); - return instruction; -}; - -static Instruction decode_b_type(uint32_t word) + if ((address & 0x80000000) == 0) + { + assert(address + size < hart->mem_size); + memcpy(hart->mem + address, &value, size); + } + else if (address == 0x80000000) + { + fwrite(&value, 1, size, stdout); + } +} + +static void store_byte(Hart* hart, uint32_t address, uint8_t value) { - Instruction instruction = decode_s_type(word); - instruction.imm = ((instruction.imm << 11) & 0x800) | (instruction.imm & 0xfffff7ff); - return instruction; -}; + store_size(hart, address, value, 1); +} -static Instruction decode_u_type(uint32_t word) +static void store_half(Hart* hart, uint32_t address, uint16_t value) { - Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.imm = word & 0xFFFFF000; - return instruction; -}; - -static Instruction decode_j_type(uint32_t word) + store_size(hart, address, value, 2); +} + +static void store_word(Hart* hart, uint32_t address, uint32_t value) { - Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.imm = sign_extend( - ((word & 0x80000000) >> 11) | - ((word & 0x000FF000) >> 0) | - ((word & 0x00100000) >> 9) | - ((word & 0x7FE00000) >> 20), 21); - return instruction; + store_size(hart, address, value, 4); } static void execute_op_imm(Hart* hart, uint32_t instruction) @@ -338,62 +315,6 @@ static void execute_branch(Hart* hart, uint32_t instruction) } } -static inline uint32_t load_size(Hart* hart, uint32_t address, uint32_t size) -{ - if ((address & 0x80000000) == 0) - { - assert(address + size < hart->mem_size); - uint32_t value = 0; - memcpy(&value, hart->mem + address, size); - return value; - } - - return 0; -} - -static uint32_t load_byte(Hart* hart, uint32_t address) -{ - return load_size(hart, address, 1); -} - -static uint32_t load_half(Hart* hart, uint32_t address) -{ - return load_size(hart, address, 2); -} - -static uint32_t load_word(Hart* hart, uint32_t address) -{ - return load_size(hart, address, 4); -} - -static inline void store_size(Hart* hart, uint32_t address, uint32_t value, uint32_t size) -{ - if ((address & 0x80000000) == 0) - { - assert(address + size < hart->mem_size); - memcpy(hart->mem + address, &value, size); - } - else if (address == 0x80000000) - { - fwrite(&value, 1, size, stdout); - } -} - -static void store_byte(Hart* hart, uint32_t address, uint8_t value) -{ - store_size(hart, address, value, 1); -} - -static void store_half(Hart* hart, uint32_t address, uint16_t value) -{ - store_size(hart, address, value, 2); -} - -static void store_word(Hart* hart, uint32_t address, uint32_t value) -{ - store_size(hart, address, value, 4); -} - static void execute_load(Hart* hart, uint32_t instruction) { const Instruction inst = decode_i_type(instruction); @@ -462,43 +383,6 @@ static void execute_misc_mem(Hart* hart, uint32_t instruction) } } -static void handle_ecall(Hart* hart) -{ - if (hart->regs[REG_A0] == 0) - { - hart->halted = true; - } -} - -static uint32_t crs_read(Hart* hart, uint16_t id) -{ - // @Todo - return 0; -} - -static void crs_write(Hart* hart, uint16_t id, uint32_t new_value) -{ - // @Todo -} - -static uint32_t crs_read_write(Hart* hart, uint16_t id, uint32_t new_value) -{ - // @Todo - return 0; -} - -static uint32_t crs_read_clear(Hart* hart, uint16_t id, uint32_t mask) -{ - // @Todo - return 0; -} - -static uint32_t crs_read_set(Hart* hart, uint16_t id, uint32_t mask) -{ - // @Todo - return 0; -} - static void execute_system(Hart* hart, uint32_t instruction) { const Instruction inst = decode_i_type(instruction); @@ -528,32 +412,32 @@ static void execute_system(Hart* hart, uint32_t instruction) } break; } - case 1: // CRSRW + case 1: // CSRRW { - uint16_t crs_id = inst.imm & 0xFFF; + uint16_t csr_id = inst.imm & 0xFFF; if (inst.rd == 0) { - crs_write(hart, crs_id, hart->regs[inst.rs1]); + csr_action(hart, csr_id, hart->regs[inst.rs1], CSR_W); } else { - hart->regs[inst.rd] = crs_read_write(hart, crs_id, hart->regs[inst.rs1]); + hart->regs[inst.rd] = csr_action(hart, csr_id, hart->regs[inst.rs1], CSR_RW); } break; } - case 2: // CRSRS + case 2: // CSRRS { - uint16_t crs_id = inst.imm & 0xFFF; + uint16_t csr_id = inst.imm & 0xFFF; uint32_t value = 0; if (inst.rs1 == 0) { - value = crs_read(hart, crs_id); + value = csr_action(hart, csr_id, 0, CSR_R); } else { - value = crs_read_set(hart, crs_id, hart->regs[inst.rs1]); + value = csr_action(hart, csr_id, hart->regs[inst.rs1], CSR_RS); } if (inst.rd != 0) @@ -562,18 +446,18 @@ static void execute_system(Hart* hart, uint32_t instruction) } break; } - case 3: // CRSRC + case 3: // CSRRC { - uint16_t crs_id = inst.imm & 0xFFF; + uint16_t csr_id = inst.imm & 0xFFF; uint32_t value = 0; if (inst.rs1 == 0) { - value = crs_read(hart, crs_id); + value = csr_action(hart, csr_id, 0, CSR_R); } else { - value = crs_read_clear(hart, crs_id, hart->regs[inst.rs1]); + value = csr_action(hart, csr_id, hart->regs[inst.rs1], CSR_RC); } if (inst.rd != 0) @@ -582,32 +466,32 @@ static void execute_system(Hart* hart, uint32_t instruction) } break; } - case 5: // CRSRWI + case 5: // CSRRWI { - uint16_t crs_id = inst.imm & 0xFFF; + uint16_t csr_id = inst.imm & 0xFFF; if (inst.rd == 0) { - crs_write(hart, crs_id, inst.rs1); + csr_action(hart, csr_id, inst.rs1, CSR_W); } else { - hart->regs[inst.rd] = crs_read_write(hart, crs_id, inst.rs1); + hart->regs[inst.rd] = csr_action(hart, csr_id, inst.rs1, CSR_RW); } break; } - case 6: // CRSRSI + case 6: // CSRRSI { - uint16_t crs_id = inst.imm & 0xFFF; + uint16_t csr_id = inst.imm & 0xFFF; uint32_t value = 0; if (inst.rs1 == 0) { - value = crs_read(hart, crs_id); + value = csr_action(hart, csr_id, 0, CSR_R); } else { - value = crs_read_set(hart, crs_id, inst.rs1); + value = csr_action(hart, csr_id, inst.rs1, CSR_RS); } if (inst.rd != 0) @@ -616,18 +500,18 @@ static void execute_system(Hart* hart, uint32_t instruction) } break; } - case 7: // CRSRCI + case 7: // CSRRCI { - uint16_t crs_id = inst.imm & 0xFFF; + uint16_t csr_id = inst.imm & 0xFFF; uint32_t value = 0; if (inst.rs1 == 0) { - value = crs_read(hart, crs_id); + value = csr_action(hart, csr_id, 0, CSR_R); } else { - value = crs_read_clear(hart, crs_id, inst.rs1); + value = csr_action(hart, csr_id, inst.rs1, CSR_RC); } if (inst.rd != 0) @@ -644,6 +528,10 @@ static void execute_system(Hart* hart, uint32_t instruction) void execute(Hart* hart, uint32_t instruction) { + // @Todo Better time (same on all cores) + // @Todo Memory-map mtime and mtimecmp + hart->time = __rdtsc(); + switch (instruction & 0x7f) { case 0x03: @@ -721,16 +609,30 @@ void execute(Hart* hart, uint32_t instruction) } assert(hart->regs[0] == 0); + + hart->instret += 1; } void execute_from(Hart* hart, uint32_t start_address) { hart->pc = start_address; - hart->halted = false; - while (!hart->halted) + while (true) { uint32_t instruction = load_word(hart, hart->pc); execute(hart, instruction); } } + +void csr_init(Hart* hart); + +void hart_init(Hart* hart, uint32_t id, char* mem, uint32_t mem_size) +{ + memset(hart, 0, sizeof(Hart)); + hart->hartid = id; + hart->priv = PRIV_M; + hart->mem = mem; + hart->mem_size = mem_size; + + csr_init(hart); +} diff --git a/emulator/hart.h b/emulator/hart.h index 8e04d19..7135ed7 100644 --- a/emulator/hart.h +++ b/emulator/hart.h @@ -1,19 +1,38 @@ #pragma once +#include "emulator/csr.h" + #include #include +#define PRIV_U 0 +#define PRIV_S 1 +#define PRIV_M 3 + struct Hart { - uint32_t pc; + uint32_t hartid; + uint32_t regs[32]; + + uint32_t pc; + uint8_t priv; + // @Todo Proper memory system char* mem; uint32_t mem_size; - - bool halted; + + // @Todo Deduplicate per machine + Csr csrs[4096]; + + uint64_t instret; + + // @Todo Deduplicate per machine + uint64_t time; }; typedef struct Hart Hart; +void hart_init(Hart* hart, uint32_t id, char* mem, uint32_t mem_size); + void execute(Hart* hart, uint32_t instruction); void execute_from(Hart* hart, uint32_t start_address); diff --git a/emulator/instruction.h b/emulator/instruction.h new file mode 100644 index 0000000..97c9fe9 --- /dev/null +++ b/emulator/instruction.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include "emulator/bits.h" + +struct Instruction +{ + uint8_t opcode; + uint8_t rs1; + uint8_t rs2; + uint8_t rd; + uint8_t funct3; + uint8_t funct7; + uint32_t imm; +}; +typedef struct Instruction Instruction; + +static Instruction decode_r_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.funct3 = (word >> 12) & 0x07; + instruction.rs1 = (word >> 15) & 0x1F; + instruction.rs2 = (word >> 20) & 0x1F; + instruction.funct7 = word >> 25; + return instruction; +}; + +static Instruction decode_i_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.funct3 = (word >> 12) & 0x07; + instruction.rs1 = (word >> 15) & 0x1F; + instruction.imm = sign_extend(word >> 20, 12); + return instruction; +}; + +static Instruction decode_s_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.funct3 = (word >> 12) & 0x07; + instruction.rs1 = (word >> 15) & 0x1F; + instruction.rs2 = (word >> 20) & 0x1F; + instruction.imm = sign_extend(((word >> 7) & 0x1F) | (word >> 25), 12); + return instruction; +}; + +static Instruction decode_b_type(uint32_t word) +{ + Instruction instruction = decode_s_type(word); + instruction.imm = ((instruction.imm << 11) & 0x800) | (instruction.imm & 0xfffff7ff); + return instruction; +}; + +static Instruction decode_u_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.imm = word & 0xFFFFF000; + return instruction; +}; + +static Instruction decode_j_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.imm = sign_extend( + ((word & 0x80000000) >> 11) | + ((word & 0x000FF000) >> 0) | + ((word & 0x00100000) >> 9) | + ((word & 0x7FE00000) >> 20), 21); + return instruction; +} diff --git a/emulator/lib.c b/emulator/lib.c index 453a3f9..6c3c7d9 100644 --- a/emulator/lib.c +++ b/emulator/lib.c @@ -1,3 +1,7 @@ #include "hart.c" #include "elf.c" +#include "csr.c" +#include "csr_unpriv_counter_timer.c" +#include "csr_machine_trap_setup.c" +#include "csr_machine_information.c" diff --git a/emulator/main.c b/emulator/main.c index 4bf99a3..5cfbd53 100644 --- a/emulator/main.c +++ b/emulator/main.c @@ -24,8 +24,7 @@ int main(int argc, char* argv[]) } struct Hart hart = {0}; - hart.mem = mem; - hart.mem_size = mem_size; + hart_init(&hart, 0, mem, mem_size); execute_from(&hart, start_address); diff --git a/main.asm b/main.asm index 3ae0850..2d41e48 100644 --- a/main.asm +++ b/main.asm @@ -13,13 +13,10 @@ l1: ret _main: - csrr a0, mhartid - la a0, my_str call print - li a0, 0 - ecall +halt: j halt .section .rodata my_str: .string "Hello, world!\n" -- cgit