From c08ea361e0e1ff2b20bd61f3a03ef1ff4bb52a5f Mon Sep 17 00:00:00 2001 From: dhower-qc <134728312+dhower-qc@users.noreply.github.com> Date: Sun, 28 Jul 2024 18:22:09 -0400 Subject: [PATCH] Many updates: (#8) * Many updates: * Highlighted IDL compilation errors * Exact lines numbers from IDL, even when source comes from YAML * Fixed bug generating decode variable indicies * Few updates to make older versions of singularity acceptable * Complete virtual address translation for all translation sizes (Sv*) * Implement Zalrsc * Parse sw_write(csr_value) for CSRs * Add Svade/Svadu * break globals.isa into builtins/include files * enable passing Strings as function arguments in IDL * compiler performance improvements --- .github/workflows/regress.yml | 6 +- README.adoc | 2 +- arch/csr/menvcfg.yaml | 24 + arch/csr/menvcfgh.yaml | 10 + arch/csr/mepc.yaml | 4 +- arch/ext/A.yaml | 13 + arch/ext/Svade.yaml | 40 + arch/ext/Svadu.yaml | 123 +++ arch/inst/A/lr.d.yaml | 87 ++ arch/inst/A/lr.w.yaml | 26 +- arch/inst/A/sc.d.yaml | 146 +++ arch/inst/A/sc.w.yaml | 152 +++ arch/inst/I/lw.yaml | 2 +- arch/inst/S/sfence.vma.yaml | 4 +- arch/isa/builtin_functions.idl | 331 ++++++ arch/isa/globals.isa | 973 ++++++++---------- arch/isa/util.idl | 151 +++ backends/arch_gen/lib/arch_gen.rb | 42 +- backends/arch_gen/tasks.rake | 3 + backends/cfg_html_doc/templates/csr.adoc.erb | 6 +- backends/cfg_html_doc/templates/inst.adoc.erb | 30 +- .../ext_pdf_doc/templates/ext_pdf.adoc.erb | 4 +- bin/.container-tag | 1 + bin/build_container | 4 +- bin/setup | 28 +- cfgs/generic_rv64/params.yaml | 25 + lib/arch_def.rb | 836 +++++++++++---- lib/idl.rb | 118 ++- lib/idl/ast.rb | 353 +++++-- lib/idl/idl.treetop | 51 +- lib/idl/passes/prune.rb | 16 +- lib/idl/passes/reachable_exceptions.rb | 33 +- lib/idl/passes/reachable_functions.rb | 60 +- lib/idl/symbol_table.rb | 22 +- lib/idl/tests/test_functions.rb | 12 +- lib/idl/type.rb | 64 +- lib/validate.rb | 1 - schemas/config_schema.json | 27 + schemas/csr_schema.json | 4 + schemas/ext_schema.json | 13 + schemas/inst_schema.json | 18 + 41 files changed, 2866 insertions(+), 999 deletions(-) create mode 100644 arch/ext/Svade.yaml create mode 100644 arch/ext/Svadu.yaml create mode 100644 arch/inst/A/lr.d.yaml create mode 100644 arch/inst/A/sc.d.yaml create mode 100644 arch/inst/A/sc.w.yaml create mode 100644 arch/isa/builtin_functions.idl create mode 100644 arch/isa/util.idl create mode 100644 bin/.container-tag diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 15c5cfaad..1517a3beb 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -12,12 +12,14 @@ jobs: uses: actions/checkout@v4 - name: Setup apptainer uses: eWaterCycle/setup-apptainer@v2.0.0 + - name: Read container tag + run: echo "CONTAINER_TAG=$(cat bin/.container-tag)" >> $GITHUB_ENV - name: Get container from cache id: cache-sif uses: actions/cache@v3 with: - path: .singularity/image.sif - key: ${{ hashFiles('container.def') }} + path: .singularity/image${{ env.CONTAINER_TAG }}.sif + key: ${{ hashFiles('container.def', 'bin/.container-tag') }} - name: Get gems and node files from cache id: cache-bundle-npm uses: actions/cache@v3 diff --git a/README.adoc b/README.adoc index 4751e080f..d94cf0ee3 100644 --- a/README.adoc +++ b/README.adoc @@ -38,7 +38,7 @@ This repository contains: == Prerequisites -The only requirement is the `Singularity CE` or `Apptainer` container system. Either one will work (they are forks). +The only requirement is the `Singularity CE` (>= 3.3) or `Apptainer` (>= 1.0) container system. Either one will work (they are forks). If it is not installed, either as your IT admin or: diff --git a/arch/csr/menvcfg.yaml b/arch/csr/menvcfg.yaml index 3a133b59a..1c8b54f25 100644 --- a/arch/csr/menvcfg.yaml +++ b/arch/csr/menvcfg.yaml @@ -158,6 +158,30 @@ menvcfg: definedBy: Svpbmt type: RW reset_value: UNDEFINED_LEGAL + ADUE: + location: 61 + description: | + If the Svadu extension is implemented, the ADUE bit controls whether hardware updating of + PTE A/D bits is enabled for S-mode and G-stage address translations. When ADUE=1, hardware + updating of PTE A/D bits is enabled during S-mode address translation, and the + implementation behaves as though the Svade extension were not implemented for S-mode address + translation. + + When the hypervisor extension is implemented, if ADUE=1, hardware updating of PTE A/D bits + is enabled during G-stage address translation, and the implementation behaves as though the + Svade extension were not implemented for G-stage address translation. When ADUE=0, the + implementation behaves as though Svade were implemented for S-mode and + G-stage address translation. + + If Svadu is not implemented, ADUE is read-only zero. + + Furthermore, for implementations with the hypervisor extension, henvcfg.ADUE is read-only + zero if menvcfg.ADUE is zero. + definedBy: Svadu + type(): | + return (implemented?(ExtensionName::Svadu)) ? CsrFieldType::RO : CsrFieldType::RW; + reset_value(): | + return (implemented?(ExtensionName::Svadu)) ? UNDEFINED_LEGAL : 0; CBZE: location: 7 description: | diff --git a/arch/csr/menvcfgh.yaml b/arch/csr/menvcfgh.yaml index 02278588e..4e6c5e0d9 100644 --- a/arch/csr/menvcfgh.yaml +++ b/arch/csr/menvcfgh.yaml @@ -28,3 +28,13 @@ menvcfgh: definedBy: Svpbmt type: RW reset_value: UNDEFINED_LEGAL + ADUE: + location: 29 + alias: menvcfg.ADUE + description: | + Alias of `menvcfg.ADUE` + definedBy: Svadu + type(): | + return (implemented?(ExtensionName::Svadu)) ? CsrFieldType::RO : CsrFieldType::RW; + reset_value(): | + return (implemented?(ExtensionName::Svadu)) ? UNDEFINED_LEGAL : 0; \ No newline at end of file diff --git a/arch/csr/mepc.yaml b/arch/csr/mepc.yaml index ff1f26d7f..78bd1160c 100644 --- a/arch/csr/mepc.yaml +++ b/arch/csr/mepc.yaml @@ -37,7 +37,7 @@ mepc: reset_value: 0 sw_read(): | if (implemented?(ExtensionName::C) && CSR[misa].C == 1'b1) { - return CSR[mepc] & ~64'b1; + return CSR[mepc].PC & ~64'b1; } else { - return CSR[mepc]; + return CSR[mepc].PC; } \ No newline at end of file diff --git a/arch/ext/A.yaml b/arch/ext/A.yaml index 427d73dbe..a398f119b 100644 --- a/arch/ext/A.yaml +++ b/arch/ext/A.yaml @@ -3,13 +3,26 @@ A: type: unprivileged long_name: Atomic instructions + company: + name: RISC-V International + url: https://riscv.org versions: - version: 2.1 state: ratified ratification_date: 2019-12 + contributors: + - name: Unknown + email: unknown@void.segfault + company: Unknown implies: - [Zaamo, 1.0] - [Zalrsc, 1.0] + required_parameters: + - MUTABLE_MISA_A + - LRSC_RESERVATION_STRATEGY + - LRSC_FAIL_ON_VA_SYNONYM + - LRSC_FAIL_ON_NON_EXACT_LRSC + - LRSC_MISALIGNED_BEHAVIOR description: | The atomic-instruction extension, named `A`, contains diff --git a/arch/ext/Svade.yaml b/arch/ext/Svade.yaml new file mode 100644 index 000000000..955854e0b --- /dev/null +++ b/arch/ext/Svade.yaml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=../../schemas/ext_schema.json + +Svade: + long_name: Exception on PTE A/D Bits + type: unprivileged + description: | + The Svade extension indicates that hardware does *not* update the A/D bits of a page table + during a page walk. Rather, encountering a PTE with the A bit clear or the D bit clear when + an operation is a write will cause a Page Fault. + versions: + - version: 1.0 + state: ratified + ratification_date: 2023-11 + url: https://github.com/riscvarchive/riscv-svadu/releases/download/v1.0/riscv-svadu.pdf + repositories: + - url: https://github.com/riscvarchive/riscv-svadu + branch: main + contributors: + - name: Aaron Durbin + company: Rivos, Inc. + - name: Andrew Waterman + company: SiFive + - name: Earl Killian + company: Aril + - name: Greg Favor + company: Ventana + - name: John Ingalls + company: SiFive + - name: Ken Dockser + company: Tenstorrent + - name: Krste Asanovic + company: SiFive + - name: Paul Donahue + - name: Ved Shanbhogue + company: Rivos, Inc. + conflicts: Svadu + doc_license: + name: Creative Commons Attribution 4.0 International License (CC-BY 4.0) + url: https://creativecommons.org/licenses/by/4.0/ + diff --git a/arch/ext/Svadu.yaml b/arch/ext/Svadu.yaml new file mode 100644 index 000000000..f5ab44cd4 --- /dev/null +++ b/arch/ext/Svadu.yaml @@ -0,0 +1,123 @@ +# yaml-language-server: $schema=../../schemas/ext_schema.json + +Svadu: + long_name: Hardware Updating of PTE A/D Bits + type: unprivileged + description: | + The Svadu extension adds support and CSR controls for hardware updating of PTE + A/D bits. The A and D bits are managed by these extensions as follows: + + * When a virtual page is accessed and the A bit is clear, the PTE is updated to + set the A bit. When the virtual page is written and the D bit is clear, the + PTE is updated to set the D bit. When G-stage address translation is in use + and is not Bare, the G-stage virtual pages may be accessed or written by + implicit accesses to VS-level memory management data structures, such as page + tables. + + * When two-stage address translation is in use, an explicit access may cause + both VS-stage and G-stage PTEs to be updated. The following rules apply to all + PTE updates caused by an explicit or an implicit memory accesses. + + + + The PTE update must be atomic with respect to other accesses to the PTE, and + must atomically perform all tablewalk checks for that leaf PTE as part of, and + before, conditionally updating the PTE value. Updates of the A bit may be + performed as a result of speculation, even if the associated memory access + ultimately is not performed architecturally. However, updates to the D bit, + resulting from an explicit store, must be exact (i.e., non-speculative), and + observed in program order by the local hart. When two-stage address + translation is active, updates of the D bit in G-stage PTEs may be performed + as a result of speculative updates of the A bit in VS-stage PTEs. + + + + The PTE update must appear in the global memory order before the memory access + that caused the PTE update and before any subsequent explicit memory access to + that virtual page by the local hart. The ordering on loads and stores provided + by FENCE instructions and the acquire/release bits on atomic instructions also + orders the PTE updates associated with those loads and stores as observed by + remote harts. + + + + The PTE update is not required to be atomic with respect to the memory access + that caused the update and a trap may occur between the PTE update and the + memory access that caused the PTE update. If a trap occurs then the A and/or D + bit may be updated but the memory access that caused the PTE update might not + occur. The hart must not perform the memory access that caused the PTE update + before the PTE update is globally visible. + + [NOTE] + ==== + The PTE updates due to memory accesses ordered-after a FENCE are not themselves + ordered by the FENCE. + + Simpler implementations that cannot precisely order the PTE update before + subsequent explicit memory accesses to the associated virtual page by the local + hart may simply order the PTE update before all subsequent explicit memory + accesses to any virtual page by the local hart. + ==== + + Svadu extension requires the page tables to be located in memory with hardware + page-table write access and _RsrvEventual_ PMA. + + <<< + + The translation of virtual addresses (or guest physical addresses) to physical + (or guest physical) addresses is accomplished with the same algorithm as + specified in the Supervisor-Level ISA extension (section "Virtual Address + Translation Process") and as modified by the hypervisor extension (section + "Two-stage Address Translation"), except that step 7 of the translation process, + instead of causing a page-fault exception due to A and/or D bits being 0 when + required to be 1, continues as follows: + + [start=7] + . If `pte.a = 0`, or if the original memory access is a store and `pte.d = 0`: + .. If a store to `pte` would violate a PMA or PMP check, raise an access-fault + exception corresponding to the original access type. + .. Perform the following steps atomically: + ... Compare `pte` to the value of the PTE at address `a + va.vpn[i] * PTESIZE`. + ... If the values match, set `pte.a` to 1 and, if the original memory access is + a store, also set `pte.d` to 1. + ... If the comparison fails, return to step 2 + + The Svadu extension adds the `ADUE` bit (bit 61) to `menvcfg`. When + `menvcfg.ADUE` is 1, hardware updating of PTE A/D bits is enabled during + single-stage address translation. When the hypervisor extension is implemented, + if `menvcfg.ADUE` is 1, hardware updating of PTE A/D bits is enabled during + G-stage address translation. When `menvcfg.ADUE` is zero, the implementation + behaves as though Svadu were not implemented. If Svadu is not implemented, + `menvcfg.ADUE` is read-only zero. Furthermore, for implementations with the + hypervisor extension, `henvcfg.ADUE` is read-only zero if `menvcfg.ADUE` is zero. + + When the hypervisor extension is implemented, the Svadu extension adds the + `ADUE` bit (bit 61) to `henvcfg`. When `henvcfg.ADUE` is 1, hardware updating of + PTE A/D bits is enabled during VS-stage address translation. When `henvcfg.ADUE` + is zero, the implementation behaves as though Svadu were not implemented for + VS-stage address translation. + versions: + - version: 1.0 + state: ratified + ratification_date: 2023-11 + url: https://github.com/riscvarchive/riscv-svadu/releases/download/v1.0/riscv-svadu.pdf + repositories: + - url: https://github.com/riscvarchive/riscv-svadu + branch: main + contributors: + - name: Aaron Durbin + company: Rivos, Inc. + - name: Andrew Waterman + company: SiFive + - name: Earl Killian + company: Aril + - name: Greg Favor + company: Ventana + - name: John Ingalls + company: SiFive + - name: Ken Dockser + company: Tenstorrent + - name: Krste Asanovic + company: SiFive + - name: Paul Donahue + - name: Ved Shanbhogue + company: Rivos, Inc. + conflicts: Svade + doc_license: + name: Creative Commons Attribution 4.0 International License (CC-BY 4.0) + url: https://creativecommons.org/licenses/by/4.0/ + diff --git a/arch/inst/A/lr.d.yaml b/arch/inst/A/lr.d.yaml new file mode 100644 index 000000000..38d656051 --- /dev/null +++ b/arch/inst/A/lr.d.yaml @@ -0,0 +1,87 @@ +# yaml-language-server: $schema=../../../schemas/inst_schema.json + +lr.d: + long_name: Load reserved doubleword + description: | + Loads a word from the address in rs1, places the value in rd, + and registers a _reservation set_ -- a set of bytes that subsumes the bytes in the + addressed word. + + The address in rs1 must be 8-byte aligned. + + If the address is not naturally aligned, a `LoadAddressMisaligned` exception or an + `LoadAccessFault` exception will be generated. The access-fault exception can be generated + for a memory access that would otherwise be able to complete except for the misalignment, + if the misaligned access should not be emulated. + + An implementation can register an arbitrarily large reservation set on each LR, provided the + reservation set includes all bytes of the addressed data word or doubleword. + An SC can only pair with the most recent LR in program order. + An SC may succeed only if no store from another hart to the reservation set can be + observed to have occurred between the LR and the SC, and if there is no other SC between the + LR and itself in program order. + An SC may succeed only if no write from a device other than a hart to the bytes accessed by + the LR instruction can be observed to have occurred between the LR and SC. Note this LR + might have had a different effective address and data size, but reserved the SC's + address as part of the reservation set. + + [NOTE] + ---- + Following this model, in systems with memory translation, an SC is allowed to succeed if the + earlier LR reserved the same location using an alias with a different virtual address, but is + also allowed to fail if the virtual address is different. + + To accommodate legacy devices and buses, writes from devices other than RISC-V harts are only + required to invalidate reservations when they overlap the bytes accessed by the LR. + These writes are not required to invalidate the reservation when they access other bytes in + the reservation set. + ---- + + Software should not set the _rl_ bit on an LR instruction unless the _aq_ bit is also set. + LR.rl and SC.aq instructions are not guaranteed to provide any stronger ordering than those + with both bits clear, but may result in lower performance. + definedBy: [A, Zalrsc] + base: 64 + assembly: xd, xs1 + encoding: + match: 00010--00000-----011-----0101111 + variables: + - name: aq + location: 26 + - name: rl + location: 27 + - name: rs1 + location: 19-15 + - name: rd + location: 11-7 + access: + s: always + u: always + vs: always + vu: always + operation(): | + if (implemented?(ExtensionName::A) && (CSR[misa].A == 1'b0)) { + raise (ExceptionCode::IllegalInstruction, $encoding); + } + + XReg virtual_address = X[rs1]; + + if (!is_naturally_aligned(virtual_address)) { + # can raise either LoadAddressMisaligned *or* LoadAccessFault + # + # from the spec: + # If the address is not naturally aligned, an address-misaligned exception or + # an access-fault exception will be generated. The access-fault exception can + # be generated for a memory access that would otherwise be able to complete except + # for the misalignment, if the misaligned access should not be emulated. + + if (LRSC_MISALIGNED_BEHAVIOR == "always raise misaligned exception") { + raise(ExceptionCode::LoadAddressMisaligned, virtual_address); + } else if (LRSC_MISALIGNED_BEHAVIOR == "always raise access fault") { + raise(ExceptionCode::LoadAccessFault, virtual_address); + } else { + unpredictable("Implementations may raise either a LoadAddressMisaligned or a LoadAccessFault when an LR/SC address is misaligned"); + } + } + + X[rd] = load_reserved<32>(virtual_address, aq, rl); diff --git a/arch/inst/A/lr.w.yaml b/arch/inst/A/lr.w.yaml index cebd75983..36fb15842 100644 --- a/arch/inst/A/lr.w.yaml +++ b/arch/inst/A/lr.w.yaml @@ -70,5 +70,27 @@ lr.w: XReg virtual_address = X[rs1]; - #X[rd] = load_reserved<32>(virtual_address); - X[rd] = read_memory<32>(virtual_address); + if (!is_naturally_aligned(virtual_address)) { + # can raise either LoadAddressMisaligned *or* LoadAccessFault + # + # from the spec: + # If the address is not naturally aligned, an address-misaligned exception or + # an access-fault exception will be generated. The access-fault exception can + # be generated for a memory access that would otherwise be able to complete except + # for the misalignment, if the misaligned access should not be emulated. + + if (LRSC_MISALIGNED_BEHAVIOR == "always raise misaligned exception") { + raise(ExceptionCode::LoadAddressMisaligned, virtual_address); + } else if (LRSC_MISALIGNED_BEHAVIOR == "always raise access fault") { + raise(ExceptionCode::LoadAccessFault, virtual_address); + } else { + unpredictable("Implementations may raise either a LoadAddressMisaligned or a LoadAccessFault when an LR/SC address is misaligned"); + } + } + + XReg load_value = load_reserved<32>(virtual_address, aq, rl); + if (xlen() == 64) { + X[rd] = load_value; + } else { + X[rd] = sext(load_value[31:0], 32); + } diff --git a/arch/inst/A/sc.d.yaml b/arch/inst/A/sc.d.yaml new file mode 100644 index 000000000..ec39bc0e7 --- /dev/null +++ b/arch/inst/A/sc.d.yaml @@ -0,0 +1,146 @@ +# yaml-language-server: $schema=../../../schemas/inst_schema.json + +sc.d: + long_name: Store conditional doubleword + description: | + `sc.d` conditionally writes a doubleword in _rs2_ to the address in _rs1_: + the `sc.d` succeeds only if the reservation is still valid and the + reservation set contains the bytes being written. If the `sc.d` succeeds, + the instruction writes the doubleword in _rs2_ to memory, and it writes zero to _rd_. + If the `sc.d` fails, the instruction does not write to memory, and it writes a + nonzero value to _rd_. For the purposes of memory protection, a failed `sc.d` + may be treated like a store. Regardless of success or failure, executing an + `sc.d` instruction invalidates any reservation held by this hart. + + The failure code with value 1 encodes an unspecified failure. + Other failure codes are reserved at this time. + Portable software should only assume the failure code will be non-zero. + + The address held in _rs1_ must be naturally aligned to the size of the operand + (_i.e._, eight-byte aligned). + If the address is not naturally aligned, an address-misaligned exception or an + access-fault exception will be generated. + The access-fault exception can be generated for a memory access that would otherwise + be able to complete except for the misalignment, + if the misaligned access should not be emulated. + + [NOTE] + -- + Emulating misaligned LR/SC sequences is impractical in most systems. + + Misaligned LR/SC sequences also raise the possibility of accessing multiple + reservation sets at once, which present definitions do not provide for. + -- + + An implementation can register an arbitrarily large reservation set on each LR, + provided the reservation set includes all bytes of the addressed data word or + doubleword. + An SC can only pair with the most recent LR in program order. + An SC may succeed only if no store from another hart to the reservation set + can be observed to have occurred between the LR and the SC, + and if there is no other SC between the LR and itself in program order. + An SC may succeed only if no write from a device other than a hart to the bytes + accessed by the LR instruction can be observed to have occurred between the LR + and SC. + Note this LR might have had a different effective address and data size, + but reserved the SC's address as part of the reservation set. + + [NOTE] + ---- + Following this model, in systems with memory translation, an SC is allowed to succeed if the + earlier LR reserved the same location using an alias with a different virtual address, but is + also allowed to fail if the virtual address is different. + + To accommodate legacy devices and buses, writes from devices other than RISC-V harts are only + required to invalidate reservations when they overlap the bytes accessed by the LR. + These writes are not required to invalidate the reservation when they access other bytes in + the reservation set. + ---- + + The SC must fail if the address is not within the reservation set of the most + recent LR in program order. + The SC must fail if a store to the reservation set from another hart can be + observed to occur between the LR and SC. + The SC must fail if a write from some other device to the bytes accessed by the + LR can be observed to occur between the LR and SC. + (If such a device writes the reservation set but does not write the bytes accessed + by the LR, the SC may or may not fail.) + An SC must fail if there is another SC (to any address) between the LR and the SC + in program order. + The precise statement of the atomicity requirements for successful LR/SC sequences + is defined by the Atomicity Axiom of the memory model. + + [NOTE] + -- + The platform should provide a means to determine the size and shape of the reservation set. + + A platform specification may constrain the size and shape of the reservation set. + + A store-conditional instruction to a scratch word of memory should be used to forcibly invalidate any existing load reservation: + + * during a preemptive context switch, and + * if necessary when changing virtual to physical address mappings, such as when migrating pages that might contain an active reservation. + + The invalidation of a hart's reservation when it executes an LR or SC imply that a hart can only hold one reservation at a time, and that an SC can only pair with the most recent LR, and LR with the next following SC, in program order. This is a restriction to the Atomicity Axiom in Section 18.1 that ensures software runs correctly on expected common implementations that operate in this manner. + -- + + An SC instruction can never be observed by another RISC-V hart before the LR instruction that established the reservation. + + [NOTE] + -- + The LR/SC sequence can be given acquire semantics by setting the aq bit on the LR instruction. The LR/SC sequence can be given release semantics by by setting the rl bit on the SC instruction. Assuming suitable mappings for other atomic operations, setting the aq bit on the LR instruction, and setting the rl bit on the SC instruction makes the LR/SC sequence sequentially consistent in the C++ memory_order_seq_cst sense. Such a sequence does not act as a fence for ordering ordinary load and store instructions before and after the sequence. Specific instruction mappings for other C++ atomic operations, or stronger notions of "sequential consistency", may require both bits to be set on either or both of the LR or SC instruction. + + If neither bit is set on either LR or SC, the LR/SC sequence can be observed to occur before or after surrounding memory operations from the same RISC-V hart. This can be appropriate when the LR/SC sequence is used to implement a parallel reduction operation. + -- + + Software should not set the _rl_ bit on an LR instruction unless the _aq_ bit is also set. + LR.rl and SC.aq instructions are not guaranteed to provide any stronger ordering than those + with both bits clear, but may result in lower performance. + definedBy: A + assembly: xd, xs2, xs1 + encoding: + match: 00011------------011-----0101111 + variables: + - name: aq + location: 26 + - name: rl + location: 27 + - name: rs2 + location: 24-20 + - name: rs1 + location: 19-15 + - name: rd + location: 11-7 + access: + s: always + u: always + vs: always + vu: always + operation(): | + if (implemented?(ExtensionName::A) && (CSR[misa].A == 1'b0)) { + raise (ExceptionCode::IllegalInstruction, $encoding); + } + + XReg virtual_address = X[rs1]; + XReg value = X[rs2]; + + if (!is_naturally_aligned(virtual_address)) { + # can raise either LoadAddressMisaligned *or* LoadAccessFault + # + # from the spec: + # If the address is not naturally aligned, an address-misaligned exception or + # an access-fault exception will be generated. The access-fault exception can + # be generated for a memory access that would otherwise be able to complete except + # for the misalignment, if the misaligned access should not be emulated. + + if (LRSC_MISALIGNED_BEHAVIOR == "always raise misaligned exception") { + raise(ExceptionCode::LoadAddressMisaligned, virtual_address); + } else if (LRSC_MISALIGNED_BEHAVIOR == "always raise access fault") { + raise(ExceptionCode::LoadAccessFault, virtual_address); + } else { + unpredictable("Implementations may raise either a LoadAddressMisaligned or a LoadAccessFault when an LR/SC address is misaligned"); + } + } + + Boolean success = store_conditional<64>(virtual_address, value, aq, rl); + X[rd] = success ? 0 : 1; diff --git a/arch/inst/A/sc.w.yaml b/arch/inst/A/sc.w.yaml new file mode 100644 index 000000000..bee1344fd --- /dev/null +++ b/arch/inst/A/sc.w.yaml @@ -0,0 +1,152 @@ +# yaml-language-server: $schema=../../../schemas/inst_schema.json + +sc.w: + long_name: Store conditional word + description: | + `sc.w` conditionally writes a word in _rs2_ to the address in _rs1_: + the `sc.w` succeeds only if the reservation is still valid and the + reservation set contains the bytes being written. If the `sc.w` succeeds, + the instruction writes the word in _rs2_ to memory, and it writes zero to _rd_. + If the `sc.w` fails, the instruction does not write to memory, and it writes a + nonzero value to _rd_. For the purposes of memory protection, a failed `sc.w` + may be treated like a store. Regardless of success or failure, executing an + `sc.w` instruction invalidates any reservation held by this hart. + + <%- if XLEN == 64 -%> + [NOTE] + If a value other than 0 or 1 is defined as a result for `sc.w`, the value will before + sign-extended into _rd_. + <%- end -%> + + The failure code with value 1 encodes an unspecified failure. + Other failure codes are reserved at this time. + Portable software should only assume the failure code will be non-zero. + + The address held in _rs1_ must be naturally aligned to the size of the operand + (_i.e._, eight-byte aligned for doublewords and four-byte aligned for words). + If the address is not naturally aligned, an address-misaligned exception or an + access-fault exception will be generated. + The access-fault exception can be generated for a memory access that would otherwise + be able to complete except for the misalignment, + if the misaligned access should not be emulated. + + [NOTE] + -- + Emulating misaligned LR/SC sequences is impractical in most systems. + + Misaligned LR/SC sequences also raise the possibility of accessing multiple + reservation sets at once, which present definitions do not provide for. + -- + + An implementation can register an arbitrarily large reservation set on each LR, + provided the reservation set includes all bytes of the addressed data word or + doubleword. + An SC can only pair with the most recent LR in program order. + An SC may succeed only if no store from another hart to the reservation set + can be observed to have occurred between the LR and the SC, + and if there is no other SC between the LR and itself in program order. + An SC may succeed only if no write from a device other than a hart to the bytes + accessed by the LR instruction can be observed to have occurred between the LR + and SC. + Note this LR might have had a different effective address and data size, + but reserved the SC's address as part of the reservation set. + + [NOTE] + ---- + Following this model, in systems with memory translation, an SC is allowed to succeed if the + earlier LR reserved the same location using an alias with a different virtual address, but is + also allowed to fail if the virtual address is different. + + To accommodate legacy devices and buses, writes from devices other than RISC-V harts are only + required to invalidate reservations when they overlap the bytes accessed by the LR. + These writes are not required to invalidate the reservation when they access other bytes in + the reservation set. + ---- + + The SC must fail if the address is not within the reservation set of the most + recent LR in program order. + The SC must fail if a store to the reservation set from another hart can be + observed to occur between the LR and SC. + The SC must fail if a write from some other device to the bytes accessed by the + LR can be observed to occur between the LR and SC. + (If such a device writes the reservation set but does not write the bytes accessed + by the LR, the SC may or may not fail.) + An SC must fail if there is another SC (to any address) between the LR and the SC + in program order. + The precise statement of the atomicity requirements for successful LR/SC sequences + is defined by the Atomicity Axiom of the memory model. + + [NOTE] + -- + The platform should provide a means to determine the size and shape of the reservation set. + + A platform specification may constrain the size and shape of the reservation set. + + A store-conditional instruction to a scratch word of memory should be used to forcibly invalidate any existing load reservation: + + * during a preemptive context switch, and + * if necessary when changing virtual to physical address mappings, such as when migrating pages that might contain an active reservation. + + The invalidation of a hart's reservation when it executes an LR or SC imply that a hart can only hold one reservation at a time, and that an SC can only pair with the most recent LR, and LR with the next following SC, in program order. This is a restriction to the Atomicity Axiom in Section 18.1 that ensures software runs correctly on expected common implementations that operate in this manner. + -- + + An SC instruction can never be observed by another RISC-V hart before the LR instruction that established the reservation. + + [NOTE] + -- + The LR/SC sequence can be given acquire semantics by setting the aq bit on the LR instruction. The LR/SC sequence can be given release semantics by by setting the rl bit on the SC instruction. Assuming suitable mappings for other atomic operations, setting the aq bit on the LR instruction, and setting the rl bit on the SC instruction makes the LR/SC sequence sequentially consistent in the C++ memory_order_seq_cst sense. Such a sequence does not act as a fence for ordering ordinary load and store instructions before and after the sequence. Specific instruction mappings for other C++ atomic operations, or stronger notions of "sequential consistency", may require both bits to be set on either or both of the LR or SC instruction. + + If neither bit is set on either LR or SC, the LR/SC sequence can be observed to occur before or after surrounding memory operations from the same RISC-V hart. This can be appropriate when the LR/SC sequence is used to implement a parallel reduction operation. + -- + + Software should not set the _rl_ bit on an LR instruction unless the _aq_ bit is also set. + LR.rl and SC.aq instructions are not guaranteed to provide any stronger ordering than those + with both bits clear, but may result in lower performance. + definedBy: A + assembly: xd, xs2, xs1 + encoding: + match: 00011------------010-----0101111 + variables: + - name: aq + location: 26 + - name: rl + location: 27 + - name: rs2 + location: 24-20 + - name: rs1 + location: 19-15 + - name: rd + location: 11-7 + access: + s: always + u: always + vs: always + vu: always + operation(): | + if (implemented?(ExtensionName::A) && (CSR[misa].A == 1'b0)) { + raise(ExceptionCode::IllegalInstruction, $encoding); + } + + XReg virtual_address = X[rs1]; + XReg value = X[rs2]; + + if (!is_naturally_aligned(virtual_address)) { + # can raise either LoadAddressMisaligned *or* LoadAccessFault + # + # from the spec: + # If the address is not naturally aligned, an address-misaligned exception or + # an access-fault exception will be generated. The access-fault exception can + # be generated for a memory access that would otherwise be able to complete except + # for the misalignment, if the misaligned access should not be emulated. + + if (LRSC_MISALIGNED_BEHAVIOR == "always raise misaligned exception") { + raise(ExceptionCode::LoadAddressMisaligned, virtual_address); + } else if (LRSC_MISALIGNED_BEHAVIOR == "always raise access fault") { + raise(ExceptionCode::LoadAccessFault, virtual_address); + } else { + unpredictable("Implementations may raise either a LoadAddressMisaligned or a LoadAccessFault when an LR/SC address is misaligned"); + } + } + + Boolean success = store_conditional<32>(virtual_address, value, aq, rl); + X[rd] = success ? 0 : 1; diff --git a/arch/inst/I/lw.yaml b/arch/inst/I/lw.yaml index e53527bbe..de0fa68cc 100644 --- a/arch/inst/I/lw.yaml +++ b/arch/inst/I/lw.yaml @@ -25,4 +25,4 @@ lw: operation(): | XReg virtual_address = X[rs1] + imm; - X[rd] = sext(read_memory<32>(virtual_address), 32); + X[rd] = read_memory<32>(virtual_address); diff --git a/arch/inst/S/sfence.vma.yaml b/arch/inst/S/sfence.vma.yaml index e7c3c959c..1ca072595 100644 --- a/arch/inst/S/sfence.vma.yaml +++ b/arch/inst/S/sfence.vma.yaml @@ -241,7 +241,7 @@ sfence.vma: } else if ((rs1 != 0) && (rs2 == 0)) { # invalidate all translations from leaf page tables containing 'vaddr' # does not affect global mappings - if (valid_vaddr?(vaddr)) { + if (canonical_vaddr?(vaddr)) { invalidate_vaddr_translations(vaddr); # then order subsequent loads and stores after the invalidation @@ -252,7 +252,7 @@ sfence.vma: } else { # invalidate all translations from leaf page tables for address space 'asid' containing 'vaddr' # does not affect global mappings - if (valid_vaddr?(vaddr)) { + if (canonical_vaddr?(vaddr)) { invalidate_asid_vaddr_translations(asid, vaddr); # then order subsequent loads and stores after the invalidation diff --git a/arch/isa/builtin_functions.idl b/arch/isa/builtin_functions.idl new file mode 100644 index 000000000..7e76efda0 --- /dev/null +++ b/arch/isa/builtin_functions.idl @@ -0,0 +1,331 @@ +%version: 1.0 + +builtin function implemented? { + returns Boolean + arguments ExtensionName extension + description { + Return true if the implementation supports `extension`. + } +} + +builtin function unpredictable { + arguments String why + description { + Indicate that the hart has reached a state that is unpredictable because the + RISC-V spec allows multiple behaviors. Generally, this will be a fatal condition + to any emulation, since it is unclear what to do next. + + The single argument _why_ is a string describing why the hart entered an + unpredictable state. + } +} + +builtin function read_hpm_counter { + returns Bits<64> + arguments Bits<5> n + description { + Returns the value of hpmcounterN. + + N must be between 3..31. + + hpmcounterN must be implemented. + } +} + +builtin function read_mcycle { + returns Bits<64> + description { + Return the current value of the cycle counter. + } +} + +builtin function sw_write_mcycle { + returns Bits<64> + arguments Bits<64> value + description { + Given a _value_ that software is trying to write into mcycle, + return the value that will actually be written. + } +} + +builtin function memory_model_acquire { + description { + Perform an acquire; that is, ensure that no subsequent + operation in program order appears to an external observer + to occur after the operation calling this function. + } +} + +builtin function memory_model_release { + description { + Perform a release; that is, ensure that no prior store + in program order can be observed external to this hart + after this function returns. + } +} + +builtin function assert { + arguments + Boolean test, # the result of a test + String message # a helpful message explaining what was expected + description { + Assert that a condition is true. Failure represents an error in the IDL model. + } +} + +builtin function notify_mode_change { + arguments PrivilegeMode new_mode, PrivilegeMode old_mode + description { + Called whenever the privilege mode changes. Downstream tools can use this to hook events. + } +} + +builtin function abort_current_instruction { + description { + Abort the current instruction, and start refetching from $pc. + } +} + + +builtin function zext { + returns XReg + arguments XReg value, XReg first_extended_bit + description { + Zero extend `value` starting at `first_extended_bit`. + + Bits [`XLEN-1`:`first_extended_bit`] of the return value + should get the value `0`; + } +} + +builtin function ebreak { + description { + Raise an `Environment Break` exception, returning control to the debug environment. + } +} + +builtin function saturate { + returns XReg + arguments XReg value, XReg max_value + description { + If unsigned `value` is less than `max_value`, return `value`. + Otherwise, return `max_value`. + } +} + +builtin function fence { + arguments Boolean PI, Boolean PR, Boolean PO, Boolean PW, Boolean SI, Boolean SR, Boolean SO, Boolean SW + description { + Execute a memory ordering fence.(according to the FENCE instruction). + } +} + +builtin function ifence { + description { + Execute a memory ordering instruction fence (according to FENCE.I). + } +} + +builtin function pow { + returns XReg + arguments XReg value, XReg exponent + description { + Return `value` to the power `exponent`. + } +} + + +builtin function maybe_cache_translation { + arguments TranslationResult result + description { + Given a translation result, potentially cache the result for later use. This function models + a TLB fill operation. A valid implementation does nothing. + } +} + +builtin function invalidate_all_translations { + description { + Locally invalidate all cached address translations, from all address ranges + and all ASIDs, including global mappings. + + A valid implementation does nothing if address caching is not used. + } +} + +builtin function sfence_all { + description { + Ensure all loads and stores after this operation see any previous address cache invalidations. + } +} + +builtin function sfence_asid { + arguments Bits asid + description { + Ensure all reads and writes using address space 'asid' see any previous address space invalidations. + + Does not have to (but may, if conservative) order any global mappings. + } +} + +builtin function sfence_vaddr { + arguments XReg vaddr + description { + Ensure all reads and writes using a leaf page table that contains 'vaddr' see any previous address space invalidations. + + Must also order any global mappings containing 'vaddr'. + } +} + +builtin function sfence_asid_vaddr { + arguments Bits asid, XReg vaddr + description { + Ensure all reads and writes using address space 'asid' and a leaf page table that contains 'vaddr' see any previous address space invalidations. + + Does not have to (but may, if conservative) order any global mappings. + } +} + +builtin function invalidate_asid_translations { + arguments Bits asid + description { + Locally invalidate all cached address translations for address space 'asid'. + Does not affect global mappings. + + A valid implementation does nothing if address caching is not used. + } +} + +builtin function invalidate_vaddr_translations { + arguments XReg vaddr + description { + Locally invalidate all cached address translations representing 'vaddr', for all address spaces. + Equally affects global mappings. + + A valid implementation does nothing if address caching is not used. + } +} + +builtin function invalidate_asid_vaddr_translations { + arguments Bits asid, XReg vaddr + description { + Locally invalidate all cached address translations for address space 'asid' that represent 'vaddr'. + Does not affect global mappings. + + A valid implementation does nothing if address caching is not used. + } +} + +builtin function read_physical_memory { + template U32 len + returns Bits + arguments XReg paddr + description { + Read from physical memory. + } +} + +builtin function write_physical_memory { + template U32 len + arguments XReg paddr, Bits value + description { + Write to physical memory. + } +} + +builtin function wfi { + description { + Wait-for-interrupt: hint that the processor + should enter a low power state until the next interrupt. + + A valid implementation is a no-op. + + The model will advance the PC; this function does not need to. + } +} + +builtin function pma_applies? { + returns Boolean + arguments + PmaAttribute attr, #< PMA attribute to check + Bits paddr, #< Start of the region + U32 len #< Size of the region, in bits + description { + Checks if _attr_ is applied to the entire physical address region + between [paddr, paddr + len). + } +} + +builtin function atomic_check_then_write_32 { + returns Boolean + arguments + Bits paddr, # the physical address + Bits<32> compare_value, # the comparison value + Bits<32> write_value # the value to be written if the value in memory matches compare_value + description { + Atomically: + + * Reads 32-bits from paddr + * Compares the read value to compare_value + * Writes write_value to paddr **if** the comparison was bitwise-equal + + returns true if the write occurs, and false otherwise + + Preconditions: + + * paddr must be aligned to 32-bits + + } +} + +builtin function atomic_check_then_write_64 { + returns Boolean + arguments + Bits paddr, # the physical address + Bits<64> compare_value, # the comparison value + Bits<64> write_value # the value to be written if the value in memory matches compare_value + description { + Atomically: + + * Reads 64-bits from paddr + * Compares the read value to compare_value + * Writes write_value to paddr **if** the comparison was bitwise-equal + + returns true if the write occurs, and false otherwise + + Preconditions: + + * paddr must be aligned to 64-bits + } +} + +builtin function atomically_set_pte_a { + returns Boolean + arguments + Bits pte_addr, + Bits pte_value, + U32 pte_len + description { + Atomically: + + * Reads the _pte_len_ value at _pte_addr_ + ** If the read value does not exactly equal pte_value, returns false + * Sets the 'A' bit and writes the result to _pte_addr_ + * return true + } +} + +builtin function atomically_set_pte_a_d { + returns Boolean + arguments + Bits pte_addr, + Bits pte_value, + U32 pte_len + description { + Atomically: + + * Reads the _pte_len_ value at _pte_addr_ + ** If the read value does not exactly equal pte_value, returns false + * Sets the 'A' and 'D' bits and writes the result to _pte_addr_ + * return true + } +} \ No newline at end of file diff --git a/arch/isa/globals.isa b/arch/isa/globals.isa index 2ce1d09f7..837a46bc2 100644 --- a/arch/isa/globals.isa +++ b/arch/isa/globals.isa @@ -1,5 +1,8 @@ %version: 1.0 +include "builtin_functions.idl" +include "util.idl" + # global state # general purpose register file @@ -23,11 +26,11 @@ Bits<66> UNDEFINED_LEGAL_DETERMINISTIC = 66'h20000000000000000; # Signals an illegal write of a WLRL field Bits<67> ILLEGAL_WLRL = 67'h40000000000000000; -// encoded as defined in the privilege spec +# encoded as defined in the privilege spec enum PrivilegeMode { M 0b011 S 0b001 - HS 0b001 // alias for S when H extension is used + HS 0b001 # alias for S when H extension is used U 0b000 VS 0b101 VU 0b100 @@ -41,7 +44,13 @@ enum MemoryOperation { } enum PmaAttribute { - RsrvEventual + RsrvNone # LR/SC not allowed + RsrvNonEventual # LR/SC allowed, but no gaurantee it will ever succeed + RsrvEventual # LR/SC with forward-progress gaurantees + + # page walk permissions + HardwarePageTableRead + HardwarePageTableWrite } # do not change these values!! the compiler assumes them @@ -54,10 +63,10 @@ enum CsrFieldType { RWRH 5 } -// generated from extension information in arch defintion +# generated from extension information in arch defintion builtin enum ExtensionName; -// XLEN encoding, as defined in CSR[mstatus].mxl, etc. +# XLEN encoding, as defined in CSR[mstatus].mxl, etc. enum XRegWidth { XLEN32 0 XLEN64 1 @@ -75,13 +84,13 @@ enum ExceptionCode { StoreAmoAccessFault 7 Ucall 8 Scall 9 - // reserved 10 + # reserved 10 Mcall 11 InstructionPageFault 12 LoadPageFault 13 - // reserved 14 + # reserved 14 StoreAmoPageFault 15 - // reserved 16-17 + # reserved 16-17 SoftwareCheck 18 HardwareError 19 InstructionGuestPageFault 20 @@ -91,11 +100,11 @@ enum ExceptionCode { } enum RoundingMode { - RNE 0 // Round to nearest, ties to even - RTZ 1 // Round toward zero - RDN 2 // Round down (towards -inf) - RUP 3 // Round up (towards +inf) - RMM 4 // Round to nearest, ties to Max Magnitude + RNE 0 # Round to nearest, ties to even + RTZ 1 # Round toward zero + RDN 2 # Round down (towards -inf) + RUP 3 # Round up (towards +inf) + RMM 4 # Round to nearest, ties to Max Magnitude } enum SatpMode { @@ -106,6 +115,18 @@ enum SatpMode { Sv57 10 } +bitfield (10) PteFlags { + RSW 9-8 # reserved + D 7 # dirty + A 6 # access + G 5 # global + U 4 # userspace permission + X 3 # execute permission + W 2 # write permission + R 1 # read permission + V 0 # valid +} + bitfield (64) Sv39PageTableEntry { N 63 PBMT 62-61 @@ -113,7 +134,7 @@ bitfield (64) Sv39PageTableEntry { PPN2 53-28 PPN1 27-19 PPN0 18-10 - PPN 53-10 // in addition to the components, we define the entire PPN + PPN 53-10 # in addition to the components, we define the entire PPN RSW 9-8 D 7 A 6 @@ -125,42 +146,6 @@ bitfield (64) Sv39PageTableEntry { V 0 } -builtin function implemented? { - returns Boolean - arguments ExtensionName extension - description { - Return true if the implementation supports `extension`. - } -} - -builtin function read_hpm_counter { - returns Bits<64> - arguments Bits<5> n - description { - Returns the value of hpmcounterN. - - N must be between 3..31. - - hpmcounterN must be implemented. - } -} - -builtin function read_mcycle { - returns Bits<64> - description { - Return the current value of the cycle counter. - } -} - -builtin function sw_write_mcycle { - returns Bits<64> - arguments Bits<64> value - description { - Given a _value_ that software is trying to write into mcycle, - return the value that will actually be written. - } -} - # floating point register file U32 FLEN = implemented?(ExtensionName::D) ? 7'd64 : 7'd32; Bits f[32] = [0, 0, 0, 0, 0, 0, 0, 0, @@ -186,19 +171,6 @@ function mode { } } -builtin function assert { - arguments Boolean test - description { - Assert that a condition is true. Failure represents a specification error. - } -} - -builtin function mode_change_notifier { - arguments PrivilegeMode new_mode, PrivilegeMode old_mode - description { - Called whenever the privilege mode changes. Downstream tools can use this to hook events. - } -} function set_mode { arguments PrivilegeMode new_mode @@ -207,47 +179,12 @@ function set_mode { } body { if (new_mode != current_mode) { - mode_change_notifier(new_mode, current_mode); + notify_mode_change(new_mode, current_mode); current_mode = new_mode; } } } -builtin function abort_current_instruction { - description { - Abort the current instruction, and start refetching from PC - } -} - -function power_of_2? { - template U32 N - returns Boolean - arguments Bits value - description { - Returns true if value is a power of two, false otherwise - } - body { - return (value != 0) && ((value & (value - 1)) == 0); - } -} - -function ary_includes? { - template U32 ARY_SIZE, U32 ELEMENT_SIZE - returns Boolean - arguments Bits ary[ARY_SIZE], Bits value - description { - Returns true if _value_ is an element of ary, and false otherwise - } - body { - for (U32 i = 0; i < ARY_SIZE; i++) { - if (ary[i] == value) { - return true; - } - } - return false; - } -} - function exception_handling_mode { returns PrivilegeMode arguments ExceptionCode exception_code @@ -256,8 +193,8 @@ function exception_handling_mode { } body { if (mode() == PrivilegeMode::M) { - // exceptions can never be taken in a less-privileged mode, so if the current - // mode is M, the value of medeleg is irrelevant + # exceptions can never be taken in a less-privileged mode, so if the current + # mode is M, the value of medeleg is irrelevant return PrivilegeMode::M; } else if (implemented?(ExtensionName::S) && ((mode() == PrivilegeMode::HS) || (mode() == PrivilegeMode::U))) { if ((CSR[medeleg] & (1 << $bits(exception_code))) != 0) { @@ -266,7 +203,7 @@ function exception_handling_mode { return PrivilegeMode::M; } } else { - assert(implemented?(ExtensionName::H) && ((mode() == PrivilegeMode::VS) || (mode() == PrivilegeMode::VU))); + assert(implemented?(ExtensionName::H) && ((mode() == PrivilegeMode::VS) || (mode() == PrivilegeMode::VU)), "Unexpected mode"); if ((CSR[medeleg] & (1 << $bits(exception_code))) != 0) { if ((CSR[hedeleg] & (1 << $bits(exception_code))) != 0) { return PrivilegeMode::VS; @@ -274,7 +211,7 @@ function exception_handling_mode { return PrivilegeMode::HS; } } else { - // if an exception is not delegated to HS-mode, it can't be delegated to VS-mode + # if an exception is not delegated to HS-mode, it can't be delegated to VS-mode return PrivilegeMode::M; } } @@ -500,7 +437,6 @@ function raise { PC = {CSR[stvec].BASE, 2'b00}; CSR[scause].INT = 1'b0; CSR[scause].CODE = $bits(exception_code); - // note that stval is set elsewhere } else if (implemented?(ExtensionName::H) && (handling_mode == PrivilegeMode::VS)) { CSR[vsepc].PC = PC; if (!vstval_readonly?()) { @@ -511,7 +447,7 @@ function raise { CSR[vscause].CODE = $bits(exception_code); } - // abort the current instruction, and start to refetch from PC + # abort the current instruction, and start to refetch from PC set_mode(handling_mode); abort_current_instruction(); } @@ -561,7 +497,7 @@ function jump_halfword { } body { # ensure that the target address is really a halfword address - assert((target_hw_addr & 0x1) == 0x0); + assert((target_hw_addr & 0x1) == 0x0, "Expected halfword-aligned address in jump_halfword"); if (ialign() != 16) { if ((target_hw_addr & 0x3) != 0) { @@ -619,44 +555,6 @@ function xlen { } } -function highest_set_bit { - returns XReg - arguments XReg value - description { - Returns the position of the highest (nearest MSB) bit that is '1', - or -1 if value is zero. - } - body { - for (U32 i=xlen()-1; i >= 0; i--) { - if (value[i] == 1) { - return i; - } - } - - # fall-through; value must be zero - return -'sd1; - } -} - -function lowest_set_bit { - returns XReg - arguments XReg value - description { - Returns the position of the lowest (nearest LSB) bit that is '1', - or XLEN if value is zero. - } - body { - for (U32 i=0; i < xlen(); i++) { - if (value[i] == 1) { - return i; - } - } - - # fall-through; value must be zero - return xlen(); - } -} - function virtual_mode? { returns Boolean description { @@ -667,100 +565,6 @@ function virtual_mode? { } } -function sext { - returns XReg - arguments XReg value, XReg first_extended_bit - description { - Sign extend `value` starting at `first_extended_bit`. - - Bits [`XLEN-1`:`first_extended_bit`] of the return value - should get the value of bit (`first_extended bit - 1`). - } - body { - # in a common case, first_extended_bit is xlen(), which is compile-time-known unless - # the effective xlen is different than XLEN in some mode - # In that common case, this function will be eliminated by the compiler - if (first_extended_bit == XLEN) { - return value; - } else { - Bits<1> sign = value[first_extended_bit-1]; - for (U32 i = XLEN-1; i >= first_extended_bit; i--) { - value[i] = sign; - } - return value; - } - } -} - -builtin function zext { - returns XReg - arguments XReg value, XReg first_extended_bit - description { - Zero extend `value` starting at `first_extended_bit`. - - Bits [`XLEN-1`:`first_extended_bit`] of the return value - should get the value `0`; - } -} - -builtin function ebreak { - description { - Raise an `Environment Break` exception, returning control to the debug environment. - } -} - -builtin function saturate { - returns XReg - arguments XReg value, XReg max_value - description { - If unsigned `value` is less than `max_value`, return `value`. - Otherwise, return `max_value`. - } -} - -builtin function fence { - arguments Boolean PI, Boolean PR, Boolean PO, Boolean PW, Boolean SI, Boolean SR, Boolean SO, Boolean SW - description { - Execute a memory ordering fence.(according to the FENCE instruction). - } -} - -builtin function ifence { - description { - Execute a memory ordering instruction fence (according to FENCE.I). - } -} - -builtin function pow { - returns XReg - arguments XReg value, XReg exponent - description { - Return `value` to the power `exponent`. - } -} - -function bit_length { - returns XReg - arguments XReg value - description { - Returns the minimum number of bits needed to represent value. - - Only works on unsigned values. - - The value 0 returns 1. - } - body { - for (XReg i = 63; i > 0; i--) { - if (value[i] == 1) { - return i; - } - } - - # if we get here, the value is 0 or 1. either way, say we need one bit - return 1; - } -} - function mask_eaddr { returns XReg arguments XReg eaddr @@ -792,85 +596,6 @@ function mask_eaddr { } } -// builtin function maybe_cache_translation { -// arguments TranslationResult result -// description { -// Given a translation result, potentially cache the result for later use. This function models -// a TLB fill operation. A valid implementation does nothing. -// } -// } - -builtin function invalidate_all_translations { - description { - Locally invalidate all cached address translations, from all address ranges - and all ASIDs, including global mappings. - - A valid implementation does nothing if address caching is not used. - } -} - -builtin function sfence_all { - description { - Ensure all loads and stores after this operation see any previous address cache invalidations. - } -} - -builtin function sfence_asid { - arguments Bits asid - description { - Ensure all reads and writes using address space 'asid' see any previous address space invalidations. - - Does not have to (but may, if conservative) order any global mappings. - } -} - -builtin function sfence_vaddr { - arguments XReg vaddr - description { - Ensure all reads and writes using a leaf page table that contains 'vaddr' see any previous address space invalidations. - - Must also order any global mappings containing 'vaddr'. - } -} - -builtin function sfence_asid_vaddr { - arguments Bits asid, XReg vaddr - description { - Ensure all reads and writes using address space 'asid' and a leaf page table that contains 'vaddr' see any previous address space invalidations. - - Does not have to (but may, if conservative) order any global mappings. - } -} - -builtin function invalidate_asid_translations { - arguments Bits asid - description { - Locally invalidate all cached address translations for address space 'asid'. - Does not affect global mappings. - - A valid implementation does nothing if address caching is not used. - } -} - -builtin function invalidate_vaddr_translations { - arguments XReg vaddr - description { - Locally invalidate all cached address translations representing 'vaddr', for all address spaces. - Equally affects global mappings. - - A valid implementation does nothing if address caching is not used. - } -} - -builtin function invalidate_asid_vaddr_translations { - arguments Bits asid, XReg vaddr - description { - Locally invalidate all cached address translations for address space 'asid' that represent 'vaddr'. - Does not affect global mappings. - - A valid implementation does nothing if address caching is not used. - } -} bitfield (8) PmpCfg { L 7 @@ -1049,6 +774,18 @@ function pmp_match { } } +function nan_box { + returns Bits<64> + arguments Bits<32> sp_value + description { + Produces a properly NaN-boxed double-precision value from a single-precision value + by adding all 1's to the upper bits. + } + body { + return {{32,{1'b1}}, sp_value}; + } +} + function mpv { returns Bits<1> description { @@ -1058,7 +795,7 @@ function mpv { if (implemented?(ExtensionName::H)) { return (XLEN == 32) ? CSR[mstatush].MPV : CSR[mstatus].MPV; } else { - assert(false); + assert(false, "TODO"); } } } @@ -1071,8 +808,8 @@ function effective_ldst_mode { the current actual privilege mode and modifications from `mstatus.MPRV`. } body { - // when the mode is M, loads and stores can be executed as if they were done from any other mode - // with the use of mstatus.MPRV + # when the mode is M, loads and stores can be executed as if they were done from any other mode + # with the use of mstatus.MPRV if (mode() == PrivilegeMode::M) { if (implemented?(ExtensionName::U) && CSR[mstatus].MPRV == 1) { if (CSR[mstatus].MPP == 0b00) { @@ -1091,7 +828,7 @@ function effective_ldst_mode { } } - // no modifiers were found, return actual mode + # no modifiers were found, return actual mode return mode(); } } @@ -1134,7 +871,7 @@ function pmp_check { return false; } } else { - assert(match_result == PmpMatchResult::PartialMatch); + assert(match_result == PmpMatchResult::PartialMatch, "PMP matching logic error"); # by defintion, any partial match fails the access, regardless of the config settings return false; @@ -1147,7 +884,7 @@ function pmp_check { function access_check { template U32 access_size - arguments Bits paddr, XReg vaddr, MemoryOperation type + arguments Bits paddr, XReg vaddr, MemoryOperation type, ExceptionCode fault_type description { Checks if the physical address paddr is able to access memory, and raises the appropriate exception if not. @@ -1155,69 +892,16 @@ function access_check { body { # check if this is a valid physical address if (paddr > ((1 << PHYS_ADDR_WIDTH) - access_size)) { - if (type == MemoryOperation::Write) { - raise (ExceptionCode::StoreAmoAccessFault, vaddr); - } else if (type == MemoryOperation::Read) { - raise (ExceptionCode::LoadAccessFault, vaddr); - } else if (type == MemoryOperation::Fetch) { - raise (ExceptionCode::InstructionAccessFault, vaddr); - } + raise(fault_type, vaddr); } # check PMP - # can return either AccessFault or AddressMisalignedException if (!pmp_check(paddr[PHYS_ADDR_WIDTH-1:0], type)) { - if (type == MemoryOperation::Write) { - raise (ExceptionCode::StoreAmoAccessFault, vaddr); - } else if (type == MemoryOperation::Read) { - raise (ExceptionCode::LoadAccessFault, vaddr); - } else if (type == MemoryOperation::Fetch) { - raise (ExceptionCode::InstructionAccessFault, vaddr); - } + raise(fault_type, vaddr); } } } -builtin function check_pma { - returns Boolean - arguments XReg paddr, PmaAttribute attr - description { - Returns True if the address at paddr has PMA Attribute 'attr' - } -} - -builtin function read_physical_memory { - template U32 len - returns Bits - arguments XReg paddr - description { - Read from physical memory. - } -} - -function is_naturally_aligned { - template U32 M, U32 N - returns Boolean - arguments Bits value - description { - Checks if M-bit value is naturally aligned to N bits. - } - body { - return true if N == 8; # everything is byte aligned - - Bits mask = (N/8) - 1; - return (value & ~mask) == value; - } -} - -builtin function write_physical_memory { - template U32 len - arguments XReg paddr, Bits value - description { - Write to physical memory. - } -} - function base32? { returns Boolean description { @@ -1234,7 +918,7 @@ function base32? { } else if (implemented?(ExtensionName::H) && mode() == PrivilegeMode::VS) { return CSR[hstatus].VSXL == $bits(xlen32); } else { - assert(implemented?(ExtensionName::H) && mode() == PrivilegeMode::VU); + assert(implemented?(ExtensionName::H) && mode() == PrivilegeMode::VU, "Unexpected mode"); return CSR[vsstatus].UXL == $bits(xlen32); } } @@ -1290,86 +974,56 @@ function current_translation_mode { if (effective_mode == PrivilegeMode::M) { return SatpMode::Bare; - } else { + } else if (CSR[misa].S == 1'b1) { # if (implemented?(ExtensionName::H) && virtual_mode?()) { # return CSR[vsatp].MODE; # } else { return CSR[satp].MODE; # } + } else { + # there is no S mode, and so there is no translation in what must be U-mode + return SatpMode::Bare; } } } -builtin function wfi { - description { - Wait-for-interrupt. - - A valid implementation is a no-op. - - The model will advance the PC; this function does not need to. - } -} - -function valid_vaddr? { - returns Boolean - arguments XReg vaddr +function page_walk { + template + U32 VA_SIZE, # virtual address size (Sv32 = 32, Sv39 = 39, Sv48 = 48, Sv57 = 57) + U32 PA_SIZE, # physical address size (Sv32 = 34, Sv39 = 56, Sv48 = 56, Sv57 = 56) + U32 PTESIZE, # length, in bits, of a Page Table Entry (Sv32 = 4, others = 8) + U32 LEVELS # levels in the page tabl (Sv32 = 2, Sv39 = 3, Sv48 = 4, Sv57 = 5) + returns Bits # the translated address + arguments + Bits vaddr, # the virtual address to translate + MemoryOperation op # the operation type description { - Checks whether 'vaddr' is a valid virtual address for the given machine state (e.g., translation mode). - } - body { - if (CSR[misa].S == 0) { - return true; - } + Translate virtual address through a page walk. - SatpMode translation_mode = current_translation_mode(); + May raise a Page Fault if an error involving the page table structure occurs along the walk. - if (translation_mode == SatpMode::Bare) { - if (vaddr >= (1 << PHYS_ADDR_WIDTH)) { - # virtual address is larger than physical address - return false; - } + Implicit reads of the page table are accessed check, and may raise Access Faults. + Implicit writes (updates of A/D) are also accessed checked, and may raise Access Faults - } else if (translation_mode == SatpMode::Sv32) { - # sv32 uses 32-bit virtual addresses, so all vaddrs are valid - return true; + The translated address _is not_ accessed checked. - } else if (implemented?(ExtensionName::Sv39) && translation_mode == SatpMode::Sv39) { - if (vaddr[63:39] != {25{vaddr[38]}}) { - # non-canonical virtual address - return false; - } - - } else if (implemented?(ExtensionName::Sv48) && translation_mode == SatpMode::Sv48) { - if (vaddr[63:48] != {16{vaddr[47]}}) { - # non-canonical virtual address - return false; - } - - } else if (implemented?(ExtensionName::Sv57) && translation_mode == SatpMode::Sv57) { - if (vaddr[63:57] != {7{vaddr[56]}}) { - # non-canonical virtual address - return false; - } - } - - # fall through: virtual address is valid - return true; - } -} - -function translate_sv39 { - returns Bits<56> - arguments Bits<64> vaddr, XReg satp, MemoryOperation op - description { - Translate virtual address using Sv39. - - Return physical address, which is not accessed checked (e.g., for PMP, PMA). + Returns the translated physical address. } body { - XReg ppn; + Bits ppn; + + # the VPN size is 10 bits in Sv32, and 9 bits in all others + U32 VPN_SIZE = (LEVELS == 2) ? 10 : 9; # if there is an exception, set up the correct type - ExceptionCode ecode = + ExceptionCode access_fault_code = + op == MemoryOperation::Read ? + ExceptionCode::LoadAccessFault : + ( op == MemoryOperation::Fetch ? + ExceptionCode::InstructionAccessFault : + ExceptionCode::StoreAmoAccessFault ); + + ExceptionCode page_fault_code = op == MemoryOperation::Read ? ExceptionCode::LoadPageFault : ( op == MemoryOperation::Fetch ? @@ -1382,143 +1036,166 @@ function translate_sv39 { ppn = CSR[satp].PPN; # } - if (vaddr[63:39] != {25{vaddr[38]}}) { + if (vaddr[xlen()-1:VA_SIZE] != {xlen()-VA_SIZE{vaddr[VA_SIZE - 1]}}) { # non-canonical virtual address raises a page fault # note that if pointer masking is enabled, # vaddr has already been transformed before reaching here - raise (ecode, vaddr); + raise (page_fault_code, vaddr); } - for (U32 i = 2; i >= 0; i--) { - XReg pte_addr = (ppn << 12) + ((vaddr >> (12 + 9*i)) & 0x1ff); + for (U32 i = (LEVELS - 1); i >= 0; i--) { + U32 vpn = (vaddr >> (12 + VPN_SIZE*i)) & ((1 << VPN_SIZE) - 1); + + Bits pte_addr = (ppn << 12) + (vpn * (PTESIZE/8)); # perform access check on the physical address of pte before it's used - access_check<64>(pte_addr, vaddr, op); + access_check(pte_addr, vaddr, MemoryOperation::Read, access_fault_code); - Sv39PageTableEntry pte = read_physical_memory<64>(pte_addr); - if (pte.V == 0 # invalid entry - || (pte.R == 0 && pte.W == 1) # write permission must also have read permission - || pte.Reserved != 0) { # reserved bits + if (!pma_applies?(PmaAttribute::HardwarePageTableRead, pte_addr, PTESIZE)) { + raise(access_fault_code, vaddr); + } + + Bits pte = read_physical_memory(pte_addr); + PteFlags pte_flags = pte[9:0]; + + # check if any reserved bits are set + # Sv39 has no reserved bits, and Sv39/48/57 all have reserved bits at 60:54 + if ((VA_SIZE != 32) && (pte[60:54] != 0)) { + raise(page_fault_code, vaddr); + } + + if (pte_flags.V == 0 || # invalid entry + (pte_flags.R == 0 && pte_flags.W == 1)) { # write permission must also have read permission # || (!implemented?(ExtensionName::Svnapot) && pte.N != 0) # || (!implemented?(ExtensionName::Svpbmt) && pte.PBMT != 0)) { # found invalid PTE - raise (ecode, vaddr); - } else if (pte.R == 1 || pte.X == 1) { + raise (page_fault_code, vaddr); + } else if (pte_flags.R == 1 || pte_flags.X == 1) { # found a leaf PTE - if (op == MemoryOperation::Read) { - if ((CSR[mstatus].MXR == 0 && pte.R == 0) - || (CSR[mstatus].MXR == 1 && pte.X == 0 && pte.R == 0)) { + # see if there is permission to perform the access + if (op == MemoryOperation::Read || op == MemoryOperation::ReadModifyWrite) { + if ((CSR[mstatus].MXR == 0 && pte_flags.R == 0) + || (CSR[mstatus].MXR == 1 && pte_flags.X == 0 && pte_flags.R == 0)) { # no read permission - raise (ExceptionCode::LoadPageFault, vaddr); + raise (page_fault_code, vaddr); } - if ((mode() == PrivilegeMode::U && pte.U == 0) - || (mode() == PrivilegeMode::M && CSR[mstatus].MPRV == 1 && CSR[mstatus].MPP == $bits(PrivilegeMode::HS) && pte.U == 1 && CSR[mstatus].SUM == 0) - || (mode() == PrivilegeMode::S && pte.U == 1 && CSR[mstatus].SUM == 0)) { + if ((mode() == PrivilegeMode::U && pte_flags.U == 0) + || (mode() == PrivilegeMode::M && CSR[mstatus].MPRV == 1 && CSR[mstatus].MPP == $bits(PrivilegeMode::HS) && pte_flags.U == 1 && CSR[mstatus].SUM == 0) + || (mode() == PrivilegeMode::S && pte_flags.U == 1 && CSR[mstatus].SUM == 0)) { # supervisor cannot read U unless mstatus.SUM = 1 - raise (ExceptionCode::LoadPageFault, vaddr); + raise (page_fault_code, vaddr); } - } else if ((op == MemoryOperation::Write) && (pte.W == 0)) { + } + if (((op == MemoryOperation::Write) || (op == MemoryOperation::ReadModifyWrite)) + && (pte_flags.W == 0)) { # no write permission - raise (ExceptionCode::StoreAmoPageFault, vaddr); - } else if ((op == MemoryOperation::Fetch) && (pte.X == 0)) { + raise (page_fault_code, vaddr); + } else if ((op == MemoryOperation::Fetch) && (pte_flags.X == 0)) { # no execute permission - raise (ExceptionCode::InstructionPageFault, vaddr); + raise (page_fault_code, vaddr); } - if (pte.U == 0) { + if (pte_flags.U == 0) { # supervisor page if (mode() == PrivilegeMode::U) { # user access to supervisor page is never allowed - raise (ecode, vaddr); + raise (page_fault_code, vaddr); } } else { # user page - if (mode() == PrivilegeMode::HS || (mode() == PrivilegeMode::M && CSR[mstatus].MPRV == 1 && CSR[mstatus].MPP == $bits(PrivilegeMode::HS))) { + if (mode() == PrivilegeMode::S || (mode() == PrivilegeMode::M && CSR[mstatus].MPRV == 1 && CSR[mstatus].MPP == $bits(PrivilegeMode::S))) { # effective S-mode access - if (op == MemoryOperation::Read) { + if (op == MemoryOperation::Read || op == MemoryOperation::ReadModifyWrite) { if (CSR[mstatus].SUM == 0) { # supervisor can only read user pages when mstatus.SUM == 1 - raise (ExceptionCode::LoadPageFault, vaddr); + raise (page_fault_code, vaddr); } } else { # supervisor can never write or execute a user page - raise (ecode, vaddr); + raise (page_fault_code, vaddr); } } } # ensure remaining PPN bits are zero, otherwise there is a misaligned super page - raise (ecode, vaddr) if (i >= 1 && pte.PPN0 != 0); - raise (ecode, vaddr) if (i == 2 && pte.PPN1 != 0); - - if (false) { // implemented?(ExtensionName::Svadu)) { - # svadu requires page tables to be located in memory with hardware page-table write access - # and RsrvEventual PMA - if (!check_pma(pte_addr, PmaAttribute::RsrvEventual)) { - raise (ExceptionCode::LoadAccessFault, vaddr); + raise (page_fault_code, vaddr) if ((i > 0) && (pte[(i-1)*VPN_SIZE:10] != 0)); + + # check access and dirty bits + if ((pte_flags.A == 0) # access is clear + || ((pte_flags.D == 0) # or dirty is clear and this is a (read-modify-)write + && ((op == MemoryOperation::Write) + || (op == MemoryOperation::ReadModifyWrite)))) { + + # check for hardware update of A/D bits + if (CSR[menvcfg].ADUE == 1'b1) { + # Svadu requires page tables to be located in memory with hardware page-table write access + # and RsrvEventual PMA + if (!pma_applies?(PmaAttribute::RsrvEventual, pte_addr, PTESIZE)) { + raise (access_fault_code, vaddr); + } + if (!pma_applies?(PmaAttribute::HardwarePageTableWrite, pte_addr, PTESIZE)) { + raise (access_fault_code, vaddr); + } + + access_check(pte_addr, vaddr, MemoryOperation::Write, access_fault_code); + + Boolean success; + Bits updated_pte; + if (pte_flags.D == 0 && op == MemoryOperation::Write) { + # try to set both A and D bits + updated_pte = pte | 0b11000000; + } else { + # try to set the A bit + updated_pte = pte | 0b01000000; + } + + if (PTESIZE == 32) { + success = atomic_check_then_write_32(pte_addr, pte, updated_pte); + } else if (PTESIZE == 64) { + success = atomic_check_then_write_64(pte_addr, pte, updated_pte); + } else { + assert(false, "Unexpected PTESIZE"); + } + + + if (!success) { + # the PTE changed between the read during the walk and the attempted atomic update + # roll back, and try this level again + i = i + 1; + } else { + # successful translation and update + return {(pte[PA_SIZE-3:(i*VPN_SIZE) + 10] << 2), vaddr[11:0]}; + } + } + + if ((CSR[menvcfg].ADUE == 1'b0) && implemented?(ExtensionName::Svade)) { + raise(page_fault_code, vaddr); } - access_check<64>(pte_addr, vaddr, MemoryOperation::Write); -# if (implemented?(ExtensionName::H) && CSR[henvcfg].ADUE == 0b1) { - # update the A bit - # this should be atomic with the translation -# write_physical_memory(pte_addr, pte & (1 << 6)); -# } else if (CSR[menvcfg].ADUE == 0b1) { - # update the A bit - # this should be atomic with the translation -# write_physical_memory(pte_addr, pte & (1 << 6)); -# } - } - if (//implemented?(ExtensionName::Svade) && - pte.A == 0) { - # trap on access with A == 0 - raise (ecode, vaddr); } # translation succeeded - return {pte.PPN2, pte.PPN1, pte.PPN0, vaddr[11:0]}; + return {(pte[PA_SIZE-3:(i*VPN_SIZE) + 10] << 2), vaddr[11:0]}; } else { # found a pointer to the next level if (i == 0) { # a pointer can't exist on the last level - raise (ecode, vaddr); + raise (page_fault_code, vaddr); } - if (pte.D == 1 || pte.A == 1 || pte.U == 1) { + if (pte_flags.D == 1 || pte_flags.A == 1 || pte_flags.U == 1) { # D, A, and U are reserved in non-leaf PTEs - raise (ecode, vaddr); + raise (page_fault_code, vaddr); } - ppn = pte.PPN << 12; # fall through to next level + ppn = pte[PA_SIZE-3:10] << 12; } } } } -function translate_load_sv48 { - returns Bits<56> - arguments XReg vaddr, XReg satp - description { - Translate virtual address using Sv48 - } - body { - return 0; - } -} - -function translate_load_sv57 { - returns Bits<56> - arguments XReg vaddr, XReg satp - description { - Translate virtual address using Sv57 - } - body { - return 0; - } -} - function translate { returns Bits arguments XReg vaddr, MemoryOperation op @@ -1531,7 +1208,7 @@ function translate { body { SatpMode translation_mode = current_translation_mode(); - PrivilegeMode effective_mode = effective_ldst_mode(); + XReg paddr; if (implemented?(ExtensionName::H) && virtual_mode?()) { return 0; @@ -1558,31 +1235,62 @@ function translate { } else { if (translation_mode == SatpMode::Bare) { # bare == no translation - if (vaddr > ((1 << PHYS_ADDR_WIDTH) - 1)) { - if (op == MemoryOperation::Read) { - raise (ExceptionCode::LoadAccessFault, vaddr); - } else if (op == MemoryOperation::Write) { - raise (ExceptionCode::StoreAmoAccessFault, vaddr); - } else { - assert(op == MemoryOperation::Fetch); - raise (ExceptionCode::InstructionAccessFault, vaddr); - } - } - return vaddr; + paddr = vaddr; + } else if (implemented?(ExtensionName::Sv32) && translation_mode == SatpMode::Sv32) { + # Sv32 page table walk + paddr = page_walk<32, 34, 32, 2>(vaddr, op); + } else if (implemented?(ExtensionName::Sv39) && translation_mode == SatpMode::Sv39) { + # Sv39 page table walk + paddr = page_walk<39, 56, 64, 3>(vaddr, op); + } else if (implemented?(ExtensionName::Sv48) && translation_mode == SatpMode::Sv48) { + # Sv48 page table walk + paddr = page_walk<48, 56, 64, 4>(vaddr, op); + } else if (implemented?(ExtensionName::Sv57) && translation_mode == SatpMode::Sv57) { + # Sv57 page table walk + paddr = page_walk<57, 56, 64, 5>(vaddr, op); + } + } + return paddr; + } +} -# } else if (implemented?(ExtensionName::Sv32) && translation_mode == SatpMode::Sv32) { -# return translate_sv32(vaddr, CSR[satp], op); +function canonical_vaddr? { + returns Boolean + arguments XReg vaddr + description { + Returns whether or not _vaddr_ is a valid (_i.e._, canonical) virtual address. - } else if (implemented?(ExtensionName::Sv39) && translation_mode == SatpMode::Sv39) { -# return 0; - return translate_sv39(vaddr, CSR[satp], op); + If pointer masking (S**pm) is enabled, then vaddr will be masked before checking + the canonical address. + } + body { + if (CSR[misa].S == 1'b0) { + # there is no translation, any address is canonical + return true; + } + + # canonical depends on the virtual address size in the current translation mode + SatpMode satp_mode; + if (virtual_mode?()) { + satp_mode = CSR[vsatp]; + } else { + satp_mode = CSR[satp]; + } -# } else if (implemented?(ExtensionName::Sv48) && translation_mode == SatpMode::Sv48) { -# return translation_sv48(vaddr, CSR[satp], op); + # calculate the effective address after pointer masking + XReg eaddr = mask_eaddr(vaddr); -# } else if (implemented?(ExtensionName::Sv57) && translation_mode == SatpMode::Sv57) { -# return translate_sv57(vaddr, CSR[satp], op); - } + if (satp_mode == SatpMode::Bare) { + return true; + } else if (satp_mode == SatpMode::Sv32) { + # Sv32 uses all 32 bits of the VA + return true; + } else if (satp_mode == SatpMode::Sv39) { + return eaddr[63:39] == {25{eaddr[38]}}; + } else if (satp_mode == SatpMode::Sv48) { + return eaddr[63:48] == {16{eaddr[47]}}; + } else if (satp_mode == SatpMode::Sv57) { + return eaddr[63:57] == {6{eaddr[56]}}; } } } @@ -1602,7 +1310,7 @@ function read_memory_aligned { : virtual_address; # may raise an exception - access_check(physical_address, virtual_address, MemoryOperation::Read); + access_check(physical_address, virtual_address, MemoryOperation::Read, ExceptionCode::LoadAccessFault); return read_physical_memory(physical_address); } @@ -1630,15 +1338,184 @@ function read_memory { result = result | (read_memory_aligned<8>(virtual_address + i) << (8*i)); } return result; + } else if (MISALIGNED_SPLIT_STRATEGY == "custom") { + unpredictable("An implementation is free to break a misaligned access any way, leading to unpredictable behavior when any part of the misaligned access causes an exception"); } } } } -//function load_reserved { -// -//} +# hart-global state to track the local reservation set +Boolean reservation_set_valid = false; +Bits reservation_set_address; +Bits reservation_set_size; +Bits reservation_physical_address; # The exact address used by the LR.W/LR.D +Bits reservation_virtual_address; # The exact address used by the LR.W/LR.D +Bits reservation_size; # the size of the LR operation (32 or 64) +function invalidate_reservation_set { + description { + Invalidates any currently held reservation set. + + [NOTE] + -- + This function may be called by the platform, independent of any actions + occurring in the local hart, for any or no reason. + + The platorm *must* call this function if an external hart or device accesses + part of this reservation set while reservation_set_valid could be true. + -- + } + body { + reservation_set_valid = false; + } +} + +function register_reservation_set { + arguments + Bits physical_address, # The (always aligned) physical address to reserve. + Bits length # mimimum length of the reservation. actual reservation may be larger + description { + Register a reservation for a physical address range that subsumes + [physical_address, physical_address + N). + } + body { + reservation_set_valid = true; + reservation_address = physical_address; + + if (LRSC_RESERVATION_STRATEGY == "reserve naturally-aligned 64-byte region") { + reservation_set_address = physical_address & ~XLEN'h3f; + reservation_set_size = 64; + } else if (LRSC_RESERVATION_STRATEGY == "reserve naturally-aligned 128-byte region") { + reservation_set_address = physical_address & ~XLEN'h7f; + reservation_set_size = 128; + } else if (LRSC_RESERVATION_STRATEGY == "reserve exactly enough to cover the access") { + reservation_set_address = physical_address; + reservation_set_size = length; + } else if (LRSC_RESERVATION_STRATEGY == "custom") { + unpredictable("Implementations may set reservation sets of any size, as long as they cover the reserved accessed"); + } else { + assert(false, "Unexpected LRSC_RESERVATION_STRATEGY"); + } + } +} + +function load_reserved { + template U32 N # the number of bits being loaded + returns Bits # the value of memory at virtual_address + arguments + Bits virtual_address, # the virtual address to load + Bits<1> aq, # acquire semantics? 0=no, 1=yes + Bits<1> rl # release semantics? 0=no, 1=yes + description { + Register a reservation for virtual_address at least N bits long + and read the value from memory. + + If aq is set, then also perform a memory model acquire. + + If rl is set, then also perform a memory model release (software is discouraged from doing so). + + This function assumes alignment checks have already occurred. + } + body { + Bits physical_address = + (CSR[misa].S == 1) + ? translate(virtual_address, MemoryOperation::Read) + : virtual_address; + + if (pma_applies?(PmaAttribute::RsrvNone, physical_address, N)) { + raise(ExceptionCode::LoadAccessFault, virtual_address); + } + + if (aq == 1) { + memory_model_acquire(); + } + if (rl == 1) { + memory_model_release(); + } + + register_reservation_set(physical_address, N); + + if (CSR[misa].S ==1 && LRSC_FAIL_ON_VA_SYNONYM) { + # also need to remember the virtual address + reservation_virtual_address = virtual_address; + } + + return read_memory_aligned(physical_address); + } +} + +function store_conditional { + template U32 N # number of bits being stored + returns Boolean # whether or not the store conditional succeeded + arguments + Bits virtual_address, # the virtual address to store to + Bits value, # the value to store + Bits<1> aq, # acquire semantics? 0=no, 1=yes + Bits<1> rl # release semantics? 0=no, 1=yes + description { + Atomically check the reservation set to ensure: + + * it is valid + * it covers the region addressed by this store + * the address setting the reservation set matches virtual address + + If the preceeding are met, perform the store and return 0. + Otherwise, return 1. + } + body { + Bits physical_address = + (CSR[misa].S == 1) + ? translate(virtual_address, MemoryOperation::Write) + : virtual_address; + + if (pma_applies?(PmaAttribute::RsrvNone, physical_address, N)) { + raise(ExceptionCode::StoreAmoAccessFault, virtual_address); + } + + # failed SC still looks like a store to memory protection + access_check(physical_address, virtual_address, MemoryOperation::Write, ExceptionCode::StoreAmoAccessFault); + + # acquire/release occur regardless of whether or not the SC succeeds + if (aq == 1) { + memory_model_acquire(); + } + if (rl == 1) { + memory_model_release(); + } + + if (reservation_set_valid == false) { + return false; + } + + if (!contains?(reservation_set_address, reservation_set_size, physical_address, N)) { + # this access is not in the reservation set + invalidate_reservation_set(); + return false; + } + + if (LRSC_FAIL_ON_NON_EXACT_LRSC) { + if (reservation_physical_address != physical_address || reservation_size != N) { + # this access does not match the most recent LR + invalidate_reservation_set(); + return false; + } + } + + if (LRSC_FAIL_ON_VA_SYNONYM) { + if (reservation_virtual_address != virtual_address || reservation_size != N) { + # this access does not match the most recent LR + invalidate_reservation_set(); + return false; + } + } + + # success. perform the store + write_physical_memory(physical_address, value); + + return true; + } +} @@ -1652,11 +1529,11 @@ function write_memory_aligned { XReg physical_address; physical_address = (CSR[misa].S == 1) - ? translate(virtual_address, MemoryOperation::Read) + ? translate(virtual_address, MemoryOperation::Write) : virtual_address; # may raise an exception - access_check(physical_address, virtual_address, MemoryOperation::Write); + access_check(physical_address, virtual_address, MemoryOperation::Write, ExceptionCode::StoreAmoAccessFault); write_physical_memory(physical_address, value); } diff --git a/arch/isa/util.idl b/arch/isa/util.idl new file mode 100644 index 000000000..e5dde3874 --- /dev/null +++ b/arch/isa/util.idl @@ -0,0 +1,151 @@ +%version: 1.0 + +# generic utility functions + +function power_of_2? { + template U32 N + returns Boolean + arguments Bits value + description { + Returns true if value is a power of two, false otherwise + } + body { + return (value != 0) && ((value & (value - 1)) == 0); + } +} + +function ary_includes? { + template U32 ARY_SIZE, U32 ELEMENT_SIZE + returns Boolean + arguments Bits ary[ARY_SIZE], Bits value + description { + Returns true if _value_ is an element of ary, and false otherwise + } + body { + for (U32 i = 0; i < ARY_SIZE; i++) { + if (ary[i] == value) { + return true; + } + } + return false; + } +} + +function highest_set_bit { + returns XReg + arguments XReg value + description { + Returns the position of the highest (nearest MSB) bit that is '1', + or -1 if value is zero. + } + body { + for (U32 i=xlen()-1; i >= 0; i--) { + if (value[i] == 1) { + return i; + } + } + + # fall-through; value must be zero + return -'sd1; + } +} + +function lowest_set_bit { + returns XReg + arguments XReg value + description { + Returns the position of the lowest (nearest LSB) bit that is '1', + or XLEN if value is zero. + } + body { + for (U32 i=0; i < xlen(); i++) { + if (value[i] == 1) { + return i; + } + } + + # fall-through; value must be zero + return xlen(); + } +} + +function sext { + returns XReg + arguments XReg value, XReg first_extended_bit + description { + Sign extend `value` starting at `first_extended_bit`. + + Bits [`XLEN-1`:`first_extended_bit`] of the return value + should get the value of bit (`first_extended bit - 1`). + } + body { + # in a common case, first_extended_bit is xlen(), which is compile-time-known unless + # the effective xlen is different than XLEN in some mode + # In that common case, this function will be eliminated by the compiler + if (first_extended_bit == XLEN) { + return value; + } else { + Bits<1> sign = value[first_extended_bit-1]; + for (U32 i = XLEN-1; i >= first_extended_bit; i--) { + value[i] = sign; + } + return value; + } + } +} + +function bit_length { + returns XReg + arguments XReg value + description { + Returns the minimum number of bits needed to represent value. + + Only works on unsigned values. + + The value 0 returns 1. + } + body { + for (XReg i = 63; i > 0; i--) { + if (value[i] == 1) { + return i; + } + } + + # if we get here, the value is 0 or 1. either way, say we need one bit + return 1; + } +} + +function is_naturally_aligned { + template U32 M, U32 N + returns Boolean + arguments Bits value + description { + Checks if M-bit value is naturally aligned to N bits. + } + body { + return true if N == 8; # everything is byte aligned + + Bits mask = (N/8) - 1; + return (value & ~mask) == value; + } +} + +function contains? { + returns Boolean + arguments + XReg region_start, + U32 region_size, + XReg target_start, + U32 target_size + description { + Given a _region_ defined by region_start, region_size, + determine if a _target_ defined by target_start, target_size + is completely contained with the region. + } + body { + return + target_start >= region_start && + (target_start + target_size) <= (region_start + region_size); + } +} \ No newline at end of file diff --git a/backends/arch_gen/lib/arch_gen.rb b/backends/arch_gen/lib/arch_gen.rb index ffb1f6459..4989e7753 100644 --- a/backends/arch_gen/lib/arch_gen.rb +++ b/backends/arch_gen/lib/arch_gen.rb @@ -530,10 +530,28 @@ def maybe_add_csr(csr_name, extra_env = {}) merged_path = gen_merged_def(:csr, arch_path, arch_overlay_path) + merged_content = File.read(merged_path) + arch_content = arch_path.nil? ? "" : File.read(arch_path) + arch_overlay_content = arch_path.nil? ? "" : File.read(arch_path) + + # figure out where the original file can be found: + # * arch_path if there is no contribution from arch_overlay + # * arch_overlay_path if there is no contribution from arch (i.e., a custom instruction) + # * merged_path if there are contributions from both + og_path = + if arch_content == merged_content + arch_path_for(:csr, csr_name) + elsif arch_overlay_content == merged_content + arch_overlay_path_for(:csr, csr_name) + else + merged_path + end + # get the csr data (not including the name key), which is redundant at this point csr_data = YAML.load_file(merged_path)[csr_name] csr_data["name"] = csr_name csr_data["fields"].each { |n, f| f["name"] = n } + csr_data["__source"] = og_path.to_s csr_yaml = YAML.dump({ csr_name => csr_data}) begin @@ -553,7 +571,7 @@ def maybe_add_csr(csr_name, extra_env = {}) @implemented_csrs ||= [] @implemented_csrs << csr_name if belongs - gen_csr_path = @gen_dir / "arch" / "csr" / csr_obj.extension_requirements[0].name / "#{csr_name}.yaml" + gen_csr_path = @gen_dir / "arch" / "csr" / csr_obj.defined_by[0].name / "#{csr_name}.yaml" FileUtils.mkdir_p gen_csr_path.dirname gen_csr_path.write csr_yaml end @@ -740,9 +758,27 @@ def maybe_add_inst(inst_name, extra_env = {}) merged_path = gen_merged_def(:inst, arch_path, arch_overlay_path) + merged_content = File.read(merged_path) + arch_content = arch_path.nil? ? "" : File.read(arch_path) + arch_overlay_content = arch_path.nil? ? "" : File.read(arch_path) + + # figure out where the original file can be found: + # * arch_path if there is no contribution from arch_overlay + # * arch_overlay_path if there is no contribution from arch (i.e., a custom instruction) + # * merged_path if there are contributions from both + og_path = + if arch_content == merged_content + arch_path_for(:inst, inst_name) + elsif arch_overlay_content == merged_content + arch_overlay_path_for(:inst, inst_name) + else + merged_path + end + # get the inst data (not including the name key), which is redundant at this point inst_data = YAML.load_file(merged_path)[inst_name] inst_data["name"] = inst_name + inst_data["__source"] = og_path.to_s inst_yaml = YAML.dump({ inst_name => inst_data}) begin @@ -777,8 +813,8 @@ def maybe_add_inst(inst_name, extra_env = {}) @implemented_instructions ||= [] @implemented_instructions << inst_name if belongs - raise "?" if inst_obj.extension_requirements[0].name.nil? - gen_inst_path = @gen_dir / "arch" / "inst" / inst_obj.extension_requirements[0].name / "#{inst_name}.yaml" + raise "?" if inst_obj.defined_by[0].name.nil? + gen_inst_path = @gen_dir / "arch" / "inst" / inst_obj.defined_by[0].name / "#{inst_name}.yaml" FileUtils.mkdir_p gen_inst_path.dirname gen_inst_path.write inst_yaml diff --git a/backends/arch_gen/tasks.rake b/backends/arch_gen/tasks.rake index 483b3fcec..b72a7b703 100644 --- a/backends/arch_gen/tasks.rake +++ b/backends/arch_gen/tasks.rake @@ -35,18 +35,21 @@ file "#{$root}/.stamps/arch-gen.stamp" => ( v["name"] = k [k, v] end + csr_obj[csr_name]["__source"] = f [csr_name, csr_obj[csr_name]] end.to_h inst_hash = Dir.glob($root / "arch" / "inst" / "**" / "*.yaml").map do |f| inst_obj = YAML.load_file(f) inst_name = inst_obj.keys[0] inst_obj[inst_name]["name"] = inst_name + inst_obj[inst_name]["__source"] = f [inst_name, inst_obj[inst_name]] end.to_h ext_hash = Dir.glob($root / "arch" / "ext" / "**" / "*.yaml").map do |f| ext_obj = YAML.load_file(f) ext_name = ext_obj.keys[0] ext_obj[ext_name]["name"] = ext_name + ext_obj[ext_name]["__source"] = f [ext_name, ext_obj[ext_name]] end.to_h diff --git a/backends/cfg_html_doc/templates/csr.adoc.erb b/backends/cfg_html_doc/templates/csr.adoc.erb index 044d85988..8693fa850 100644 --- a/backends/cfg_html_doc/templates/csr.adoc.erb +++ b/backends/cfg_html_doc/templates/csr.adoc.erb @@ -68,7 +68,7 @@ a| <%= field.description %> |==== <%- end -%> -<%- if csr.fields.map(&:has_custom_write?).any? -%> +<%- if csr.fields.map(&:has_custom_sw_write?).any? -%> == Software write This CSR may store a value that is different from what software attempts to write. @@ -79,7 +79,7 @@ written value: [idl] ---- <%- csr.fields.each do |field| -%> -<%- if field.has_custom_write? -%> +<%- if field.has_custom_sw_write? -%> <%= field.name %> = <%= field["sw_write(csr_value)"] %> <%- else -%> <%= field.name %> = csr_value.<%= field.name %> @@ -95,7 +95,7 @@ This CSR may return a value that is different from what is stored in hardware. [subs="specialchars,macros"] ---- -<%= csr.pruned_sw_read_ast(arch_def.sym_table.deep_clone).gen_adoc %> +<%= csr.pruned_sw_read_ast(arch_def).gen_adoc %> ---- <%- end -%> diff --git a/backends/cfg_html_doc/templates/inst.adoc.erb b/backends/cfg_html_doc/templates/inst.adoc.erb index 37ddf1227..fbc428f7f 100644 --- a/backends/cfg_html_doc/templates/inst.adoc.erb +++ b/backends/cfg_html_doc/templates/inst.adoc.erb @@ -5,6 +5,21 @@ *<%= inst.long_name %>* +This instruction is defined by<%- if inst.defined_by.size > 1 -%> any of the following<%- end -%>: + + <%- inst.defined_by.each do |ext| -%> + * `<%= ext.name %>`, `<%= ext.version_requirement %>` + <%- end -%> + +<%- unless inst.extension_requirements.empty? -%> +Additionally, this instruction is only defined if the following <%- if inst.extension_requirements.size == 1 -%>extension is<%- else -%>extensions are<%- end -%> also present and active: + + <%- inst.extension_requirements.each do |ext| -%> + * `<%= ext.name %>`, `<%= ext.version_requirement %>` + <%- end -%> + +<%- end -%> + == Encoding <%- if arch_def.multi_xlen? && inst.multi_encoding? -%> @@ -67,6 +82,7 @@ RV64:: ==== RV32:: + +[source.idl] ---- <%- inst.decode_variables(32).each do |d| -%> <%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; @@ -75,6 +91,7 @@ RV32:: RV64:: + +[source,idl] ---- <%- inst.decode_variables(64).each do |d| -%> <%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; @@ -82,6 +99,7 @@ RV64:: ---- ==== <%- else -%> +[source,idl] ---- <%- inst.decode_variables(arch_def.config_params["XLEN"]).each do |d| -%> <%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; @@ -91,17 +109,19 @@ RV64:: == Execution -<%- if inst.operation_ast(arch_def.idl_compiler).nil? -%> +<%- xlens = inst.base.nil? ? (arch_def.multi_xlen? ? [32, 64] : [arch_def.mxlen]) : [inst.base] -%> -<%- else -%> +<%- if inst.key?("operation()") -%> [tabs] ==== -Pruned:: +<%- xlens.each do |effective_xlen| -%> +Pruned, XLEN == <%= effective_xlen %>:: + [source,idl,subs="specialchars,macros"] ---- -<%= inst.pruned_operation_ast(arch_def.sym_table).gen_adoc %> +<%= inst.pruned_operation_ast(arch_def.sym_table, effective_xlen).gen_adoc %> ---- +<%- end -%> Original:: + @@ -118,7 +138,7 @@ Original:: This instruction may result in the following synchronous exceptions: - <%- exception_list.each do |etype| -%> + <%- exception_list.sort.each do |etype| -%> * <%= etype %> <%- end -%> diff --git a/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb b/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb index d20d8c03b..4170fef32 100644 --- a/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb +++ b/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb @@ -139,7 +139,7 @@ RV64:: <%- else -%> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump i.wavedrom_desc(i.respond_to?(:base) ? i.base : 64) %> +<%= JSON.dump i.wavedrom_desc(i.base.nil? ? 64 : i.base) %> .... <%- end -%> @@ -166,7 +166,7 @@ RV64:: ---- <%- else -%> ---- -<%- i.decode_variables(i.respond_to?(:base) ? i.base : 64).each do |d| -%> +<%- i.decode_variables(i.base.nil? ? 64 : i.base).each do |d| -%> <%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; <%- end -%> ---- diff --git a/bin/.container-tag b/bin/.container-tag new file mode 100644 index 000000000..ceab6e11e --- /dev/null +++ b/bin/.container-tag @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/bin/build_container b/bin/build_container index 6a0e01a88..0c3e5a970 100755 --- a/bin/build_container +++ b/bin/build_container @@ -2,6 +2,8 @@ ROOT=$(realpath $(dirname $(dirname $BASH_SOURCE[0]))) +CONTAINER_TAG=`cat ${ROOT}/bin/.container-tag` + # uncomment below if you have sudo permission and don't have fakeroot permission NEED_SUDO=0 cat /etc/subgid | grep "^$(id -u):" @@ -58,7 +60,7 @@ fi $SUDO singularity build --force \ $FAKEROOT \ - ${ROOT}/.singularity/image.sif \ + ${ROOT}/.singularity/image-${CONTAINER_TAG}.sif \ ${ROOT}/container.def if [ $? -ne 0 ]; then echo "Container build failed." 2>&1 diff --git a/bin/setup b/bin/setup index 0233477ad..4eb028dcb 100755 --- a/bin/setup +++ b/bin/setup @@ -4,32 +4,32 @@ ROOT=$(dirname $(dirname $(realpath $BASH_SOURCE[0]))) OLDWD=$PWD cd $ROOT -CONTAINER_TAG=0.1 +CONTAINER_TAG=`cat ${ROOT}/bin/.container-tag` if [ ! -d $ROOT/.home ]; then mkdir $ROOT/.home fi -if [ ! -f $ROOT/.singularity/image.sif ]; then +if [ ! -f $ROOT/.singularity/image-$CONTAINER_TAG.sif ]; then echo "Fetching container..." if [ ! -d "${ROOT}/.singularity" ]; then mkdir -p ${ROOT}/.singularity fi - singularity pull --disable-cache ${ROOT}/.singularity/image.sif oras://docker.io/riscvintl/spec-generator:$CONTAINER_TAG + singularity pull --disable-cache ${ROOT}/.singularity/image-$CONTAINER_TAG.sif oras://docker.io/riscvintl/spec-generator:$CONTAINER_TAG fi if [ ! -f $ROOT/.bundle/config ]; then OLDDIR=$PWD cd $ROOT - singularity run --home ${ROOT}/.home .singularity/image.sif bundle config set --local path ${ROOT}/.home/.gems - singularity run --home ${ROOT}/.home .singularity/image.sif bundle config set --local cache_path ${ROOT}/.home/.cache + singularity run --home ${ROOT}/.home .singularity/image-$CONTAINER_TAG.sif bundle config set --local path ${ROOT}/.home/.gems + singularity run --home ${ROOT}/.home .singularity/image-$CONTAINER_TAG.sif bundle config set --local cache_path ${ROOT}/.home/.cache cd $OLDDIR fi if [ ! -d $ROOT/.home/.gems ]; then OLDDIR=$PWD cd $ROOT - singularity run --home ${ROOT}/.home .singularity/image.sif bundle install + singularity run --home ${ROOT}/.home .singularity/image-$CONTAINER_TAG.sif bundle install cd $OLDDIR fi @@ -39,8 +39,8 @@ fi if [[ ! -z "$DEVELOPMENT" && $DEVELOPMENT -eq 1 ]]; then if [ ! -d "${ROOT}/.home/.yard/gem_index"]; then - singularity run --home ${ROOT}/.home .singularity/image.sif bundle exec --gemfile ${ROOT}/Gemfile yard config --gem-install-yri - singularity run --home ${ROOT}/.home .singularity/image.sif bundle exec --gemfile ${ROOT}/Gemfile yard gems + singularity run --home ${ROOT}/.home .singularity/image-$CONTAINER_TAG.sif bundle exec --gemfile ${ROOT}/Gemfile yard config --gem-install-yri + singularity run --home ${ROOT}/.home .singularity/image-$CONTAINER_TAG.sif bundle exec --gemfile ${ROOT}/Gemfile yard gems touch ${ROOT}/.stamps/dev_gems fi fi @@ -64,11 +64,11 @@ if [[ ! -z "$VSCODE" && $VSCODE -eq 1 ]]; then fi if [ ! -d ${ROOT}/node_modules ]; then - singularity run --home ${PWD}/.home .singularity/image.sif npm i + singularity run --home ${PWD}/.home .singularity/image-$CONTAINER_TAG.sif npm i fi -BUNDLE="singularity run --home ${PWD}/.home .singularity/image.sif bundle" -RUBY="singularity run --home ${PWD}/.home .singularity/image.sif bundle exec ruby" -RAKE="singularity run --home ${PWD}/.home .singularity/image.sif bundle exec rake" -NPM="singularity run --home ${PWD}/.home .singularity/image.sif npm" -NPX="singularity run --home ${PWD}/.home .singularity/image.sif npx" +BUNDLE="singularity run --home ${PWD}/.home .singularity/image-$CONTAINER_TAG.sif bundle" +RUBY="singularity run --home ${PWD}/.home .singularity/image-$CONTAINER_TAG.sif bundle exec ruby" +RAKE="singularity run --home ${PWD}/.home .singularity/image-$CONTAINER_TAG.sif bundle exec rake" +NPM="singularity run --home ${PWD}/.home .singularity/image-$CONTAINER_TAG.sif npm" +NPX="singularity run --home ${PWD}/.home .singularity/image-$CONTAINER_TAG.sif npx" diff --git a/cfgs/generic_rv64/params.yaml b/cfgs/generic_rv64/params.yaml index 0f713d7f7..90c2bb628 100644 --- a/cfgs/generic_rv64/params.yaml +++ b/cfgs/generic_rv64/params.yaml @@ -284,6 +284,30 @@ params: # * 3264: VSXLEN can be changed (via hstatus.VSXL) between 32 and 64 VUXLEN: 64 + # Strategy used to handle reservation sets + # + # * "reserve naturally-aligned 64-byte region": Always reserve the 64-byte block containing the LR/SC address + # * "reserve naturally-aligned 128-byte region": Always reserve the 128-byte block containing the LR/SC address + # * "reserve exactly enough to cover the access": Always reserve exactly the LR/SC access, and no more + # * "custom": Custom behavior, leading to an 'unpredictable' call on any LR/SC + LRSC_RESERVATION_STRATEGY: reserve naturally-aligned 64-byte region + + # whether or not an SC will fail if its VA does not match the VA of the prior LR, + # even if the physical address of the SC and LR are the same + LRSC_FAIL_ON_VA_SYNONYM: false + + # what to do when an LR/SC address is misaligned: + # + # * 'always raise misaligned exception': self-explainitory + # * 'always raise access fault': self-explainitory + # * 'custom': Custom behavior; misaligned LR/SC may sometimes raise a misaligned exception and sometimes raise a access fault. Will lead to an 'unpredictable' call on any misaligned LR/SC access + LRSC_MISALIGNED_BEHAVIOR: always raise misaligned exception + + # whether or not a Store Conditional fails if its physical address and size do not + # exactly match the physical address and size of the last Load Reserved in program order + # (independent of whether or not the SC is in the current reservation set) + LRSC_FAIL_ON_NON_EXACT_LRSC: false + hpm_events: - 'L1_ICACHE_MISS' - 'L2_CACHE_MISS' @@ -294,6 +318,7 @@ custom_exception_codes: [] extensions: - [A, 2.1] - [B, 1.0] + - [C, 2.2] - [D, 2.2] # - [F, 2.2] - [I, 2.1] diff --git a/lib/arch_def.rb b/lib/arch_def.rb index 58bdf857e..003932b6d 100644 --- a/lib/arch_def.rb +++ b/lib/arch_def.rb @@ -36,13 +36,33 @@ # is warranted, e.g., the CSR Field 'alias' returns a CsrFieldAlias object # instead of a simple string class ArchDefObject - attr_reader :data + attr_reader :data, :name, :long_name, :description + + # @return [String] Source file that data for this object can be attributed to + # @return [nil] if the source isn't known + def __source + @data["__source"] + end + + # The raw content of definedBy in the data. + # @note Generally, you should prefer to use {#defined_by?}, etc. from Ruby + # + # @return [String] An extension name + # @return [Array(String, Number)] An extension name and versions + # @return [Array<*>] A list of extension names or extension names and versions + def definedBy + @data["definedBy"] + end # @param data [Hash] Hash with fields to be added def initialize(data) raise "Bad data" unless data.is_a?(Hash) @data = data + @name = data["name"] + @long_name = data["long_name"] + @description = data["description"] + end def inspect @@ -61,21 +81,21 @@ def keys = @data.keys def key?(k) = @data.key?(k) # adds accessor functions for any properties in the data - def method_missing(method_name, *args, &block) - if @data.key?(method_name.to_s) - raise "Unexpected argument to '#{method_name}" unless args.empty? + # def method_missing(method_name, *args, &block) + # if @data.key?(method_name.to_s) + # raise "Unexpected argument to '#{method_name}" unless args.empty? - raise "Unexpected block given to '#{method_name}" if block_given? + # raise "Unexpected block given to '#{method_name}" if block_given? - @data[method_name.to_s] - else - super - end - end + # @data[method_name.to_s] + # else + # super + # end + # end - def respond_to_missing?(method_name, include_private = false) - @data.key?(method_name.to_s) || super - end + # def respond_to_missing?(method_name, include_private = false) + # @data.key?(method_name.to_s) || super + # end # @overload defined_by?(ext_name, ext_version) # @param ext_name [#to_s] An extension name @@ -88,14 +108,14 @@ def defined_by?(*args) if args.size == 1 raise ArgumentError, "Parameter must be an ExtensionVersion" unless args[0].is_a?(ExtensionVersion) - extension_requirements.any? do |r| + defined_by.any? do |r| r.satisfied_by?(args[0]) end elsif args.size == 2 raise ArgumentError, "First parameter must be an extension name" unless args[0].respond_to?(:to_s) raise ArgumentError, "Second parameter must be an extension version" unless args[0].respond_to?(:to_s) - extension_requirements.any? do |r| + defined_by.any? do |r| r.satisfied_by?(args[0].to_s, args[1].to_s) end end @@ -116,28 +136,73 @@ def extension_requirement?(obj) end private :extension_requirement? - # @return [Array] Extension requirements for the instruction. If *any* requirement is met, the instruction is defined - def extension_requirements - return @extension_requirements unless @extension_requirements.nil? + # @return [Array] Extension(s) that define the instruction. If *any* requirement is met, the instruction is defined. + def defined_by + return @defined_by unless @defined_by.nil? - @extension_requirements = [] + @defined_by = [] if @data["definedBy"].is_a?(Array) # could be either a single extension with requirement, or a list of requirements if extension_requirement?(@data["definedBy"][0]) - @extension_requirements << to_extension_requirement(@data["definedBy"][0]) + @defined_by << to_extension_requirement(@data["definedBy"][0]) else # this is a list @data["definedBy"].each do |r| - @extension_requirements << to_extension_requirement(r) + @defined_by << to_extension_requirement(r) end end else - @extension_requirements << to_extension_requirement(@data["definedBy"]) + @defined_by << to_extension_requirement(@data["definedBy"]) + end + + raise "empty requirements" if @defined_by.empty? + + @defined_by + end + + # @return [Integer] THe source line number of +path+ in the YAML file + # @param path [Array] Path to the scalar you want. + # @example + # yaml = <<~YAML + # misa: + # sw_read(): ... + # fields: + # A: + # type(): ... + # YAML + # misa_csr.source_line("sw_read()") #=> 2 + # mis_csr.source_line("fields", "A", "type()") #=> 5 + def source_line(*path) + + # find the line number of this operation() in the *original* file + yaml_filename = @data["__source"] + raise "No __source for #{name}" if yaml_filename.nil? + line = nil + path_idx = 0 + Psych.parse_stream(File.read(yaml_filename), filename: yaml_filename) do |doc| + mapping = doc.children[0] + data = mapping.children[1] + while path_idx < path.size + idx = 0 + while idx < data.children.size + if data.children[idx].value == path[path_idx] + if path_idx == path.size - 1 + line = data.children[idx + 1].start_line + if data.children[idx + 1].style == Psych::Nodes::Scalar::LITERAL + line += 1 # the string actually begins on the next line + end + return line + else + data = data.children[idx + 1] + path_idx += 1 + break + end + end + idx += 2 + end + end end - - raise "empty requirements" if @extension_requirements.empty? - - @extension_requirements + raise "Didn't find key '#{path}' in #{@data['__source']}" end end @@ -152,6 +217,12 @@ class CsrField < ArchDefObject # @return [Range] Range of the aliased field that is being pointed to Alias = Struct.new(:field, :range) + # @return [Integer] The base XLEN required for this CsrField to exist. One of [32, 64] + # @return [nil] if the CsrField exists in any XLEN + def base + @data["base"] + end + # @param parent_csr [Csr] The Csr that defined this field # @param field_data [Hash] Field data from the arch spec def initialize(parent_csr, field_data) @@ -168,6 +239,79 @@ def exists_in_cfg?(possible_xlens, extensions) (@data["definedBy"].nil? || extensions.any? { |e| defined_by?(e) } ) end + # @return [Idl::FunctionBodyAst] Abstract syntax tree of the type() function + # @return [nil] if the type property is not a function + # @param idl_compiler [Idl::Compiler] A compiler + def type_ast(idl_compiler) + return @type_ast unless @type_ast.nil? + return nil if @data["type()"].nil? + + @type_ast = idl_compiler.compile_func_body( + @data["type()"], + name: "CSR[#{name}].type()", + input_file: csr.__source, + input_line: csr.source_line("fields", name, "type()"), + type_check: false + ) + + raise "unexpected #{@type_ast.class}" unless @type_ast.is_a?(Idl::FunctionBodyAst) + + @type_ast + end + + # @return [Idl::FunctionBodyAst] Abstract syntax tree of the type() function, after it has been type checked + # @return [nil] if the type property is not a function + # @param idl_compiler [Idl::Compiler] A compiler + def type_checked_type_ast(arch_def) + @type_checked_type_asts ||= {} + ast = @type_checked_type_asts[arch_def.name] + return ast unless ast.nil? + + symtab = arch_def.sym_table.deep_clone + symtab.push + # all CSR instructions are 32-bit + symtab.add( + "__expected_return_type", + Idl::Type.new(:enum_ref, enum_class: arch_def.sym_table.get("CsrFieldType")) + ) + + ast = type_ast(arch_def.idl_compiler) + arch_def.idl_compiler.type_check( + ast, + symtab, + "CSR[#{name}].type()" + ) + @type_checked_type_asts[arch_def.name] = ast + end + + # @return [Idl::FunctionBodyAst] Abstract syntax tree of the type() function, after it has been type checked and pruned + # @return [nil] if the type property is not a function + # @param idl_compiler [Idl::Compiler] A compiler + def pruned_type_ast(arch_def) + @pruned_type_asts ||= {} + ast = @pruned_type_asts[arch_def.name] + return ast unless ast.nil? + + ast = type_checked_type_ast(arch_def).prune(arch_def.sym_table.deep_clone) + + symtab = arch_def.sym_table.deep_clone + symtab.push + # all CSR instructions are 32-bit + symtab.add( + "__expected_return_type", + Idl::Type.new(:enum_ref, enum_class: arch_def.sym_table.get("CsrFieldType")) + ) + + arch_def.idl_compiler.type_check( + ast, + symtab, + "CSR[#{name}].type()" + ) + @pruned_type_asts[arch_def.name] = ast + end + + # returns the definitive type for a configuration + # # @param arch_def [ArchDef] A config # @return [String] # The type of the field. One of: @@ -192,23 +336,11 @@ def type(arch_def) idl = @data["type()"] raise "type() is nil for #{csr.name}.#{name} #{@data}?" if idl.nil? - expected_return_type = - Idl::Type.new(:enum_ref, enum_class: arch_def.sym_table.get("CsrFieldType")) - sym_table = arch_def.sym_table - - ast = arch_def.idl_compiler.compile_func_body( - idl, - symtab: sym_table, - return_type: expected_return_type, - name: "type", - parent: "#{csr.name}.#{name}" - ) - - sym_table = sym_table.deep_clone(clone_values: true) + sym_table = arch_def.sym_table.deep_clone(clone_values: true) sym_table.push # for consistency with template functions begin - case ast.return_value(sym_table) + case pruned_type_ast(arch_def).return_value(sym_table) when 0 "RO" when 1 @@ -233,7 +365,6 @@ def type(arch_def) end end @type_cache[arch_def] = type - type end # @return [Alias,nil] The aliased field, or nil if there is no alias @@ -263,45 +394,35 @@ def alias @alias end - # @return [Boolean] True if the field has a custom write function (i.e., `sw_write(csr_value)` exists in the spec) - def has_custom_write? - @data.key?("sw_write(csr_value)") && !@data["sw_write(csr_value)"].empty? - end - - def sw_write_ast(arch_def, effective_xlen = nil) - return @sw_write_ast unless @sw_write_ast.nil? - return nil if @data["sw_write(csr_value)"].nil? - - # now, parse the function - - symtab = arch_def.sym_table.deep_clone - - # push the csr_value - symtab.push - symtab.add("csr_value", Idl::Var.new("csr_value", csr.bitfield_type(arch_def, effective_xlen))) + # @return [Array] List of functions reachable from this CSR's sw_read or a field's sw_wirte function def reachable_functions(arch_def) fns = [] if has_custom_sw_read? - ast = sw_read_ast(arch_def) + ast = pruned_sw_read_ast(arch_def) symtab = arch_def.sym_table.deep_clone symtab.push fns.concat(ast.reachable_functions(symtab)) @@ -505,14 +783,14 @@ def reachable_functions(arch_def) if arch_def.multi_xlen? implemented_fields_for(arch_def, 32).each do |field| - fns.concat(field.reachable_functions(arch_def.sym_table, 32)) + fns.concat(field.reachable_functions(arch_def, 32)) end implemented_fields_for(arch_def, 64).each do |field| - fns.concat(field.reachable_functions(arch_def.sym_table, 64)) + fns.concat(field.reachable_functions(arch_def, 64)) end else implemented_fields_for(arch_def, arch_def.mxlen).each do |field| - fns.concat(field.reachable_functions(arch_def.sym_table, arch_def.mxlen)) + fns.concat(field.reachable_functions(arch_def, arch_def.mxlen)) end end @@ -764,34 +1042,79 @@ def has_custom_sw_read? @data.key?("sw_read()") && !@data["sw_read()"].empty? end - def sw_read_ast(arch_def) + # @return [FunctionBodyAst] The abstract syntax tree of the sw_read() function, after being type checked + # @param arch_def [ImplArchDef] A configuration + def type_checked_sw_read_ast(arch_def) + @type_checked_sw_read_asts ||= {} + ast = @type_checked_sw_read_asts[arch_def.name] + return ast unless ast.nil? + + symtab = arch_def.sym_table.deep_clone + symtab.push + # all CSR instructions are 32-bit + symtab.add( + "__instruction_encoding_size", + Idl::Var.new("__instruction_encoding_size", Idl::Type.new(:bits, width: 6), 32) + ) + symtab.add( + "__expected_return_type", + Idl::Type.new(:bits, width: 128) + ) + + ast = sw_read_ast(arch_def.idl_compiler) + arch_def.idl_compiler.type_check( + ast, + symtab, + "CSR[#{name}].sw_read()" + ) + @type_checked_sw_read_asts[arch_def.name] = ast + end + + # @return [FunctionBodyAst] The abstract syntax tree of the sw_read() function + # @param idl_compiler [Idl::Compiler] A compiler + def sw_read_ast(idl_compiler) return @sw_read_ast unless @sw_read_ast.nil? return nil if @data["sw_read()"].nil? # now, parse the function - extra_syms = { - # all CSR instructions are 32-bit - "__instruction_encoding_size" => - Idl::Var.new("__instruction_encoding_size", Idl::Type.new(:bits, width: 6), 32) - } - - @sw_read_ast = arch_def.idl_compiler.compile_func_body( + @sw_read_ast = idl_compiler.compile_func_body( @data["sw_read()"], return_type: Idl::Type.new(:bits, width: 128), # big int to hold special return values - symtab: arch_def.sym_table, name: "CSR[#{name}].sw_read()", - input_file: "CSR #{name}", - extra_syms: + input_file: source_line("sw_read()"), + type_check: false ) - raise "unexpected #{@sw_read_ast.class}" unless @sw_read_ast.is_a?(Idl::FunctionBodyAst) @sw_read_ast end - def pruned_sw_read_ast(symtab) - sw_read_ast(symtab.archdef).prune(symtab) + def pruned_sw_read_ast(arch_def) + @pruned_sw_read_asts ||= {} + ast = @pruned_sw_read_asts[arch_def.name] + return ast unless ast.nil? + + ast = type_checked_sw_read_ast(arch_def).prune(arch_def.sym_table.deep_clone) + + symtab = arch_def.sym_table.deep_clone + symtab.push + # all CSR instructions are 32-bit + symtab.add( + "__instruction_encoding_size", + Idl::Var.new("__instruction_encoding_size", Idl::Type.new(:bits, width: 6), 32) + ) + symtab.add( + "__expected_return_type", + Idl::Type.new(:bits, width: 128) + ) + + arch_def.idl_compiler.type_check( + ast, + symtab, + "CSR[#{name}].sw_read()" + ) + @pruned_sw_read_asts[arch_def.name] = ast end # @example Result for an I-type instruction @@ -843,14 +1166,62 @@ def exists_in_cfg?(possible_xlens, extensions) # model of a specific instruction in a specific base (RV32/RV64) class Instruction < ArchDefObject - def fill_symtab(global_symtab) - symtab = global_symtab.deep_clone(clone_values: true) + # @return [Hash] Hash of access permissions for each mode. The key is the lowercase name of a privilege mode, and the value is one of ['never', 'sometimes', 'always'] + def access + @data["access"] + end + + # @return [String] Details of the access restrictions + # @return [nil] if no details are available + def access_detail + @data["access_detail"] + end + + # @return [Integer] XLEN that must be effective for instruction to exist + # @return [nil] if instruction exists in all XLENs + def base + @data["base"] + end + + # @return [String] Assembly format + def assembly + @data["assembly"] + end + + # @return [Array] List of extensions requirements (in addition to one returned by {#defined_by}) that must be met for the instruction to exist + def extension_requirements + return [] unless @data.key?("requires") + + @extension_requirements = [] + if @data["requires"].is_a?(Array) + # could be either a single extension with requirement, or a list of requirements + if extension_requirement?(@data["requires"][0]) + @extension_requirements << to_extension_requirement(@data["requires"][0]) + else + # this is a list + @data["requires"].each do |r| + @extension_requirements << to_extension_requirement(r) + end + end + else + @extension_requirements << to_extension_requirement(@data["requires"]) + end + + @extension_requirements + end + + def fill_symtab(global_symtab, effective_xlen) + symtab = global_symtab.deep_clone symtab.push symtab.add( "__instruction_encoding_size", - Idl::Var.new("__instruction_encoding_size", Idl::Type.new(:bits, width:encoding_width.bit_length), encoding_width) + Idl::Var.new("__instruction_encoding_size", Idl::Type.new(:bits, width: encoding_width.bit_length), encoding_width) + ) + symtab.add( + "__effective_xlen", + Idl::Var.new("__effective_xlen", Idl::Type.new(:bits, width: 7), effective_xlen) ) - @encodings[symtab.archdef.config_params["XLEN"]].decode_variables.each do |d| + @encodings[effective_xlen].decode_variables.each do |d| qualifiers = [] qualifiers << :signed if d.sext? width = d.size @@ -863,57 +1234,90 @@ def fill_symtab(global_symtab) end private :fill_symtab - # type check the instruction operation in the context of symtab - # - # @param global_symtab [Idl::SymbolTable] A symbol table with global scope populated - # @raise Idl::AstNode::TypeError if there is a type problem - def type_check_operation(global_symtab) - global_symtab.archdef.idl_compiler.type_check( - operation_ast(global_symtab.archdef.idl_compiler), - fill_symtab(global_symtab), - "#{name}.operation()" - ) - end - # @param global_symtab [Idl::SymbolTable] Symbol table with global scope populated and a configuration loaded # @return [Idl::FunctionBodyAst] A pruned abstract syntax tree - def pruned_operation_ast(global_symtab) + def pruned_operation_ast(global_symtab, effective_xlen) + @pruned_asts ||= {} - type_check_operation(global_symtab) - operation_ast(global_symtab.archdef.idl_compiler).prune(fill_symtab(global_symtab)) + arch_def = global_symtab.archdef + + pruned_ast = @pruned_asts[arch_def.name] + return pruned_ast unless pruned_ast.nil? + + return nil unless @data.key?("operation()") + + pruned_ast = type_checked_operation_ast(arch_def, effective_xlen).prune(fill_symtab(global_symtab, effective_xlen)) + arch_def.idl_compiler.type_check( + pruned_ast, + fill_symtab(global_symtab, effective_xlen), + "#{name}.operation() (pruned)" + ) + + @pruned_asts[arch_def.name] = pruned_ast end # @param symtab [Idl::SymbolTable] Symbol table with global scope populated + # @param effective_xlen [Integer] The effective XLEN to evaluate against # @return [Array] List of all functions that can be reached from operation() - def reachable_functions(symtab) + def reachable_functions(symtab, effective_xlen) if @data["operation()"].nil? [] else - pruned_operation_ast(symtab).reachable_functions(fill_symtab(symtab)) + # RubyProf.start + pruned_operation_ast(symtab, effective_xlen).reachable_functions(fill_symtab(symtab, effective_xlen)) + # result = RubyProf.stop + # RubyProf::FlatPrinter.new(result).print($stdout) + # exit end end # @param symtab [Idl::SymbolTable] Symbol table with global scope populated + # @param effective_xlen [Integer] Effective XLEN to evaluate against # @return [Array] List of all exceptions that can be reached from operation() - def reachable_exceptions(symtab) + def reachable_exceptions(symtab, effective_xlen) if @data["operation()"].nil? [] else - pruned_operation_ast(symtab).reachable_exceptions(fill_symtab(symtab)).uniq + pruned_operation_ast(symtab).reachable_exceptions(fill_symtab(symtab, effective_xlen)).uniq end end # @param symtab [Idl::SymbolTable] Symbol table with global scope populated + # @param effective_xlen [Integer] Effective XLEN to evaluate against. If nil, evaluate against all valid XLENs # @return [Array] List of all exceptions that can be reached from operation() - def reachable_exceptions_str(symtab) + def reachable_exceptions_str(symtab, effective_xlen=nil) if @data["operation()"].nil? [] else # RubyProf.start etype = symtab.get("ExceptionCode") - pruned_operation_ast(symtab).reachable_exceptions(fill_symtab(symtab)).uniq.map { |code| - etype.element_name(code) - } + if effective_xlen.nil? + if symtab.archdef.multi_xlen? + if base.nil? + ( + pruned_operation_ast(symtab, 32).reachable_exceptions(fill_symtab(symtab, 32)).uniq.map { |code| + etype.element_name(code) + } + + pruned_operation_ast(symtab, 64).reachable_exceptions(fill_symtab(symtab, 64)).uniq.map { |code| + etype.element_name(code) + } + ).uniq + else + pruned_operation_ast(symtab, base).reachable_exceptions(fill_symtab(symtab, base)).uniq.map { |code| + etype.element_name(code) + } + end + else + effective_xlen = symtab.archdef.mxlen + pruned_operation_ast(symtab, effective_xlen).reachable_exceptions(fill_symtab(symtab, effective_xlen)).uniq.map { |code| + etype.element_name(code) + } + end + else + pruned_operation_ast(symtab, effective_xlen).reachable_exceptions(fill_symtab(symtab, effective_xlen)).uniq.map { |code| + etype.element_name(code) + } + end # result = RubyProf.stop # RubyProf::FlatPrinter.new(result).print(STDOUT) end @@ -1025,7 +1429,7 @@ def extract_location(location) def inst_pos_to_var_pos s = size map = Array.new(32, nil) - @encoding_fields.sort { |a, b| b.range.last <=> a.range.last }.each do |ef| + @encoding_fields.each do |ef| ef.range.to_a.reverse.each do |ef_i| raise "unexpected" if s <= 0 @@ -1044,9 +1448,9 @@ def inst_range_to_var_range(r) raise "?" if var_bits[r.last].nil? parts = [var_bits[r.last]..var_bits[r.last]] r.to_a.reverse[1..].each do |i| - if var_bits[i] == (parts.last.first - 1) - raise "??" if parts.last.last.nil? - parts[-1] = var_bits[i]..parts.last.last + if var_bits[i] == (parts.last.min - 1) + raise "??" if parts.last.max.nil? + parts[-1] = var_bits[i]..parts.last.max else parts << Range.new(var_bits[i], var_bits[i]) end @@ -1131,7 +1535,7 @@ def bits end end - # the number of bits in the field, _including any implicit ones_ + # @return [Integer] the number of bits in the field, _including any implicit bits_ def size size_in_encoding + @left_shift end @@ -1152,11 +1556,11 @@ def extract so_far = 0 bits.each do |b| if b.is_a?(Integer) - op = "encoding[#{b}]" + op = "$encoding[#{b}]" ops << op so_far += 1 elsif b.is_a?(Range) - op = "encoding[#{b.end}:#{b.begin}]" + op = "$encoding[#{b.end}:#{b.begin}]" ops << op so_far += b.size end @@ -1211,6 +1615,10 @@ def initialize(name, range) def opcode? name.match?(/^[01]+$/) end + + def to_s + "#{name}[#{range}]" + end end # @param format [String] Format of the encoding, as 0's, 1's and -'s (for decode variables) @@ -1219,16 +1627,29 @@ def initialize(format, decode_vars) @format = format @opcode_fields = [] - msb = @format.size - @format.split("-").each do |e| - if e.empty? - msb -= 1 + field_chars = [] + @format.chars.each_with_index do |c, idx| + if c == "-" + next if field_chars.empty? + + field_text = field_chars.join("") + field_lsb = @format.size - idx + field_msb = @format.size - idx - 1 + field_text.size + @opcode_fields << Field.new(field_text, field_lsb..field_msb) + + field_chars.clear + next else - @opcode_fields << Field.new(e, (msb - e.size + 1)..msb) - msb -= e.size + field_chars << c end end + # add the least significant field + unless field_chars.empty? + field_text = field_chars.join("") + @opcode_fields << Field.new(field_text, 0...field_text.size) + end + @decode_variables = [] decode_vars&.each do |var| @decode_variables << DecodeVariable.new(self, var) @@ -1261,16 +1682,33 @@ def multi_encoding? @data.key?("encoding") && @data["encoding"].key?("RV32") end + # @return [FunctionBodyAst] A type-checked abstract syntax tree of the operation + # @param arch_def [ImplArchDef] A configuration + # @param effective_xlen [Integer] 32 or 64, the effective xlen to type check against + def type_checked_operation_ast(arch_def, effective_xlen) + @type_checked_operation_ast ||= {} + ast = @type_checked_operation_ast[arch_def.name] + return ast unless ast.nil? + + return nil unless @data.key?("operation()") + + ast = operation_ast(arch_def.idl_compiler) + + arch_def.idl_compiler.type_check(ast, fill_symtab(arch_def.sym_table, effective_xlen), "#{name}.operation()") + + @type_checked_operation_ast[arch_def.name] = ast + end + # @return [FunctionBodyAst] The abstract syntax tree of the instruction operation def operation_ast(idl_compiler) return @operation_ast unless @operation_ast.nil? return nil if @data["operation()"].nil? # now, parse the operation - @operation_ast = idl_compiler.compile_inst_operation( self, - input_file: "Instruction #{name}" + input_file: @data["__source"], + input_line: source_line("operation()") ) raise "unexpected #{@operation_ast.class}" unless @operation_ast.is_a?(Idl::FunctionBodyAst) @@ -1332,30 +1770,6 @@ def rv64? !@data.key?("base") || base == 64 end - # @return [Array] Extension requirements for the instruction. If *any* requirement is met, the instruction is defined - def extension_requirements - return @extension_requirements unless @extension_requirements.nil? - - @extension_requirements = [] - if @data["definedBy"].is_a?(Array) - # could be either a single extension with requirement, or a list of requirements - if extension_requirement?(@data["definedBy"][0]) - @extension_requirements << to_extension_requirement(@data["definedBy"][0]) - else - # this is a list - @data["definedBy"].each do |r| - @extension_requirements << to_extension_requirement(r) - end - end - else - @extension_requirements << to_extension_requirement(@data["definedBy"]) - end - - raise "empty requirements" if @extension_requirements.empty? - - @extension_requirements - end - # @return [Array] Extension exclusions for the instruction. If *any* exclusion is met, the instruction is not defined def extension_exclusions return @extension_exclusions unless @extension_excludions.nil? @@ -1419,6 +1833,23 @@ class Extension < ArchDefObject # @return [ArchDef] The architecture defintion attr_reader :arch_def + # @return [String] Company that developed the extension + # @return [nil] if the company isn't known + def company + @data["company"] + end + + # @return [{ name: String, url: String}] The name and URL of a document license the doc falls under + # @return [nil] if the license isn't known + def doc_license + @data["doc_license"] + end + + # @return [Array] versions hash from config + def versions + @data["versions"] + end + # @param ext_data [Hash] The extension data from the architecture spec # @param arch_def [ArchDef] The architecture defintion def initialize(ext_data, arch_def) @@ -1522,8 +1953,10 @@ def version_requirement @requirement end + # @param name [#to_s] Extension name + # @param requirements (see Gem::Requirement#new) def initialize(name, *requirements) - @name = name + @name = name.to_s requirements = if requirements.empty? [">= 0"] @@ -1872,11 +2305,20 @@ def implemented_functions puts " Finding all reachable functions from instruction operations" implemented_instructions.each do |inst| - inst_funcs = inst.reachable_functions(sym_table) - inst_funcs.each do |f| - @implemented_functions << f unless @implemented_functions.any? { |i| i.name == f.name } - end + @implemented_functions << + if inst.base.nil? + if multi_xlen? + (inst.reachable_functions(sym_table, 32) + + inst.reachable_functions(sym_table, 64)) + else + inst.reachable_functions(sym_table, mxlen) + end + else + inst.reachable_functions(sym_table, inst.base) + end end + @implemented_functions.flatten!.uniq!(&:name) + puts " Finding all reachable functions from CSR operations" diff --git a/lib/idl.rb b/lib/idl.rb index 6fa4db093..751fcb633 100644 --- a/lib/idl.rb +++ b/lib/idl.rb @@ -58,11 +58,49 @@ def compile_file(path, symtab: nil, type_check: true) ast = m.to_ast + ast.children.each do |child| + next unless child.is_a?(IncludeStatementAst) + + if child.filename.empty? + raise SyntaxError, <<~MSG + While parsing #{path}:#{child.lineno}: + + Empty include statement + MSG + end + + include_path = + if child.filename[0] == "/" + Pathname.new(child.filename) + else + (path.dirname / child.filename) + end + + unless include_path.exist? + raise SyntaxError, <<~MSG + While parsing #{path}:#{child.lineno}: + + Path #{include_path} does not exist + MSG + end + unless include_path.readable? + raise SyntaxError, <<~MSG + While parsing #{path}:#{child.lineno}: + + Path #{include_path} cannot be read + MSG + end + + include_ast = compile_file(include_path, symtab: nil, type_check: false) + ast.replace_include!(child, include_ast) + end + ast.set_input_file(path.to_s) if type_check begin ast.type_check(symtab) rescue AstNode::TypeError, AstNode::InternalError => e + warn "\n" warn e.what warn e.bt exit 1 @@ -79,20 +117,17 @@ def compile_file(path, symtab: nil, type_check: true) # @param return_type [Type] Expected return type, if known # @param symtab [SymbolTable] Symbol table to use for type checking # @param name [String] Function name, used for error messages - # @param parent [String] Parent class of the function, used for error messages # @param input_file [Pathname] Path to the input file this source comes from # @param input_line [Integer] Starting line in the input file that this source comes from # @param no_rescue [Boolean] Whether or not to automatically catch any errors # @return [Ast] The root of the abstract syntax tree - def compile_func_body(body, return_type: nil, symtab: SymbolTable.new, name: nil, parent: nil, input_file: nil, input_line: 0, no_rescue: false, extra_syms: {}) + def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file: nil, input_line: 0, no_rescue: false, extra_syms: {}, type_check: true) @parser.set_input_file(input_file, input_line) - cloned_symtab = symtab.deep_clone - m = @parser.parse(body, root: :function_body) if m.nil? raise SyntaxError, <<~MSG - While parsing #{parent}::#{name} #{@parser.failure_line}:#{@parser.failure_column} + While parsing #{name} at #{input_file}:#{input_line + @parser.failure_line} #{@parser.failure_reason} MSG @@ -100,46 +135,51 @@ def compile_func_body(body, return_type: nil, symtab: SymbolTable.new, name: nil # fix up left recursion ast = m.to_ast + ast.set_input_file(input_file, input_line) # type check - cloned_symtab.push - cloned_symtab.add("__expected_return_type", return_type) unless return_type.nil? + unless type_check == false + cloned_symtab = symtab.deep_clone - extra_syms.each { |k, v| - cloned_symtab.add(k, v) - } + cloned_symtab.push + cloned_symtab.add("__expected_return_type", return_type) unless return_type.nil? - begin - ast.statements.each do |s| - s.type_check(cloned_symtab) - end - rescue AstNode::TypeError => e - raise e if no_rescue + extra_syms.each { |k, v| + cloned_symtab.add(k, v) + } - if name && parent - warn "In function #{name} of #{parent}:" - elsif name && parent.nil? - warn "In function #{name}:" + begin + ast.statements.each do |s| + s.type_check(cloned_symtab) + end + rescue AstNode::TypeError => e + raise e if no_rescue + + if name && parent + warn "In function #{name} of #{parent}:" + elsif name && parent.nil? + warn "In function #{name}:" + end + warn e.what + exit 1 + rescue AstNode::InternalError => e + raise if no_rescue + + if name && parent + warn "In function #{name} of #{parent}:" + elsif name && parent.nil? + warn "In function #{name}:" + end + warn e.what + warn e.backtrace + exit 1 + ensure + cloned_symtab.pop end - warn e.what - exit 1 - rescue AstNode::InternalError => e - raise if no_rescue - if name && parent - warn "In function #{name} of #{parent}:" - elsif name && parent.nil? - warn "In function #{name}:" - end - warn e.what - warn e.backtrace - exit 1 - ensure - cloned_symtab.pop + ast.freeze_tree end - ast.freeze_tree - ast end @@ -157,7 +197,7 @@ def compile_inst_operation(inst, input_file: nil, input_line: 0) m = @parser.parse(operation, root: :instruction_operation) if m.nil? raise SyntaxError, <<~MSG - While parsing #{input_file}:#{@parser.failure_line}:#{@parser.failure_column} + While parsing #{input_file}:#{input_line + @parser.failure_line} #{@parser.failure_reason} MSG @@ -165,7 +205,7 @@ def compile_inst_operation(inst, input_file: nil, input_line: 0) # fix up left recursion ast = m.to_ast - ast.set_input_file("Inst #{inst.name} (#{input_file})", input_line) + ast.set_input_file(input_file, input_line) ast.freeze_tree ast @@ -184,6 +224,7 @@ def type_check(ast, symtab, what) rescue AstNode::TypeError => e warn "While type checking #{what}:" warn e.what + warn e.backtrace exit 1 rescue AstNode::InternalError => e warn "While type checking #{what}:" @@ -208,6 +249,7 @@ def compile_expression(expression, symtab, pass_error: false) end ast = m.to_ast + ast.set_input_file("[EXPRESSION]", 0) begin ast.type_check(symtab) rescue AstNode::TypeError => e diff --git a/lib/idl/ast.rb b/lib/idl/ast.rb index 848d0ce4e..f9e5ca722 100644 --- a/lib/idl/ast.rb +++ b/lib/idl/ast.rb @@ -36,6 +36,7 @@ class AstNode ConstBoolType = Type.new(:boolean, qualifiers: [:const]).freeze BoolType = Type.new(:boolean).freeze VoidType = Type.new(:void).freeze + StringType = Type.new(:string).freeze # @return [String] Source input file attr_reader :input_file @@ -105,12 +106,22 @@ def initialize(what) # exception type raised when the value of IDL code is requested (via node.value(...)) but # cannot be provided because some part the code isn't known at compile time class ValueError < StandardError - attr_reader :lineno, :file + attr_reader :lineno, :file, :reason - def initialize(what, lineno, file) - super(what) + def initialize(lineno, file, reason) + super(reason) @lineno = lineno @file = file + @reason = reason + end + + def message + <<~WHAT + In file #{input_file} + On line #{lineno} + A value error occured + #{reason} + WHAT end end @@ -161,16 +172,74 @@ def text_value input[interval] end + # @return [String] returns +-2 lines around the current interval + def lines_around + cnt = 0 + interval_start = interval.min + while cnt < 2 + cnt += 1 if input[interval_start] == "\n" + break if interval_start.zero? + + interval_start -= 1 + end + + cnt = 0 + interval_end = interval.max + while cnt < 3 + cnt += 1 if input[interval_end] == "\n" + break if interval_end >= (input.size - 1) + + interval_end += 1 + end + + [ + input[interval_start..interval_end], + (interval.min - interval_start..interval.max - interval_start), + (interval_start + 1)..interval_end + ] + end + # raise a type error # # @param reason [String] Error message # @raise [AstNode::TypeError] always def type_error(reason) + lines, problem_interval, lines_interval = lines_around + + lines = + if $stdout.isatty + [ + lines[0...problem_interval.min], + "\u001b[31m", + lines[problem_interval], + "\u001b[0m", + lines[(problem_interval.max + 1)..] + ].join("") + else + [ + lines[0...problem_interval.min], + "**HERE** >> ", + lines[problem_interval], + " << **HERE**", + lines[(problem_interval.max + 1)..] + ].join("") + end + + starting_lineno = input[0..lines_interval.min].count("\n") + lines = lines.lines.map do |line| + starting_lineno += 1 + "#{@starting_line + starting_lineno - 1}: #{line}" + end.join("") + msg = <<~WHAT In file #{input_file} On line #{lineno} - A type error occured - #{reason} + In the code: + + #{lines.gsub("\n", "\n ")} + + A type error occured + #{$stdout.isatty ? "\u001b[31m#{reason}\u001b[0m" : reason} WHAT raise AstNode::TypeError, msg end @@ -194,13 +263,7 @@ def internal_error(reason) # @param reason [String] Error message # @raise [AstNode::ValueError] always def value_error(reason) - msg = <<~WHAT - In file #{input_file} - On line #{lineno} - A value error occured - #{reason} - WHAT - raise AstNode::ValueError.new(msg, lineno, input_file) + raise AstNode::ValueError.new(lineno, input_file, reason) end # unindent a multiline string, getting rid of all common leading whitespace (like <<~ heredocs) @@ -231,6 +294,15 @@ def freeze_tree freeze end + # @return [String] A string representing the path to this node in the tree + def path + if parent.nil? + self.class.name + else + "#{parent.path}.#{self.class.name}" + end + end + # @!macro [new] type_check # type check this node and all children # @@ -380,6 +452,19 @@ module Declaration def add_symbol(symtab) = raise NotImplementedError, "#{self.class.name} must implment add_symbol" end + class IncludeStatementSyntaxNode < Treetop::Runtime::SyntaxNode + def to_ast = IncludeStatementAst.new(input, interval, string.to_ast) + end + + class IncludeStatementAst < AstNode + # @return [String] filename to include + def filename = @children[0].text_value[1..-2] + + def initialize(input, interval, filename) + super(input, interval, [filename]) + end + end + class IdSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast = IdAst.new(input, interval) end @@ -541,6 +626,20 @@ def bitfields = definitions.select { |e| e.is_a?(BitfieldDefinitionAst) } # @return {Array] List of all function definitions def functions = definitions.select { |e| e.is_a?(FunctionDefAst) } + # replaces an include statement with the ast in that file, making + # it a direct child of this IsaAst + # + # @param include_ast [IncludeStatementAst] The include, which must be a child of this IsaAst + # @Param isa_ast [IsaAst] The result of compiling the include + def replace_include!(include_ast, isa_ast) + # find the index of the child + idx = children.index(include_ast) + internal_error "Can't find include ast in children" if idx.nil? + + @children[idx] = isa_ast.children + @children.flatten! + end + # @!macro type_check def type_check(symtab) definitions.each { |d| d.type_check(symtab) } @@ -803,7 +902,7 @@ def type_check(symtab) @fields.each do |f| f.type_check(symtab) r = f.range(symtab) - type_error "Field position (#{r}) is larger than the bitfield width (#{@size.value(symtab)})" if r.first >= @size.value(symtab) + type_error "Field position (#{r}) is larger than the bitfield width (#{@size.value(symtab)} #{@size.text_value})" if r.first >= @size.value(symtab) end add_symbol(symtab) @@ -991,7 +1090,7 @@ def type_check(symtab) lsb_value = lsb.value(symtab) if var.type(symtab).kind == :bits && msb_value >= var.type(symtab).width - type_error "Range too large for bits (range top = #{msb_value}, range width = #{var.type(symtab).width})" + type_error "Range too large for bits (msb = #{msb_value}, range size = #{var.type(symtab).width})" end range_size = msb_value - lsb_value + 1 @@ -2087,7 +2186,11 @@ def value(symtab) elsif (op == "||") && lhs_value == true true else - eval "lhs_value #{op} rhs.value(symtab)", binding, __FILE__, __LINE__ + if op == "&&" + lhs_value && rhs.value(symtab) + else + lhs_value || rhs.value(symtab) + end end elsif op == "==" begin @@ -2192,7 +2295,31 @@ def value(symtab) lhs.value(symtab) | rhs.value(symtab) else - v = eval "lhs.value(symtab) #{op} rhs.value(symtab)", binding, __FILE__, __LINE__ + v = + case op + when "+" + lhs.value(symtab) + rhs.value(symtab) + when "-" + lhs.value(symtab) - rhs.value(symtab) + when "*" + lhs.value(symtab) * rhs.value(symtab) + when "/" + lhs.value(symtab) / rhs.value(symtab) + when "%" + lhs.value(symtab) % rhs.value(symtab) + when "^" + lhs.value(symtab) ^ rhs.value(symtab) + when "|" + lhs.value(symtab) | rhs.value(symtab) + when "&" + lhs.value(symtab) & rhs.value(symtab) + when ">>" + lhs.value(symtab) >> rhs.value(symtab) + when "<<" + lhs.value(symtab) << rhs.value(symtab) + else + internal_error "Unhandled binary op #{op}" + end v_trunc = v & ((1 << type(symtab).width) - 1) warn "WARNING: The value of '#{text_value}' is truncated from #{v} to #{v_trunc} because the result is only #{type(symtab).width} bits" if v != v_trunc v_trunc @@ -2579,7 +2706,7 @@ def value(symtab) range = bitfield.type(symtab).range(@field_name) (bitfield.value(symtab) >> range.first) & ((1 << range.size) - 1) else - internal_error "TODO" + type_error "#{bitfield.text_value} is Not a bitfield." end end @@ -2703,7 +2830,17 @@ def type_check(symtab) # @!macro value def value(symtab) - val = val_trunc = eval("#{op}#{exp.value(symtab)}", binding, __FILE__, __LINE__) + val = val_trunc = + case op + when "-" + -exp.value(symtab) + when "~" + ~exp.value(symtab) + when "!" + !exp.value(symtab) + else + internal_error "Unhandled unary op #{op}" + end if type(symtab).integral? val_trunc = val & ((1 << type(symtab).width) - 1) if type(symtab).signed? && ((((val_trunc >> (type(symtab).width - 1))) & 1) == 1) @@ -3230,10 +3367,27 @@ def return_values(symtab) def to_idl = "#{return_expression.to_idl} if (#{condition.to_idl});" end - class ExecutionCommentAst < AstNode - def type_check(_symtab, _global); end + # @api private + class CommentSyntaxNode < Treetop::Runtime::SyntaxNode + def to_ast = CommentAst(input, interval) end + # represents a comment + class CommentAst < AstNode + def initialize(input, interval) + super(input, interval, []) + end + + # @!macro type_check + def type_check(symtab); end + + # @return [String] The comment text, with the leading hash and any leading space removed + # @example + # # This is a comment #=> "This is a comment" + def content = text_value[1..].strip + end + + # @api private class BuiltinTypeNameSyntaxNode < Treetop::Runtime::SyntaxNode def to_ast if !respond_to?(:i) @@ -3244,6 +3398,17 @@ def to_ast end end + # represents a type name of one of the builtin types: + # + # * Bits + # * Boolean + # * String + # + # And their aliases: + # + # * XReg (Bits) + # * U32 (Bits<32>) + # * U64 (Bits<64>) class BuiltinTypeNameAst < AstNode def bits_expression = @children[0] @@ -3263,7 +3428,7 @@ def type_check(symtab) bits_expression.type_check(symtab) type_error "Bits width (#{bits_expression.value(symtab)}) must be positive" unless bits_expression.value(symtab).positive? end - unless ["Bits", "XReg", "Boolean", "U32", "U64"].include?(@type_name) + unless ["Bits", "String", "XReg", "Boolean", "U32", "U64"].include?(@type_name) type_error "Unimplemented builtin type #{text_value}" end end @@ -3284,6 +3449,8 @@ def type(symtab) Bits32Type when "U64" Bits64Type + when "String" + StringType when "Bits" Type.new(:bits, width: bits_expression.value(symtab)) else @@ -3324,13 +3491,15 @@ def initialize(input, interval) def type_check(_symtab); end def type(symtab) - @type + @type end # @!macro value def value(_symtab) text_value.gsub('"', "") end + + def to_idl = text_value end module IntLiteralSyntaxNode @@ -3350,12 +3519,12 @@ def initialize(input, interval) # @!macro type_check def type_check(symtab) - if text_value.delete("_") =~ /([0-9]+)?'(s?)([bodh]?)(.*)/ + if text_value.delete("_") =~ /^((XLEN)|([0-9]+))?'(s?)([bodh]?)(.*)$/ # verilog-style literal width = ::Regexp.last_match(1) - value_text = ::Regexp.last_match(4) + value_text = ::Regexp.last_match(6) - if width.nil? + if width.nil? || width == "XLEN" width = symtab.archdef.config_params["XLEN"] memoize = false end @@ -3371,13 +3540,13 @@ def type(symtab) return @types[cache_idx] unless @types[cache_idx].nil? case text_value.delete("_") - when /([0-9]+)?'(s?)([bodh]?)(.*)/ + when /^((XLEN)|([0-9]+))?'(s?)([bodh]?)(.*)$/ # verilog-style literal width = ::Regexp.last_match(1) - signed = ::Regexp.last_match(2) + signed = ::Regexp.last_match(4) memoize = true - if width.nil? + if width.nil? || width == "XLEN" width = symtab.archdef.config_params["XLEN"] memoize = false end @@ -3386,7 +3555,7 @@ def type(symtab) t = Type.new(:bits, width: width.to_i, qualifiers:) @types[cache_idx] = t if memoize t - when /0([bdx]?)([0-9a-fA-F]*)(s?)/ + when /^0([bdx]?)([0-9a-fA-F]*)(s?)$/ # C++-style literal signed = ::Regexp.last_match(3) @@ -3394,7 +3563,7 @@ def type(symtab) type = Type.new(:bits, width: width(symtab), qualifiers:) @types[cache_idx] = type type - when /([0-9]*)(s?)/ + when /^([0-9]*)(s?)$/ # basic decimal signed = ::Regexp.last_match(2) @@ -3413,17 +3582,17 @@ def width(symtab) text_value_no_underscores = text_value.delete("_") case text_value_no_underscores - when /([0-9]+)?'(s?)([bodh]?)(.*)/ + when /^((XLEN)|([0-9]+))?'(s?)([bodh]?)(.*)$/ # verilog-style literal width = ::Regexp.last_match(1) memoize = true - if width.nil? + if width.nil? || width == "XLEN" width = archdef.config_params["XLEN"] memoize = false end # @width = width if memoize width - when /0([bdx]?)([0-9a-fA-F]*)(s?)/ + when /^0([bdx]?)([0-9a-fA-F]*)(s?)$/ signed = ::Regexp.last_match(3) width = signed == "s" ? value(symtab).bit_length + 1 : value(symtab).bit_length @@ -3431,7 +3600,7 @@ def width(symtab) # @width = width width - when /([0-9]*)(s?)/ + when /^([0-9]*)(s?)$/ signed = ::Regexp.last_match(3) width = signed == "s" ? value(symtab).bit_length + 1 : value(symtab).bit_length @@ -3448,20 +3617,20 @@ def width(symtab) def value(symtab) # return @value unless @value.nil? - if text_value.delete("_") =~ /([0-9]+)?'(s?)([bodh]?)(.*)/ + if text_value.delete("_") =~ /^((XLEN)|([0-9]+))?'(s?)([bodh]?)(.*)$/ # verilog-style literal width = ::Regexp.last_match(1) - signed = ::Regexp.last_match(2) + signed = ::Regexp.last_match(4) memoize = true - if width.nil? + if width.nil? || width == "XLEN" width = symtab.archdef.config_params["XLEN"] memoize = false end v = if !signed.empty? && ((unsigned_value >> (width.to_i - 1)) == 1) - -(2**width.to_i - unsigned_value) + -(2**width.to_i - unsigned_value) else unsigned_value end @@ -3480,26 +3649,24 @@ def unsigned_value # return @unsigned_value unless @unsigned_value.nil? case text_value.delete("_") - when /([0-9]+)?'(s?)([bodh]?)(.*)/ + when /^((XLEN)|([0-9]+))?'(s?)([bodh]?)(.*)$/ # verilog-style literal - radix_id = ::Regexp.last_match(3) - value = ::Regexp.last_match(4) + radix_id = ::Regexp.last_match(5) + value = ::Regexp.last_match(6) radix_id = "d" if radix_id.empty? - # ensure we actually have enough bits to represent the value - # @unsigned_value = - case radix_id - when "b" - value.to_i(2) - when "o" - value.to_i(8) - when "d" - value.to_i(10) - when "h" - value.to_i(16) - end - when /0([bdx]?)([0-9a-fA-F]*)(s?)/ + case radix_id + when "b" + value.to_i(2) + when "o" + value.to_i(8) + when "d" + value.to_i(10) + when "h" + value.to_i(16) + end + when /^0([bdx]?)([0-9a-fA-F]*)(s?)$/ # C++-style literal radix_id = ::Regexp.last_match(1) value = ::Regexp.last_match(2) @@ -3517,14 +3684,15 @@ def unsigned_value when "x" value.to_i(16) end - when /([0-9]*)(s?)/ + + when /^([0-9]*)(s?)$/ # basic decimal value = ::Regexp.last_match(1) # @unsigned_value = value.to_i(10) value.to_i(10) else - internal_error "Unhandled int value" + internal_error "Unhandled int value '#{text_value}'" end end @@ -3547,25 +3715,27 @@ class FunctionCallExpressionAst < AstNode include Rvalue include Executable + def targs = children[0...@num_targs] + def args = children[@num_targs..] + def initialize(input, interval, function_name, targs, args) raise ArgumentError, "targs shoudl be an array" unless targs.is_a?(Array) raise ArgumentError, "args shoudl be an array" unless args.is_a?(Array) super(input, interval, targs + args) + @num_targs = targs.size @name = function_name - @targs = targs - @args = args end # @return [Boolean] whether or not the function call has a template argument def template? - !@targs.empty? + !targs.empty? end # @return [Array] Template argument nodes def template_arg_nodes - @targs + targs end def template_values(symtab) @@ -3576,7 +3746,7 @@ def template_values(symtab) # @return [Array] Function argument nodes def arg_nodes - @args + args end def func_type(symtab) @@ -3613,9 +3783,9 @@ def type_check(symtab) end end - func_def_type.type_check_call(template_values(symtab)) + func_def_type.type_check_call(template_values(symtab), self) else - func_def_type.type_check_call + func_def_type.type_check_call([], self) end num_args = arg_nodes.size @@ -3626,12 +3796,12 @@ def type_check(symtab) a.type_check(symtab) end arg_nodes.each_with_index do |a, idx| - unless a.type(symtab).convertable_to?(func_def_type.argument_type(idx, template_values(symtab), arg_nodes, symtab)) - type_error "Wrong type for argument number #{idx + 1}. Expecting #{func_def_type.argument_type(idx, template_values(symtab))}, got #{a.type(symtab)}" + unless a.type(symtab).convertable_to?(func_def_type.argument_type(idx, template_values(symtab), arg_nodes, symtab, self)) + type_error "Wrong type for argument number #{idx + 1}. Expecting #{func_def_type.argument_type(idx, template_values(symtab), self)}, got #{a.type(symtab)}" end end - if func_def_type.return_type(template_values(symtab)).nil? + if func_def_type.return_type(template_values(symtab), self).nil? internal_error "No type determined for function" end @@ -3641,13 +3811,18 @@ def type_check(symtab) # @!macro type def type(symtab) func_def_type = symtab.get(name) - func_def_type.return_type(template_values(symtab)) + func_def_type.return_type(template_values(symtab), self) end # @!macro value def value(symtab) + # sometimes we want to evaluate for a specific XLEN + if name == "xlen" && !symtab.get("__effective_xlen").nil? + return symtab.get("__effective_xlen").value + end + func_def_type = symtab.get(name) - type_error "not a function" unless func_def_type.is_a?(FunctionType) + type_error "#{name} is not a function" unless func_def_type.is_a?(FunctionType) if func_def_type.builtin? if name == "implemented?" extname_ref = arg_nodes[0] @@ -3664,7 +3839,7 @@ def value(symtab) template_values << targ.value(symtab) end - func_def_type.return_value(template_values, arg_nodes, symtab) + func_def_type.return_value(template_values, arg_nodes, symtab, self) end alias execute value @@ -3850,8 +4025,12 @@ def initialize(input, interval, name, targs, return_types, arguments, desc, body @argument_nodes = arguments @desc = desc @body = body + + @reachable_functions_cache ||= {} end + attr_reader :reachable_functions_cache + def description unindent(@desc) end @@ -4707,7 +4886,7 @@ def type_check(symtab) archdef = symtab.archdef idx_text = @idx.is_a?(String) ? @idx : @idx.text_value - if !archdef.all_known_csr_names.index { |csr_name| csr_name == idx_text }.nil? + if !archdef.csr(idx_text).nil? # this is a known csr name # nothing else to check @@ -4729,18 +4908,15 @@ def type_check(symtab) def csr_def(symtab) archdef = symtab.archdef idx_text = @idx.is_a?(String) ? @idx : @idx.text_value - if !archdef.all_known_csr_names.index { |csr_name| csr_name == idx_text }.nil? + csr = archdef.csr(idx_text) + if !csr.nil? # this is a known csr name - csr_index = archdef.csrs.index { |csr| csr.name == idx_text } - - archdef.csrs[csr_index] + csr else # this is an expression begin idx_value = @idx.value(symtab) - csr_index = archdef.csrs.index { |csr| csr.address == idx_value } - - archdef.csrs[csr_index] + csr_index = archdef.csrs.find { |csr| csr.address == idx_value } rescue ValueError # we don't know at compile time which CSR this is... nil @@ -4769,7 +4945,7 @@ def value(symtab) end # @!macro to_idl - def to_idl = "CSR[#{idx.to_idl}]" + def to_idl = "CSR[#{@idx.to_idl}]" end class CsrSoftwareWriteSyntaxNode < Treetop::Runtime::SyntaxNode @@ -4896,23 +5072,18 @@ def type_check(symtab) index = symtab.archdef.csrs.index { |csr| csr.address == idx.value(symtab) } type_error "No csr number '#{idx.value(symtab)}' was found" if index.nil? else - index = symtab.archdef.csrs.index { |csr| csr.name == idx.text_value } - type_error "No csr named '#{idx.text_value}' was found" if index.nil? + csr = symtab.archdef.csr(idx.text_value) + type_error "No csr named '#{idx.text_value}' was found" if csr.nil? end - - symtab.archdef.csrs[index] end def csr_def(symtab) - index = - if idx.is_a?(IntLiteralAst) - # make sure this value is a defined CSR - symtab.archdef.csrs.index { |csr| csr.address == idx.text_value.to_i } - else - symtab.archdef.csrs.index { |csr| csr.name == idx.text_value } - end - - symtab.archdef.csrs[index] + if idx.is_a?(IntLiteralAst) + # make sure this value is a defined CSR + symtab.archdef.csrs.find { |csr| csr.address == idx.text_value.to_i } + else + symtab.archdef.csr(idx.text_value) + end end # @!macro type diff --git a/lib/idl/idl.treetop b/lib/idl/idl.treetop index bcaa4fc7c..d3336ed3d 100644 --- a/lib/idl/idl.treetop +++ b/lib/idl/idl.treetop @@ -4,6 +4,8 @@ grammar Idl space* '%version:' space+ version_string space+ definitions:( + include_statement + / global_definition / enum_definition @@ -16,6 +18,10 @@ grammar Idl )+ end + rule include_statement + 'include' space+ string space+ + end + # declaring a global variable or constant rule global_definition single_declaration_with_initialization space* ';' @@ -47,16 +53,16 @@ grammar Idl rule int # verilog style: explicit bit width - ([0-9]+)? "'" 'b' [0-1] [0-1_]* / - ([0-9]+)? "'" 'o' [0-7] [0-7_]* / - ([0-9]+)? "'" 'd'? [0-9] [0-9_]* / - ([0-9]+)? "'" 'h' [0-9a-fA-F] [0-9a-fA-F_]* / + (([0-9]+)/'XLEN')? "'" 'b' [0-1] [0-1_]* / + (([0-9]+)/'XLEN')? "'" 'o' [0-7] [0-7_]* / + (([0-9]+)/'XLEN')? "'" 'd'? [0-9] [0-9_]* / + (([0-9]+)/'XLEN')? "'" 'h' [0-9a-fA-F] [0-9a-fA-F_]* / # verilog style: explicit bit width, signed - ([0-9]+)? "'" 'sb' [0-1] [0-1_]* / - ([0-9]+)? "'" 'so' [0-7] [0-7_]* / - ([0-9]+)? "'" 's' 'd'? [0-9] [0-9_]* / - ([0-9]+)? "'" 'sh' [0-9a-fA-F] [0-9a-fA-F_]* / + (([0-9]+)/'XLEN')? "'" 'sb' [0-1] [0-1_]* / + (([0-9]+)/'XLEN')? "'" 'so' [0-7] [0-7_]* / + (([0-9]+)/'XLEN')? "'" 's' 'd'? [0-9] [0-9_]* / + (([0-9]+)/'XLEN')? "'" 'sh' [0-9a-fA-F] [0-9a-fA-F_]* / # c++ style: signed '0b' [0-1] [0-1_]* 's' / @@ -359,7 +365,7 @@ grammar Idl end rule function_call_template_arguments - first:(int / rval) rest:(space* ',' space* arg:(int / rval))* + first:(rval) rest:(space* ',' space* arg:(rval))* end rule function_call @@ -420,7 +426,7 @@ grammar Idl end rule rval - (id / int / builtin_read_only_var / builtin_read_write_var / string) + (int / builtin_read_only_var / builtin_read_write_var / string / id) end rule assignment @@ -526,10 +532,12 @@ grammar Idl 'Boolean' ![A-Za-z0-9] / - # 64-bit unsigned integer + 'String' ![A-Za-z0-9] / + + # alias for Bits<64> 'U64' ![A-Za-z0-9] / - # 32-bit unsigned integer + # alias for Bits<32> 'U32' ![A-Za-z0-9] end @@ -564,8 +572,8 @@ grammar Idl builtin_type_name / keyword end - rule execution_comment - '#' content:(!"\n" .)* "\n" # + rule comment + '#' content:(!"\n" .)* "\n" # end rule function_statement @@ -614,22 +622,9 @@ grammar Idl end rule space - ([ \n] / single_line_comment / block_comment / execution_comment) { + ([ \n] / comment) { def space? = true } end - rule block_comment - '/*' - ( - !'*/' - (. / "\n") - )* - '*/' - end - - rule single_line_comment - '//' (!"\n" .)* "\n" - end - end diff --git a/lib/idl/passes/prune.rb b/lib/idl/passes/prune.rb index cd7290fb6..42e095635 100644 --- a/lib/idl/passes/prune.rb +++ b/lib/idl/passes/prune.rb @@ -52,7 +52,7 @@ def prune(symtab) v = value(symtab) create_literal(v) rescue ValueError - FunctionCallExpressionAst.new(input, interval, name, @targs.map { |t| t.prune(symtab) }, @args.map { |a| a.prune(symtab)} ) + FunctionCallExpressionAst.new(input, interval, name, targs.map { |t| t.prune(symtab) }, args.map { |a| a.prune(symtab)} ) end end end @@ -297,6 +297,20 @@ def prune(symtab) end end + class ConditionalReturnStatementAst + def prune(symtab) + begin + if condition.value(symtab) + return return_expression.prune(symtab) + else + return NoopAst.new + end + rescue ValueError + ConditionalReturnStatementAst.new(input, interval, return_expression.prune(symtab), condition.prune(symtab)) + end + end + end + class ConditionalStatementAst def prune(symtab) if condition.value(symtab) diff --git a/lib/idl/passes/reachable_exceptions.rb b/lib/idl/passes/reachable_exceptions.rb index eceafef01..528ada8d2 100644 --- a/lib/idl/passes/reachable_exceptions.rb +++ b/lib/idl/passes/reachable_exceptions.rb @@ -8,13 +8,15 @@ module Idl class AstNode # @return [Array] List of all functions that can be reached (via function calls) from this node def reachable_exceptions(symtab) - children.reduce([]) { |list, e| list.concat e.prune(symtab).reachable_exceptions(symtab) }.uniq + children.reduce([]) do |list, e| + list.concat e.reachable_exceptions(symtab) + end.uniq end end class FunctionCallExpressionAst def reachable_exceptions(symtab) - if (name == "raise") + if name == "raise" # first argument is the exception code_ast = arg_nodes[0] begin @@ -31,18 +33,18 @@ def reachable_exceptions(symtab) fns = [] if template? template_arg_nodes.each do |t| - fns.concat(t.prune(symtab).reachable_exceptions(symtab)) + fns.concat(t.reachable_exceptions(symtab)) end end arg_nodes.each do |a| - fns.concat(a.prune(symtab).reachable_exceptions(symtab)) + fns.concat(a.reachable_exceptions(symtab)) end - body_symtab = func_def_type.apply_template_values(template_values(symtab)) - func_def_type.apply_arguments(body_symtab, arg_nodes, symtab) - unless func_def_type.builtin? + body_symtab = func_def_type.apply_template_values(template_values(symtab), self) + func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self) + fns.concat(func_def_type.body.prune(body_symtab, args_already_applied: true).reachable_exceptions(body_symtab)) end @@ -63,6 +65,23 @@ def reachable_exceptions(symtab) end end + class ConditionalReturnStatementAst + def reachable_functions(symtab) + fns = condition.reachable_exceptions(symtab) + if condition.value(symtab) + fns.concat return_expression.reachable_exceptions(symtab) + begin + return_expression.execute(symtab) + rescue ValueError + # ok + end + fns + else + [] + end + end + end + class ConditionalStatementAst def reachable_exceptions(symtab) if condition.value(symtab) diff --git a/lib/idl/passes/reachable_functions.rb b/lib/idl/passes/reachable_functions.rb index c02ada0cb..e7574e187 100644 --- a/lib/idl/passes/reachable_functions.rb +++ b/lib/idl/passes/reachable_functions.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require_relative "prune" - # finds all reachable functions from a give sequence of statements module Idl class AstNode # @return [Array] List of all functions that can be reached (via function calls) from this node def reachable_functions(symtab) - children.reduce([]) { |list, e| list.concat e.prune(symtab).reachable_functions(symtab) }.uniq(&:name) + children.reduce([]) do |list, e| + list.concat e.reachable_functions(symtab) + end.uniq(&:name) end end @@ -16,25 +16,45 @@ class FunctionCallExpressionAst def reachable_functions(symtab) func_def_type = func_type(symtab) + tvals = template_values(symtab) + + body_symtab = func_def_type.apply_template_values(tvals, self) + + # have we seen this exact same call already?? + key = nil + begin + key = [ + name, + tvals, + func_def_type.argument_values(body_symtab, arg_nodes, symtab, self) + ].hash + fns = func_def_type.func_def_ast.reachable_functions_cache[key] + return fns unless fns.nil? + rescue ValueError + # fall through, we need to evaluate + end + fns = [] if template? template_arg_nodes.each do |t| - fns.concat(t.prune(symtab).reachable_functions(symtab)) + fns.concat(t.reachable_functions(symtab)) end end arg_nodes.each do |a| - fns.concat(a.prune(symtab).reachable_functions(symtab)) + fns.concat(a.reachable_functions(symtab)) end - body_symtab = func_def_type.apply_template_values(template_values(symtab)) - func_def_type.apply_arguments(body_symtab, arg_nodes, symtab) + func_def_type.apply_arguments(body_symtab, arg_nodes, symtab, self) unless func_def_type.builtin? - fns.concat(func_def_type.body.prune(body_symtab, args_already_applied: true).reachable_functions(body_symtab)) + prune_symtab = body_symtab #.deep_clone + fns.concat(func_def_type.body.prune(prune_symtab, args_already_applied: true).reachable_functions(body_symtab)) end - fns.push(func_def_type.func_def_ast).uniq(&:name) + fns = fns.push(func_def_type.func_def_ast).uniq(&:name) + func_def_type.func_def_ast.reachable_functions_cache[key] = fns unless key.nil? + fns end end @@ -51,10 +71,28 @@ def reachable_functions(symtab) end end + class ConditionalReturnStatementAst + def reachable_functions(symtab) + fns = condition.reachable_functions(symtab) + if condition.value(symtab) + fns.concat return_expression.reachable_functions(symtab) + begin + return_expression.execute(symtab) + rescue ValueError + # ok + end + fns + else + [] + end + end + end + class ConditionalStatementAst def reachable_functions(symtab) + fns = condition.reachable_functions(symtab) if condition.value(symtab) - fns = action.reachable_functions(symtab) + fns.concat action.reachable_functions(symtab) action.add_symbol(symtab) if action.is_a?(Declaration) begin action.execute(symtab) if action.is_a?(Executable) @@ -80,6 +118,8 @@ def reachable_functions(symtab) class ForLoopAst def reachable_functions(symtab) + # puts path + # puts to_idl symtab.push symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab))) fns = init.reachable_functions(symtab) diff --git a/lib/idl/symbol_table.rb b/lib/idl/symbol_table.rb index 34479995e..c088fe17f 100644 --- a/lib/idl/symbol_table.rb +++ b/lib/idl/symbol_table.rb @@ -21,6 +21,9 @@ def initialize(name, type, value = nil, decode_var: false, template_index: nil, @function_name = function_name end + def to_s + "VAR: #{type} #{name} #{value.nil? ? 'NO VALUE' : value}" + end def clone Var.new( @@ -157,12 +160,13 @@ def deep_freeze end # pushes a new scope + # @return [SymbolTable] self def push # puts "push #{caller[0]}" # @scope_caller ||= [] # @scope_caller.push caller[0] @scopes << {} - + self end # pops the top of the scope stack @@ -285,6 +289,8 @@ def print # @return [SymbolTable] a deep clone of this SymbolTable def deep_clone(clone_values: false, freeze_global: true) + raise "don't do this" unless freeze_global + # globals are frozen, so we can just return a shallow clone # if we are in global scope if levels == 1 @@ -293,12 +299,13 @@ def deep_clone(clone_values: false, freeze_global: true) return copy end - copy = SymbolTable.new(@archdef) + copy = dup + # back up the table to global scope copy.instance_variable_set(:@scopes, []) c_scopes = copy.instance_variable_get(:@scopes) + c_scopes.push(@scopes[0]) - in_global = true - @scopes.each do |scope| + @scopes[1..].each do |scope| c_scopes << {} scope.each do |k, v| if clone_values @@ -306,14 +313,7 @@ def deep_clone(clone_values: false, freeze_global: true) else c_scopes.last[k] = v end - if freeze_global && in_global - c_scopes.last[k].freeze - end end - in_global = false - end - if freeze_global - copy.instance_variable_get(:@scopes).first.freeze end copy end diff --git a/lib/idl/tests/test_functions.rb b/lib/idl/tests/test_functions.rb index 7fc0b219a..bc4b6fa62 100644 --- a/lib/idl/tests/test_functions.rb +++ b/lib/idl/tests/test_functions.rb @@ -63,7 +63,7 @@ def test_that_reachable_raise_analysis_respects_transitive_known_values IDL - t = Tempfile.new('idl') + t = Tempfile.new("idl") t.write idl t.close @@ -72,11 +72,10 @@ def test_that_reachable_raise_analysis_respects_transitive_known_values ast = @compiler.compile_file(path, symtab: @symtab) test_ast = ast.functions.select { |f| f.name == "test" }[0] - assert_equal [1], test_ast.body.reachable_exceptions(@symtab) + assert_equal [1], test_ast.body.prune(@symtab).reachable_exceptions(@symtab) end - - def test_that_reachable_raise_analysis_respects_known_paths_down_an_unkn0wn_path + def test_that_reachable_raise_analysis_respects_known_paths_down_an_unknown_path idl = <<~IDL.strip %version: 1.0 enum Choice { @@ -123,7 +122,7 @@ def test_that_reachable_raise_analysis_respects_known_paths_down_an_unkn0wn_path IDL - t = Tempfile.new('idl') + t = Tempfile.new("idl") t.write idl t.close @@ -132,6 +131,7 @@ def test_that_reachable_raise_analysis_respects_known_paths_down_an_unkn0wn_path ast = @compiler.compile_file(path, symtab: @symtab) test_ast = ast.functions.select { |f| f.name == "test" }[0] - assert_equal [1], test_ast.body.reachable_exceptions(@symtab) + pruned_test_ast = test_ast.body.prune(@symtab) + assert_equal [1], pruned_test_ast.reachable_exceptions(@symtab) end end diff --git a/lib/idl/type.rb b/lib/idl/type.rb index ef01670f4..1ff6aec5a 100644 --- a/lib/idl/type.rb +++ b/lib/idl/type.rb @@ -474,11 +474,11 @@ def builtin? = @func_def_ast.builtin? def num_args = @func_def_ast.num_args - def type_check_call(template_values = []) + def type_check_call(template_values, func_call_ast) raise "Missing template values" if templated? && template_values.empty? if templated? - symtab = apply_template_values(template_values) + symtab = apply_template_values(template_values, func_call_ast) @func_def_ast.type_check_template_instance(symtab) else @@ -497,20 +497,20 @@ def template_types(symtab) = @func_def_ast.template_types(symtab) def templated? = @func_def_ast.templated? - def apply_template_values(template_values = []) - raise "Missing template values" if templated? && template_values.empty? + def apply_template_values(template_values = [], func_call_ast) + func_call_ast.type_error "Missing template values" if templated? && template_values.empty? - raise "wrong number of template values" unless template_names.size == template_values.size + func_call_ast.type_error "wrong number of template values in call to #{name}" unless template_names.size == template_values.size symtab = @symtab.deep_clone symtab.pop while symtab.levels != 1 - raise "Symbol table should be at global scope" unless symtab.levels == 1 + func_call_ast.type_error "Symbol table should be at global scope" unless symtab.levels == 1 symtab.push template_values.each_with_index do |value, idx| - raise "template value should be an Integer" unless value.is_a?(Integer) + func_call_ast.type_error "template value should be an Integer" unless value.is_a?(Integer) symtab.add!(template_names[idx], Var.new(template_names[idx], template_types(symtab)[idx], value, template_index: idx, function_name: @func_def_ast.name)) end @@ -519,58 +519,76 @@ def apply_template_values(template_values = []) # apply the arguments as Vars. # then add the value to the Var - def apply_arguments(symtab, argument_nodes, call_site_symtab) + def apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) idx = 0 @func_def_ast.arguments(symtab).each do |atype, aname| + func_call_ast.type_error "Missing argument #{idx}" if idx >= argument_nodes.size begin symtab.add!(aname, Var.new(aname, atype, argument_nodes[idx].value(call_site_symtab))) - rescue AstNode::ValueError + rescue AstNode::ValueError => e symtab.add!(aname, Var.new(aname, atype)) end idx += 1 end end + # @return [Array] Array of argument values, if known + # @return [nil] if at least one argument value is not known + def argument_values(symtab, argument_nodes, call_site_symtab, func_call_ast) + idx = 0 + values = [] + @func_def_ast.arguments(symtab).each do |atype, aname| + func_call_ast.type_error "Missing argument #{idx}" if idx >= argument_nodes.size + begin + values << argument_nodes[idx].value(call_site_symtab) + rescue AstNode::ValueError => e + return nil + end + idx += 1 + end + values + end + # @param template_values [Array] Template values for the call, in declaration order - # @param call_site_symtab [SymbolTable] Symbol table at the call site + # @param func_call_ast [FunctionCallExpressionAst] The function call interested in the return type # return [Type] type of the call return - def return_type(template_values) - symtab = apply_template_values(template_values) + def return_type(template_values, func_call_ast) + symtab = apply_template_values(template_values, func_call_ast) # apply_arguments(symtab, argument_nodes, call_site_symtab) @func_def_ast.return_type(symtab).clone end - def return_value(template_values, argument_nodes, call_site_symtab) - symtab = apply_template_values(template_values) - apply_arguments(symtab, argument_nodes, call_site_symtab) + def return_value(template_values, argument_nodes, call_site_symtab, func_call_ast) + symtab = apply_template_values(template_values, func_call_ast) + apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) @func_def_ast.body.return_value(symtab) end # @param template_values [Array] Template values to apply, required if {#templated?} # @return [Array] return types - def return_types(template_values, argument_nodes, call_site_symtab) - symtab = apply_template_values(template_values) - apply_arguments(symtab, argument_nodes, call_site_symtab) + def return_types(template_values, argument_nodes, call_site_symtab, func_call_ast) + symtab = apply_template_values(template_values, func_call_ast) + apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) @func_def_ast.return_types(symtab).map(&:clone) end - def argument_type(index, template_values, argument_nodes, call_site_symtab) + def argument_type(index, template_values, argument_nodes, call_site_symtab, func_call_ast) return nil if index >= @func_def_ast.num_args - symtab = apply_template_values(template_values) - apply_arguments(symtab, argument_nodes, call_site_symtab) + symtab = apply_template_values(template_values, func_call_ast) + apply_arguments(symtab, argument_nodes, call_site_symtab, func_call_ast) arguments = @func_def_ast.arguments(symtab) arguments[index][0].clone end - def argument_name(index, template_values = []) + def argument_name(index, template_values = [], func_call_ast) return nil if index >= @func_def_ast.num_args - symtab = apply_template_values(template_values) + symtab = apply_template_values(template_values, func_call_ast) # apply_arguments(symtab, argument_nodes, call_site_symtab) arguments = @func_def_ast.arguments(symtab) diff --git a/lib/validate.rb b/lib/validate.rb index 9d2f4bb30..ebb15212b 100644 --- a/lib/validate.rb +++ b/lib/validate.rb @@ -50,7 +50,6 @@ class ValidationError < ::StandardError # @param result [JsonSchemer::Result] JsonSchemer result def initialize(result) nerrors = result.count - pp result.to_a[0].keys msg = +"#{nerrors} error(s) during validations\n\n" result.to_a.each do |r| msg << diff --git a/schemas/config_schema.json b/schemas/config_schema.json index f8963cde8..af62d58c4 100644 --- a/schemas/config_schema.json +++ b/schemas/config_schema.json @@ -476,6 +476,33 @@ "minimum": 1, "maximum": 63, "description": "Number of supported virtualized guest external interrupts.\nCorresponds to the GEILEN parameter in RISC-V specifications." + }, + "LRSC_RESERVATION_STRATEGY": { + "type": "string", + "enum": [ + "reserve naturally-aligned 64-byte region", + "reserve naturally-aligned 128-byte region", + "reserve exactly enough to cover the access", + "custom" + ], + "description": "Strategy used to handle reservation sets\n\n * 'reserve naturally-aligned 64-byte region': Always reserve the 64-byte block containing the LR/SC address\n * 'reserve naturally-aligned 128-byte region': Always reserve the 128-byte block containing the LR/SC address\n * 'reserve exactly enough to cover the access': Always reserve exactly the LR/SC access, and no more\n * 'custom': Custom behavior, leading to an 'unpredictable' call on any LR/SC" + }, + "LRSC_FAIL_ON_VA_SYNONYM": { + "type": "boolean", + "description": "whether or not an SC will fail if its VA does not match the VA of the prior LR, even if the physical address of the SC and LR are the same" + }, + "LRSC_FAIL_ON_NON_EXACT_LRSC": { + "type": "boolean", + "description": "whether or not a Store Conditional fails if its physical address and size do not\nexactly match the physical address and size of the last Load Reserved in program order\n(independent of whether or not the SC is in the current reservation set)\n" + }, + "LRSC_MISALIGNED_BEHAVIOR": { + "type": "string", + "enum": [ + "always raise misaligned exception", + "always raise access fault", + "custom" + ], + "description": "what to do when an LR/SC address is misaligned:\n\n * 'always raise misaligned exception': self-explainitory\n * 'always raise access fault': self-explainitory\n * 'custom': Custom behavior; misaligned LR/SC may sometimes raise a misaligned exception and sometimes raise a access fault. Will lead to an 'unpredictable' call on any misaligned LR/SC access" } }, "additionalProperties": false diff --git a/schemas/csr_schema.json b/schemas/csr_schema.json index 69a065932..74c61fb85 100644 --- a/schemas/csr_schema.json +++ b/schemas/csr_schema.json @@ -256,6 +256,10 @@ "sw_read()": { "type": "string", "description": "Function that returns the value of the CSR when read by software (i.e., a Zicsr instruction). If not specified, the value last written (through hw_write) is returned." + }, + "__source": { + "description": "Path to the source file this defintion came from; used by downstream tooling -- not expected to be in handwritten files", + "type": "string" } }, "additionalProperties": false, diff --git a/schemas/ext_schema.json b/schemas/ext_schema.json index 95e3dca5c..fb09359df 100644 --- a/schemas/ext_schema.json +++ b/schemas/ext_schema.json @@ -107,6 +107,19 @@ { "type": "array", "items": { "$ref": "schema_defs.json#/$defs/extension_requirement" }} ] }, + "conflicts": { + "description": "Extension(s) that conflict with this extension; both cannot be implemented at the same time", + "oneOf": [ + { "$ref": "schema_defs.json#/$defs/extension_requirement" }, + { "type": "array", "items": { "$ref": "schema_defs.json#/$defs/extension_requirement" }} + ] + }, + "required_parameters": { + "type": "array", + "items": { + "type": "string" + } + }, "contributors": { "description": "List of contributors to this version of the extension", "type": "array", diff --git a/schemas/inst_schema.json b/schemas/inst_schema.json index f69626a5c..072079c42 100644 --- a/schemas/inst_schema.json +++ b/schemas/inst_schema.json @@ -116,6 +116,20 @@ ], "description": "Extension(s) that defines the instruction" }, + "requires": { + "oneOf": [ + { + "$ref": "schema_defs.json#/$defs/extension_requirement" + }, + { + "type": "array", + "items": { + "$ref": "schema_defs.json#/$defs/extension_requirement" + } + } + ], + "description": "Extension(s) that must also be present (in addition to definedBy) for the instruction to exist" + }, "excludedBy": { "oneOf": [ { @@ -207,6 +221,10 @@ } }, "additionalProperties": false + }, + "__source": { + "description": "Path to the source file. Used by downstream tooling; not expected to be found in handwritten files", + "type": "string" } } }