Skip to content

Commit 2929656

Browse files
committed
Merge branch 'release-1.2'
2 parents f505bb2 + 94e6b63 commit 2929656

File tree

11 files changed

+154
-62
lines changed

11 files changed

+154
-62
lines changed

README.md

100644100755
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# lxp32-cpu
22

3-
A lightweight, open source 32-bit CPU core optimized for FPGA implementation.
3+
LXP32 is a small and FPGA friendly 32-bit CPU IP core based on a simple, original instruction set. Its key features include:
4+
5+
* portability (described in behavioral VHDL, not tied to any particular vendor);
6+
* 3-stage hazard-free pipeline;
7+
* 256 registers implemented as a RAM block;
8+
* only 30 distinct opcodes;
9+
* separate instruction and data buses, optional instruction cache;
10+
* WISHBONE compatibility;
11+
* 8 interrupts with hardwired priorities;
12+
* optional divider.
13+
14+
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.
15+
16+
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.
417

518
Project website: [https://lxp32.github.io/](https://lxp32.github.io/)

doc/lxp32-trm.pdf

-366 Bytes
Binary file not shown.

doc/src/trm/frontmatter.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
\Large a lightweight open source 32-bit CPU core\par
1616
\LARGE \textbf{Technical Reference Manual}\par
1717
\vspace{1.2\onelineskip}
18-
\large Version 1.1\par
18+
\large Version 1.2\par
1919
\vspace*{4\onelineskip}
2020
\end{center}
2121
\vspace*{\fill}

doc/src/trm/lxp32-trm.tex

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ \section{Addressing}
164164

165165
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.
166166

167-
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).
167+
\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).
168168

169169
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.
170170

@@ -441,10 +441,16 @@ \section{Low Latency Interface}
441441

442442
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).
443443

444-
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).
445-
446444
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.
447445

446+
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:
447+
448+
\begin{codeparbreakable}
449+
\instr{lc} r0, 0x10000000
450+
\instr{jmp} r0
451+
\emph{// 0x04000000 will appear on lli_adr_o or ibus_adr_o}
452+
\end{codeparbreakable}
453+
448454
\section{WISHBONE instruction bus}
449455

450456
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.
@@ -464,7 +470,17 @@ \section{WISHBONE data bus}
464470

465471
\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}).
466472

