Close
0%
0%

Tridora-CPU

a homebrew FPGA CPU with a native Pascal compiler and a simple development environment

Similar projects worth following
Tridora is a homebrew CPU written in Verilog and a matching software environment, including a Pascal compiler and assembler.
Everything was created from the ground up (except soldering stuff). Everything is as simple as possible while still being reasonably useful. Everything is open source, so you can read, understand and modify the whole system, hardware and software.

Overview

  • homebrew CPU written in Verilog implemented on an FPGA
  • 32-bit word-oriented stack machine architecture
  • running at 77 MHz on an Arty-A7 board with four clocks per instruction
  • has its own instruction set architecture, compatible with nothing
  • additional IO controllers on FPGA: UART (serial console), SD-Card, VGA
  • Pascal compiler written from zero
  • CPU and compiler were designed together
  • minimal operating system
  • editor, compiler, assembler run natively
  • so you can develop programs directly on the machine
  • small: CPU has ~500 lines of Verilog, compiler ~9000 LoC
  • compiler written in Pascal and can compile itself
  • cross-compiler/-assembler can be compiled with FPC
  • compiler does its own Pascal dialect with some restrictions and some extensions
  • emulator available here

The name comes from a certain fictional monster with three heads. The prefix tri- is greek for three, and the Tridora-CPU has three stacks instead of just one like almost all other CPUs. It also has a strange mixture of features from three different eras of computing:

  • a simple instruction set without integer multiply/divide like an 8-bit CPU
  • speed is like a fast 16-bit CPU, also 16-bit instruction words
  • 32-bit word size from the 32-bit era

It might remind you of the UCSD-P-System and early Turbo-Pascal versions.

Other inspirations were, among others, in no particular order:

  • the Novix 4016 CPU (a stack machine CPU designed for Forth, mainly by Charles Moore)
  • the J1 CPU by James Bowman (which is not entirely unlike the Novix 4016)
  • the Lilith computer by Niklaus Wirth and his team (a stack CPU designed for Modula-2)
  • the PERQ workstation (also a stack CPU designed for Pascal)
  • the Magic-1 by Bill Buzbee
  • the OPC by revaldinho

Source Repository

  Sources are published on Gitlab.

Supported Boards

  • Arty A7-35T (with two PMODs for microSD cards and VGA output)
  • Nexys A7 (planned)

Demo Videos

   See the README on Gitlab or the YouTube channel.

Pascal Language

  • Wirth Pascal
  • arbitrary length strings (2GB)
  • safe strings (runtime information about max/current size)
  • tiny sets (machine word sized), that means no SET OF CHAR
  • array literals with IN-operator, which can replace most uses of SET OF CHAR
  • nested procedures
  • procedure/function parameters as specified in Wirth Pascal
  • 32 bit software floating point with low precision (5-6 digits)
  • break and exit statements, no continue yet
  • static variable initialization for global variables
  • non-standard file i/o (because the standard sucks, obl. XKCD reference)

Standard Library

  • everything from Wirth Pascal
  • some things from TP3.0
  • some graphics functionality (to be expanded in the future)

Operating System

  • not a real operating system, more of a program loader
  • some assembly routines for I/O resident in memory
  • one program image loaded at a time at a fixed address
  • most parts of the operating system are contained in the program image
  • file system is very primitive: only contiguous blocks, no subdirectories
  • simple shell reminiscent of TP3.0, edit, compile, run programs

Building the Compiler

  • you need to have FPC and Python3 installed
  • on Linux, you need make installed
  • in the pcomp directory, run make (or make.bat on Windows)
  • on Linux, you can also run make nativeprogs and make examples

Getting the ROM image

  • there are two formats for the ROM image, one for the emulator (rommon.prog) and one for building the FPGA bitstream (rom.mem)
  • to get the rommon.prog file, either copy it from the tridoraemu package file or build it
  • for rom.mem and rommon.prog, find both files in the lib directory after running make nativeprogs (or make.bat) in the pcomp directory (see above)

