From 4bdc440debf1ee030816d21b145ec65609dc9908 Mon Sep 17 00:00:00 2001 From: Alasdair Date: Tue, 14 May 2024 22:39:33 +0100 Subject: [PATCH] Handle address translation for misaligned loads and stores better Refactor the LOAD and STORE instruction so they split misaligned accesses into multiple sub-accesses and perform address translation separately. This means we should handle the case where a misaligned access straddles a page boundary in a sensible way, even if we don't yet cover the full range of possibilities allowed for any RISC-V implementation. There are options for the order in which misaligned happen, i.e. from high-to-low or from low-to-high as well as the granularity of the splitting, either all the way to bytes or to the largest aligned size. In addition tidy up the implementation in a few ways: - Very long lines on the LOAD encdec were fixed by adding a helper - Add some linebreaks in the code so it reads as less claustrophobic - Ensure we use the same names for arguments in encdec/execute/assembly. Previously we used 'size' and 'width'. I opted for 'width' consistently. --- Makefile | 1 + c_emulator/riscv_platform.c | 10 ++ c_emulator/riscv_platform.h | 3 + c_emulator/riscv_platform_impl.c | 4 + c_emulator/riscv_platform_impl.h | 3 + c_emulator/riscv_sim.c | 16 ++++ model/prelude.sail | 1 + model/riscv_insts_base.sail | 92 +++++++++--------- model/riscv_mem.sail | 8 ++ model/riscv_vmem_utils.sail | 155 +++++++++++++++++++++++++++++++ ocaml_emulator/platform.ml | 3 + 11 files changed, 252 insertions(+), 44 deletions(-) create mode 100644 model/riscv_vmem_utils.sail diff --git a/Makefile b/Makefile index 268e36b9b..c9a753579 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,7 @@ SAIL_VM_SRCS += riscv_vmem_pte.sail SAIL_VM_SRCS += riscv_vmem_ptw.sail SAIL_VM_SRCS += riscv_vmem_tlb.sail SAIL_VM_SRCS += riscv_vmem.sail +SAIL_VM_SRCS += riscv_vmem_utils.sail # Non-instruction sources PRELUDE = prelude.sail $(SAIL_XLEN) $(SAIL_FLEN) $(SAIL_VLEN) prelude_mem_metadata.sail prelude_mem.sail diff --git a/c_emulator/riscv_platform.c b/c_emulator/riscv_platform.c index 644cb1e8d..21b4121cf 100644 --- a/c_emulator/riscv_platform.c +++ b/c_emulator/riscv_platform.c @@ -62,6 +62,16 @@ bool sys_enable_bext(unit u) return rv_enable_bext; } +bool sys_misaligned_order_decreasing(unit u) +{ + return rv_misaligned_order_decreasing; +} + +bool sys_misaligned_to_byte(unit u) +{ + return rv_misaligned_to_byte; +} + uint64_t sys_pmp_count(unit u) { return rv_pmp_count; diff --git a/c_emulator/riscv_platform.h b/c_emulator/riscv_platform.h index 450a64eba..d95312b2e 100644 --- a/c_emulator/riscv_platform.h +++ b/c_emulator/riscv_platform.h @@ -12,6 +12,9 @@ bool sys_enable_writable_fiom(unit); bool sys_enable_vext(unit); bool sys_enable_bext(unit); +bool sys_misaligned_order_decreasing(unit); +bool sys_misaligned_to_byte(unit); + uint64_t sys_pmp_count(unit); uint64_t sys_pmp_grain(unit); diff --git a/c_emulator/riscv_platform_impl.c b/c_emulator/riscv_platform_impl.c index cad634ef2..152b12baa 100644 --- a/c_emulator/riscv_platform_impl.c +++ b/c_emulator/riscv_platform_impl.c @@ -6,6 +6,10 @@ uint64_t rv_pmp_count = 0; uint64_t rv_pmp_grain = 0; +/* Defaults for misaligned access behavior in the generated simulator */ +bool rv_misaligned_order_decreasing = false; +bool rv_misaligned_to_byte = false; + bool rv_enable_svinval = false; bool rv_enable_zcb = false; bool rv_enable_zfinx = false; diff --git a/c_emulator/riscv_platform_impl.h b/c_emulator/riscv_platform_impl.h index 111090d39..0eb41a4a2 100644 --- a/c_emulator/riscv_platform_impl.h +++ b/c_emulator/riscv_platform_impl.h @@ -11,6 +11,9 @@ extern uint64_t rv_pmp_count; extern uint64_t rv_pmp_grain; +extern bool rv_misaligned_order_decreasing; +extern bool rv_misaligned_to_byte; + extern bool rv_enable_svinval; extern bool rv_enable_zcb; extern bool rv_enable_zfinx; diff --git a/c_emulator/riscv_sim.c b/c_emulator/riscv_sim.c index 913543144..ca64ddd68 100644 --- a/c_emulator/riscv_sim.c +++ b/c_emulator/riscv_sim.c @@ -56,6 +56,8 @@ enum { OPT_PMP_GRAIN, OPT_ENABLE_SVINVAL, OPT_ENABLE_ZCB, + OPT_MISALIGNED_ORDER_DEC, + OPT_MISALIGNED_TO_BYTE, }; static bool do_dump_dts = false; @@ -127,6 +129,8 @@ static struct option options[] = { {"enable-misaligned", no_argument, 0, 'm' }, {"pmp-count", required_argument, 0, OPT_PMP_COUNT }, {"pmp-grain", required_argument, 0, OPT_PMP_GRAIN }, + {"misaligned-order-decreasing", no_argument, 0, OPT_MISALIGNED_ORDER_DEC}, + {"misaligned-to-byte", no_argument, 0, OPT_MISALIGNED_TO_BYTE }, {"enable-next", no_argument, 0, 'N' }, {"ram-size", required_argument, 0, 'z' }, {"disable-compressed", no_argument, 0, 'C' }, @@ -315,6 +319,18 @@ static int process_args(int argc, char **argv) } rv_pmp_grain = pmp_grain; break; + case OPT_MISALIGNED_ORDER_DEC: + fprintf(stderr, + "misaligned access virtual addresses will be translated in " + "decreasing order.\n"); + rv_misaligned_order_decreasing = true; + break; + case OPT_MISALIGNED_TO_BYTE: + fprintf(stderr, + "misaligned accesses will be split into individual " + "byte operations.\n"); + rv_misaligned_to_byte = true; + break; case 'C': fprintf(stderr, "disabling RVC compressed instructions.\n"); rv_enable_rvc = false; diff --git a/model/prelude.sail b/model/prelude.sail index 45d49c0ed..c8b02b0d0 100644 --- a/model/prelude.sail +++ b/model/prelude.sail @@ -10,6 +10,7 @@ default Order dec $include $include +$include $include $include $include "mapping.sail" diff --git a/model/riscv_insts_base.sail b/model/riscv_insts_base.sail index 599c0a7a4..450bbe23d 100644 --- a/model/riscv_insts_base.sail +++ b/model/riscv_insts_base.sail @@ -299,8 +299,8 @@ union clause ast = LOAD : (bits(12), regidx, regidx, bool, word_width, bool, boo /* unsigned loads are only present for widths strictly less than xlen, signed loads also present for widths equal to xlen */ -mapping clause encdec = LOAD(imm, rs1, rd, is_unsigned, size, false, false) if (size_bytes(size) < sizeof(xlen_bytes)) | (not(is_unsigned) & size_bytes(size) <= sizeof(xlen_bytes)) - <-> imm @ rs1 @ bool_bits(is_unsigned) @ size_enc(size) @ rd @ 0b0000011 if (size_bytes(size) < sizeof(xlen_bytes)) | (not(is_unsigned) & size_bytes(size) <= sizeof(xlen_bytes)) +function valid_load_encdec(width : word_width, is_unsigned : bool) -> bool = + (size_bytes(width) < sizeof(xlen_bytes)) | (not(is_unsigned) & size_bytes(width) <= sizeof(xlen_bytes)) val extend_value : forall 'n, 0 < 'n <= xlen. (bool, bits('n)) -> xlenbits function extend_value(is_unsigned, value) = if is_unsigned then zero_extend(value) else sign_extend(value) @@ -313,9 +313,8 @@ function is_aligned(vaddr : xlenbits, width : word_width) -> bool = DOUBLE => vaddr[2..0] == zeros(), } -// Return true if the address is misaligned and we don't support misaligned access. -function check_misaligned(vaddr : xlenbits, width : word_width) -> bool = - not(plat_enable_misaligned_access()) & not(is_aligned(vaddr, width)) +mapping clause encdec = LOAD(imm, rs1, rd, is_unsigned, width, false, false) if valid_load_encdec(width, is_unsigned) + <-> imm @ rs1 @ bool_bits(is_unsigned) @ size_enc(width) @ rd @ 0b0000011 if valid_load_encdec(width, is_unsigned) function clause execute(LOAD(imm, rs1, rd, is_unsigned, width, aq, rl)) = { let offset : xlenbits = sign_extend(imm); @@ -327,20 +326,29 @@ function clause execute(LOAD(imm, rs1, rd, is_unsigned, width, aq, rl)) = { /* Get the address, X(rs1) + offset. Some extensions perform additional checks on address validity. */ match ext_data_get_addr(rs1, offset, Read(Data), width_bytes) { - Ext_DataAddr_Error(e) => { ext_handle_data_check_error(e); RETIRE_FAIL }, + Ext_DataAddr_Error(e) => { + ext_handle_data_check_error(e); + RETIRE_FAIL + }, + Ext_DataAddr_OK(vaddr) => { - if check_misaligned(vaddr, width) - then { handle_mem_exception(vaddr, E_Load_Addr_Align()); RETIRE_FAIL } - else match translateAddr(vaddr, Read(Data)) { - TR_Failure(e, _) => { handle_mem_exception(vaddr, e); RETIRE_FAIL }, - TR_Address(paddr, _) => - - match mem_read(Read(Data), paddr, width_bytes, aq, rl, false) { - MemValue(result) => { X(rd) = extend_value(is_unsigned, result); RETIRE_SUCCESS }, - MemException(e) => { handle_mem_exception(vaddr, e); RETIRE_FAIL }, - }, + if check_misaligned(vaddr, width) then { + handle_mem_exception(vaddr, E_Load_Addr_Align()); + return RETIRE_FAIL + }; + + match vmem_read(vaddr, width_bytes, aq, rl, false) { + Err(vaddr, e) => { + handle_mem_exception(vaddr, e); + RETIRE_FAIL + }, + + Ok(data) => { + X(rd) = extend_value(is_unsigned, data); + RETIRE_SUCCESS + } } - }, + } } } @@ -362,17 +370,15 @@ mapping maybe_u = { false <-> "" } -mapping clause assembly = LOAD(imm, rs1, rd, is_unsigned, size, aq, rl) - <-> "l" ^ size_mnemonic(size) ^ maybe_u(is_unsigned) ^ maybe_aq(aq) ^ maybe_rl(rl) ^ spc() ^ reg_name(rd) ^ sep() ^ hex_bits_signed_12(imm) ^ "(" ^ reg_name(rs1) ^ ")" +mapping clause assembly = LOAD(imm, rs1, rd, is_unsigned, width, aq, rl) + <-> "l" ^ size_mnemonic(width) ^ maybe_u(is_unsigned) ^ maybe_aq(aq) ^ maybe_rl(rl) ^ spc() ^ reg_name(rd) ^ sep() ^ hex_bits_signed_12(imm) ^ "(" ^ reg_name(rs1) ^ ")" /* ****************************************************************** */ union clause ast = STORE : (bits(12), regidx, regidx, word_width, bool, bool) -mapping clause encdec = STORE(imm7 @ imm5, rs2, rs1, size, false, false) if size_bytes(size) <= sizeof(xlen_bytes) - <-> imm7 : bits(7) @ rs2 @ rs1 @ 0b0 @ size_enc(size) @ imm5 : bits(5) @ 0b0100011 if size_bytes(size) <= sizeof(xlen_bytes) +mapping clause encdec = STORE(imm7 @ imm5, rs2, rs1, width, false, false) if size_bytes(width) <= sizeof(xlen_bytes) + <-> imm7 : bits(7) @ rs2 @ rs1 @ 0b0 @ size_enc(width) @ imm5 : bits(5) @ 0b0100011 if size_bytes(width) <= sizeof(xlen_bytes) -/* NOTE: Currently, we only EA if address translation is successful. - This may need revisiting. */ function clause execute (STORE(imm, rs2, rs1, width, aq, rl)) = { let offset : xlenbits = sign_extend(imm); let width_bytes = size_bytes(width); @@ -383,32 +389,30 @@ function clause execute (STORE(imm, rs2, rs1, width, aq, rl)) = { /* Get the address, X(rs1) + offset. Some extensions perform additional checks on address validity. */ match ext_data_get_addr(rs1, offset, Write(Data), width_bytes) { - Ext_DataAddr_Error(e) => { ext_handle_data_check_error(e); RETIRE_FAIL }, - Ext_DataAddr_OK(vaddr) => - if check_misaligned(vaddr, width) - then { handle_mem_exception(vaddr, E_SAMO_Addr_Align()); RETIRE_FAIL } - else match translateAddr(vaddr, Write(Data)) { - TR_Failure(e, _) => { handle_mem_exception(vaddr, e); RETIRE_FAIL }, - TR_Address(paddr, _) => { - let eares = mem_write_ea(paddr, width_bytes, aq, rl, false); - match (eares) { - MemException(e) => { handle_mem_exception(vaddr, e); RETIRE_FAIL }, - MemValue(_) => { - let rs2_val = X(rs2); - match mem_write_value(paddr, width_bytes, rs2_val[width_bytes * 8 - 1 .. 0], aq, rl, false) { - MemValue(true) => RETIRE_SUCCESS, - MemValue(false) => internal_error(__FILE__, __LINE__, "store got false from mem_write_value"), - MemException(e) => { handle_mem_exception(vaddr, e); RETIRE_FAIL } - } - } - } + Ext_DataAddr_Error(e) => { + ext_handle_data_check_error(e); + RETIRE_FAIL + }, + + Ext_DataAddr_OK(vaddr) => { + if check_misaligned(vaddr, width) then { + handle_mem_exception(vaddr, E_SAMO_Addr_Align()); + return RETIRE_FAIL + }; + + match vmem_write_from_register(vaddr, width_bytes, unsigned(rs2), aq, rl, false) { + Ok() => RETIRE_SUCCESS, + Err(vaddr, e) => { + handle_mem_exception(vaddr, e); + return RETIRE_FAIL } } + } } } -mapping clause assembly = STORE(imm, rs2, rs1, size, aq, rl) - <-> "s" ^ size_mnemonic(size) ^ maybe_aq(aq) ^ maybe_rl(rl) ^ spc() ^ reg_name(rs2) ^ sep() ^ hex_bits_signed_12(imm) ^ opt_spc() ^ "(" ^ opt_spc() ^ reg_name(rs1) ^ opt_spc() ^ ")" +mapping clause assembly = STORE(imm, rs2, rs1, width, aq, rl) + <-> "s" ^ size_mnemonic(width) ^ maybe_aq(aq) ^ maybe_rl(rl) ^ spc() ^ reg_name(rs2) ^ sep() ^ hex_bits_signed_12(imm) ^ opt_spc() ^ "(" ^ opt_spc() ^ reg_name(rs1) ^ opt_spc() ^ ")" /* ****************************************************************** */ union clause ast = ADDIW : (bits(12), regidx, regidx) diff --git a/model/riscv_mem.sail b/model/riscv_mem.sail index 82227ad7e..cbcbef948 100644 --- a/model/riscv_mem.sail +++ b/model/riscv_mem.sail @@ -38,6 +38,14 @@ function is_aligned_addr forall 'n. (addr : xlenbits, width : int('n)) -> bool = unsigned(addr) % width == 0 +function check_misaligned(vaddr : xlenbits, width : word_width) -> bool = { + if plat_enable_misaligned_access() then { + false + } else { + not(is_aligned_addr(vaddr, size_bytes(width))) + } +} + function read_kind_of_flags (aq : bool, rl : bool, res : bool) -> option(read_kind) = match (aq, rl, res) { (false, false, false) => Some(Read_plain), diff --git a/model/riscv_vmem_utils.sail b/model/riscv_vmem_utils.sail new file mode 100644 index 000000000..06041d01a --- /dev/null +++ b/model/riscv_vmem_utils.sail @@ -0,0 +1,155 @@ +/*=======================================================================================*/ +/* This Sail RISC-V architecture model, comprising all files and */ +/* directories except where otherwise noted is subject the BSD */ +/* two-clause license in the LICENSE file. */ +/* */ +/* SPDX-License-Identifier: BSD-2-Clause */ +/*=======================================================================================*/ + +/* This file implements utility functions for accessing memory that + * can be used by instruction definitions. + */ + +/* This is a external option that controls the order in which the model + * performs misaligned accesses. + */ +val sys_misaligned_order_decreasing = { + ocaml: "Platform.misaligned_order_decreasing", + _: "sys_misaligned_order_decreasing" +} : unit -> bool + +/* This is an external option that, when true, causes all misaligned accesses + * to be split into single byte operations. + */ +val sys_misaligned_to_byte = { + ocaml: "Platform.misaligned_to_byte", + _: "sys_misaligned_to_byte" +} : unit -> bool + +val split_misaligned : forall 'width, 'width in {1, 2, 4, 8}. + (xlenbits, int('width)) -> {'n 'bytes, 'width == 'n * 'bytes & 'bytes > 0. (int('n), int('bytes))} + +function split_misaligned(vaddr, width) = { + let misaligned_to_byte = sys_misaligned_to_byte(); + match width { + 1 => (1, 1), + + 2 if is_aligned_addr(vaddr, 2) => (1, 2), + 2 => (2, 1), + + 4 if is_aligned_addr(vaddr, 4) => (1, 4), + 4 if is_aligned_addr(vaddr, 2) & not(misaligned_to_byte) => (2, 2), + 4 => (4, 1), + + 8 if is_aligned_addr(vaddr, 8) => (1, 8), + 8 if is_aligned_addr(vaddr, 4) & not(misaligned_to_byte) => (2, 4), + 8 if is_aligned_addr(vaddr, 2) & not(misaligned_to_byte) => (4, 2), + 8 => (8, 1) + } +} + +type valid_misaligned_order('n, 'first, 'last, 'step) -> Bool = + ('first == 0 & 'last == 'n - 1 & 'step == 1) + | ('first == 'n - 1 & 'last == 0 & 'step == -1) + +val misaligned_order : forall 'n. + int('n) -> {'first 'last 'step, valid_misaligned_order('n, 'first, 'last, 'step). (int('first), int('last), int('step))} + +function misaligned_order(n) = { + if sys_misaligned_order_decreasing() then { + (n - 1, 0, -1) + } else { + (0, n - 1, 1) + } +} + +val vmem_read : forall 'width, 'width in {1, 2, 4, 8} & 'width <= xlen_bytes. + (xlenbits, int('width), bool, bool, bool) -> result(bits(8 * 'width), (xlenbits, ExceptionType)) + +function vmem_read(vaddr, bytes, aq, rl, res) = { + /* If the load is misaligned, split into `n` (single-copy-atomic) memory operations, + each of `bytes` width. If the load is aligned, then `n` = 1 and bytes will remain + unchanged. */ + let ('n, bytes) = split_misaligned(vaddr, bytes); + var data = zeros(8 * n * bytes); + + let (first, last, step) = misaligned_order(n); + var i : range(0, 'n - 1) = first; + var finished : bool = false; + + repeat { + let offset = i; + let vaddr = vaddr + (offset * bytes); + match translateAddr(vaddr, Read(Data)) { + TR_Failure(e, _) => return Err(vaddr, e), + + TR_Address(paddr, _) => match mem_read(Read(Data), paddr, bytes, aq, rl, res) { + MemException(e) => return Err(vaddr, e), + + MemValue(v) => { + data[(8 * (offset + 1) * bytes) - 1 .. 8 * offset * bytes] = v + }, + } + }; + + if offset == last then { + finished = true + } else { + i = offset + step + } + } until finished; + + Ok(data) +} + +/* Currently this function takes the X register index as an argument. + * It does this so the register access only occurs after the effective + * address for the access has been announced. This is for the RVWMO + * operational model. + */ +val vmem_write_from_register : forall 'width, 'width in {1, 2, 4, 8} & 'width <= xlen_bytes. + (xlenbits, int('width), range(0, 31), bool, bool, bool) -> result(unit, (xlenbits, ExceptionType)) + +function vmem_write_from_register(vaddr, bytes, reg, aq, rl, res) = { + /* If the store is misaligned, split into `n` (single-copy-atomic) memory operations, + each of `bytes` width. If the store is aligned, then `n` = 1 and bytes will remain + unchanged. */ + let ('n, bytes) = split_misaligned(vaddr, bytes); + + let (first, last, step) = misaligned_order(n); + var i : range(0, 'n - 1) = first; + var finished : bool = false; + + repeat { + let offset = i; + let vaddr = vaddr + (offset * bytes); + match translateAddr(vaddr, Write(Data)) { + TR_Failure(e, _) => return Err(vaddr, e), + + /* NOTE: Currently, we only announce the effective address if address translation is successful. + This may need revisiting, particularly in the misaligned case. */ + TR_Address(paddr, _) => match mem_write_ea(paddr, bytes, aq, rl, false) { + MemException(e) => return Err(vaddr, e), + + MemValue(()) => { + let reg_val = X(reg); + let data = reg_val[(8 * (offset + 1) * bytes) - 1 .. 8 * offset * bytes]; + match mem_write_value(paddr, bytes, data, aq, rl, false) { + MemException(e) => return Err(vaddr, e), + MemValue(true) => (), + MemValue(false) => + internal_error(__FILE__, __LINE__, "got false from mem_write_value"), + } + } + } + }; + + if offset == last then { + finished = true + } else { + i = offset + step + } + } until finished; + + Ok() +} diff --git a/ocaml_emulator/platform.ml b/ocaml_emulator/platform.ml index d10029a8b..769a43fbb 100644 --- a/ocaml_emulator/platform.ml +++ b/ocaml_emulator/platform.ml @@ -99,6 +99,9 @@ let enable_writable_fiom () = !config_enable_writable_fiom let pmp_count () = !config_pmp_count let pmp_grain () = !config_pmp_grain +let misaligned_to_byte () = false +let misaligned_order_decreasing () = false + let rom_base () = arch_bits_of_int64 P.rom_base let rom_size () = arch_bits_of_int !rom_size_ref