rvalp/book/programs/chapter.tex
2021-02-11 15:11:44 -06:00

627 lines
20 KiB
TeX

\chapter{Writing RISC-V Programs}
\enote{Introduce the ISA register names and aliases in here?}%
This chapter introduces each of the RV32I instructions by developing programs
that demonstrate their usefulness.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Use {\tt ebreak} to Stop \rvddt{} Execution}
\index{Instruction!ebreak}
\label{uguide:ebreak}
It is a good idea to learn how to stop before learning how to go!
The \insn{ebreak} instruction exists for the sole purpose of transferring control back
to a debugging environment.\cite[p.~24]{rvismv1v22:2017}
When \rvddt{} executes an \insn{ebreak} instruction, it will immediately terminate any
executing {\em trace} or {\em go} command currently executing and return to the
command prompt without advancing the \reg{pc} register.
The machine language encoding shows that \insn{ebreak} has no operands.
\DrawInsnTypeEPicture{ebreak}{00000000000100000000000001110011}
\listingRef{ebreak/ebreak.out} demonstrates that since \rvddt{} does
not advance the \reg{pc} when it encounters an \insn{ebreak} instruction,
subsequent {\em trace} and/or {\em go} commands will re-execute the same \insn{ebreak}
and halt the simulation again (and again).
This feature is intended to help prevent overzealous users from accidently
running past the end of a code fragment.\footnote{This was one of the first {\em enhancements}
I needed for myself \tt:-)}
\listing{ebreak/ebreak.S}{A one-line \insn{ebreak} program.}
\listing{ebreak/ebreak.out}{\insn{ebreak} stopps \rvddt{} without advancing \reg{pc}.}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Using the \insn{addi} Instruction}
\index{Instruction!addi}
\label{uguide:addi}
\enote{Define what constant and immediate values are somewhere.}%
The detailed description of how the \insn{addi} instruction is executed
is that it:
\begin{enumerate}
\item Sign-extends the immediate operand.
\item Add the sign-extended immediate operand to the contents of the \reg{rs1} register.
\item Store the sum in the \reg{rd} register.
\item Add four to the \reg{pc} register (point to the next instruction.)
\end{enumerate}
In the following example \reg{rs1} = \reg{x28}, \reg{rd} = \reg{x29} and
the immediate operand is -1.
\DrawInsnTypeIPicture{addi x29, x28, -1}{11111111111111100000111010010011}
Depending on the values of the fields in this instruction a number of
different operations can be performed. The most obvious is that it
can add things. But it can also be used to copy registers, set a
register to zero and even, when you need to, accomplish nothing.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{No Operation}
\index{Instruction!nop}
It might seem odd but it is sometimes important to be able to execute
an instruction that accomplishes nothing while simply advancing the
\reg{pc} to the next instruction. One reason for this is to fill
unused memory between two instructions in a program.%
\footnote{This can happen during the evolution of one portion of code
that reduces in size but has to continue to fit into a system without
altering any other code\ldots\ or sometimes you just need to waste
a small amount of time in a device driver.}
An instruction that accomplishes nothing is called a \insn{nop}
(sometimes systems call these \insn{noop}). The name means
{\em no operation}.
The intent of a \insn{nop} is to execute without having any side effects
other than to advance the \reg{pc} register.
The \insn{addi} instruction can serve as a \insn{nop} by coding it like this:
\DrawInsnTypeIPicture{addi x0, x0, 0}{00000000000000000000000000010011}
The result will be to add zero to zero and discard the result (because you
can never store a value into the x0 register.)
The RISC-V assembler provides a pseudoinstruction specifically for this
purpose that you can use to improve the readability of your code. Note
that the \insn{addi} and \insn{nop} instructions in \listingRef{nop/nop.S}
are assembled into the exact same binary machine instructions
as can be seen by comparing it to
\verb@objdump@ \listingRef{nop/nop.lst},
and \verb@rvddt@ \listingRef{nop/nop.out} output.
%(The \hex{00000013} you can see are stored at addresses \hex{0} and \hex{4})
%as seen by looking at the \verb@objdump@ listing in \listingRef{nop/nop.lst}.
%In fact, you can see that objdump shows both instructions as a \insn{nop}
%while \listingRef{nop/nop.out} shows that \rvddt{} displays both as
%\verb@addi x0, x0, 0@.
\listing{nop/nop.S}{Demonstrate that \insn{addi} can be used as a \insn{nop}.}
\index{objdump}
\listing{nop/nop.lst}{Using \insn{addi} to perform a \insn{nop}}
\listing{nop/nop.out}{Using \insn{addi} to perform a \insn{nop}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Copying the Contents of One Register to Another}
By adding zero to one register and storing the sum in another register
the \insn{addi} instruction can be used to copy the value stored in one
register to another register. The following instruction will copy
the contents of \reg{t4} into \reg{t3}.
\DrawInsnTypeIPicture{addi t3, t4, 0}{00000000000011101000111000010011}
\index{Instruction!mv}
This is a commonly required operation. To make your intent clear
you may use the \insn{mv} pseudoinstruction for this purpose.
\listingRef{mv/mv.S} shows the source of a program that is dumped in
\listingRef{mv/mv.lst} illustrating that the assembler has generated the
same machine instruction (\hex{000e8e13} at addresses \hex{0} and \hex{4})
for both of the instructions.
\listing{mv/mv.S}{Comparing \insn{addi} to \insn{mv}}
\listing{mv/mv.lst}{An objdump of an \insn{addi} and \insn{mv} Instruction.}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Setting a Register to Zero}
Recall that \reg{x0} always contains the value zero. Any register
can be set to zero by copying the contents of \reg{x0} using \insn{mv}
(aka \insn{addi}).%
\footnote{There are other pseudoinstructions (such as \insn{li}) that can also
turn into an \insn{addi} instruction. Objdump might display `{\tt addi t3,x0,0}'
as `{\tt mv t3,x0}' or `{\tt li t3,0}'.}
For example, to set \reg{t3} to zero:
\DrawInsnTypeIPicture{addi t3, x0, 0}{00000000000000000000111000010011}
\listing{mvzero/mv.S}{Using \insn{mv} (aka \insn{addi}) to zero-out a register.}
\listingRef{mvzero/mv.out} traces the execution of the program in
\listingRef{mvzero/mv.S} showing how \reg{t3} is changed from \hex{f0f0f0f0}
(seen on $\ell 16$) to \hex{00000000} (seen on $\ell 26$.)
\listing{mvzero/mv.out}{Setting \reg{t3} to zero.}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Adding a 12-bit Signed Value}
\DrawInsnTypeIPicture{addi x1, x7, 4}{00000000010000111000000010010011}
{\small
\begin{verbatim}
addi t0, zero, 4 # t0 = 4
addi t1, t1, 100 # t1 = 104
addi t0, zero, 0x123 # t0 = 0x123
addi t0, t0, 0xfff # t0 = 0x122 (subtract 1)
addi t0, zero, 0xfff # t0 = 0xffffffff (-1) (diagram out the chaining carry)
# refer back to the overflow/truncation discussion in binary chapter
addi x0, x0, 0 # no operation (pseudo: nop)
addi rd, rs, 0 # copy reg rs to rd (pseudo: mv rd, rs)
\end{verbatim}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{todo}
Ideas for the order of introducing instructions.
\section{Other Instructions With Immediate Operands}
\label{uguide:andi}
\label{uguide:ori}
\label{uguide:xori}
\label{uguide:slti}
\label{uguide:sltiu}
\label{uguide:srai}
\label{uguide:slli}
\label{uguide:srli}
{\small
\begin{verbatim}
andi
ori
xori
slti
sltiu
srai
slli
srli
\end{verbatim}
}
\section{Transferring Data Between Registers and Memory}
RV is a load-store architecture. This means that the only way that the
CPU can interact with the memory is via the {\em load} and {\em store}
instructions. All other data manipulation must be performed on register
values.
Copying values from memory to a register (first examples using regs set with addi):
\label{uguide:lb}
\label{uguide:lh}
\label{uguide:lw}
\label{uguide:lbu}
\label{uguide:lhu}
{\small
\begin{verbatim}
lb
lh
lw
lbu
lhu
\end{verbatim}
}
Copying values from a register to memory:
\label{uguide:sb}
\label{uguide:sh}
\label{uguide:sw}
{\small
\begin{verbatim}
sb
sh
sw
\end{verbatim}
}
\section{RR operations}
\label{uguide:add}
\label{uguide:sub}
\label{uguide:and}
\label{uguide:or}
\label{uguide:sra}
\label{uguide:srl}
\label{uguide:sll}
\label{uguide:xor}
\label{uguide:sltu}
\label{uguide:slt}
{\small
\begin{verbatim}
add
sub
and
or
sra
srl
sll
xor
sltu
slt
\end{verbatim}
}
\section{Setting registers to large values using lui with addi}
\label{uguide:lui}
\label{uguide:auipc}
{\small
\begin{verbatim}
addi // useful for values from -2048 to 2047
lui // useful for loading any multiple of 0x1000
Setting a register to any other value must be done using a combo of insns:
auipc // Load an address relative the the current PC (see la pseudo)
addi
lui // Load constant into into bits 31:12 (see li pseudo)
addi // add a constant to fill in bits 11:0
if bit 11 is set then need to +1 the lui value to compensate
\end{verbatim}
}
\section{Labels and Branching}
Start to introduce addressing here?
\label{uguide:beq}
\label{uguide:bne}
\label{uguide:blt}
\label{uguide:bge}
\label{uguide:bltu}
\label{uguide:bgeu}
\label{uguide:bgt}
\label{uguide:ble}
\label{uguide:bgtu}
\label{uguide:beqz}
\label{uguide:bnez}
\label{uguide:blez}
\label{uguide:bgez}
\label{uguide:bltz}
\label{uguide:bgtz}
{\small
\begin{verbatim}
beq
bne
blt
bge
bltu
bgeu
bgt rs, rt, offset # pseudo for: blt rt, rs, offset (reverse the operands)
ble rs, rt, offset # pseudo for: bge rt, rs, offset (reverse the operands)
bgtu rs, rt, offset # pseudo for: bltu rt, rs, offset (reverse the operands)
bleu rs, rt, offset # pseudo for: bgeu rt, rs, offset (reverse the operands)
beqz rs, offset # pseudo for: beq rs, x0, offset
bnez rs, offset # pseudo for: bne rs, x0, offset
blez rs, offset # pseudo for: bge x0, rs, offset
bgez rs, offset # pseudo for: bge rs, x0, offset
bltz rs, offset # pseudo for: blt rs, x0, offset
bgtz rs, offset # pseudo for: blt x0, rs, offset
\end{verbatim}
}
\section{Jumps}
Introduce and present subroutines but not nesting until introduce stack operations.
\label{uguide:jal}
\label{uguide:jalr}
{\small
\begin{verbatim}
jal
jalr
\end{verbatim}
}
\section{Pseudoinstructions}
{\small
\begin{verbatim}
li rd,constant lui rd,(constant >>U 12)+(constant & 0x00000800 ? 1 : 0)
addi rd,rd,(constant & 0xfff)
la rd,label
auipc rd,((label-.) >>U 12) + ((label-.) & 0x00000800 ? 1 : 0)
addi rd,rd,((label-(.-4)) & 0xfff)
l{b|h|w} rd,label
auipc rd,((label-.) >>U 12) + ((label-.) & 0x00000800 ? 1 : 0)
l{b|h|w} rd,((label-(.-4)) & 0xfff)(rd)
s{b|h|w} rd,label,rt # rt used as a temp reg for the operation (default=x6)
auipc rt,((label-.) >>U 12) + ((label-.) & 0x00000800 ? 1 : 0)
s{b|h|w} rd,((label-(.-4)) & 0xfff)(rt)
call label auipc x1,((label-.) >>U 12) + ((label-.) & 0x00000800 ? 1 : 0)
jalr x1,((label-(.-4)) & 0xfff)(x1)
tail label,rt # rt used as a temp reg for the operation (default=x6)
auipc rt,((label-.) >>U 12) + ((label-.) & 0x00000800 ? 1 : 0)
jalr x0,((label-(.-4)) & 0xfff)(rt)
mv rd,rs addi rd,rs,0
j label jal x0,label
jal label jal x1,label
jr rs jalr x0,0(rs)
jalr rs jalr x1,0(rs)
ret jalr x0,0(x1)
\end{verbatim}
}
\subsection{The {\tt li} Pseudoinstruction}
Note that the {\tt li} pseudoinstruction includes a conditional addition of 1 to the operand
in the {\tt lui} instruction. This is because the immediate operand in the
{\tt addi} instruction is sign-extended before it is added to \verb@rd@.
If the immediate operand to the {\tt addi} has its most-significant-bit set to 1 then
it will have the effect of subtracting 1 from the operand in the \verb@lui@ instruction.
Consider the case of putting the value {\tt 0x12345800} into register {\tt x5}:
{\small
\begin{verbatim}
li x5,0x12345800
\end{verbatim}
}
{\color{red}
A naive (incorrect) solution might be:
{\small
\begin{verbatim}
lui x5,0x12345 // x5 = 0x12345000
addi x5,x5,0x800 // x5 = 0x12345000 + sx(0x800) = 0x12345000 + 0xfffff800 = 0x12344800
\end{verbatim}
}
The result of the above code is that an incorrect value has been placed into x5.
}
To remedy this problem, the value used in the {\tt lui} instruction can altered
(by adding 1 to its operand) to compensate for the sign-extention in the {\tt addi}
instruction:
{\small
\begin{verbatim}
lui x5,0x12346 // x5 = 0x12346000 (note this is 0x12345 + 1)
addi x5,x5,0x800 // x5 = 0x12346000 + sx(0x800) = 0x12346000 + 0xfffff800 = 0x12345800
\end{verbatim}
}
Keep in mind that the {\em only} time that this altering of the operand in the {\tt lui}
instruction should take place is when the most-significant-bit of the operand in the
{\tt addi} is set to one.
Consider the case where we wish to put the value {\tt 0x12345700} into register {\tt x5}:
{\small
\begin{verbatim}
lui x5,0x12345 // x5 = 0x12345000 (note this is 0x12345 + 0)
addi x5,x5,0x700 // x5 = 0x12345000 + sx(0x700) = 0x12345000 + 0x00000700 = 0x12345700
\end{verbatim}
}
The sign-extension in this example performed by the {\tt addi} instruction will convert the
{\tt 0x700} to {\tt 0x00000700} before the addition.
Therefore, the {\tt li} pseudoinstruction must {\em only} increment the operand of the
{\tt lui} instruction when it is known that the operand of the subsequent {\tt addi}
instruction will be a negative number.
\subsection{The {\tt la} Pseudoinstruction}
The \verb@la@ (and others that use \verb@auipc@ such as
the \verb@l{b|h|w}@, \verb@s{b|h|w}@, \verb@call@, and \verb@tail@) pseudoinstructions
also compensate for a sign-ended negative number when adding a 12-bit immediate
operand. The only difference is that these use a \verb@pc@-relative addressing mode.
For example, consider the task of putting an address represented by the label \verb@var1@
into register x10:
{\small
\begin{verbatim}
00010040 la x10,var1
00010048 ... # note that the la pseudoinstruction expands into 8 bytes
...
var1:
00010900 .word 999 # a 32-bit integer constant stored in memory at address var1
\end{verbatim}
}
The \verb@la@ instruction here will expand into:
{\small
\begin{verbatim}
00010040 auipc x10,((var1-.) >>U 12) + ((var1-.) & 0x00000800 ? 1 : 0)
00010044 addi x10,x10,((var1-(.-4)) & 0xfff)
\end{verbatim}
}
Note that \verb@auipc@ will shift the immediate operand to the left 12 bits and then
add that to the \verb@pc@ register (see \autoref{insn:auipc}.)
The assembler will calculate the value of \verb@(var1-.)@ by subtracting the address
represented by the label \verb@var1@ from the address of the current instruction
(which is expressed as '.') resulting in the number of bytes from the current instruction
to the target label\ldots{} which is \verb@0x000008c0@.
Therefore the expanded pseudoinstruction example will become:
{\small
\begin{verbatim}
00010040 auipc x10,((0x00010900-0x00010040) >>U 12) + ((0x00010900-0x00010040) & 0x00000800 ? 1 : 0)
00010044 addi x10,x10,((0x00010900-(0x00010044-4)) & 0xfff) # note the extra -4 here!
\end{verbatim}
}
After performing the subtractions, it will reduce to this:
{\small
\begin{verbatim}
00010040 auipc x10,(0x000008c0 >>U 12) + ((0x000008c0) & 0x00000800 ? 1 : 0)
00010044 addi x10,x10,(0x000008c0 & 0xfff)
\end{verbatim}
}
Continuing to reduce the math operations we get:
{\small
\begin{verbatim}
00010040 auipc x10,0x00000 + 1 # add 1 here because 0x8c0 below has MSB = 1
00010044 addi x10,x10,0x8c0
\end{verbatim}
}
\ldots and\ldots
{\small
\begin{verbatim}
00010040 auipc x10,0x00001
00010044 addi x10,x10,0x8c0
\end{verbatim}
}
Note that the the \verb@la@ exhibits the same sort of technique as the \verb@li@ in that
if/when the immediate operand of the \verb@addi@ instruction has its most significant
bit set then the operand in the \verb@auipc@ has to be incremented by 1 to compensate.
\section{Relocation}
Because expressions that refer to constants and address labels are common in
assembly language programs, a shorthand notation is available for calculating
the pairs of values that are used in the implementation of things like the
\verb@li@ and \verb@la@ pseudoinstructions (that have to be written to
compensate for the sign-extension that will take place in the immediate operand
that appears in instructions like \verb@addi@ and \verb@jalr@.)
\subsection{Absolute Addresses}
To refer to an absolute value, the following operators can be used:
{\small
\begin{verbatim}
%hi(constant) // becomes: (constant >>U 12)+(constant & 0x00000800 ? 1 : 0)
%lo(constant) // becomes: (constant & 0xfff)
\end{verbatim}
}
Thus, the \verb@li@ pseudoinstruction can be expressed like this:
{\small
\begin{verbatim}
li rd,constant lui rd,%hi(constant)
addi rd,rd,%lo(constant)
\end{verbatim}
}
\subsection{PC-Relative Addresses}
The following can be used for PC-relative addresses:
{\small
\begin{verbatim}
%pcrel_hi(symbol) // becomes: ((symbol-.) >>U 12) + ((symbol-.) & 0x00000800 ? 1 : 0)
%pcrel_lo(lab) // becomes: ((symbol-lab) & 0xfff)
\end{verbatim}
}
Note the subtlety involved with the \verb@lab@ on \verb@%pcrel_lo@. It is needed to
determine the address of the instruction that contains the corresponding \verb@%pcrel_hi@.
(The label \verb@lab@ MUST be on a line that used a \verb@%pcrel_hi()@ or get an
error from the assembler.)
Thus, the \verb@la rd,label@ pseudoinstruction can be expressed like this:
{\small
\begin{verbatim}
xxx: auipc rd,%pcrel_hi(label)
addi rd,rd,%pcrel_lo(xxx) // the xxx tells pcrel_lo where to find the matching pcrel_hi
\end{verbatim}
}
Examples of using the \verb@auipc@ \& \verb@addi@ together with \verb@%pcrel_hi()@ and
\verb@%pcrel_lo()@:
{\small
\begin{verbatim}
xxx: auipc t1,%pcrel_hi(yyy) // (yyy-xxx) >>U 12) + ((yyy-xxx) & 0x00000800 ? 1 : 0)
addi t1,t1,%pcrel_lo(xxx) // ((yyy-xxx) & 0xfff)
...
yyy: // the address: yyy is saved into t1 above
...
\end{verbatim}
}
Things like this are legal:
{\small
\begin{verbatim}
label: auipc t1,%pcrel_hi(symbol)
addi t2,t1,%pcrel_lo(label)
addi t3,t1,%pcrel_lo(label)
lw t4,%pcrel_lo(label)(t1)
sw t5,%pcrel_lo(label)(t1)
\end{verbatim}
}
\section{Relaxation}
\enote{I'm not sure I want to get into the details of how this is done. Just assume it works.}%
In the simplest of terms, {\em Relaxation} refers to the ability of the
linker (not the compiler!) to determine if/when the instructions that
were generated with the \verb@xxx_hi@ and \verb@xxx_lo@ operators are
unneeded (and thus waste execution time and memory) and can therefore
be removed.
However, doing so is not trivial as it will result in moving things around
in memory, possibly changing the values of address labels in the
already-assembled program! Therefore, while the motivation for
rexation is obvious, the process of implementing it is non-trivial.
See: \url{https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md}