Building the FPGA bitstream

  • install Vivado (known to work with 2024.2)
  • install the package for your board in Vivado (Tools -> Vivado Store -> Boards)
  • copy the ROM image (rom.mem) into the tridoracpu directory (see above)
  • start Vivado and open the project file tridoracpu.xpr in the tridoracpu directory
  • run synthesis, implementation and bitstream generation (Flow -> Generate Bitstream)
  • program your device (Flow...
Read more »

  • Sprite Animation With a Tiny Bit of Hardware Acceleration

    slederer05/21/2026 at 20:35 0 comments

    A new demo is out, showing an animation using software sprites. At first, that did not go well and there was much flickering. Then I added some minimal hardware acceleration to the video controller.  It does only pixel shifting and masking in hardware, which were the most costly parts in the software sprite routines. Now the sprite drawing is about five times faster, resulting in a smooth, flicker-free animation.

    Here is a video showing the animation demo before and after adding the tiny hardware accelerator:

    The demo only works on the real hardware, because it plays a sound effect and the emulator does not support the audio controller yet.

  • 2025 Christmas Demo

    slederer12/26/2025 at 14:57 0 comments

    The 2025 christmas demo is here.


    It uses a recreation of the well-known VGA fire effect, and it required some assembly language routines for a smooth animation. There is also some very simple tile-based animation. I wanted to have some music and sound in the demo but that did not work out. There seems to be a bug in the interrupt mechanism that is triggered when code is executed in DRAM (i.e. with wait states). So I probably have long debugging sessions with the integrated logic analyzer ahead of me. And I should finally draw that big timing diagram.

    Merry Christmas and happy holidays everyone!

  • October 2025 Update - Data Cache and Audio

    slederer10/14/2025 at 21:07 0 comments

    The October 2025 Update is here!

    With this update of the logic design, the Tridora-CPU now has a data cache. It is very similar to the instruction cache that was added with the April 2025 update: It caches the 16 bytes that the DRAM controller delivers anyway.

    For decent performance, it had to be implemented as a write-back cache. So when a write to the cached area happens, the cache is not invalidated (as in the simpler write-through cache). Instead, the write is going to RAM and will update the cache. This is because with the stack machine architecture and especially in the code generated by the Pascal compiler, it is very common to read, write and read again the same variable. So a write-through cache would be invalidated quite often and would have a much worse performance.

    Fortunately, the write-back cache was easier to implement than I thought, and the result is really noticable.

    Building the "hello,world" program now takes about 15 seconds instead of 20.

    There were also some minor tweaks for the SD-Card controller so it has a slightly higher transfer rate, and that brings down the build time to about 14 seconds.

    Updated benchmark results are here:

    https://gitlab.com/slederer/Tridora-CPU/-/blob/main/examples/benchmarks.results.text

    In comparison to the version without instruction and data cache, the new version has a three times better performance in the empty loop benchmark. In comparison to the last version with instruction cache, the improvement is factor 1.6.

    The other important thing in the October 2025 is the audio controller. The Tridora-CPU can now make sounds, using a Digilent AMP2 PMOD. At first, I implemented four channels of rectangle waves and a noise generator. Then I scrapped that and went for interrupt-driven sample playback. So now you can play a sample file in the background from your Pascal programs.

    Here is a video of the Tridora-CPU making some noises and playing music: 

  • ECL-Rogue on the Tridora-CPU: A Porting Adventure

    slederer05/27/2025 at 21:59 0 comments

    I have also been working on the software side, fixing bugs in the compiler and assembler that showed up when compiling more complex software.

    The latest result is ECL-Rogue: A variant of the well-known game Rogue that was originally written in C on Unix. This version is written in Pascal, and it was written for the Pascal-Compiler on the PDP-10, a 36-Bit word-oriented mainframe computer from the 1970s.

    It took many small changes to make it compile with Tridora-Pascal (and FreePascal), and there were also parts written in PDP-10 assembly language (e.g. accessing the score file) and system calls to the TOPS-20 operating system that needed to be rewritten.

    Apparently the PDP-10 Pascal compiler was very close to the original Pascal compiler from Niklaus Wirth and his team, and that means: No strings! Just packed array of char, padded with spaces. That required a lot of little changes. I also noticed that Tridora-Pascal is a lot stricter than FreePascal when accessing strings: In FreePascal (and Turbo-Pascal in the good old days), it is not an error to read/write chars beyond the current end of the string. So, more of those little changes.

    But in the end, I got it working while keeping the source code mostly the same, and now you can play ECL-Rogue on the Tridora-CPU. You can also compile it with FreePascal.

    Link to the source repository of the ported version (see the README there for the original version):

    https://gitlab.com/slederer/ecl-rogue-m

    Check out this video of me playing:

    The Tridora emulator image has also been updated to include ECL-Rogue.

  • The Tridora Got an Instruction Cache

    slederer05/27/2025 at 21:40 0 comments

    There have been a few updates to the Tridora-CPU:

    The clock frequency was reduced from 83MHz to 77MHz to allow for some additions to the Verilog code: I have at last implemented an instruction cache.


    Now on the Tridora-CPU, the first 64KB of RAM are static RAM (created from BRAM). The rest is DRAM (256MB on the Arty-A7), and this is accessed via a memory controller generated my MIG, the Memory Interface Generator in Vivado. This memory controller has a latency of 21 cycles and always transfers at least 16 bytes. Good for bursting data to cache lines in modern CPUs, not optimal for my simplicistic design.

    To make matters worse, the CPU uses 32-bit words, but 16-bit instructions. So for an instruction fetch, the CPU reads 32 bits, then throws half of them away to get the instruction. For the next instruction, it will fetch the same 32 bits again, then throw the other half away to get the second instruction in that 32 bit word. This does not matter when executing out of SRAM, but is really bad when executing out of DRAM.

    So an instruction cache was needed. I added a signal from the CPU that indicates an instruction fetch (in contrast to a data read/write), and implemented a simple caching mechanism that just stores the 16 bytes from the last read, and satisfies the read request from that if the read address is in the same 16-byte segment.

    In the optimal case, 8 instructions will now take 53 clock cycles instead of 200.

    I have created a benchmark program in Pascal for some measurements, and in some benchmarks the improvement is about factor two.

    In others, it is less noticable, depending on the amount of data memory accesses which are not cached. Also, some benchmarks exercise operations which are assembly subroutines which reside in SRAM, for example, integer multiplication or all floating point operations.

    Many programs do not run noticably faster though, because they fit inside the first 64KB of RAM. For the benchmark to show any difference, I had to hack the assembly code to move the code above the 64KB boundary.

    The full benchmark results can be seen here: https://gitlab.com/slederer/Tridora-CPU/-/raw/main/examples/benchmarks.results.text

    This is the benchmark source code: https://gitlab.com/slederer/Tridora-CPU/-/raw/main/examples/benchmarks.pas

  • Reduced Compile Times with new Assembler Feature

    slederer11/18/2024 at 00:46 0 comments

    The assembler got a new feature to decrease compile/assembly times.

    It can now import preassembled binaries, which is used for the standard library.

    This is not really a linker, because it cannot do relocation. So it can only import one binary at a fixed address, but that is good enough for now.

    Compiling and assembling "Hello World" now takes 20 seconds instead of 60.

    See the video showing this feature in action:

View all 6 project logs

Enjoy this project?

Share

Discussions

zpekic wrote 02/15/2026 at 20:09 point

Very impressive project, lot to learn from here! Was the CPU design done to facilitate easy execution of the P-code? However it is not a P-code CPU to allow efficient running of other programming languages or environments? 

  Are you sure? yes | no

slederer wrote 02/16/2026 at 20:08 point

Thanks, I do hope that this project is not only educational for me but also for others. That's one reason why I try to keep everything as simple as reasonably possible.

The idea is that the stack machine design makes it easy to write a compiler. It is inspired by Forth CPUs like the J1 and the NC4016/RTX2000 but with additions to make access to local variables easy.

It should be possible to write a compiler for other programming languages of the Algol family tree like C, Modula or Ada. But I believe the word-oriented architecture would be problematic for C. BCPL would be a good fit.

  Are you sure? yes | no

zpekic wrote 02/16/2026 at 20:45 point

Thanks for the explanation. Did you find that you had to modify your original CPU design a lot to make Pascal compiler writing easier? Btw, it would be fun to compare your Pascal benchmark results to running the same source on TP3.0 on CP/M or DOS (same clock frequency of course). 

  Are you sure? yes | no

slederer wrote 02/16/2026 at 23:59 point

The CPU was designed from the start with a Pascal compiler in mind, so I did not change much afterwards. I think just once I changed an instruction to fit the compiler better (changed the ordering of the STOREI args), and at the end I added two instructions to help with byte handling to accelerate the native editor.

A benchmark comparison would be great, for sure! The Tridora-CPU might be faster than a Z80 per Clock. It will be definitely slower per clock than an 8086 or 68k, because those have multiply/divide instructions and can do a lot more in a single instruction with all these fancy addressing modes. So a comparison with a 50MHz eZ80 system with CP/M and Turbo Pascal would be most interesting. Some of the benchmarks would not run though, because they assume 32 bit integers at least for the loop counters.

  Are you sure? yes | no

zpekic wrote 02/17/2026 at 18:50 point

I was thinking clocking your design to exactly 4.77MHz and compare :-) I believe it will be faster than PC/XT with TP3.0 - while Borland TP was a speed champ in the day, 8086 doesn't seem like the optimal CPU for Pascal. For integer multiply, I think it is ok to "cheat" a bit, and use intrinsic hardware multiplier present in most FPGAs. These are combinatorial and can multiply in 1 cycle. Division of course in worst case will take n (32) cycles. After all, there have been such dedicated multiplier chips used for high end custom CPUs in the past too. Btw, how did you implement FP? Just for some retro-fun, I used an old IC and connected it to FPGA Floating Point using Am9511 coprocessor | Details | Hackaday.io

  Are you sure? yes | no

