mirror of
https://github.com/johnwinans/rvalp.git
synced 2025-10-04 00:22:49 -04:00
627 lines
20 KiB
TeX
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}
|