Skip to content

Commit

Permalink
Merge branch 'release-1.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
catharanthus committed Oct 21, 2021
2 parents f505bb2 + 94e6b63 commit 2929656
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 62 deletions.
15 changes: 14 additions & 1 deletion README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# lxp32-cpu

A lightweight, open source 32-bit CPU core optimized for FPGA implementation.
LXP32 is a small and FPGA friendly 32-bit CPU IP core based on a simple, original instruction set. Its key features include:

* portability (described in behavioral VHDL, not tied to any particular vendor);
* 3-stage hazard-free pipeline;
* 256 registers implemented as a RAM block;
* only 30 distinct opcodes;
* separate instruction and data buses, optional instruction cache;
* WISHBONE compatibility;
* 8 interrupts with hardwired priorities;
* optional divider.

The LXP32 processor was successfully used in commercial projects, is [well documented](https://github.com/lxp32/lxp32-cpu/raw/develop/doc/lxp32-trm.pdf) and comes with a verification environment.

LXP32 lacks some features of more advanced processors, such as nested interrupt handling, debugging support, floating-point and memory management units. LXP32 ISA (Instruction Set Architecture) does not currently have a C compiler, only assembly based workflow is supported.

Project website: [https://lxp32.github.io/](https://lxp32.github.io/)
Binary file modified doc/lxp32-trm.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion doc/src/trm/frontmatter.tex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
\Large a lightweight open source 32-bit CPU core\par
\LARGE \textbf{Technical Reference Manual}\par
\vspace{1.2\onelineskip}
\large Version 1.1\par
\large Version 1.2\par
\vspace*{4\onelineskip}
\end{center}
\vspace*{\fill}
Expand Down
59 changes: 51 additions & 8 deletions doc/src/trm/lxp32-trm.tex
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ \section{Addressing}

All addressing in \lxp{} is indirect. In order to access a memory location, its address must be stored in a register; any available register can be used for this purpose.

Some instructions, namely \instr{lsb} (\instrname{Load Signed Byte}), \instr{lub} (\instrname{Load Unsigned Byte}) and \instr{sb} (\instrname{Store Byte}) provide byte-granular access, in which case all 32 bits in the address are significant. Otherwise the least two address bits are ignored as \lxp{} doesn't support unaligned access to 32-bit data words (during simulation, a warning is emitted if such a transaction is attempted).
\lxp{} uses a 32-bit address space. Each address refers to an individual byte. Some instructions, namely \instr{lsb} (\instrname{Load Signed Byte}), \instr{lub} (\instrname{Load Unsigned Byte}) and \instr{sb} (\instrname{Store Byte}) provide byte-granular access, in which case all 32 bits in the address are significant. Otherwise the least two address bits are ignored as \lxp{} doesn't support unaligned access to 32-bit data words (during simulation, a warning is emitted if such a transaction is attempted).

A special rule applies to pointers that refer to instructions: since instructions are always word-aligned, the least significant bit is interpreted as the \code{IRF} (\emph{Interrupt Return Flag}). See Section \ref{sec:interrupthandling} for details.

Expand Down Expand Up @@ -441,10 +441,16 @@ \section{Low Latency Interface}

The simplest slaves such as on-chip RAM blocks which are never busy can be trivially connected to the LLI by connecting address, data and read enable ports and tying the \signal{lli\_busy\_i} signal to a logical \code{0} (you can even ignore \signal{lli\_re\_o} in this case, although doing so can theoretically increase power consumption).

Note that the \signal{lli\_adr\_o} signal has a width of 30 bits since it addresses words, not bytes (instructions are always word-aligned).

Since the \signal{lli\_re\_o} output signal is not registered, this interface is not suitable for interaction with off-chip peripherals. Also, care should be taken to avoid introducing too much additional combinatorial delay on its outputs.

The instruction bus, whether LLI or WISHBONE, doesn't support access to individual bytes and uses a 30-bit address port to address 32-bit words (instructions are always word-aligned). The lower two bits of the 32-bit address are ignored for the purpose of addressing. Consider the following example:

\begin{codeparbreakable}
\instr{lc} r0, 0x10000000
\instr{jmp} r0
\emph{// 0x04000000 will appear on lli_adr_o or ibus_adr_o}
\end{codeparbreakable}

\section{WISHBONE instruction bus}

The \lxp{}C CPU fetches instructions over the WISHBONE bus. Its parameters are defined in the WISHBONE datasheet (Appendix \ref{app:wishbonedatasheet}). For a detailed description of the bus protocol refer to the WISHBONE specification, revision B3.
Expand All @@ -464,7 +470,17 @@ \section{WISHBONE data bus}

\lxp{} uses the WISHBONE bus to interact with data memory and other peripherals. This bus is distinct from the instruction bus; its parameters are defined in the WISHBONE datasheet (Appendix \ref{app:wishbonedatasheet}).

This bus uses a 30-bit \signal{dbus\_adr\_o} port to address 32-bit words; the \signal{dbus\_sel\_o} port is used to select individual bytes to be written or read. Alternatively, with the \code{DBUS\_RMW} option (Section \ref{sec:generics}) the \signal{dbus\_sel\_o} port is not used; byte-granular write access is performed using the read-modify-write cycle instead.
The data bus uses a 30-bit \signal{dbus\_adr\_o} port to address 32-bit words; the \signal{dbus\_sel\_o} port is used to select individual bytes to be written or read. The upper 30 bits of the address appear on the \signal{dbus\_adr\_o} port, while the lower two bits are decoded to create a 4-bit \signal{dbus\_sel\_o} signal. Consider:

\begin{codeparbreakable}
\instr{lc} r0, 0x20000002
\instr{sb} r0, 0x55
\emph{// write 0x55 to the address in r0}
\emph{// 0x08000000 will appear on dbus_adr_o}
\emph{// 0x4 will appear on dbus_sel_o}
\end{codeparbreakable}

The byte-granular access feature is optional. If it is not needed, the \signal{dbus\_sel\_o} port can be left unconnected. It is also possible to set the \code{DBUS\_RMW} generic to \code{true} to enable byte-granular access emulation using the read-modify-write (RMW) cycle, which works even if the interconnect or slave doesn't provide the [SEL\_I()] port (Section \ref{sec:generics}).

For a detailed description of the bus protocol refer to the WISHBONE specification, revision B3.

Expand Down Expand Up @@ -634,7 +650,7 @@ \section{Running simulation using makefiles}

\begin{itemize}
\item \shellcmd{batch} -- simulate the design in batch mode. Results will be written to the standard output. This is the default target.
\item \shellcmd{gui} -- simulate the design in GUI mode. Note: since GHDL doesn't have a GUI, the simulation itself will be run in batch mode; upon a successful completion, GTKWave will be run automatically to display dumped waveforms.
\item \shellcmd{gui} -- simulate the design in GUI mode. Note: since GHDL doesn't have a GUI, the simulation itself will be run in batch mode; upon a completion, GTKWave will be run automatically to display the dumped waveforms.
\item \shellcmd{compile} -- compile only, don't run simulation.
\item \shellcmd{clean} -- delete all the produced artifacts.
\end{itemize}
Expand Down Expand Up @@ -693,7 +709,7 @@ \section{\shellcmd{lxp32asm} -- Assembler and linker}
Linkable objects are combined into a single executable module. References to symbols defined in external modules are resolved at this stage.
\end{enumerate}

In the simplest case there is only one input source file which doesn't contain external symbol references. If there are multiple input files, one of them must define the \code{entry} symbol at the beginning of the code.
In the simplest case there is only one input source file which doesn't contain external symbol references. If there are multiple input files, one of them must define the \code{entry} (or \code{Entry}) symbol at the beginning of the code.

\subsection{Command line syntax}
\label{subsec:assemblercmdline}
Expand Down Expand Up @@ -1646,17 +1662,33 @@ \subsection{Directives}
The first token of a directive statement always starts with the \code{\#} character.

\begin{codepar}
\instr{\#define} \emph{identifier} \emph{token} [ \emph{token} ... ]
\instr{\#define} \emph{identifier} [ \emph{token} ... ]
\end{codepar}

Defines a macro that will be substituted with zero or more tokens. The \code{\emph{identifier}} must satisfy the requirements listed in Section \ref{sec:symbols}. Tokens can be anything, including keywords, identifiers, literals and separators (i.e. comma and colon characters).

\begin{codepar}
\instr{\#error} [ \emph{msg} ]
\end{codepar}

Defines a macro that will be substituted with one or more tokens. The \code{\emph{identifier}} must satisfy the requirements listed in Section \ref{sec:symbols}. Tokens can be anything, including keywords, identifiers, literals and separators (i.e. comma and colon characters).
Raises a compiler error. If \emph{msg} is supplied, uses it as an error message.

\begin{codepar}
\instr{\#export} \emph{identifier}
\end{codepar}

Declares \code{\emph{identifier}} as an exported symbol. Exported symbols can be referenced by other modules.

\begin{codepar}
\instr{\#ifdef} | \instr{\#ifndef} \emph{identifier}
\code{...}
\instr{\#else}
\code{...}
\instr{\#endif}
\end{codepar}

Define C preprocessor-style conditional sections which are processed or not based on whether a certain macro has been defined. \instr{\#else} is optional. Can be nested.

\begin{codepar}
\instr{\#import} \emph{identifier}
\end{codepar}
Expand Down Expand Up @@ -1796,6 +1828,17 @@ \section{Data bus}

\chapter{List of changes}

\section*{Version 1.2 (2021-10-21)}

This release introduces a few non-breaking changes to the software and testbench. The CPU RTL description hasn't been changed from the previous release.

\begin{itemize}
\item \shellcmd{lxp32asm} now supports C-style conditional processing directives: \instr{\#ifdef}, \instr{\#ifndef}, \instr{\#else} and \instr{\#endif}.
\item \instr{\#define} directive can now declare a macro with zero subsitute tokens.
\item A new \instr{\#error} directive.
\item Minor changes to the testbench.
\end{itemize}

\section*{Version 1.1 (2019-01-11)}

This release introduces a minor but technically breaking hardware change: the START\_ADDR generic, which used to be 30-bit, has been for convenience extended to a full 32-bit word; the two least significant bits are ignored.
Expand Down
Binary file modified misc/highlight/akelpad/asm.coder
Binary file not shown.
2 changes: 1 addition & 1 deletion misc/highlight/notepad++/LXP32Assembly.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">add and call cjmpe cjmpne cjmpsg cjmpsge cjmpsl cjmpsle cjmpug cjmpuge cjmpul cjmpule divs divu hlt jmp iret lc lcs lsb lub lw mods modu mov mul neg nop not or ret sb sl srs sru sub sw xor</Keywords>
<Keywords name="Keywords2">cr irp iv0 iv1 iv2 iv3 iv4 iv5 iv6 iv7 r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 r32 r33 r34 r35 r36 r37 r38 r39 r40 r41 r42 r43 r44 r45 r46 r47 r48 r49 r50 r51 r52 r53 r54 r55 r56 r57 r58 r59 r60 r61 r62 r63 r64 r65 r66 r67 r68 r69 r70 r71 r72 r73 r74 r75 r76 r77 r78 r79 r80 r81 r82 r83 r84 r85 r86 r87 r88 r89 r90 r91 r92 r93 r94 r95 r96 r97 r98 r99 r100 r101 r102 r103 r104 r105 r106 r107 r108 r109 r110 r111 r112 r113 r114 r115 r116 r117 r118 r119 r120 r121 r122 r123 r124 r125 r126 r127 r128 r129 r130 r131 r132 r133 r134 r135 r136 r137 r138 r139 r140 r141 r142 r143 r144 r145 r146 r147 r148 r149 r150 r151 r152 r153 r154 r155 r156 r157 r158 r159 r160 r161 r162 r163 r164 r165 r166 r167 r168 r169 r170 r171 r172 r173 r174 r175 r176 r177 r178 r179 r180 r181 r182 r183 r184 r185 r186 r187 r188 r189 r190 r191 r192 r193 r194 r195 r196 r197 r198 r199 r200 r201 r202 r203 r204 r205 r206 r207 r208 r209 r210 r211 r212 r213 r214 r215 r216 r217 r218 r219 r220 r221 r222 r223 r224 r225 r226 r227 r228 r229 r230 r231 r232 r233 r234 r235 r236 r237 r238 r239 r240 r241 r242 r243 r244 r245 r246 r247 r248 r249 r250 r251 r252 r253 r254 r255 rp sp</Keywords>
<Keywords name="Keywords3">#define #export #import #include #message</Keywords>
<Keywords name="Keywords3">#define #else #endif #error #export #ifdef #ifndef #import #include #message</Keywords>
<Keywords name="Keywords4">.align .byte .reserve .word</Keywords>
<Keywords name="Keywords5"></Keywords>
<Keywords name="Keywords6"></Keywords>
Expand Down
84 changes: 68 additions & 16 deletions tools/src/lxp32asm/assembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ void Assembler::processFile(const std::string &filename) {
_state=Initial;
_currentFileName=filename;
processFileRecursive(filename);

if(!_currentLabels.empty())
throw std::runtime_error("Symbol definition must be followed by an instruction or data definition statement");

if(!_sectionEnabled.empty())
throw std::runtime_error("#endif expected");

// Examine symbol table
for(auto const &sym: _obj.symbols()) {
Expand Down Expand Up @@ -71,9 +77,6 @@ void Assembler::processFileRecursive(const std::string &filename) {
_line=savedLine;
_state=savedState;
_currentFileName=savedFileName;

if(!_currentLabels.empty())
throw std::runtime_error("Symbol definition must be followed by an instruction or data definition statement");
}

void Assembler::addIncludeSearchDir(const std::string &dir) {
Expand Down Expand Up @@ -207,13 +210,26 @@ void Assembler::expand(TokenList &list) {
// Perform macro substitution
for(auto &token: list) {
auto it=_macros.find(token);
// Note: we don't expand a macro identifier in the #define statement
// since that would lead to counter-intuitive results
if(it==_macros.end()||
(newlist.size()==1&&newlist[0]=="#define")||
(newlist.size()==3&&newlist[1]==":"&&newlist[2]=="#define"))
newlist.push_back(std::move(token));
else for(auto const &replace: it->second) newlist.push_back(replace);
bool substitute=false;
if(it!=_macros.end()) {
substitute=true;
// Don't substitute macros for a second token in certain directives
if(newlist.size()==1) {
if(newlist[0]=="#define") substitute=false;
else if(newlist[0]=="#ifdef") substitute=false;
else if(newlist[0]=="#ifndef") substitute=false;
}
else if(newlist.size()==3&&newlist[1]==":") {
if(newlist[2]=="#define") substitute=false;
else if(newlist[2]=="#ifdef") substitute=false;
else if(newlist[2]=="#ifndef") substitute=false;
}
}

if(substitute) {
for(auto const &replace: it->second) newlist.push_back(replace);
}
else newlist.push_back(std::move(token));
}
list=std::move(newlist);
}
Expand All @@ -225,12 +241,16 @@ void Assembler::elaborate(TokenList &list) {
if(list.size()>=2&&list[1]==":") {
if(!validateIdentifier(list[0]))
throw std::runtime_error("Ill-formed identifier: \""+list[0]+"\"");
_currentLabels.push_back(std::move(list[0]));
if(isSectionEnabled()) _currentLabels.push_back(std::move(list[0]));
list.erase(list.begin(),list.begin()+2);
}

if(list.empty()) return;

// If the section is disabled, we look only for #ifdef, #ifndef, #else or #endif
if(!isSectionEnabled()&&list[0]!="#ifdef"&&list[0]!="#ifndef"&&
list[0]!="#else"&&list[0]!="#endif") return;

// Process statement itself
if(list[0][0]=='#') elaborateDirective(list);
else {
Expand All @@ -249,7 +269,7 @@ void Assembler::elaborateDirective(TokenList &list) {
assert(!list.empty());

if(list[0]=="#define") {
if(list.size()<3)
if(list.size()<2)
throw std::runtime_error("Wrong number of tokens in the directive");
if(_macros.find(list[1])!=_macros.end())
throw std::runtime_error("Macro \""+list[1]+"\" has been already defined");
Expand All @@ -258,17 +278,17 @@ void Assembler::elaborateDirective(TokenList &list) {
_macros.emplace(list[1],TokenList(list.begin()+2,list.end()));
}
else if(list[0]=="#export") {
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
if(!validateIdentifier(list[1])) throw std::runtime_error("Ill-formed identifier: \""+list[1]+"\"");
_exportedSymbols.push_back(list[1]);
}
else if(list[0]=="#import") {
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
if(!validateIdentifier(list[1])) throw std::runtime_error("Ill-formed identifier: \""+list[1]+"\"");
_obj.addImportedSymbol(list[1]);
}
else if(list[0]=="#include") {
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
auto filename=Utils::dequoteString(list[1]);
if(Utils::isAbsolutePath(filename)) return processFileRecursive(filename);
else {
Expand All @@ -284,10 +304,35 @@ void Assembler::elaborateDirective(TokenList &list) {
throw std::runtime_error("Cannot locate include file \""+filename+"\"");
}
else if(list[0]=="#message") {
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
auto msg=Utils::dequoteString(list[1]);
std::cout<<currentFileName()<<":"<<line()<<": "<<msg<<std::endl;
}
else if(list[0]=="#error") {
if(list.size()<2) throw std::runtime_error("#error directive encountered");
auto msg=Utils::dequoteString(list[1]);
throw std::runtime_error(msg);
}
else if(list[0]=="#ifdef") {
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
if(_macros.find(list[1])!=_macros.end()) _sectionEnabled.push_back(true);
else _sectionEnabled.push_back(false);
}
else if(list[0]=="#ifndef") {
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
if(_macros.find(list[1])!=_macros.end()) _sectionEnabled.push_back(false);
else _sectionEnabled.push_back(true);
}
else if(list[0]=="#else") {
if(list.size()!=1) throw std::runtime_error("Wrong number of tokens in the directive");
if(_sectionEnabled.empty()) throw std::runtime_error("Unexpected #else");
_sectionEnabled.back()=!_sectionEnabled.back();
}
else if(list[0]=="#endif") {
if(list.size()!=1) throw std::runtime_error("Wrong number of tokens in the directive");
if(_sectionEnabled.empty()) throw std::runtime_error("Unexpected #endif");
_sectionEnabled.pop_back();
}
else throw std::runtime_error("Unrecognized directive: \""+list[0]+"\"");
}

Expand Down Expand Up @@ -392,6 +437,13 @@ LinkableObject::Word Assembler::elaborateInstruction(TokenList &list) {
return rva;
}

bool Assembler::isSectionEnabled() const {
if(_sectionEnabled.empty()) return true;
bool enabled=true;
for(auto b: _sectionEnabled) enabled=enabled&&b;
return enabled;
}

bool Assembler::validateIdentifier(const std::string &str) {
/*
* Valid identifier must satisfy the following requirements:
Expand Down
2 changes: 2 additions & 0 deletions tools/src/lxp32asm/assembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Assembler {
std::string _currentFileName;
std::vector<std::string> _includeSearchDirs;
std::vector<std::string> _exportedSymbols;
std::vector<bool> _sectionEnabled;
public:
void processFile(const std::string &filename);

Expand All @@ -62,6 +63,7 @@ class Assembler {
LinkableObject::Word elaborateDataDefinition(TokenList &list);
LinkableObject::Word elaborateInstruction(TokenList &list);

bool isSectionEnabled() const;
static bool validateIdentifier(const std::string &str);
static Integer numericLiteral(const std::string &str);
static std::vector<Operand> getOperands(const TokenList &list);
Expand Down
Loading

0 comments on commit 2929656

Please sign in to comment.