slederer wrote 02/18/2026 at 22:56 point

I wanted to have multiply/divide instructions at first, just with the *,/ operators in Verilog, but that gave me a timing error during synthesis, and then I thought, if the 6502 does not need it, then I don't need it too ;)

For floating point, I made my own software implementation with a 32 bit format (23 bits mantissa without hidden bit).

That Am9511 project sounds quite interesting, I had no idea such a chip existed. That must have been before the IEEE-754 standardisation. It even uses a 32 bit floating point format that is similar to what I made up for my own float32 implementation. It seems that it could even accelerate integer multiplication using the fixed point mode.

  Are you sure? yes | no

zpekic wrote 02/19/2026 at 08:52 point

MUL should be synthesizable because it is combinatorial, DIV is sequential with state so at some level code must implement the appropriate algorithm. For my CPU it is in microcode. In any case, I think MUL in hardware is much more useful because of array index calculations (array dimensions 2+), so compiler / interpreter can use it extensively. Am9511 is still a fascinating chip even after 45+ years. The follow-up chip (9512) was IEEE compliant, but in any case it is easy to convert one format to another. This is a great article from the era still useful for anybody trying to implement FP (esp. discussion about errors and Chebyshev polynomials) techdocs/amd/am9511a/Am9511_Algorithm_Details.pdf at master · z88dk/techdocs

  Are you sure? yes | no

