\section{A tutorial \riscv\ example} \label{sec:riscv} We introduce the basic features of Sail via a small example from our \riscv\ model that includes just two instructions: add immediate and load double. We start by including two files from the main sail-riscv development: \begin{lstlisting} $include "prelude.sail" $include "riscv_xlen64.sail" \end{lstlisting} The prelude sets up basic definitions we will use, it can vary on a per-architecture basis to account for stylistic differences in ISA specifications. \texttt{riscv\_xlen64.sail} introduces some type synonyms. It creates a integer type xlen, which is 64. Sail supports definitions which are generic over both regular types, and integers (think const generics in C++, but more expressive). We also create a type \ll{xlenbits} for bitvectors of length \ll{xlen}. \sailtype{xlen} \sailtype{xlen_bytes} \sailtype{xlenbits} For the purpose of this example, we also introduce a type synonym for bitvectors of length 5, which represent registers. \sailtype{regbits} We now set up some basic architectural state. First creating a register of type \ll{xlenbits} for both the program counter \ll{PC}, and the next program counter, \ll{nextPC}. We define the general purpose registers as a vector of 32 \ll{xlenbits} bitvectors. The \ll{dec} keyword isn't important in this example, but Sail supports two different numbering schemes for (bit)vectors \ll{inc}, for most significant bit is zero, and \ll{dec} for least significant bit is zero. We then define a getter and setter for the registers, which ensure that the zero register is treated specially (in \riscv\ register 0 is always hardcoded to be 0). Finally we overload both the read (\ll{rX}) and write (\ll{wX}) functions as simply \ll{X}. This allows us to write registers as \ll{X(r) = value} and read registers as \ll{value = X(r)}. Sail supports flexible ad-hoc overloading, and has an expressive l-value language in assignments, with the aim of allowing pseudo-code like definitions. \begin{lstlisting} register PC : xlenbits register nextPC : xlenbits register Xs : vector(32, dec, xlenbits) \end{lstlisting} \sailval{rX} \sailfn{rX} \sailval{wX} \sailfn{wX} \sailoverloadXXX We also give a function \ll{MEMr} for reading memory, this function just points at a builtin we have defined elsewhere. Note that functions in Sail are annotated with effects. This effect system is quite basic, but indicates whether or not functions read or write registers (rreg and wreg), read and write memory (rmem and wmem), as well as a host of other concurrency model related effects. They also indicate whether a function throws exceptions or has other non-local control flow (the escape effect). \sailval{MEMr} \sailfn{MEMr} It's common when defining architecture specifications to break instruction semantics down into separate functions that handle decoding (possibly even in several stages) into custom intermediate datatypes and executing the decoded instructions. However it's often desirable to group the relevant parts of these functions and datatypes together in one place, as they would usually be found in an architecture reference manual. To support this Sail supports \emph{scattered} definitions. First we give types for the execute and decode functions, as well as the \ll{ast} union. \sailtype{iop} \begin{lstlisting} scattered union ast val decode : bits(32) -> option(ast) effect pure val execute : ast -> unit effect {rmem, rreg, wreg} \end{lstlisting} Now we provide the clauses for the add-immediate \ll{ast} type, as well as its execute and decode clauses. We can define the decode function by directly pattern matching on the bitvector representing the instruction. Sail supports vector concatenation patterns (\ll{@} is the vector concatenation operator), and uses the types provided (e.g. \ll{bits(12)} and \ll{regbits}) to destructure the vector in the correct way. We use the \ll{EXTS} library function that sign-extends its argument. \begin{lstlisting} union clause ast = ITYPE : (bits(12), regbits, regbits, iop) \end{lstlisting} \sailfclITYPEdecode \sailfclITYPEexecute \noindent Now we do the same thing for the load-double instruction: \begin{lstlisting} union clause ast = LOAD : (bits(12), regbits, regbits) \end{lstlisting} \sailfclLOADdecode \sailfclLOADexecute Finally we define the fallthrough case for the decode function. Note that the clauses in a scattered function will be matched in the order they appear in the file. The actual code for this example, as well as our more complete \riscv\ specification can be found on our github at \anonymise{\url{https://github.com/rems-project/sail-riscv/blob/master/model/riscv_duopod.sail}}.