467-
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.
473+
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:
474+
475+
\begin{codeparbreakable}
476+
\instr{lc} r0, 0x20000002
477+
\instr{sb} r0, 0x55
478+
\emph{// write 0x55 to the address in r0}
479+
\emph{// 0x08000000 will appear on dbus_adr_o}
480+
\emph{// 0x4 will appear on dbus_sel_o}
481+
\end{codeparbreakable}
482+
483+
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}).
468484

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

@@ -634,7 +650,7 @@ \section{Running simulation using makefiles}
634650

635651
\begin{itemize}
636652
\item \shellcmd{batch} -- simulate the design in batch mode. Results will be written to the standard output. This is the default target.
637-
\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.
653+
\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.
638654
\item \shellcmd{compile} -- compile only, don't run simulation.
639655
\item \shellcmd{clean} -- delete all the produced artifacts.
640656
\end{itemize}
@@ -693,7 +709,7 @@ \section{\shellcmd{lxp32asm} -- Assembler and linker}
693709
Linkable objects are combined into a single executable module. References to symbols defined in external modules are resolved at this stage.
694710
\end{enumerate}
695711

696-
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.
712+
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.
697713

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

16481664
\begin{codepar}
1649-
\instr{\#define} \emph{identifier} \emph{token} [ \emph{token} ... ]
1665+
\instr{\#define} \emph{identifier} [ \emph{token} ... ]
1666+
\end{codepar}
1667+
1668+
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).
1669+
1670+
\begin{codepar}
1671+
\instr{\#error} [ \emph{msg} ]
16501672
\end{codepar}
16511673

1652-
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).
1674+
Raises a compiler error. If \emph{msg} is supplied, uses it as an error message.
16531675

16541676
\begin{codepar}
16551677
\instr{\#export} \emph{identifier}
16561678
\end{codepar}
16571679

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

1682+
\begin{codepar}
1683+
\instr{\#ifdef} | \instr{\#ifndef} \emph{identifier}
1684+
\code{...}
1685+
\instr{\#else}
1686+
\code{...}
1687+
\instr{\#endif}
1688+
\end{codepar}
1689+
1690+
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.
1691+
16601692
\begin{codepar}
16611693
\instr{\#import} \emph{identifier}
16621694
\end{codepar}
@@ -1796,6 +1828,17 @@ \section{Data bus}
17961828

17971829
\chapter{List of changes}
17981830

1831+
\section*{Version 1.2 (2021-10-21)}
1832+
1833+
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.
1834+
1835+
\begin{itemize}
1836+
\item \shellcmd{lxp32asm} now supports C-style conditional processing directives: \instr{\#ifdef}, \instr{\#ifndef}, \instr{\#else} and \instr{\#endif}.
1837+
\item \instr{\#define} directive can now declare a macro with zero subsitute tokens.
1838+
\item A new \instr{\#error} directive.
1839+
\item Minor changes to the testbench.
1840+
\end{itemize}
1841+
17991842
\section*{Version 1.1 (2019-01-11)}
18001843

18011844
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.

misc/highlight/akelpad/asm.coder

200 Bytes
Binary file not shown.

misc/highlight/notepad++/LXP32Assembly.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<Keywords name="Folders in comment, close"></Keywords>
2727
<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>
2828
<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>
29-
<Keywords name="Keywords3">#define #export #import #include #message</Keywords>
29+
<Keywords name="Keywords3">#define #else #endif #error #export #ifdef #ifndef #import #include #message</Keywords>
3030
<Keywords name="Keywords4">.align .byte .reserve .word</Keywords>
3131
<Keywords name="Keywords5"></Keywords>
3232
<Keywords name="Keywords6"></Keywords>

tools/src/lxp32asm/assembler.cpp

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ void Assembler::processFile(const std::string &filename) {
3030
_state=Initial;
3131
_currentFileName=filename;
3232
processFileRecursive(filename);
33+
34+
if(!_currentLabels.empty())
35+
throw std::runtime_error("Symbol definition must be followed by an instruction or data definition statement");
36+
37+
if(!_sectionEnabled.empty())
38+
throw std::runtime_error("#endif expected");
3339

3440
// Examine symbol table
3541
for(auto const &sym: _obj.symbols()) {
@@ -71,9 +77,6 @@ void Assembler::processFileRecursive(const std::string &filename) {
7177
_line=savedLine;
7278
_state=savedState;
7379
_currentFileName=savedFileName;
74-
75-
if(!_currentLabels.empty())
76-
throw std::runtime_error("Symbol definition must be followed by an instruction or data definition statement");
7780
}
7881

7982
void Assembler::addIncludeSearchDir(const std::string &dir) {
@@ -207,13 +210,26 @@ void Assembler::expand(TokenList &list) {
207210
// Perform macro substitution
208211
for(auto &token: list) {
209212
auto it=_macros.find(token);
210-
// Note: we don't expand a macro identifier in the #define statement
211-
// since that would lead to counter-intuitive results
212-
if(it==_macros.end()||
213-
(newlist.size()==1&&newlist[0]=="#define")||
214-
(newlist.size()==3&&newlist[1]==":"&&newlist[2]=="#define"))
215-
newlist.push_back(std::move(token));
216-
else for(auto const &replace: it->second) newlist.push_back(replace);
213+
bool substitute=false;
214+
if(it!=_macros.end()) {
215+
substitute=true;
216+
// Don't substitute macros for a second token in certain directives
217+
if(newlist.size()==1) {
218+
if(newlist[0]=="#define") substitute=false;
219+
else if(newlist[0]=="#ifdef") substitute=false;
220+
else if(newlist[0]=="#ifndef") substitute=false;
221+
}
222+
else if(newlist.size()==3&&newlist[1]==":") {
223+
if(newlist[2]=="#define") substitute=false;
224+
else if(newlist[2]=="#ifdef") substitute=false;
225+
else if(newlist[2]=="#ifndef") substitute=false;
226+
}
227+
}
228+
229+
if(substitute) {
230+
for(auto const &replace: it->second) newlist.push_back(replace);
231+
}
232+
else newlist.push_back(std::move(token));
217233
}
218234
list=std::move(newlist);
219235
}
@@ -225,12 +241,16 @@ void Assembler::elaborate(TokenList &list) {
225241
if(list.size()>=2&&list[1]==":") {
226242
if(!validateIdentifier(list[0]))
227243
throw std::runtime_error("Ill-formed identifier: \""+list[0]+"\"");
228-
_currentLabels.push_back(std::move(list[0]));
244+
if(isSectionEnabled()) _currentLabels.push_back(std::move(list[0]));
229245
list.erase(list.begin(),list.begin()+2);
230246
}
231247

232248
if(list.empty()) return;
233249

250+
// If the section is disabled, we look only for #ifdef, #ifndef, #else or #endif
251+
if(!isSectionEnabled()&&list[0]!="#ifdef"&&list[0]!="#ifndef"&&
252+
list[0]!="#else"&&list[0]!="#endif") return;
253+
234254
// Process statement itself
235255
if(list[0][0]=='#') elaborateDirective(list);
236256
else {
@@ -249,7 +269,7 @@ void Assembler::elaborateDirective(TokenList &list) {
249269
assert(!list.empty());
250270

251271
if(list[0]=="#define") {
252-
if(list.size()<3)
272+
if(list.size()<2)
253273
throw std::runtime_error("Wrong number of tokens in the directive");
254274
if(_macros.find(list[1])!=_macros.end())
255275
throw std::runtime_error("Macro \""+list[1]+"\" has been already defined");
@@ -258,17 +278,17 @@ void Assembler::elaborateDirective(TokenList &list) {
258278
_macros.emplace(list[1],TokenList(list.begin()+2,list.end()));
259279
}
260280
else if(list[0]=="#export") {
261-
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
281+
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
262282
if(!validateIdentifier(list[1])) throw std::runtime_error("Ill-formed identifier: \""+list[1]+"\"");
263283
_exportedSymbols.push_back(list[1]);
264284
}
265285
else if(list[0]=="#import") {
266-
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
286+
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
267287
if(!validateIdentifier(list[1])) throw std::runtime_error("Ill-formed identifier: \""+list[1]+"\"");
268288
_obj.addImportedSymbol(list[1]);
269289
}
270290
else if(list[0]=="#include") {
271-
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
291+
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
272292
auto filename=Utils::dequoteString(list[1]);
273293
if(Utils::isAbsolutePath(filename)) return processFileRecursive(filename);
274294
else {
@@ -284,10 +304,35 @@ void Assembler::elaborateDirective(TokenList &list) {
284304
throw std::runtime_error("Cannot locate include file \""+filename+"\"");
285305
}
286306
else if(list[0]=="#message") {
287-
if(list.size()!=2) std::runtime_error("Wrong number of tokens in the directive");
307+
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
288308
auto msg=Utils::dequoteString(list[1]);
289309
std::cout<<currentFileName()<<":"<<line()<<": "<<msg<<std::endl;
290310
}
311+
else if(list[0]=="#error") {
312+
if(list.size()<2) throw std::runtime_error("#error directive encountered");
313+
auto msg=Utils::dequoteString(list[1]);
314+
throw std::runtime_error(msg);
315+
}
316+
else if(list[0]=="#ifdef") {
317+
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
318+
if(_macros.find(list[1])!=_macros.end()) _sectionEnabled.push_back(true);
319+
else _sectionEnabled.push_back(false);
320+
}
321+
else if(list[0]=="#ifndef") {
322+
if(list.size()!=2) throw std::runtime_error("Wrong number of tokens in the directive");
323+
if(_macros.find(list[1])!=_macros.end()) _sectionEnabled.push_back(false);
324+
else _sectionEnabled.push_back(true);
325+
}
326+
else if(list[0]=="#else") {
327+
if(list.size()!=1) throw std::runtime_error("Wrong number of tokens in the directive");
328+
if(_sectionEnabled.empty()) throw std::runtime_error("Unexpected #else");
329+
_sectionEnabled.back()=!_sectionEnabled.back();
330+
}
331+
else if(list[0]=="#endif") {
332+
if(list.size()!=1) throw std::runtime_error("Wrong number of tokens in the directive");
333+
if(_sectionEnabled.empty()) throw std::runtime_error("Unexpected #endif");
334+
_sectionEnabled.pop_back();
335+
}
291336
else throw std::runtime_error("Unrecognized directive: \""+list[0]+"\"");
292337
}
293338

@@ -392,6 +437,13 @@ LinkableObject::Word Assembler::elaborateInstruction(TokenList &list) {
392437
return rva;
393438
}
394439

440+
bool Assembler::isSectionEnabled() const {
441+
if(_sectionEnabled.empty()) return true;
442+
bool enabled=true;
443+
for(auto b: _sectionEnabled) enabled=enabled&&b;
444+
return enabled;
445+
}
446+
395447
bool Assembler::validateIdentifier(const std::string &str) {
396448
/*
397449
* Valid identifier must satisfy the following requirements:

tools/src/lxp32asm/assembler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Assembler {
4242
std::string _currentFileName;
4343
std::vector<std::string> _includeSearchDirs;
4444
std::vector<std::string> _exportedSymbols;
45+
std::vector<bool> _sectionEnabled;
4546
public:
4647
void processFile(const std::string &filename);
4748

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

66+
bool isSectionEnabled() const;
6567
static bool validateIdentifier(const std::string &str);
6668
static Integer numericLiteral(const std::string &str);
6769
static std::vector<Operand> getOperands(const TokenList &list);

0 commit comments

Comments
 (0)