Forvis Reading Guide
User Manual:
Open the PDF directly: View PDF .
Page Count: 32
Download | |
Open PDF In Browser | View PDF |
Forvis: A Formal RISC-V ISA Specification A Reading Guide Rishiyur S. Nikhil Bluespec, Inc. c 2018-2019 R.S.Nikhil Revision: January 23, 2019 *** DRAFT: this document is still being written *** Abbreviations, acronyms and terminology and links CSR FPR GPR Hart ISA PC RVWMO RZtso spec Sv32 Sv39 Sv48 WMM Control and Status Register Floating Point Register General Purpose Register Hardware Thread. Not to be confused with software threads such as POSIX threads, “pthreads”, and processes. A hart has, in hardware, its own PC and fetch unit, and can work concurrently with other harts Instruction Set Architecture Program Counter RISC-V Weak Memory Ordering (default memory model) RISC-V Optional TSO Weak Memory Model Specification Virtual Memory System in RV32 systems Virtual Memory System in RV64 systems Optional additional Virtual Memory System in RV64 systems Weak Memory Model For more information on terminology and concepts, and information on RISC-V, we recommend these fine books: • “The RISC-V Reader: An Open Architecture Atlas”, by Patterson and Waterman [5] • “Computer Architecture: A Quantitative Approach”, by Hennessy and Patterson [1] • “Computer Organization and Design: The Hardware/Software Interface” (RISC-V Edition) by Patterson and Hennessy [4] and the RISC-V Foundation web site: https://riscv.org Thanks ... • to the original creators of RISC-V for making all this possible in the first place. • to Bluespec, Inc. for supporting this work. • to the RISC-V Foundation for recognizing the importance of formal specs and constituting the ISA Formal Specification Technical Group. • to the members of the RISC-V Foundation’s ISA Formal Specification Technical Group with whom we have wonderful weekly discussions on this topic. ii Contents 1 Introduction 1 1.1 Brief intro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Forvis goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2.1 Extension for concurrent behavior and weak memory models . . . . . . . . . . . . . . 2 About the choice of Haskell, and the level of Haskell features used . . . . . . . . . . . . . . . 3 1.3 2 Guided Tour Overview 2.1 3 A Note about Executing the Spec as a RISC-V Simulator . . . . . . . . . . . . . . . . . . . . 3 Common Stuff (shared by the various instruction specs) 3.1 5 . . . . . . . . . . . . . . . . . . . . . . . . 5 3.1.1 Base ISA type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 3.1.2 Key architectural types: instructions and registers . . . . . . . . . . . . . . . . . . . . 5 3.1.3 Major Opcodes for 32-bit instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.1.4 Exception Codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.1.5 Memory responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.1.6 Privilege Levels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.2 File GPR_File.hs: General Purpose Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.3 File Memory.hs: Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.4 File Machine_State.hs: architectural and machine state . . . . . . . . . . . . . . . . . . . . . 10 3.4.1 Handling RV32 and RV64 simultaneously . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.4.2 Machine State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 File Forvis_Spec_Common.hs: Common “instruction-finishing” functions . . . . . . . . . . . . 13 3.5 File Arch_Defs.hs: basic architectural definitions 5 4 File Forvis_Spec_I: Base Integer Instruction Specs 13 4.1 Algebraic Data Type for I instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 4.2 Sub-opcodes for I instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.3 Decoder for I instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.4 The type of each I-instruction semantic function . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.5 Dispatcher for I instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.6 Semantics of each I instruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 4.7 File ALU.hs: A pure integer ALU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 5 File Forvis_Spec.hs: Instruction Fetch and Execution Dispatch 19 5.1 Instruction Fetch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 5.2 Execution Dispatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 1 6 Privileged architecture 6.1 20 File CSR_File.hs: Control and Status Registers . . . . . . . . . . . . . . . . . . . . . . . . . 20 6.1.1 CSR addresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 6.1.2 The MISA CSR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 6.1.3 The MSTATUS CSR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 6.2 File Virtual_Mem.hs: Virtual Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 6.3 Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 7 Sequential (one-instruction-at-a-time) interpretation 23 8 Other source code files 23 8.1 File FPU.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Bibliography 25 A Haskell cheat sheet for reading Forvis code 26 2 This page intentionally left blank Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 1 1 Introduction 1.1 Brief intro The Forvis spec is written in the well-known functional programming language Haskell. This document is not the spec; it is just an initial reading guide for the spec code, both for people unfamiliar with Haskell and for those who know Haskell but would like a guided tour to get familiar with the spec. You may wish to open the code in your favorite code-viewing editor, and have this document open on the side. The code fragments in this document are automatically extracted from the actual spec code. Once you are familiar with the code, this reading guide should no longer be necessary. The Haskell code for Forvis can be compiled and executed as a RISC-V simulator, executing RISCV ELF file binaries. A separate document explains the details of how to do this. Another document describes how to extend this spec for new ISA extensions. Please skip to Sec.2 if you wish to dive into the technical stuff. The rest of Sec. 1 contains goals and rationale. Appendix. A contains a small Haskell cheat sheet to which you may want to refer if you are unfamiliar with Haskell and encounter something unfamiliar in the code. 1.2 Forvis goals This is a work-in-progress, one of several similar concurrent efforts within the “ISA Formal Specification” Technical Group constituted by The RISC-V Foundation (https://riscv.org). We welcome your feedback, comments and suggestions.1 The original English-language specs for RISC-V are: • The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA, Andrew Waterman and Krste Asanovic, Document Version 20181106-Base-Ratification, November 6, 2018. [7] • The RISC-V Instruction Set Manual, Volume II: Privileged Architecture, Andrew Waterman and Krste Asanovic (eds.), Document Version 20181203-Base-Ratification, December 3, 2018. [8]: Forvis is a formal specification of those specs, i.e., it is written in a precise, unambiguous language (here, Haskell) without regard to hardware implementation considerations; clarity and precision are paramount concerns. In contrast, specs written a natural language such as English are often prone to ambiguity, inconsistency and incompleteness. Further, a formal spec can be parsed and processed automatically, connecting to other formal analysis and transformation tools. In addition to precision and completeness, Forvis also has these goals: • Readability: This spec should be readable by people who may be completely unfamiliar with Haskell or other formal specification languages. Examples of our target audience: • RISC-V Assembly Language programmers as a reference explaining the instructions they use. 1 Forvis, and this document, are available at: https://github.com/rsnikhil/Forvis_RISCVISA-Spec Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 2 • Compiler writers targeting RISC-V, as a reference explaining the instructions they generate. • RISC-V CPU hardware designers, as a refernce explaining the instructions interpreted by their designs. • Students studying RISC-V. • Designers of new RISC-V ISA extensions, who may want to extend these specs to include their extensions. • Users of formal methods, who wish to prove properties (especially correctness) of compilers and hardware designs. • Modularity: RISC-V is one of the most modular ISAs. It supports: • A couple of base ISAs: RV32I (32-bit integers) and RV64I (64-bit integers) (an RV128I base is under development) • Numerous extensions, such as M (Integer Multiply/Divide), A (Atomic Memory Ops), F (single precision floating point), D (double precision floating point), C (compressed 16b insructions), E (embedded). • An optional Privilege Architecture, with M (machine) and optional S (supervisor) and U (user) privilege levels. • Implementation options, such as whether misaligned memory accesses are handled or cause a trap, whether interrupt delegation is supported or not, etc. Implementations can combine these flexibly in a ’mix-and-match’ manner. Some of these options can coexist in a single implementation, and some may be dynamically switched on and off. Forvis tries to capture all these possibilities. • Concurrency and non-determinism: RISC-V, like most modern ISAs, has opportunities for concurrency and legal non-determinism. For example, even in a single hart (hardware thread), it is expected that most implementations will have pipelined (concurrent) fetch and execute units, and that the instructions returned by the fetch unit may be unpredictable after earlier code that writes to instruction memory, unless mediated by a FENCE.I instruction. RISC-V has a Weak Memory Model, so that in a multi-hart system, memory-writes by one hart may be “seen” in a different order by another hart unless mediated by FENCE and AMO instructions. In particular, different implementations, and even different runs of the same program on the same implementation, may return different results from reading memory on different runs. • Executabality: Forvis constitutes an “operational” semantics (as opposed to an “axiomatic” semantics). The spec can actually be executed as a Haskell program, representing a RISCV “implementation”, i.e., it can execute RISC-V binaries. The README file in the code repository explains how to execute the code. 1.2.1 Extension for concurrent behavior and weak memory models Although it is convenient to directly execute this Haskell code as a Haskell program, thereby giving us a sequential RISC-V simulator for free, the code (specifically, the file Forvis_Spec.hs) can also be treated as a generic functional program with an alternate interpretation (non-Haskell, and changing what we mean by the “Machine State” that is an argument to each spec function). Such an alternate interpreter can demonstrate all kinds of concurrencies (e.g., due to out-of-order execution, pipelining, different kinds of speculation, and more) and non-deterministic interaction Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 3 with weak memory models. We believe it can describe the complete range of concurrent behaviors seen in actual implementations (and more concurrent behaviors not seen in practical implementations). Describing this alternate interpretation is planned as a follow-up document. We have a general idea of how this concurrent interpreter works but are still working out the details. The concurrency is not exposed in the spec text, but is implicit in the data flow. The central ideas come from “implicit dataflow” computation (cf. “Implicit Parallel Programming in pH ”[3]). 1.3 About the choice of Haskell, and the level of Haskell features used We chose to use the well-known programming language Haskell [6] because it is a pure functional language, with no side effects. ISA specs are sometimes hard to read because of hidden state, and their updates by side-effect are hard to keep track of; in our Haskell code, all state is visible and all updates can be seen explicitly as recomputation of new state. Forvis spec code is written in “extremely elementary” Haskell so that it is readable by people who may be totally unfamiliar with Haskell and who may have no interest in learning Haskell. It uses a very small, extremely simple subset of Haskell2 (just simple types, function definition and function application) and none of the features that may be even slightly unfamiliar to the audience (no Currying/partial-application, lambda-expressions, laziness, typeclasses, monads, etc.) For those without prior exposure to Haskell, this document explains the minimal Haskell notation necessary to read the Forvis spec code. Using extremely simple Haskell will also make it easier for authors of new ISA extensions to extend these specs to cover their ISA extensions, even if they are unfamiliar with Haskell. Using extremely simple Haskell will also make it easy to parse and connect to other tools, such as proof assistants, theorem provers, and so on (including the alternate “concurrent” interpreter described at the end of the next section). 2 Guided Tour Overview This guided tour focuses on the base Integer instruction set (RV32I and RV64I), since that is enough to get familiar with the overall structure and style of Forvis. Then, at your convenience, you can peruse the specs for the other standard instruction extensions (A, M, C, F, D, and Zifencei), each of which is in its own separate file. Fig.1 shows an overview of (most of) the source files and illustrates their roles.3 We’ll begin by studying common infrastructure for the semantics (modules shown in green): • Arch_Defs: Basic types for register names and values, constant definitions for opcodes, memory responses, etc. • GPR_File: Modeling the integer register file • Memory_File: Modeling memory • Machine_State: A data structure that holds the entire machine state, including the PC, CPU registers, and memory. 2 We believe that the Haskell used here is simple enough that only minor syntactic transformation would be needed to render it into some other functional language such as SML, OCaml, or Scheme. 3 For every Haskell module Foo in the figure, you’ll find a file src/Foo.hs in the repository. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 4 Figure 1: Overview of Forvis files/modules • Forvis_Spec_Common: Functions that capture the few standard ways in which all instructions “finish”. Then we’ll focus on the spec for the base integer instruction set (modules shown in red): Forvis_Spec_I and ALU. We’ll also discuss Forvis_Spec which specifies instruction-fetch and the top-level executiondispatch (dispatching to the I spec or one of the other specs). Finally, we’ll discuss the Priviliged Architecture instructions (modules shown in yellow) in Forvis_Spec_Priv, along with the Control-and-Status Register instructions Zicsr and register file CSR_File. At this point you should have a good understanding of how the semantics are expressed. In this document we will not discuss the modules shown in blue for the various ISA extensions, because they all follow the same general plan as Forvis_Spec_I: Haskell module (append .hs for filename) Forvis_Spec_I64 Forvis_Spec_C Forvis_Spec_A Forvis_Spec_M Forvis_Spec_Zifencei Forvis_Spec_F Forvis_Spec_D Description Base Integer Instruction Set, RV64 only Compressed instructions Atomic ops Integer Multiply/Divide instructions FENCE.I instruction Single-precision Floating Point instructions Double-precision Floating Point instructions Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 2.1 5 A Note about Executing the Spec as a RISC-V Simulator The bottom of Fig. 1 indicates that the spec source code can be executed in two separate and different ways: • Sequential, one-instruction-at-a-time interpretation: This is done by merely compiling the program with a Haskell compiler and executing it. The details of how to do this, and how to compile RISC-V ELF binaries for it, are described in a separate document. • Concurrent interpretation: The same spec source code, instead of being treated as a Haskell program can be viewed as a generic functional program, executed on an alternative interpreter that exposes all the concurrency (and more) found in pipelined, speculative, superscalar, outof-order, and multi-core implementations, which provokes interesting interactions with a Weak Memory Model memory system. This interpreter will replace modules like GPR_File, Memory, and Machine_State with concurrent versions. Although we have a clear idea of the nature of this alternative concurrent interpreter, it has not yet been implemented at time of this writing. 3 Common Stuff (shared by the various instruction specs) 3.1 3.1.1 File Arch_Defs.hs: basic architectural definitions Base ISA type The following defines a data type RV with two possible values, RV32 and RV64. It is analogous to an “enum” declaration in C, defining a family of constants. The deriving clause says that Haskell can automatically extend the equality operator == to work on values of type RV, and that Haskell can automatically extend the show() function to work on such values, producing printable Strings "RV32" and "RV64", respectively. line 23 Arch_Defs.hs -- Future: add RV128 data RV = RV32 | RV64 deriving (Eq, Show) 3.1.2 Key architectural types: instructions and registers Througout the spec, we use Haskell’s “unbounded integer” type (Integer) to represent values that are typically represented in hardware as bit vectors of fixed size. Unbounded integers are truly unbounded and have no limit such as the typical 32 bits or 64 bits found in most programming languages. Unbounded integers never overflow. In this spec, we take care of 32-bit and 64-bit overflow explicitly (inside module ALU). Below, we define Haskell “type synonyms” as more readable synonyms for Haskell’s Integer type. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 6 line 31 Arch_Defs.hs -- These are just synonyms of ’Integer’, for readability type Instr_32b = Integer type Instr_16b = Integer type InstrField = Integer -- various fields of instructions -- General-purpose registers type GPR_Addr = Integer type GPR_Val = Integer ...more... -- 5-bit addrs, 0..31 -- 32-bit or 64-bit values This Haskell function decides whether a particular instruction is a 32-bit instruction or a 16-bit (compressed) instruction, by testing its two least-significant bits. line 53 Arch_Defs.hs -- instruction or not (’C’ instrs have 2 lsbs not equal to 2’b11) is_instr_C :: Integer -> Bool is_instr_C u16 = ((u16 .&. 0x3) /= 0x3) {-# INLINE is_instr_C #-} Here, and everywhere in the spec, you can safely ignore the INLINE annotation. These are “pragmas“ or “directives” to the Haskell compiler when we compiler this spec into a sequential simulator, and are purely meant to improve the performance (speed) of the simulator. In accordance with the their semantic unimportance, we write these INLINE annotations below the corresponding function. 3.1.3 Major Opcodes for 32-bit instructions The 7 least-significant bits of a 32-bit instruction constitute its “major opcode”. This section defines them for the base “I” instruction set. line 62 Arch_Defs.hs -- Major opcodes -- ----------------- ’I’ (Base instruction set) opcode_LUI opcode_AUIPC opcode_JAL opcode_JALR ...more... = = = = 0x37 0x17 0x6F 0x67 :: :: :: :: InstrField InstrField InstrField InstrField ----- 7’b_01_101_11 7’b_00_101_11 7’b_11_011_11 7’b_11_001_11 Later in the file, we see major-opcode definitions for the “A” (Atomics) extension4 , the “F” and “D” extensions (single-precision and double floating point). 4 I am grateful to my colleague Joe Stoy for introducing me to the etymology of the word “atomic”. It can be read as “a+tom+ic”. The “tom” (as in “tomography”) means “cut”, and the “a“ negates it (→ “uncuttable”). Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 7 Most instructions also have other fields that further refine the opcode; we call them “sub-opcodes”. These are generally defined in the separate modules for each extention because they are only used locally there (for example, in a section labelled “Sub-opcodes for ’I’ instructions” in file Forvis_Spec_I.hs). However, some sub-opcodes are used in multiple modules and are therefore defined in this file (Arch_Defs.hs). These include all the memory-operation sub-opcodes such as funct3_LB, funct3_SB, msbs5_AMO_ADD, etc., as well as funct3_PRIV. 3.1.4 Exception Codes We define a type synonym for exception codes, and the values of all the standard exception codes for traps: line 161 Arch_Defs.hs -- Trap exception codes exc_code_instr_addr_misaligned exc_code_instr_access_fault ...more... = = 0 :: Exc_Code 1 :: Exc_Code and for interrupts: line 148 Arch_Defs.hs type Exc_Code = Integer -- Interrupt exception codes exc_code_u_software_interrupt exc_code_s_software_interrupt ...more... 3.1.5 = = 0 :: Exc_Code 1 :: Exc_Code Memory responses We define a type Mem_Result for responses from memory. This may be Mem_Result_Ok (successful), in which case it returns a value (irrelevant for STORE instructions, but relevant for LOAD, loadreserved, store-conditional, and AMO ops). Otherwise it is a Mem_Result_Err, in which case it returns an exception code (such as misalignment error, an access error, or a page fault.) line 210 Arch_Defs.hs -- Either Ok with value, or Err with an exception code data Mem_Result = Mem_Result_Ok | Mem_Result_Err Integer Exc_Code When returning a result, we construct result-expressions like this: Mem_Result_Ok value-expression Mem_Result_Err exception-value-expression When fielding a result, we deconstruct it using a case-expression like this: case mem-result of Mem_Result_Ok v -> use v in an expression Mem_Result_Err ec -> use ec in an expression Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 3.1.6 8 Privilege Levels RISC-V defines 3 standard privilege levels: Machine, Supervisor and User: line 218 Arch_Defs.hs -- Machine, Supervisor and User type Priv_Level = InstrField m_Priv_Level s_Priv_Level u_Priv_Level 3.2 = 3 :: Priv_Level = 1 :: Priv_Level = 0 :: Priv_Level File GPR_File.hs: General Purpose Registers This module implements a file of general-purpose registers. We represent it using Haskell’s Data_Map.Map type, which is an associative map (like a Python “dictionary”) that associates register names with values). This representation choice is purely internal to this module because in the export list in the module header at the top of the file: line 4 GPR_File.hs module GPR_File (GPR_File, mkGPR_File, gpr_read, gpr_write, print_GPR_File) where we mention the type GPR_File without exporting its internal representation. If, instead, we had said “GPR_File(..)”, we’d expose its internal detail. Thus, we can freely change the representation to something else (and change the API functions accordingly) without affecting any of the rest of the modules. In other words, GPR_File is an abstract type for the rest of the modules. Here is the representation and constructor: newtype GPR_File = GPR_File line 37 GPR_File.hs (Data_Map.Map GPR_Addr GPR_Val) mkGPR_File :: GPR_File mkGPR_File = GPR_File (Data_Map.fromList (zip [0..31] (repeat (fromIntegral 0)))) The zip function constructs a listof intial values, associating each register address (0..31) with 0 (arbitrarily chosen, since the spec does not specify the initial value of any register). This is followed by the API functions gpr_read and gpr_write. The latter always writes 0 into GPR 0, so we can only ever read 0 from GPR 0.5 Note: we do not here model accesses to the register file that concurrent, interleaved, and returning results out of order. This is fine for sequential interpretation, but will have to be enriched for concurrent interpretation. 5 In “seq val1 (..)” in the last line of gpr_write, only the part in parentheses is relevant, doing the actual GPR register file update; the rest is a wrapper that is merely a Haskell performance optimization for the simulator, concerned with Haskell’s lazy evaluation regime. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 3.3 9 File Memory.hs: Memory This module implements a model of memory. We represent it using Haskell’s Data_Map.Map type, which is an associative map (like a Python “dictionary”) that associates addresses with values). This representation choice is purely internal to this module; for all other modules it is an abstract data type accessible only via the API we export from this module (so we can freely change the internal representation, in future, if we wish). Here is the representation and constructor: ------ line 33 Memory.hs This is a private internal representation that can be changed at will; only the exported API can be used by clients. We choose 32-bit data to cover the most common accesses in RV32 and RV64, and since AMO ops are either 32b or 64b ’f_reserved_addr’ is the address range reserved by an ’LR’ (Load Reserved) op. data Mem = Mem { f_dm :: Data_Map.Map Integer Integer, f_reserved_addr :: Maybe (Integer, Integer) } mkMem :: [(Integer, Integer)] -> Mem mkMem addr_byte_list = let addr_word_list = addr_byte_list_to_addr_word_list addr_byte_list in Mem {f_dm = Data_Map.fromList addr_word_list, f_reserved_addr = Nothing } [In anticipation of supporting the ’A’ ISA option (atomics), we also have a field that “remembers” the address of the most recent Load-Reserved instruction, to be matched against the next StoreConditional instruction.] The contructor’s argument is a list of (address, byte) pairs, and it initializes the data map with those contents. As a practical simulation-speed consideration, we represent memory in 32-bit words (even though it is byte-addressable) since most accesses are at 32-bit or 64-bit granularity (even in RV64, instruction accesses are at 32-bit granularity). Later in the file we see the API to read memory: line 102 Memory.hs mem_read :: Mem -> InstrField -> Integer -> (Mem_Result, Mem) mem_read mem funct3 addr = ...more... and to write memory: line 142 Memory.hs mem_write :: Mem -> InstrField -> Integer -> Integer -> (Mem_Result, Mem) mem_write mem funct3 addr stv = ...more... Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 10 In both cases, the first argument is the memory itself, the second argument (funct3 is the same 3-bit value in the original LOAD or STORE instruction indicating the size of the access (byte, halfword, word or doubleword). The third argument is the memory byte-address and the mem_write function has a fourth argument which is the store-value. The internal details are not too interesting other; they’re doing some bit-manipulation to accommodate the fact that our representation is in 4-byte words, and the access size may be for 1, 2, 4 or 8 bytes. The last fragment of the function checks if the access is aligned: line 129 Memory.hs if (is_LOAD_aligned funct3 addr) then (Mem_Result_Ok u64, mem) else (Mem_Result_Err exc_code_load_addr_misaligned, mem) and returns an exception result if so. A future version of this spec will make this a parameter, since an implementation is allowed to handle misaligned accesses directly (and not return an exception). Later in the file you will also see the function mem_amo that handles read-modify-write operations in the “A” (atomics) extension, but you can ignore it for now while we focus on the base Integer instruction set. Note: we do not model caches, or write-buffers, or any such hardware implementation artifact here. This is fine for sequential interpretation, but will have to be enriched for concurrent interpretation. 3.4 File Machine_State.hs: architectural and machine state [Reminder: this is for the simple, sequential, one-instruction-at-a-time interpreter. The concurrent interpreter has a substantially different machine state.] 3.4.1 Handling RV32 and RV64 simultaneously Although hardware implementations are typically either RV32 systems or RV64 systems, the spec encompasses implementations that can simultaneously support both. For example, machineprivilege code may run in RV64 mode while supervisor- and user-privilege code may run in RV32 mode. There is also a future RV128 being defined. In Forvis, which covers RV32 and RV64 and their simultaneous use, we represent everything using unbounded integers (Haskell’s “Integer” type). The semantics of each instruction are defined to be governed by the current RV setting which is available in the architectural state (specifically, MISA.MXL, MSTATUS.SXL, MSTATUS.UXL, etc.). RV32 has a smaller integer instruction set than RV64, and limits calculations on values to 32-bit arithmetic. 3.4.2 Machine State We define a new type representing a complete “machine state”. The representation is a record (or struct). Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 11 line 38 Machine_State.hs data Machine_State = Machine_State { -- Architectural state f_pc :: Integer, f_gprs :: GPR_File, f_fprs :: FPR_File, f_csrs :: CSR_File, f_priv :: Priv_Level, -- Memory and mory mapped IO f_mem :: Mem, f_mmio :: MMIO, -- Implementation options -- Legal memory addresses: list of (addr_start, addr_lim) f_mem_addr_ranges :: [(Integer, Integer)], -- For convenience and debugging only; no semantic relevance f_rv :: RV, -- redundant copy of info in CSR MISA f_run_state :: Run_State, f_last_instr_trapped :: Bool, f_verbosity :: Int } The first few fields represent a RISC-V hart’s basic architectural state: a Program Counter, general purpose registers, floating-point regsiters, control-and-status Registers, and the current privilege level at which it is running. This is followed by two fields representing memory and memory-mapped I/O devices.6 Finally, we have fields that are not semantically relevant, but are needed or useful in simulation or formal reasoning, gathering statistics, etc., including a list of legal address ranges (memory load/store instructions should trap if accessing anything outside this range). This record-with-fields is a purely internal representation choice in this module. Clients of this module only access it via the mstate_function API that follows.7 The following function is a constructor that returns a new machine state: line 90 Machine_State.hs -- Make a Machine_State, given initial PC and memory contents mkMachine_State :: RV -> -- Initial RV32/RV64 Integer -> -- Initial value of misa Integer -> -- Initial value of PC [(Integer,Integer)] -> -- List of legal memory addresses ([(Integer, Integer)]) -> -- Initial mem contents (addr-&-byte list) Machine_State -- result mkMachine_State rv misa initial_PC addr_ranges addr_byte_list = let mstate = Machine_State {f_pc = initial_PC, 6 We have not yet discussed FPRs, CSRs and MMIO, but they can be ignored for now while we focus on the base Integer instruction set. 7 Haskell has export-import mechanisms to enforce this external invisibility of our representation choice, but we have omitted them here to avoid clutter. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved f_gprs f_fprs f_csrs f_priv = = = = mkGPR_File, mkFPR_File, mkCSR_File rv m_Priv_Level, 12 misa, f_mem = mkMem addr_byte_list, f_mmio = mkMMIO, f_mem_addr_ranges = addr_ranges, f_rv f_run_state f_last_instr_trapped f_verbosity } = = = = rv, Run_State_Running, False, 0 in mstate The misa argument is passed down to the CSR register file constructor; it can be ignored for now. The addr_byte_list is passed down into the memory constructor to intialize memory. All functions that “update” the machine state are written in purely functional style: the first argument is typically a machine state, and the final result is the new machine state. This will be evident in their type signatures: somefunction :: Machine_State -> ...other arguments... -> Machine_State For those unfamiliar with functional programming, it is sometimes startling to see something as “large” as a machine state passed as an argument and returned as a result, but rest assured this is fine for our spec; these are just like functions in mathematics. What follows in the file is a series of API functions to read or update the machine state, such as the following to access and update the PC: line 120 Machine_State.hs mstate_pc_read :: Machine_State -> Integer mstate_pc_read mstate = f_pc mstate mstate_pc_write :: Machine_State -> Integer -> Machine_State mstate_pc_write mstate val = mstate { f_pc = val } The mstate_pc_read function just applies the f_pc field selector to the machine state to extract that field. The mstate_pc_write function uses Haskell’s “field update” notation: mstate { f_pc = val } to construct (and return) a new machine state in which the f_pc field has the new value. In many of the API functions, such as those to read and write GPRs, FPRs or CSRs, the function merely invokes the appropriate API of the corresponding component (GPR file, FPR file or CSR file). Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 13 In the API functions for read, write and atomic memory operations, such as mstate_mem_read, we check if the given address is a supported memory address and return an exception if not. Otherwise, we triage the address to determine if it is for actual memory or for a memory-mapped I/O device, and direct the request to the appropriate component. The API functions for FENCE, FENCE.I and SFENCE.VMA are currenty no-ops in the spec since they only come into play when there is concurrency involving multiple paths to memory from one or more harts (hardware threads). For a sequential one-instruction-at-a-time interpretation, without multiple paths to memory, with just one hart, it is fine to treat them as no-ops (more accurately, as the identity function on the machine state). The file ends with a number of functions to aid in simulation, to move console input and output between the machine state and the console, to “tick” IO devices (which logically run concurrently with the CPU), etc. 3.5 File Forvis_Spec_Common.hs: Common “instruction-finishing” functions Although there are dozens of different instruction opcodes (hundreds, if we count ISA extensions), there are only five or six ways in which they all “finish”– possibly write a value to a destination register, possibly increment the PC by 2 or 4, or write a new value into the PC, possibly increment the MINSTRET (number of instructions retired) register, and so on. Another possibility is to trap, which does standard things like storing a cause in the MCAUSE register, storing the current PC in the xEPC register, deciding whether the next PC should come from the MTVEC, STVEC or UTVEC register (taking into account delegation in the MIDELEG and MEDELEG registers), manipulate the MSTATUS register in a certain stylized manner (“pushing” the interrupt-enable and privilege stacks), and so on. Rather than replicate these few patterns in each instruction’s semantic function, we collect them in this file in standard functions and just invoke them from each instruction’s semantic function. For example, this function captures the common finish of all ALU instructions, which: • write a result value rd_val to the GPR rd; • increment the PC by 4 or 2, depending on boolean is_C, which indicates whether the current instruction is a regular 32-bit instruction or a 16-bit C (compressed) instruction; • and increment the MINSTRET (instructions retired) counter. line 47 Forvis_Spec_Common.hs finish_rd_and_pc_incr :: Machine_State -> GPR_Addr -> Integer -> Bool -> Machine_State finish_rd_and_pc_incr mstate rd rd_val is_C = let mstate1 = mstate_gpr_write mstate rd rd_val pc = mstate_pc_read mstate1 delta = if is_C then 2 else 4 mstate2 = mstate_pc_write mstate1 (pc + delta) mstate3 = incr_minstret mstate2 in mstate3 4 File Forvis_Spec_I: Base Integer Instruction Specs We are now ready to look at this module which covers the whole RV32 Integer instruction set. The organization of the code in this module is also followed in the modules for other extensions (I64, Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 14 C, A, M, Zfencei, F, D, Priv, Zicsr): • Declaration of a type data Instr_I, a data structure representing all the instructions in this group, along with each one’s logical fields. This is a Haskell “algebraic data type”. These are like “abstract syntax trees” for instructions in this group. • Definitions of sub-opcodes for instructions in group I. These are values of fields in a 32-bit instruction that collectively refine it to a more specific instruction opcode. • A decode function decode_I of type: decode_I :: RV -> Instr_32b -> Maybe Instr_I that takes a a 32-bit instruction and returns a result that is either: • Nothing: this is not an instruction in this group, • or Just adt: this is an instruction in this group, and adt is a value of the algebraic data type Instr_I, i.e., the logical view of the instruction. The RV argument is because these functions serve for both the RV32 and RV64 base integer instructions, but some instructions are only valid in RV64. • An execution-dispatch function exec_instr_I that dispatches each kind of instruction in the group to a specific execution function for that particular kind of instruction. • A series of functions exec_LUI, exec_AUIPC, exec_JAL, ..., one per opcode, describing the semantics of that particular kind of instruction. 4.1 Algebraic Data Type for I instructions The file begins with a Haskell data type declaration for the type Instr_I data Instr_I = LUI | AUIPC | JAL | JALR GPR_Addr GPR_Addr line 30 Forvis_Spec_I.hs InstrField -- rd, InstrField -- rd, imm20 imm20 GPR_Addr GPR_Addr InstrField GPR_Addr InstrField imm21 rs1, imm12 -- rd, -- rd, ...more... This should be read as follows: “A value of type Instr_I is • either a LUI instruction, in which case it has two fields of type GPR_Addr and InstrField (the destination register rd and an immediate value), • or a AUIPC instruction, in which case it has two fields of type GPR_Addr and InstrField (the destination register rd and an immediate value), • or a JAL instruction, in which case it has two fields of type GPR_Addr and InstrField (the destination register rd and an immediate value), • or a JALR instruction, in which case it has three fields of type GPR_Addr, GPR_Addr and InstrField (the destination register rd, the source register rs1, and an immediate value), • ... and so on.” Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 4.2 15 Sub-opcodes for I instructions The next section defines values of other fields in a 32-bit instruction that further refine the group opcode into a specific opcode: line 84 Forvis_Spec_I.hs -- opcode_JALR sub-opcodes funct3_JALR = 0x0 :: InstrField -- 3’b_000 -- opcode_BRANCH sub-opcodes funct3_BEQ = 0x0 :: InstrField funct3_BNE = 0x1 :: InstrField ...more... -- 3’b_000 -- 3’b_001 These are values in the 3-bit field in bits [14:12] of a 32-bit instruction. 4.3 Decoder for I instructions The next section defines the function decode_I whose arguments are rv (because some I instructions are only valid in RV64 and not in RV32) and a 32-bit instruction. The result is of type Maybe Instr_I, i.e., it is: • either Nothing: this is not an I instruction, • or Just instr_I: this is an I instruction, and the field instr_I is value of type Instr_I, the logical view of the instruction. line 148 Forvis_Spec_I.hs decode_I :: RV -> Instr_32b -> Maybe Instr_I decode_I rv instr_32b = let -- Symbolic names for notable bitfields in the 32b instruction ’instr_32b’ opcode = bitSlice instr_32b 6 0 rd = bitSlice instr_32b 11 7 funct3 = bitSlice instr_32b 14 12 rs1 = bitSlice instr_32b 19 15 rs2 = bitSlice instr_32b 24 20 funct7 = bitSlice instr_32b 31 25 ...more... The first few lines of the function use bitSlice to extract bit-fields of the instruction. This is a helpfunction defined in Bit_Utils.hs, and bitSlice x j k is equivalent to the Verilog/SystemVerilog bit-selection x[j,k]. Note that some field-extractions can involve more complex bit-shuffling, such as: imm21_J = (( shiftL .|. (shiftL .|. (shiftL .|. (shiftL line 168 Forvis_Spec_I.hs (bitSlice instr_32b 31 31) (bitSlice instr_32b 30 21) (bitSlice instr_32b 20 20) (bitSlice instr_32b 19 12) 20) 1) 11) 12)) Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 16 The decode function essentially abtracts away lower-level details of how fields are laid out in 32-bit parcels and returns a higher-level, more abstract view of type Instr_I. The decode function finally defines the result m_instr_I (of type Maybe Instr_I) by dispatching on a series of conditions, each checking for a particular opcode: line 198 Forvis_Spec_I.hs m_instr_I | opcode==opcode_LUI = Just (LUI rd imm20_U) | opcode==opcode_AUIPC = Just (AUIPC rd imm20_U) | opcode==opcode_JAL = Just | opcode==opcode_JALR, funct3==funct3_JALR = Just ...more... (JAL (JALR rd rd imm21_J) rs1 imm12_I) If none of the conditions match, it returns Nothing 4.4 The type of each I-instruction semantic function The functions describing the semantics of each I instruction (to follow, shortly) all have the same type. In such a situation, it’s good to give a name to this type (using a type-synonym): line 257 Forvis_Spec_I.hs type Spec_Instr_I = Bool -> Instr_I -> Machine_State -> Machine_State -is_C instr_I mstate mstate’ The first argument is a boolean (we’ll consistently use the variable is_C for this) indicating whether the current instruction is a regular 32-bit instruction or a 16-bit (C, compressed) instruction. In RISC-V, each 16-bit instruction is defined as a “short form” for a specific corresponding 32-bit instruction. Thus, we define the semantics in one function, but use the parameter is_C to remember whether we’re doing this for a 32-bit or for a 16-bit instruction. For almost all instructions, we either update the PC with the address of the next instruction, or we remember address of the next instruction (e.g., in JAL and JALR). This next-instruction-address may be the current PC +2 or +4, depending on is_C. The second argument is the instruction in its decoded form; the third argument is the machine state, and the final outcome after executing the instruction is the new machine state. 4.5 Dispatcher for I instructions The next function is simply a dispatcher that takes an Instr_I value and, based on the kind of I instruction it is, dispatches to a specific execution function for that kind of of instruction. line 261 Forvis_Spec_I.hs exec_instr_I :: Spec_Instr_I exec_instr_I is_C instr_I mstate = case instr_I of LUI rd imm20 -> exec_LUI is_C instr_I mstate AUIPC rd imm20 -> exec_AUIPC is_C instr_I mstate JAL rd imm21 -> exec_JAL is_C instr_I mstate Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved JALR rd ...more... rs1 imm12 -> exec_JALR is_C instr_I 17 mstate It uses the Haskell pattern-matching case statement to determine which kind of instruction it is, and invokes the appropriate function. Note, it has no check for illegal instructions; the fact that the argument is of type Instr_I means it can only be a valid I instruction. 4.6 Semantics of each I instruction This is the meat of instruction semantics: what exactly does each kind of instruction do? There is one exec_FOO function for each opcode FOO. We examine excerpts of a few of them, for illustration. The first I instruction, LUI, is very simple: line 316 Forvis_Spec_I.hs exec_LUI :: Spec_Instr_I exec_LUI is_C (LUI rd imm20) mstate = let xlen = mstate_xlen_read mstate rd_val = sign_extend 32 xlen (shiftL imm20 12) mstate1 = finish_rd_and_pc_incr mstate rd rd_val is_C in mstate1 The second argument is the pattern (LUI rd imm20) that is matched against the instruction, and gives us bindings for the rd and imm20 fields as a result. There is no chance that this pattern fails, since this function is only called by the dispatcher exec_instr_I (above) when it is this kind of instruction. It uses the 20-bit immedate to calculate a value rd_val to save in the destination register rd, and calls the standard “finish” function described in Sec. 3.5 to write the destination register and increment the PC. The JALR instruction does a bit more: line 370 Forvis_Spec_I.hs exec_JALR :: Spec_Instr_I exec_JALR is_C (JALR rd rs1 imm12) mstate = let misa = mstate_csr_read mstate csr_addr_misa xlen = mstate_xlen_read mstate pc = mstate_pc_read mstate rd_val = if is_C then pc + 2 else pc + 4 s_offset = sign_extend 12 rs1_val = mstate_gpr_read xlen imm12 mstate rs1 new_pc = alu_add xlen rs1_val s_offset new_pc’ = clearBit new_pc 0 aligned = if (misa_flag misa ’C’) then Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 18 True else ((new_pc’ .&. 0x3) == 0) mstate1 = if aligned then finish_rd_and_pc mstate rd rd_val new_pc’ else finish_trap mstate exc_code_instr_addr_misaligned in mstate1 new_pc’ We see that that saved “return-address” (rd_val is calculated as PC+2 or PC+4 depending on is_C. The jump-target PC is initially calculated by adding the offset to the rs1_val. Then, per the spec, we clear its bit [0] to 0. Then we check if is is properly aligned, which decision itself depends on whether MISA.C is currently active or not. Finally, if the target is properly aligned, we finish normally (updating rd and incrementing PC), otherwise we finish by trapping with exception code exc_code_instr_addr_misaligned, and storing new_pc’ in the TVAL CSR. The functions for memory load instructions exec_LB, exec_LH, exec_LW, exec_LD all funnel back through a common exec_LOAD help-function. Let’s focus on this excerpt: line 487 Forvis_Spec_I.hs -- Read mem, possibly with virtual mem translation is_instr = False (result1, mstate1) = mstate_vm_read mstate is_instr exc_code_load_access_fault funct3 eaddr2 Its work is done by mstate_vm_read, which is in file Virtual_Mem.hs, which does all memory reads. By examining mstate, it can decide whether the address is a virtual or physical address, and do the translation if needed. During translation or subsequent memory access, it may encounter a fault. The final result is of type Mem_Result (described in Sec. 3.1.5), indicating that it’s ok (and contains the appropriate payload), or an error (and contains the appropriate exception code). In the case of an access fault, the is_instr boolean allows the memory system to determine if it should return an instruction-fetch access fault or a load access fault. Moving further down the file, the ADD instruction is handled by the following function: line 593 Forvis_Spec_I.hs exec_ADD :: Spec_Instr_I exec_ADD is_C (ADD rd rs1 rs2) mstate = exec_OP alu_add is_C rd rs1 rs2 mstate This just dispatches to the function exec_OP which is used by all the opcode_OP functions. To this common function, we pass the function alu_add (defined in file ALU.hs) to indicate the specific function to be performed on the operands. The exec_OP function is simple. We read the Rs1 and Rs2 registers, perform the specified alu_op, and finish by updating the destination register rd and incrementing the PC. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 19 line 635 Forvis_Spec_I.hs exec_OP :: (Int -> Integer -> Integer -> Integer) -> Bool -> GPR_Addr -> GPR_Addr -> GPR_Addr -> Machine_State -> Machine_State exec_OP alu_op is_C rd rs1 rs2 mstate = let xlen = mstate_xlen_read mstate rs1_val = mstate_gpr_read mstate rs1 -- read rs1 rs2_val = mstate_gpr_read mstate rs2 -- read rs2 rd_val = alu_op xlen rs1_val rs2_val -- compute rd_val mstate1 = finish_rd_and_pc_incr mstate rd rd_val is_C in mstate1 Near the end of the file we have the semantics of ECALL: line 667 Forvis_Spec_I.hs exec_ECALL :: Spec_Instr_I exec_ECALL is_C (ECALL) mstate = let priv = mstate_priv_read mstate exc_code | priv == m_Priv_Level = exc_code_ECall_from_M | priv == s_Priv_Level = exc_code_ECall_from_S | priv == u_Priv_Level = exc_code_ECall_from_U | True = error ("Illegal priv " ++ show (priv)) tval = 0 mstate1 = finish_trap mstate exc_code tval in mstate1 It decides the exception code depending on the current privilege level, and then finishes with a trap, supplying that exception code, and 0 for the “trap value” (that goes into the xTVAL register). 4.7 File ALU.hs: A pure integer ALU The module ALU represents the “pure integer ALU”, i.e., pure functions of one or two inputs representing the various integer arithmetic, logic and comparison operatinos of interest. Most of the functions are quite straightforward, invoking Haskell primitives to perform the actual operations. All considerations of whether we are dealing with 32-bit or 64-bit input and output values, whether they are to be interpreted as signed or unsigned values, etc., are confined to this module. Outside this module, all values are “stored” as Haskell’s Integer data type. 5 5.1 File Forvis_Spec.hs: Instruction Fetch and Execution Dispatch Instruction Fetch An instruction fetch can have three possible outcomes. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 20 • It can trap, due to a misaligned access, or access fault • It can return a regular 32-bit instruction • If the C (compressed) extension is active (MISA.C bit set), it can return a 16-bit C instruction. These outcomes are expressed in a type definition, and the header of the instr_fetch function: line 58 Forvis_Spec.hs data Fetch_Result = Fetch_Trap Exc_Code | Fetch_C Integer | Fetch Integer deriving (Show) instr_fetch :: Machine_State -> (Fetch_Result, Machine_State) instr_fetch mstate = The instr_fetch function takes the current machine state as argument, and attempts to read and instruction from memory, returning a 2-tuple: a Fetch Result and the updated machine state. Note, while we may think of an instruction-fetch as a pure “read”, the machine state can in general be updated during virtual memory translation, and if the machine state models caches for concurrent interpretation. The body of the instr_fetch function first checks the ’C’ flag in CSR MISA to see if compressed instructions are supported. If not, it reads a 4-byte (32-bit) instruction from memory. If ’C’ is supported, it first reads 2 bytes from memory, and checks if it encodes a possible ’C’ instruction. If not, it then reads 2 more bytes from memory and returns it as a full 32-bit instruction. Of course, either of these two reads can fault, and this is the reason we read two bytes at a time: the first read may succeed with a ’C’ instruction, in which case we do not want to encounter a fault for reading two more bytes which may be unnecessary in the program flow. 5.2 Execution Dispatch A function exec_instr (and it’s counterpart exec_instr_C for ’C’ compressed instructions) that uses all the spec_OPCODE to update the machine state by executing exactly one instruction. 6 Privileged architecture 6.1 File CSR_File.hs: Control and Status Registers CSR_File.hs implements a file of Control and Status registers. The module begins with definitions of reset values for CSRs at the User, Supervisor and Machine levels of privilege. 6.1.1 CSR addresses The next few sections define the addresses of all the standard CSRs (Control and Status Registers), at User, Supervisor and Machine Privilege levels. csr_addr_ustatus csr_addr_uie csr_addr_utvec line 223 CSR_File.hs = 0x000 :: CSR_Addr = 0x004 :: CSR_Addr = 0x005 :: CSR_Addr Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved csr_addr_uscratch ...more... 6.1.2 21 = 0x040 :: CSR_Addr The MISA CSR A key CSR is MISA (“Machine ISA Register”). The 2 most-significant bits are called MXL and it encodes the current “native” width of the ISA (width of PC and GPRs), which can be 32, 64 or 128 bits. line 483 CSR_File.hs -- Codes xl_rv32 xl_rv64 xl_rv128 for = 1 = 2 = 3 MXL, SXL, UXL :: Integer :: Integer :: Integer The lower 26 bits are named by letters of the alphabet, A-Z, with bit 0 being A and Bit 25 begin Z. The function misa_flag, when given the value in the MISA register and a letter of the alphabet (uppercase or lowercase), returns a boolean indicating whether the corresponding bit is set or not. line 489 CSR_File.hs -- Test whether a particular MISA ’letter’ bit (A-Z) is set misa_flag :: Integer -> Char -> Bool misa_flag misa letter = if ( isAsciiUpper letter) then (((shiftR misa ((ord letter) - (ord ’A’))) .&. 1) == 1) else if (isAsciiLower letter) then (((shiftR misa ((ord letter) - (ord ’a’))) .&. 1) == 1) else error "Illegal argument to misa_flag" This is followed by symbolic-name definitions for the integer bit positions of the 26 alphabets and the MXL field. line 503 CSR_File.hs misa_A_bitpos = 0 :: Int misa_B_bitpos = 1 :: Int misa_C_bitpos = 2 :: Int ...more... 6.1.3 The MSTATUS CSR Another key CSR is MSTATUS (“Machine Mode Status”). The code starts with definitions for the integer bit positions of its fields. This is followed by two help-functions that are used in defining the semantics of exceptions (interrupts and traps) and returns-from-exceptions. The least-significant 8 fields of MSTATUS represent a shallow“stack”of interrupt-enable and privilege bits. On an exception, we push new values on to this stack, and when we return-from-exception we pop the stack. The function mstatus_stack_fields Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 22 extracts the stack, returning the fields as an 8-tuple: (mpp,spp,mpie,spie,upie,mie,sie,uie). The inverse function, mstatus_upd_stack_fields takes an MSTATUS value and an 8-tuple of new stack values, and returns a new MSTATUS with the stack updated. Not all fields in MSTATUS are used (they may be defined in future versions of the spec). The next few definitions describe “masks” that restrict an MSTATUS value to defined fields so that we do not disturb the undefined fields. Some of the fields of MSTATUS are visible as the SSTATUS (Supervisor Status) and USTATUS (User Status) at lower privilege levels. Masks for these views are also defined here. The API functions csr_read and csr_write. The main subtlety here is that the certain distinct CSR addresses refer to “views” of the same underlying register with various restrictions: • USTATUS and SSTATUS are restricted views of MSTATUS • UIE and SIE are restricted views of MIE • UIP and SIP are restricted views of MIP The functions mstatus_stack_fields and mstatus_upd_stack_fields encapsulate reading and writing the “stack” in the MSTATUS register containing the “previous privilege”, “previous interrupt enable” and “interrupt enable” fields. This stack is pushed on traps/interrupts, and popped on URET/SRET/MRET instructions. The function fn_interrupt_pending was mentioned earlier in Sec. 6.3; it analyzes the MSTATUS, MIP, MIE and current privilege level to decide whether a machine/supervisor/user external/software/timer interrupt is pending, and if so, which one. 6.2 File Virtual_Mem.hs: Virtual Memory Essentially all the code to support virtual memory is in the file Virtual_Mem.hs. There are four broad classes of memory access: instruction fetch, loads, stores, and AMOs. The function fn_vm_is_active checks whether the effective address computed in each kind of memory access is a virtual memory address that needs to be translated to a physical memory address. It examines the current privilege level, and the value in the “mode” field of CSR SATP. It also takes into account that if MSTATUS.MPRV is set, then loads, stores and AMOs should be regarded as occurring at the privilege level MSTATUS.MPP instead of the current privilege. line 47 Virtual_Mem.hs fn_vm_is_active :: Machine_State -> Bool -> Bool fn_vm_is_active mstate is_instr = In file Forvis_Spec.hs, in the spec functions for the four classes of memory access (instr_fetch, spec_LOAD, spec_STORE and spec_AMO), the code first invokes fn_vm_is_active to check if virtualto-physical address translation is required. If so, it then invokes the function vm_translate to perform the translation. This function can return a memory access fault or page fault, or a successful translation with a physical address. We use the Memory_Result type to return this range of results. The vm_translate function may also modify machine state (“access” and “dirty” bits in page tables, internal cache- and TLB-tracking state, etc.), and so the machine state is both an argument and a result of the function. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 23 line 155 Virtual_Mem.hs -- vm_translate translates a virtual address into a physical address. -- Notes: -- ’is_instr’ is True if this is for an instruction-fetch as opposed to LOAD/STORE -- ’is_read’ is True for LOAD, False for STORE/AMO -- 1st component of tuple result is ’Mem_Result_Err exc_code’ if there was a trap -and ’Mem_Result_Ok pa’ if it successfully translated to a phys addr -- 2nd component of tuple result is new mem state, potentially modified -(page table A D bits, cache tracking, TLB tracking, ...) vm_translate :: Machine_State -> Bool -> vm_translate mstate is_instr 6.3 Bool -> Integer -> (Mem_Result, Machine_State) is_read va = Interrupts line 243 Forvis_Spec.hs mstate_take_interrupt_if_any :: Machine_State -> (Maybe Exc_Code, Machine_State) mstate_take_interrupt_if_any mstate = In the file Forvis_Spec.hs the take_interrupt_if_any function can be applied between any two instruction executions. It uses the function fn_interrupt_pending that examines MSTATUS, MIP, MIE and the current privilege level to check if there is an interrupt is pending and the hart is ready to handle it. If so, it applies mstate_upd_on_trap to update the machine state, which it returns along with True. Otherwise, it returns False and the unchanged machine state. 7 Sequential (one-instruction-at-a-time) interpretation The sequential interpreter has a machine state M as described in Sec. 3.4, and a list spec fns of spec functions as described in the previous section, i.e., each having the type: Machine_State -> Instr -> (Bool, Machine_State) The interpreter performs the following, forever: It uses the memory-access API function mstate_mem_read to read an instruction from M. It then applies each function from spec fns, one by one until one of them returns (True,M’), i.e., one of them successfully decodes and executes the instruction. If all the functions in spec fns return (False,...), the interpreter applies the finish_trap function to M with the ILLEGAL_INSTRUCTION exception code to produce the next state M’. 8 Other source code files Bit_Utils.hs contains utilities for bit manipulation, including sign- and zero-extension, truncation, conversion, etc. that are relevant for these semantics. Forvis Reading Guide c 2018-2019 R.S.Nikhil; All Rights Reserved 24 Most of the remaining files are not part of ISA semantics, but infrastructure for building a “system”: a boot ROM, a memory, and I/O devices such as a timer (MTIME and MTIMECMP), a softwareinterrupt location (MSIP), and a UART for console I/O. Main.hs is a driver program that just dispatches to one of two use-cases, Main_RunProgram.hs (free-running) or Main_TandemVerifier.hs (Tandem Verification). Main_RunProgram.hs reads RISC-V binaries (ELF), initializes architecture state and memory, and calls RunProgram to run the loaded program, up to a specified maximum number of instructions. Run_Program.hs contains the FETCH-EXECUTE loop, along with some heuristic stopping-conditions (maximum instruction count, detected self-loop, detected non-zero write into tohost memory location, etc. Main_TandemVerifier.hs sets up Forvis to be a slave processs to a tandem verifier, receiving commands on stdin and sending responses on stdout. The commands allow a tandem verifier to initialize architecture state, execute 1 or more instructions, and query archtectural state. Responses include tandem verification packets which the verifier can use to check an implementation. Addres_Map.hs specifies the address map for the “system”: the base address and address range for each memory and I/O device. Memory.hs implements a memory model with read, write and AMO functions. MMIO.hs implements the memory-mapped I/O system. Mem_Ops.hs defines instruction field values that specify the type and size of memory operations. These are duplicates of defs in Forvis_Spec.hs where they are in the specs of LOAD, STORE and AMO instructions. They are repeated here because this information is also needed in Memory.hs, MMIO.hs and other places. UART.hs is a model of the popular National Semiconductor NS16550A UART. Elf.hs and Read_Hex_File.hs are functions for reading ELF files and “Hex Memory” files, respectively. 8.1 File FPU.hs The module FPU is the floating-point analog of the integer ALU module, and represents a “pure floating point ALU”, encapsulating all floating-point arithmetic, logic, comparision and conversion operations. All considerations of single-precision vs. double-precision, conversions between these and integers etc., are confined to this module. c 2018-2019 R.S.Nikhil; All Rights Reserved 25 References [1] J. L. Hennessy and D. A. Patterson. Computer Architecture: A Quantitative Approach (6th Edition). Morgan Kaufman, 2017. [2] D. E. Knuth. Literate Programming. The Computer Journal. British Computer Society., 27(2):97–11, 1984. [3] R. S. Nikhil and Arvind. Implicit Parallel Programming in pH. Morgan Kaufman, Inc., 2001. [4] D. Patterson and J. L. Hennessy. Computer Organization and Design: The Hardware/Software Interface (RISC-V Edition). Morgan Kaufmann, 2017. [5] D. Patterson and A. Waterman. The RISC-V Reader: An Open Architecture Atlas. Strawberry Canyon, 2017. [6] S. Peyton Jones (Editor). Haskell 98 Language and Libraries: The Revised Report. Cambridge University Press, May 5 2003. haskell.org. [7] A. Waterman and K. Asanovic. The RISC-V Instruction Set Manual. Technical Report Document Version 20181106-Base-Ratification, The RISC-V Foundation (riscv.org), November 6 2018. [8] A. Waterman and K. Asanovic. The RISC-V Instruction Set Manual. Technical Report Document Version 20181203-Base-Ratification, The RISC-V Foundation (riscv.org), December 3 2018. c 2018-2019 R.S.Nikhil; All Rights Reserved A 26 Haskell cheat sheet for reading Forvis code Haskell is a pure functional language: everything is expressed as pure mathematical functions from arguments to results, and composition of functions. There is no sequencing, and no concept of updatable variables (traditional “assignment statement”) Each Haskell file is a Haskell module and has the form: module module-name where import another-module-name ... import another-module-name ... constant-or-function-or-type-definition ... constant-or-function-or-type-definition ... Comments begin with “--” and extend through the end of the line. Haskell relies on “layout” to convey text structure, i.e., indentation instead of brackets and semicolons. A constant definition looks like this: foo = value-expression :: type A function definition looks like this: fn :: arg-type -> ... -> arg-type -> resul-type fn arg ... arg = function-body-expression Note: in Haskell, function arguments, both in definitions and in applications, are typically just juxtaposed and not enclosed in parentheses and commas, thus: fn arg ... arg instead of: fn ( arg, ..., arg ) A definition like this: type Instr = Word32 just defines a new type synonym (Instr) for an existing type (Word32); this is done just for readability. A definition like this: data newtype = ... defines a new type; these will be explained as we go along. For readability, large expressions are sometimes deconstructed using “let” expressions to provide meaningful names to intermediate sub-expressions, define local help-functions, etc. For example, instead of: x + f y z - g a b c we may write, equivalently: c 2018-2019 R.S.Nikhil; All Rights Reserved 27 let tmp1 = f y z tmp2 = g a b c result = x + tmp1 + tmp2 in result Conditional expressions may be written using if-then-else which can of course be nested: x = if cond-expr1 then expr1 else if cond-expr2 then expr2 else expr3 or using case which can also be nested: x = case cond-expr1 of True -> expr1 False -> case cond-expr2 of True -> expr2 False -> expr3 or may be folded into a definition: x | cond-expr1 = expr1 | cond-expr2 = expr2 | True = expr3 The following table shows some operators in Haskell and their counterparts in C, where the notations differ. Haskell not x x /= y x .&. y x .|. y complement x shiftL x n shiftR x n C ! x x != y x&y x|y ~x x << n x >> n Boolean negation Not-equals operator Bitwise AND operator Bitwise OR operator Bitwise complement Left shift Right shift (arith if x is signed, logical otherwise)
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.5 Linearized : No Page Count : 32 Page Mode : UseOutlines Author : Title : Subject : Creator : LaTeX with hyperref package Producer : pdfTeX-1.40.18 Create Date : 2019:01:23 21:41:40-05:00 Modify Date : 2019:01:23 21:41:40-05:00 Trapped : False PTEX Fullbanner : This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) kpathsea version 6.2.3EXIF Metadata provided by EXIF.tools