slederer wrote 02/21/2026 at 00:50 point

Yeah, I noticed that not having MUL really slows down multidimensional array access, for example in programs like Conway's game of life.

Maybe I should implement a math coprocessor at some point, that has the usual floating point stuff and integer multiply and divide. I believe the early SPARC CPUs also had integer multiply/divide in the FPU.

I already thought about having some special I/O registers that do iterative shifts/adds to speed up multiplication/division without complicating the CPU.  This was after implementing a shifter/masking register in the framebuffer controller to speed up drawing pixels. That made drawing software sprites faster by a factor of five.

  Are you sure? yes | no

zpekic wrote 02/21/2026 at 19:41 point

If you have direct access to both TOS and NOS on stack, then one extra register of the same width is enough for both MUL and DIV. It doesn't have to be program accessible. DIV can produce both the quotient and reminder so with a SWAP / DROP you can get the result of interest. I used this book extensively to learn how to implement these operations for my CPU (I won't link but look up ""Digital Computer Arithmetic" by Joseph J.F. Cavanagh") (I have a parametric design, so CPU can work on 16 and 32-bit, producing 32 or 64 bit products / 16 or 32 bit remainders or quotients) 

  Are you sure? yes | no

Ed S wrote 10/23/2024 at 06:30 point

Splendid and marvellous!

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates