\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 with some basic type synonyms. We create a type \ll{xlen_t} for bitvectors of length 64, then we define a type \ll{regno}, which is a type synonym for the builtin type \ll{atom}. The type \ll{atom('n)} is a number which is exactly equal to the type variable \ll{atom('n)}. Type variables are syntactically marked with single quotes, as in ML. A \emph{constraint} can be attached to this type synonym---ensuring that it is only used where we can guarantee that its value will be between 0 and 31. Sail supports a rich variety of numeric types, including range types, which are statically checked. We then define a synonym \ll{regbits} for \ll{bits(5)}. We don't want to manually convert between \ll{regbits} and \ll{regno} all the time, so we define a function that maps between them and declare it as a \emph{cast}, which allows the type-checker to insert it where needed. By default, we do not do any automatic casting (except between basic numeric types when safe), but to allow idioms in ISA vendor description documents we support flexible user defined casts. To ensure that the constraint on the \ll{regno} type synonym is satisfied, we return an existentially quantified type \ll{\{'n, 0 <= 'n < 32. regno('n)\}}. \sailtype{xlen_t} \sailtype{regno} \sailtype{regbits} \sailval{regbits_to_regno} \sailfn{regbits_to_regno} We now set up some basic architectural state. First creating a register of type \ll{xlen_t} 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{xlen_t} 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 : xlen_t register nextPC : xlen_t register Xs : vector(32, dec, xlen_t) \end{lstlisting} \sailval{rX} % TODO: Fix funcl commands \sailfclrX \sailfclMMMrX \sailval{wX} \sailfn{wX} \sailoverloadHHX 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, and declare them as scattered functions, as well as the \ll{ast} union. \sailtype{iop} \begin{lstlisting} scattered union ast val decode : bits(32) -> option(ast) effect pure scattered function decode val execute : ast -> unit effect {rmem, rreg, wreg} scattered function execute \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} %\sailfndecodeSomeITYPE %\sailfnexecuteITYPE \noindent Now we do the same thing for the load-double instruction: \begin{lstlisting} union clause ast = LOAD : (bits(12), regbits, regbits) \end{lstlisting} %\sailfndecodeSomeLOAD %\sailfnexecuteLOAD Finally we define the fallthrough case for the decode function, and end all our scattered definitions. Note that the clauses in a scattered function will be matched in the order they appear in the file. %\sailfndecodeNone \begin{lstlisting} end ast end decode end execute \end{lstlisting} 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/blob/sail2/riscv/riscv_duopod.sail}}.