<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TinyComputers.io (Posts about fortran)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/fortran.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Original site content © 2022–2026 Tiny Machines Workshop, LLC, except where otherwise noted. Some rights reserved.</copyright><lastBuildDate>Mon, 28 Sep 2026 16:42:29 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Building Language Compilers for the Z80: An Anthology of Retrocomputing Languages</title><link>https://tinycomputers.io/posts/building-language-compilers-for-the-z80.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;p&gt;Over the past year, I have been building a collection of programming language compilers and interpreters targeting the venerable &lt;a href="https://go.tinymachines.io/PvFWUc"&gt;Zilog&lt;/a&gt; Z80 microprocessor. What started as an experiment in retrocomputing has grown into a comprehensive suite of tools spanning multiple programming paradigms: from the functional elegance of &lt;a href="https://go.tinymachines.io/N9NhaA"&gt;LISP&lt;/a&gt; to the object-oriented messaging of Smalltalk, from the structured programming of Pascal and Fortran to the low-level control of C. This anthology documents the common architectural patterns, the unique challenges of targeting an 8-bit processor, and the unexpected joys of bringing modern language implementations to &lt;a href="https://go.tinymachines.io/hm3DAG"&gt;1970s hardware&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My fascination with the Z80 began in the mid-1990s when I got my first &lt;a href="https://go.tinymachines.io/3bwxsQ"&gt;TI-85 graphing calculator&lt;/a&gt;. That unassuming device, marketed for algebra and calculus homework, contained a Z80 running at 6 MHz with 28KB of RAM. Discovering that I could write programs in &lt;a href="https://go.tinymachines.io/qCn2kf"&gt;Z80 assembly&lt;/a&gt; and run them on this pocket computer was revelatory. I accumulated a small library of Z80 assembly books and spent countless hours learning the instruction set, writing simple games, and understanding how software meets hardware at the most fundamental level. Three decades later, this project represents a return to that formative obsession, now armed with modern tools and a deeper understanding of language implementation.&lt;/p&gt;
&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/building-language-compilers-for-the-z80_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;33 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;The RetroShield Platform&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://go.tinymachines.io/RnrxnG"&gt;RetroShield&lt;/a&gt; is a family of hardware adapters that bridge vintage microprocessors to modern Arduino development boards. The product line covers a remarkable range of classic CPUs: the MOS 6502 (powering the Apple II and Commodore 64), the Motorola 6809 (used in the TRS-80 Color Computer), the Intel 8085, the SC/MP, and the Zilog Z80. Each variant allows the original processor to execute real machine code while the Arduino emulates memory, peripherals, and I/O.&lt;/p&gt;
&lt;p&gt;For this project, I focused exclusively on the RetroShield Z80. The Z80's rich instruction set, hardware BCD support via the DAA instruction, and historical significance as the CPU behind CP/M made it an ideal target for language implementation experiments. The RetroShield Z80 connects the actual Z80 chip to an &lt;a href="https://go.tinymachines.io/dPUbbQ"&gt;Arduino Mega&lt;/a&gt; (or &lt;a href="https://go.tinymachines.io/nH673v"&gt;Teensy&lt;/a&gt; adapter for projects requiring more RAM), which emulates the memory and peripheral chips. This arrangement provides the authenticity of running on actual Z80 silicon while offering the convenience of modern development workflows.  &lt;/p&gt;
&lt;p&gt;The standard memory map provides 8KB of ROM at addresses 0x0000-0x1FFF and 6KB of RAM at 0x2000-0x37FF, though the Teensy adapter expands this significantly to 256KB. Serial I/O is handled through an emulated &lt;a href="https://go.tinymachines.io/y5waFB"&gt;MC6850 ACIA&lt;/a&gt; chip at ports 0x80 and 0x81, providing the familiar RS-232 interface that connects these vintage programs to modern terminals.&lt;/p&gt;
&lt;p&gt;It needs to be mentioned that if you do have a Z80 RetroShield and you want to run the binaries produced by the compilers collections on actual hardware, you will need a couple things:  1) &lt;a href="https://go.tinymachines.io/vefknY"&gt;bin2c&lt;/a&gt;, this is a program that will take a Z80 binary and turn it into a &lt;code&gt;PROGMEM&lt;/code&gt; statement that you can put into an Arduino sketch. 2) Look at &lt;a href="https://go.tinymachines.io/ftCVdh"&gt;this sketch&lt;/a&gt; - there is code in there for emulating the MC6850 ACIA.&lt;/p&gt;
&lt;h3&gt;Common Compiler Architecture: Lexer, Parser, AST, Codegen&lt;/h3&gt;
&lt;p&gt;Every compiler in this collection follows a similar multi-stage architecture, a pattern that has proven itself across decades of compiler construction. Understanding this common structure reveals how the same fundamental approach can target vastly different source languages while producing efficient Z80 machine code.&lt;/p&gt;
&lt;h4&gt;The Lexer: Breaking Text into Tokens&lt;/h4&gt;
&lt;p&gt;The lexer (or tokenizer) is the first stage of compilation, responsible for transforming raw source code into a stream of tokens. Each language has its own lexical grammar: LISP recognizes parentheses and symbols, C identifies keywords and operators, &lt;a href="https://go.tinymachines.io/QGyqtK"&gt;Smalltalk&lt;/a&gt; distinguishes between message selectors and literals. Despite these differences, every lexer performs the same fundamental task of categorizing input characters into meaningful units.&lt;/p&gt;
&lt;p&gt;In our Rust implementations, the lexer typically maintains a position in the source string and provides a &lt;code&gt;next_token()&lt;/code&gt; method that advances through the input. This produces tokens like &lt;code&gt;Token::Integer(42)&lt;/code&gt;, &lt;code&gt;Token::Plus&lt;/code&gt;, or &lt;code&gt;Token::Identifier("factorial")&lt;/code&gt;. The lexer handles the tedious work of skipping whitespace, recognizing multi-character operators, and converting digit sequences into numbers.&lt;/p&gt;
&lt;h4&gt;The Parser: Building the Abstract Syntax Tree&lt;/h4&gt;
&lt;p&gt;The parser consumes the token stream and constructs an Abstract Syntax Tree (AST) that represents the hierarchical structure of the program. Most of our compilers use &lt;a href="https://go.tinymachines.io/xLp4gf"&gt;recursive descent parsing&lt;/a&gt;, a technique where each grammar rule becomes a function that may call other rule functions. This approach is intuitive, produces readable code, and handles the grammars of most programming languages effectively.&lt;/p&gt;
&lt;p&gt;For example, parsing an arithmetic expression like &lt;code&gt;3 + 4 * 5&lt;/code&gt; requires understanding operator precedence. The parser might have functions like &lt;code&gt;parse_expression()&lt;/code&gt;, &lt;code&gt;parse_term()&lt;/code&gt;, and &lt;code&gt;parse_factor()&lt;/code&gt;, each handling operators at different precedence levels. The result is an AST where the multiplication is grouped as a subtree, correctly representing that it should be evaluated before the addition.&lt;/p&gt;
&lt;h4&gt;Code Generation: Emitting Z80 Machine Code&lt;/h4&gt;
&lt;p&gt;The code generator walks the AST and emits Z80 machine code. This is where the rubber meets the road: abstract operations like "add two numbers" become concrete sequences of Z80 instructions like &lt;code&gt;LD A,(HL)&lt;/code&gt;, &lt;code&gt;ADD A,E&lt;/code&gt;, and &lt;code&gt;LD (DE),A&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Most of our compilers generate code directly into a byte buffer, manually encoding each instruction's opcode and operands. This approach, while requiring intimate knowledge of the Z80 instruction set, gives us precise control over the generated code and avoids the complexity of an intermediate representation or separate assembler pass.&lt;/p&gt;
&lt;h3&gt;The DAA Instruction and BCD Arithmetic&lt;/h3&gt;
&lt;p&gt;One of the most fascinating aspects of Z80 programming is the DAA (Decimal Adjust Accumulator) instruction, opcode 0x27. This single instruction makes the Z80 surprisingly capable at decimal arithmetic, which proves essential for implementing numeric types on an 8-bit processor.&lt;/p&gt;
&lt;h4&gt;What is BCD?&lt;/h4&gt;
&lt;p&gt;Binary Coded Decimal (BCD) is a numeric representation where each decimal digit is stored in 4 bits (a nibble). Rather than storing the number 42 as binary 00101010 (its true binary representation), BCD stores it as 0100 0010, with the first nibble representing 4 and the second representing 2. This "packed BCD" format stores two decimal digits per byte.&lt;/p&gt;
&lt;p&gt;While BCD is less space-efficient than pure binary (you can only represent 0-99 in a byte rather than 0-255), it has a crucial advantage: decimal arithmetic produces exact decimal results without rounding errors. This is why BCD was the standard for financial calculations on mainframes and why pocket calculators (including the famous TI series) used BCD internally.&lt;/p&gt;
&lt;h4&gt;How DAA Works&lt;/h4&gt;
&lt;p&gt;When you perform binary addition on two BCD digits, the result may not be valid BCD. Adding 0x09 and 0x01 gives 0x0A, but 0x0A is not a valid BCD digit. The DAA instruction corrects this: it examines the result and the half-carry flag (which indicates a carry from bit 3 to bit 4, i.e., from the low nibble to the high nibble) and adds 0x06 to any nibble that exceeds 9. After DAA, that 0x0A becomes 0x10, correctly representing decimal 10 in BCD.&lt;/p&gt;
&lt;p&gt;This process works for both addition (after ADD or ADC instructions) and subtraction (after SUB or SBC instructions, where DAA subtracts 0x06 instead of adding it). The Z80 remembers whether the previous operation was addition or subtraction through its N flag.&lt;/p&gt;
&lt;h4&gt;BCD in Our Compilers&lt;/h4&gt;
&lt;p&gt;Several of our compilers use 4-byte packed BCD integers, supporting numbers up to 99,999,999 (8 decimal digits). The addition routine loads bytes from both operands starting from the least significant byte, adds them with ADC (add with carry) to propagate carries between bytes, applies DAA to correct each byte, and stores the result. The entire operation takes perhaps 20 bytes of code but provides exact decimal arithmetic on an 8-bit processor.&lt;/p&gt;
&lt;p&gt;Here is a simplified version of our BCD addition loop:&lt;/p&gt;
&lt;div class="crt-terminal"&gt;bcd_add:
    LD B, 4          ; 4 bytes to process
    OR A             ; Clear carry flag
bcd_add_loop:
    LD A, (DE)       ; Load byte from first operand
    ADC A, (HL)      ; Add byte from second operand with carry
    DAA              ; Decimal adjust
    LD (DE), A       ; Store result
    DEC HL           ; Move to next byte
    DEC DE
    DJNZ bcd_add_loop
    RET&lt;/div&gt;

&lt;p&gt;This pattern appears in kz80_c, kz80_fortran, kz80_smalltalk, and kz80_lisp, demonstrating how a hardware feature designed in 1976 still provides practical benefits for language implementation.&lt;/p&gt;
&lt;h3&gt;The Evolution: From Assembly to C to Rust&lt;/h3&gt;
&lt;p&gt;The journey of implementing these compilers taught us valuable lessons about choosing the right tool for the job, and our approach evolved significantly over time.&lt;/p&gt;
&lt;h4&gt;First Attempt: Pascal in Z80 Assembly&lt;/h4&gt;
&lt;p&gt;Our first language implementation was &lt;a href="https://go.tinymachines.io/62P52c"&gt;kz80_pascal&lt;/a&gt;, a Pascal interpreter written entirely in Z80 assembly language. This approach seemed natural: if you are targeting the Z80, why not write directly in its native language?&lt;/p&gt;
&lt;p&gt;The reality proved challenging. Z80 assembly, while powerful, is unforgiving. Building a recursive descent parser in assembly requires manually managing the call stack, carefully preserving registers across function calls, and debugging through hex dumps of memory. The resulting interpreter works and provides an interactive REPL for Pascal expressions, but extending it requires significant effort. Every new feature means more assembly, more potential for subtle bugs, and more time spent on implementation details rather than language design.&lt;/p&gt;
&lt;h4&gt;Second Attempt: Fortran 77 in C with SDCC&lt;/h4&gt;
&lt;p&gt;For &lt;a href="https://go.tinymachines.io/bkKd9Y"&gt;kz80_fortran&lt;/a&gt;, we tried a different approach: writing the interpreter in C and cross-compiling with SDCC (Small Device C Compiler). This was dramatically more productive. C provided structured control flow, automatic stack management, and the ability to organize code into manageable modules.&lt;/p&gt;
&lt;p&gt;The result is a comprehensive Fortran 77 subset with floating-point arithmetic (via BCD), subroutines and functions, arrays, and block IF statements. The C source compiles to approximately 19KB of Z80 code, fitting comfortably in ROM with room for program storage in RAM.&lt;/p&gt;
&lt;p&gt;However, this approach has limitations. SDCC produces functional but not always optimal code, and debugging requires understanding both the C source and the generated assembly. The interpreter also requires the Teensy adapter with 256KB RAM, as the Arduino Mega's 4KB is insufficient for the runtime data structures.&lt;/p&gt;
&lt;h4&gt;The Rust Workbench: Our Final Form&lt;/h4&gt;
&lt;p&gt;Our breakthrough came with the realization that we did not need the compiler itself to run on the Z80, only the generated code. This insight led to what we call the "&lt;a href="https://go.tinymachines.io/vRcCyM"&gt;Rust&lt;/a&gt; workbench" approach: write the compiler in Rust, running on a modern development machine, and have it emit Z80 binary images.&lt;/p&gt;
&lt;p&gt;This architecture provides enormous advantages:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modern tooling&lt;/strong&gt;: Cargo manages dependencies and builds, rustc catches bugs at compile time, and we have access to the entire Rust ecosystem for testing and development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fast iteration&lt;/strong&gt;: Compiling a Rust program takes seconds; testing the generated Z80 code in our emulator takes milliseconds. Compare this to the multi-minute flash cycles required when the compiler runs on the target.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Comprehensive testing&lt;/strong&gt;: Each compiler includes both Rust unit tests (testing the lexer, parser, and code generator individually) and integration tests that compile source programs and verify their output in the emulator.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero-dependency output&lt;/strong&gt;: Despite being written in Rust, the generated Z80 binaries have no runtime dependencies. They are pure machine code that runs directly on the hardware.&lt;/p&gt;
&lt;p&gt;This approach now powers kz80_lisp, kz80_c, kz80_lua, kz80_smalltalk, kz80_chip8, and retrolang. Each is a standalone Rust binary that reads source code and produces a 32KB ROM image.&lt;/p&gt;
&lt;h3&gt;The Z80 Emulator&lt;/h3&gt;
&lt;p&gt;None of this would be practical without a way to test generated code quickly. Our &lt;a href="https://go.tinymachines.io/EgkNMw"&gt;RetroShield Z80 Emulator&lt;/a&gt; provides exactly this: a cycle-accurate Z80 emulation with the same memory map and I/O ports as the real hardware.&lt;/p&gt;
&lt;p&gt;The emulator comes in two versions: a simple passthrough mode (&lt;code&gt;retroshield&lt;/code&gt;) that connects stdin/stdout directly to the emulated serial port, and a full TUI debugger (&lt;code&gt;retroshield_nc&lt;/code&gt;) with register displays, disassembly views, memory inspection, and single-step execution. The passthrough mode enables scripted testing, piping test inputs through the emulator and comparing outputs against expected results. The TUI debugger proves invaluable when tracking down code generation bugs.&lt;/p&gt;
&lt;p&gt;The emulator uses the &lt;a href="https://go.tinymachines.io/CHmzah"&gt;superzazu/z80&lt;/a&gt; library for CPU emulation, which provides accurate flag behavior and correct cycle counts. Combined with our MC6850 ACIA emulation, it provides a faithful recreation of the RetroShield environment without requiring physical hardware.&lt;/p&gt;
&lt;h3&gt;Self-Hosting Compilers: LISP and C&lt;/h3&gt;
&lt;p&gt;Two of our compilers achieve something remarkable: they can compile themselves and run on the target hardware. This property, called "self-hosting," is a significant milestone in compiler development.&lt;/p&gt;
&lt;h4&gt;What Does Self-Hosting Mean?&lt;/h4&gt;
&lt;p&gt;A self-hosting compiler is one written in the language it compiles. The classic example is the C compiler: most C compilers are themselves written in C. But this creates a chicken-and-egg problem: how do you compile a C compiler if you need a C compiler to compile it?&lt;/p&gt;
&lt;p&gt;The solution is bootstrapping. You start with a minimal compiler written in some other language (or in machine code), use it to compile a slightly better compiler written in the target language, and iterate until you have a full-featured compiler that can compile its own source code. Once bootstrapped, the compiler becomes self-sustaining: future versions compile themselves.&lt;/p&gt;
&lt;h4&gt;kz80_lisp: A Self-Hosted LISP Compiler&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://go.tinymachines.io/ScnfbU"&gt;kz80_lisp&lt;/a&gt; (&lt;a href="https://go.tinymachines.io/MtSrVk"&gt;crates.io&lt;/a&gt;) includes a LISP-to-Z80 compiler written in LISP itself. The &lt;code&gt;compiler.lisp&lt;/code&gt; file defines functions that traverse LISP expressions and emit Z80 machine code bytes directly into memory. When you call &lt;code&gt;(COMPILE '(+ 1 2))&lt;/code&gt;, it generates the actual Z80 instructions to load 1 and 2 and add them.&lt;/p&gt;
&lt;p&gt;The self-hosted compiler supports arithmetic expressions, nested function calls, and can generate code that interfaces with the runtime's I/O primitives. While not a full replacement for the Rust-based code generator, it demonstrates that LISP is expressive enough to describe its own compilation to machine code.&lt;/p&gt;
&lt;h4&gt;kz80_c: A Self-Hosted C Compiler&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://go.tinymachines.io/w5Vpxz"&gt;kz80_c&lt;/a&gt; (&lt;a href="https://go.tinymachines.io/VQDKUb"&gt;crates.io&lt;/a&gt;) goes further: its &lt;code&gt;self/cc.c&lt;/code&gt; file is a complete C compiler written in the C subset it compiles. This compiler reads C source from stdin and outputs Z80 binary to stdout, making it usable in shell pipelines:&lt;/p&gt;
&lt;div class="crt-terminal"&gt;# printf 'int main() { puts("Hello!"); return 0; }\x00' | \
    retroshield self/cc.bin &amp;gt; hello.bin
# retroshield hello.bin
Hello!&lt;/div&gt;

&lt;p&gt;The self-hosted C compiler supports all arithmetic operators, pointers, arrays, global variables, control flow statements, and recursive functions. Its main limitation is memory: the compiler source is approximately 66KB, exceeding the 8KB input buffer available on the Z80. This is a fundamental hardware constraint, not a compiler bug. In theory, a "stage 0" minimal compiler could bootstrap larger compilers.&lt;/p&gt;
&lt;h4&gt;Why Self-Hosting Matters&lt;/h4&gt;
&lt;p&gt;Self-hosting is more than a technical achievement; it validates the language implementation. If the compiler can compile itself correctly, it demonstrates that the language is expressive enough for real programs and that the code generator produces working code under complex conditions. For our Z80 compilers, self-hosting also connects us to the history of computing: the original Small-C compiler by Ron Cain in 1980 was similarly self-hosted on Z80/CP-M systems.&lt;/p&gt;
&lt;h3&gt;The Language Implementations&lt;/h3&gt;
&lt;h4&gt;kz80_lisp&lt;/h4&gt;
&lt;p&gt;A minimal LISP interpreter and compiler featuring the full suite of list operations (CAR, CDR, CONS), special forms (QUOTE, IF, COND, LAMBDA, DEFINE), and recursive function support. The implementation includes a pure-LISP floating-point library and the self-hosted compiler mentioned above.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;kz80_lisp v0.1
&amp;gt; (+ 21 21)
42
&amp;gt; (DEFINE (SQUARE X) (* X X))
SQUARE
&amp;gt; (SQUARE 7)
49&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/ScnfbU"&gt;https://github.com/ajokela/kz80_lisp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/MtSrVk"&gt;https://crates.io/crates/kz80_lisp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;kz80_c&lt;/h4&gt;
&lt;p&gt;A C compiler supporting char (8-bit), int (16-bit), float (BCD), pointers, arrays, structs, and a preprocessor with &lt;code&gt;#define&lt;/code&gt; and &lt;code&gt;#include&lt;/code&gt;. The runtime library provides serial I/O and comprehensive BCD arithmetic functions. The self-hosted variant can compile and run C programs entirely on the Z80.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;# cat fibonacci.c
int fib(int n) {
    if (n &amp;lt;= 1) return n;
    return fib(n-1) + fib(n-2);
}
int main() {
    puts("Fibonacci:");
    for (int i = 0; i &amp;lt; 10; i = i + 1)
        print_num(fib(i));
    return 0;
}

# kz80_c fibonacci.c -o fib.bin
# retroshield -l fib.bin
Fibonacci:
0 1 1 2 3 5 8 13 21 34&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/w5Vpxz"&gt;https://github.com/ajokela/kz80_c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/VQDKUb"&gt;https://crates.io/crates/kz80_c&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;kz80_smalltalk&lt;/h4&gt;
&lt;p&gt;A Smalltalk subset compiler implementing the language's distinctive message-passing syntax with left-to-right operator evaluation. Expressions like &lt;code&gt;1 + 2 * 3&lt;/code&gt; evaluate to 9 (not 7), matching Smalltalk's uniform treatment of binary messages. All arithmetic uses BCD with the DAA instruction.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;# echo "6 * 7" | kz80_smalltalk /dev/stdin -o answer.bin
# retroshield -l answer.bin
Tiny Smalltalk on Z80
42&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/gtMXru"&gt;https://github.com/ajokela/kz80_smalltalk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/gErH6P"&gt;https://crates.io/crates/kz80_smalltalk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;kz80_lua&lt;/h4&gt;
&lt;p&gt;A Lua compiler producing standalone ROM images with an embedded virtual machine. Supports tables (Lua's associative arrays), first-class functions, closures, and familiar control structures. The generated VM interprets Lua bytecode, with frequently-used operations implemented in native Z80 code for performance.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;# cat factorial.lua
function factorial(n)
    if n &amp;lt;= 1 then return 1 end
    return n * factorial(n - 1)
end
print("5! =", factorial(5))

# kz80_lua factorial.lua -o fact.bin
# retroshield -l fact.bin
Tiny Lua v0.1
5! =    120&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/VgyTbq"&gt;https://github.com/ajokela/kz80_lua&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/YpSypT"&gt;https://crates.io/crates/kz80_lua&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;kz80_fortran&lt;/h4&gt;
&lt;p&gt;A &lt;a href="https://go.tinymachines.io/FcKgLz"&gt;Fortran 77&lt;/a&gt; interpreter with free-format input, REAL numbers via BCD floating point, block IF/THEN/ELSE/ENDIF, DO loops, subroutines, and functions. Requires the Teensy adapter for sufficient RAM. Written in C and cross-compiled with SDCC.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;FORTRAN-77 Interpreter v0.3
RetroShield Z80
Ready.
&amp;gt; INTEGER X, Y
&amp;gt; X = 7
&amp;gt; Y = X * 6
&amp;gt; WRITE(*,*) 'Answer:', Y
Answer: 42&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/bkKd9Y"&gt;https://github.com/ajokela/kz80_fortran&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;kz80_pascal&lt;/h4&gt;
&lt;p&gt;A Pascal interpreter implemented in pure Z80 assembly. Provides an interactive REPL for expression evaluation with integer arithmetic, boolean operations, and comparison operators. A testament to the challenges of assembly language programming.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;Tiny Pascal v0.1
For RetroShield Z80
(Expression Eval Mode)

&amp;gt; 2 + 3 * 4
 = 00014
&amp;gt; TRUE AND (5 &amp;gt; 3)
 = TRUE&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/62P52c"&gt;https://github.com/ajokela/kz80_pascal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;retrolang&lt;/h4&gt;
&lt;p&gt;A custom systems programming language with Pascal/C-like syntax, featuring 16-bit integers, 8-bit bytes, pointers, arrays, inline assembly, and full function support with recursion. Compiles to readable Z80 assembly before assembling to binary.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;# cat squares.rl
proc main()
    var i: int;
    print("Squares: ");
    for i := 1 to 5 do
        printi(i * i);
        printc(32);
    end;
    println();
end;

# retrolang squares.rl --binary -o squares.bin
# retroshield -l squares.bin
Squares: 1 4 9 16 25&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/A9LeRz"&gt;https://github.com/ajokela/retrolang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/KQduy8"&gt;https://crates.io/crates/retrolang&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;kz80_chip8&lt;/h4&gt;
&lt;p&gt;A static recompiler that transforms CHIP-8 programs into native Z80 code. Rather than interpreting CHIP-8 bytecode at runtime, the compiler analyzes each instruction and generates equivalent Z80 sequences. Classic games like Space Invaders and Tetris run directly on the hardware.&lt;/p&gt;
&lt;div class="crt-terminal"&gt;# kz80_chip8 -d ibm_logo.ch8
200: 00E0  CLS
202: A22A  LD   I, 22A
204: 600C  LD   V0, 0C
206: 6108  LD   V1, 08
208: D01F  DRW  V0, V1, 15
20A: 7009  ADD  V0, 09
20C: A239  LD   I, 239
20E: D01F  DRW  V0, V1, 15
...

# kz80_chip8 ibm_logo.ch8 -o ibm.bin
# retroshield -l ibm.bin
CHIP-8 on Z80
[displays IBM logo]&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/h3PusE"&gt;https://github.com/ajokela/kz80_chip8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;: &lt;a href="https://go.tinymachines.io/BEur3s"&gt;https://crates.io/crates/kz80_chip8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Why Rust for Compiler Development?&lt;/h3&gt;
&lt;p&gt;The choice of Rust for our compiler workbench was not accidental. Several features make it exceptionally well-suited for this work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strong typing catches bugs early&lt;/strong&gt;. When you're generating machine code, off-by-one errors or type mismatches can produce binaries that crash or compute wrong results. Rust's type system prevents many such errors at compile time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pattern matching excels at AST manipulation&lt;/strong&gt;. Walking a syntax tree involves matching on node types and recursively processing children. Rust's &lt;code&gt;match&lt;/code&gt; expressions with destructuring make this natural and exhaustive (the compiler warns if you forget a case).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero-cost abstractions&lt;/strong&gt;. We can use high-level constructs like iterators, enums with data, and trait objects without runtime overhead. The generated compiler code is as efficient as hand-written C.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Excellent tooling&lt;/strong&gt;. Cargo's test framework made it easy to build comprehensive test suites. Each compiler has dozens to hundreds of tests that run in seconds, providing confidence when making changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory safety without garbage collection&lt;/strong&gt;. This matters less for the compilers themselves (which are desktop tools) but more for our mental model: thinking about ownership and lifetimes transfers naturally to thinking about Z80 register allocation and stack management.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building these compilers has been a journey through computing history, from the Z80's 1976 architecture to modern Rust tooling, from the fundamentals of lexing and parsing to the intricacies of self-hosting. The BCD arithmetic that seemed like a curiosity became a practical necessity; the emulator that started as a debugging aid became essential infrastructure; the Rust workbench that felt like an optimization became the key to productivity.&lt;/p&gt;
&lt;p&gt;The Z80 remains a remarkable teaching platform. Its simple instruction set is comprehensible in an afternoon, yet implementing real languages for it requires genuine compiler engineering. Every language in this collection forced us to think carefully about representation, evaluation, and code generation in ways that higher-level targets often obscure.&lt;/p&gt;
&lt;p&gt;All of these projects are open source under BSD-3-Clause licenses. The compilers are available on both GitHub and crates.io, ready to install with &lt;code&gt;cargo install&lt;/code&gt;. Whether you are interested in retrocomputing, compiler construction, or just curious how programming languages work at the metal level, I hope these tools and their source code prove useful.&lt;/p&gt;
&lt;p&gt;The Z80 may be nearly 50 years old, but it still has lessons to teach.&lt;/p&gt;</description><category>bcd</category><category>c</category><category>compilers</category><category>daa</category><category>fortran</category><category>lisp</category><category>lua</category><category>pascal</category><category>retrocomputing</category><category>rust</category><category>smalltalk</category><category>z80</category><guid>https://tinycomputers.io/posts/building-language-compilers-for-the-z80.html</guid><pubDate>Fri, 12 Dec 2025 18:00:00 GMT</pubDate></item><item><title>Building a Browser-Based Z80 Emulator for the RetroShield</title><link>https://tinycomputers.io/posts/browser-based-z80-emulator-retroshield.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;p&gt;There's something deeply satisfying about running code on vintage hardware. The blinking cursor, the deliberate pace of execution, the direct connection between your keystrokes and the machine's response. The &lt;a href="https://go.tinymachines.io/pPBdMZ"&gt;RetroShield&lt;/a&gt; by Erturk Kocalar brings this experience to modern makers by allowing real vintage CPUs like the Zilog Z80 to run on Arduino boards. But what if you could experience that same feeling directly in your web browser?&lt;/p&gt;
&lt;p&gt;That's exactly what I set out to build: a complete Z80 emulator that runs RetroShield firmware in WebAssembly, complete with authentic CRT visual effects and support for multiple programming language interpreters.&lt;/p&gt;
&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/browser-based-z80-emulator-retroshield_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;15 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;Try It Now&lt;/h3&gt;
&lt;p&gt;Select a ROM below and click "Load ROM" to start. Click on the terminal to focus it, then type to interact with the interpreter.&lt;/p&gt;
&lt;style&gt;
@font-face {
    font-family: 'Glass TTY VT220';
    src: url('https://tinycomputers.io/z80-emulator/Glass_TTY_VT220.woff') format('woff'),
         url('https://tinycomputers.io/z80-emulator/Glass_TTY_VT220.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
}

.emulator-container {
    max-width: 900px;
    margin: 0 auto;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

.control-panel {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    margin-bottom: 15px;
    padding: 15px;
    background: #f5f5f5;
    border-radius: 8px;
}

.control-panel label {
    font-weight: 600;
    margin-right: 5px;
}

.control-panel select {
    padding: 8px 12px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background: white;
    min-width: 200px;
}

.control-panel button {
    padding: 8px 16px;
    font-size: 14px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.2s;
}

.btn-primary {
    background: #007bff;
    color: white;
}

.btn-primary:hover {
    background: #0056b3;
}

.btn-secondary {
    background: #6c757d;
    color: white;
}

.btn-secondary:hover {
    background: #545b62;
}

.crt-container {
    position: relative;
    border-radius: 20px;
    overflow: hidden;
    box-shadow: 0 0 40px rgba(0, 255, 0, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5);
}

#terminal {
    background: #0a0a0a;
    color: #00ff00;
    font-family: 'Glass TTY VT220', "Courier New", Courier, monospace;
    font-size: 18px;
    line-height: 1.4;
    padding: 20px;
    border-radius: 20px;
    height: 400px;
    overflow-y: auto;
    white-space: pre-wrap;
    word-wrap: break-word;
    border: none;
    text-shadow: 0 0 5px rgba(0, 255, 0, 0.5), 0 0 10px rgba(0, 255, 0, 0.3);
    animation: textShadow 1.6s infinite;
    position: relative;
}

#terminal:focus {
    outline: none;
}

.crt-container::before {
    content: " ";
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
                linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
    z-index: 2;
    background-size: 100% 4px, 3px 100%;
    pointer-events: none;
    border-radius: 20px;
}

.crt-container::after {
    content: " ";
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: rgba(18, 16, 16, 0.1);
    opacity: 0;
    z-index: 2;
    pointer-events: none;
    animation: flicker 0.15s infinite;
    border-radius: 20px;
}

.vignette {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: radial-gradient(ellipse at center, transparent 0%, transparent 60%, rgba(0, 0, 0, 0.6) 100%);
    pointer-events: none;
    z-index: 3;
    border-radius: 20px;
}

@keyframes flicker {
    0% { opacity: 0.27861; }
    5% { opacity: 0.34769; }
    10% { opacity: 0.23604; }
    15% { opacity: 0.90626; }
    20% { opacity: 0.18128; }
    25% { opacity: 0.83891; }
    30% { opacity: 0.65583; }
    35% { opacity: 0.67807; }
    40% { opacity: 0.26559; }
    45% { opacity: 0.84693; }
    50% { opacity: 0.96019; }
    55% { opacity: 0.08594; }
    60% { opacity: 0.20313; }
    65% { opacity: 0.71988; }
    70% { opacity: 0.53455; }
    75% { opacity: 0.37288; }
    80% { opacity: 0.71428; }
    85% { opacity: 0.70419; }
    90% { opacity: 0.7003; }
    95% { opacity: 0.36108; }
    100% { opacity: 0.24387; }
}

@keyframes textShadow {
    0% { text-shadow: 0.4px 0 1px rgba(0,30,255,0.5), -0.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    5% { text-shadow: 2.8px 0 1px rgba(0,30,255,0.5), -2.8px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    10% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    15% { text-shadow: 0.4px 0 1px rgba(0,30,255,0.5), -0.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    20% { text-shadow: 3.5px 0 1px rgba(0,30,255,0.5), -3.5px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    25% { text-shadow: 1.6px 0 1px rgba(0,30,255,0.5), -1.6px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    30% { text-shadow: 0.7px 0 1px rgba(0,30,255,0.5), -0.7px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    35% { text-shadow: 3.9px 0 1px rgba(0,30,255,0.5), -3.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    40% { text-shadow: 3.9px 0 1px rgba(0,30,255,0.5), -3.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    45% { text-shadow: 2.2px 0 1px rgba(0,30,255,0.5), -2.2px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    50% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    55% { text-shadow: 2.4px 0 1px rgba(0,30,255,0.5), -2.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    60% { text-shadow: 2.2px 0 1px rgba(0,30,255,0.5), -2.2px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    65% { text-shadow: 2.9px 0 1px rgba(0,30,255,0.5), -2.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    70% { text-shadow: 0.5px 0 1px rgba(0,30,255,0.5), -0.5px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    75% { text-shadow: 1.9px 0 1px rgba(0,30,255,0.5), -1.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    80% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    85% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    90% { text-shadow: 3.4px 0 1px rgba(0,30,255,0.5), -3.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    95% { text-shadow: 2.2px 0 1px rgba(0,30,255,0.5), -2.2px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    100% { text-shadow: 2.6px 0 1px rgba(0,30,255,0.5), -2.6px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
}

.scanline {
    position: absolute;
    width: 100%;
    height: 2px;
    background: rgba(0, 255, 0, 0.1);
    z-index: 4;
    pointer-events: none;
    animation: scanlineMove 8s linear infinite;
}

@keyframes scanlineMove {
    0% { top: 0%; }
    100% { top: 100%; }
}

.status-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 15px;
    background: #333;
    color: #fff;
    border-radius: 0 0 8px 8px;
    font-size: 13px;
    font-family: monospace;
    margin-top: -8px;
}

.status-left, .status-right {
    display: flex;
    gap: 20px;
}

.status-item {
    display: flex;
    align-items: center;
    gap: 5px;
}

.status-indicator {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #666;
}

.status-indicator.running {
    background: #00ff00;
    box-shadow: 0 0 5px #00ff00;
}

.status-indicator.halted {
    background: #ff0000;
}

.rom-info {
    margin-top: 20px;
    padding: 15px;
    background: #e9ecef;
    border-radius: 8px;
}

.rom-info h4 {
    margin-top: 0;
    margin-bottom: 10px;
}

.rom-info p {
    margin: 5px 0;
    font-size: 14px;
}

.keyboard-hint {
    margin-top: 15px;
    padding: 10px 15px;
    background: #fff3cd;
    border-radius: 4px;
    font-size: 13px;
    color: #856404;
}

#rom-name { font-weight: bold; }
#rom-tips { font-style: italic; margin-top: 10px; }

#cursor {
    display: inline;
    background-color: #00ff00;
    animation: blink 1s step-end infinite;
}

@keyframes blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}
&lt;/style&gt;

&lt;div class="emulator-container"&gt;
    &lt;div class="control-panel"&gt;
        &lt;label for="rom-select"&gt;ROM:&lt;/label&gt;
        &lt;select id="rom-select"&gt;
            &lt;option value=""&gt;-- Select a ROM --&lt;/option&gt;
            &lt;option value="fortran77.bin" selected&gt;Fortran 77 Interpreter&lt;/option&gt;
            &lt;option value="mint.z80.bin"&gt;MINT (Minimal Interpreter)&lt;/option&gt;
            &lt;option value="firth.z80.bin"&gt;Firth Forth&lt;/option&gt;
            &lt;option value="monty.z80.bin"&gt;Monty&lt;/option&gt;
            &lt;option value="pascal.bin"&gt;Retro Pascal&lt;/option&gt;
            &lt;option value="basic_gs47b.bin"&gt;Grant Searle BASIC&lt;/option&gt;
            &lt;option value="efex.bin"&gt;EFEX Monitor&lt;/option&gt;
        &lt;/select&gt;
        &lt;button id="btn-load" class="btn-primary"&gt;Load ROM&lt;/button&gt;
        &lt;button id="btn-reset" class="btn-secondary"&gt;Reset&lt;/button&gt;
        &lt;button id="btn-clear" class="btn-secondary"&gt;Clear&lt;/button&gt;
    &lt;/div&gt;
    &lt;div class="crt-container"&gt;
        &lt;div id="terminal" tabindex="0"&gt;&lt;span id="terminal-content"&gt;&lt;/span&gt;&lt;span id="cursor"&gt; &lt;/span&gt;&lt;/div&gt;
        &lt;div class="vignette"&gt;&lt;/div&gt;
        &lt;div class="scanline"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="status-bar"&gt;
        &lt;div class="status-left"&gt;
            &lt;div class="status-item"&gt;
                &lt;span class="status-indicator" id="status-indicator"&gt;&lt;/span&gt;
                &lt;span id="status-text"&gt;Idle&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="status-item"&gt;
                &lt;span&gt;PC:&lt;/span&gt;
                &lt;span id="status-pc"&gt;0000&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="status-right"&gt;
            &lt;div class="status-item"&gt;
                &lt;span&gt;Cycles:&lt;/span&gt;
                &lt;span id="status-cycles"&gt;0&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="status-item"&gt;
                &lt;span&gt;Speed:&lt;/span&gt;
                &lt;span id="status-speed"&gt;0 MHz&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="keyboard-hint"&gt;
        &lt;strong&gt;Tip:&lt;/strong&gt; Click on the terminal to focus it, then type to send input. Try loading Fortran 77 and entering: &lt;code&gt;INTEGER X&lt;/code&gt; then &lt;code&gt;X = 42&lt;/code&gt; then &lt;code&gt;WRITE(*,*) X&lt;/code&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class="rom-info" id="rom-info"&gt;
    &lt;h5&gt;ROM Information&lt;/h5&gt;
    &lt;p id="rom-name"&gt;&lt;/p&gt;
    &lt;p id="rom-description"&gt;Select a ROM above to load it into the emulator.&lt;/p&gt;
    &lt;p id="rom-tips"&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;script type="module"&gt;
import init, { Z80Emulator } from '/z80-emulator/retro_z80_emulator.js';

const ROM_INFO = {
    'fortran77.bin': {
        name: 'Fortran 77 Interpreter',
        description: 'A Fortran 77 subset interpreter with BCD floating point, DO loops, IF/THEN/ELSE, arrays, and SUBROUTINE/FUNCTION support.',
        serial: 'acia',
        tips: 'Type PROGRAM to start a program, END to finish, then RUN to execute. Try: INTEGER X followed by X = 42 then WRITE(*,*) X'
    },
    'mint.z80.bin': {
        name: 'MINT (Minimal Interpreter)',
        description: 'A minimal stack-based interpreter similar to Forth. Very compact and efficient.',
        serial: 'acia',
        tips: 'MINT uses single-character commands. Try: 1 2 + . (adds 1+2 and prints result)'
    },
    'firth.z80.bin': {
        name: 'Firth Forth',
        description: 'A Forth interpreter by John Hardy. Full Forth implementation with standard words.',
        serial: 'acia',
        tips: 'Standard Forth commands. Try: 2 3 * . (multiplies 2*3 and prints 6)'
    },
    'monty.z80.bin': {
        name: 'Monty',
        description: 'An interpreted language by John Hardy inspired by Python and Forth.',
        serial: 'acia',
        tips: 'Try: print "Hello World" or basic arithmetic expressions.'
    },
    'pascal.bin': {
        name: 'Retro Pascal',
        description: 'A minimal Pascal interpreter in expression evaluation mode.',
        serial: 'acia',
        tips: 'Type arithmetic expressions: 2 + 3 or 10 * 4 + 2. Boolean: TRUE AND FALSE'
    },
    'basic_gs47b.bin': {
        name: 'Grant Searle BASIC',
        description: 'Microsoft BASIC adapted by Grant Searle.',
        serial: '8251',
        tips: 'Standard BASIC. Try: PRINT 2+2 or write a program with line numbers.'
    },
    'efex.bin': {
        name: 'EFEX Monitor',
        description: 'A simple machine code monitor for examining and modifying memory.',
        serial: '8251',
        tips: 'Type ? for help. Use D to dump memory, E to examine/edit.'
    }
};

let emulator = null;
let running = false;
let animationId = null;
let lastCycles = 0;
let lastTime = 0;

const terminal = document.getElementById('terminal');
const terminalContent = document.getElementById('terminal-content');
const cursor = document.getElementById('cursor');
const romSelect = document.getElementById('rom-select');
const btnLoad = document.getElementById('btn-load');
const btnReset = document.getElementById('btn-reset');
const btnClear = document.getElementById('btn-clear');
const statusIndicator = document.getElementById('status-indicator');
const statusText = document.getElementById('status-text');
const statusPC = document.getElementById('status-pc');
const statusCycles = document.getElementById('status-cycles');
const statusSpeed = document.getElementById('status-speed');
const romName = document.getElementById('rom-name');
const romDescription = document.getElementById('rom-description');
const romTips = document.getElementById('rom-tips');

function appendOutput(text) {
    let output = '';
    for (const char of text) {
        const code = char.charCodeAt(0);
        if (code === 13) {
        } else if (code === 10) {
            output += '\n';
        } else if (code === 8) {
            if (terminalContent.textContent.length &gt; 0) {
                terminalContent.textContent = terminalContent.textContent.slice(0, -1);
            }
        } else if (code &gt;= 32 &amp;&amp; code &lt; 127) {
            output += char;
        }
    }
    terminalContent.textContent += output;
    terminal.scrollTop = terminal.scrollHeight;
}

function updateStatus() {
    if (!emulator) return;
    const pc = emulator.get_pc();
    const cycles = emulator.get_cycles();
    const halted = emulator.is_halted();
    statusPC.textContent = pc.toString(16).toUpperCase().padStart(4, '0');
    statusCycles.textContent = cycles.toLocaleString();
    if (halted) {
        statusIndicator.className = 'status-indicator halted';
        statusText.textContent = 'Halted';
        running = false;
    } else if (running) {
        statusIndicator.className = 'status-indicator running';
        statusText.textContent = 'Running';
    } else {
        statusIndicator.className = 'status-indicator';
        statusText.textContent = 'Idle';
    }
    const now = performance.now();
    if (lastTime &gt; 0) {
        const elapsed = (now - lastTime) / 1000;
        const cyclesDelta = Number(cycles) - Number(lastCycles);
        if (elapsed &gt; 0) {
            const mhz = (cyclesDelta / elapsed / 1000000).toFixed(2);
            statusSpeed.textContent = mhz + ' MHz';
        }
    }
    lastCycles = cycles;
    lastTime = now;
}

function runLoop() {
    if (!running || !emulator) return;
    emulator.run(50000);
    const output = emulator.get_output_string();
    if (output.length &gt; 0) {
        appendOutput(output);
    }
    updateStatus();
    if (!emulator.is_halted()) {
        animationId = requestAnimationFrame(runLoop);
    }
}

async function loadRom(filename) {
    if (!emulator) return;
    try {
        terminalContent.textContent = 'Loading ' + filename + '...\n';
        const response = await fetch('/z80-emulator/roms/' + filename);
        if (!response.ok) {
            throw new Error('Failed to load ROM: ' + response.statusText);
        }
        const buffer = await response.arrayBuffer();
        const data = new Uint8Array(buffer);
        emulator.load_rom(data);
        const info = ROM_INFO[filename];
        if (info &amp;&amp; info.serial === '8251') {
            emulator.set_8251_mode(true);
        } else {
            emulator.set_8251_mode(false);
        }
        terminalContent.textContent = '';
        running = true;
        lastCycles = 0;
        lastTime = 0;
        runLoop();
        if (info) {
            romName.textContent = info.name;
            romDescription.textContent = info.description;
            romTips.textContent = 'Tips: ' + info.tips;
        }
    } catch (err) {
        terminalContent.textContent += 'Error: ' + err.message + '\n';
        console.error(err);
    }
}

btnLoad.addEventListener('click', function() {
    const rom = romSelect.value;
    if (rom) {
        if (animationId) {
            cancelAnimationFrame(animationId);
        }
        running = false;
        loadRom(rom);
    }
});

btnReset.addEventListener('click', function() {
    if (emulator) {
        if (animationId) {
            cancelAnimationFrame(animationId);
        }
        emulator.reset();
        terminalContent.textContent = '';
        running = true;
        lastCycles = 0;
        lastTime = 0;
        runLoop();
    }
});

btnClear.addEventListener('click', function() {
    terminalContent.textContent = '';
});

terminal.addEventListener('keydown', function(e) {
    if (!emulator || !running) return;
    if (e.key === 'Enter') {
        emulator.send_char(13);
        e.preventDefault();
    } else if (e.key === 'Backspace') {
        emulator.send_char(8);
        e.preventDefault();
    } else if (e.key.length === 1) {
        emulator.send_char(e.key.charCodeAt(0));
        e.preventDefault();
    }
});

terminal.addEventListener('paste', function(e) {
    if (!emulator || !running) return;
    const text = e.clipboardData.getData('text');
    emulator.send_string(text);
    e.preventDefault();
});

async function main() {
    try {
        await init();
        emulator = new Z80Emulator();
        terminalContent.textContent = 'Z80 Emulator ready. Select a ROM and click "Load ROM" to begin.\n';
        updateStatus();
    } catch (err) {
        terminalContent.textContent = 'Failed to initialize emulator: ' + err.message + '\n';
        console.error(err);
    }
}

main();
&lt;/script&gt;

&lt;hr&gt;
&lt;h3&gt;The RetroShield Platform&lt;/h3&gt;
&lt;p&gt;Before diving into the emulator, it's worth understanding what makes the RetroShield special. Unlike software emulators that simulate a CPU in code, the RetroShield uses a &lt;em&gt;real&lt;/em&gt; vintage microprocessor. The Z80 variant features an actual Zilog Z80 chip running at its native speed, connected to an Arduino Mega or Teensy that provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memory emulation&lt;/strong&gt;: The Arduino's SRAM serves as the Z80's RAM, while program code is stored in the Arduino's flash memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O peripherals&lt;/strong&gt;: Serial communication, typically through an emulated MC6850 ACIA or Intel 8251 USART&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clock generation&lt;/strong&gt;: The Arduino provides the clock signal to the Z80&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This hybrid approach means you get authentic Z80 behavior - every timing quirk, every undocumented opcode - while still having the convenience of USB connectivity and easy program loading.&lt;/p&gt;
&lt;p&gt;The RetroShield is &lt;a href="https://go.tinymachines.io/vpBf6E"&gt;open source hardware&lt;/a&gt; and available on &lt;a href="https://go.tinymachines.io/CkAc8A"&gt;Tindie&lt;/a&gt;. For larger programs, the &lt;a href="https://go.tinymachines.io/uMfyfB"&gt;Teensy adapter&lt;/a&gt; expands available RAM from about 4KB to 256KB.&lt;/p&gt;
&lt;h4&gt;The Hardware Up Close&lt;/h4&gt;
&lt;p&gt;Here's my RetroShield Z80 setup with the Teensy adapter:&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 with Teensy adapter - overhead view" src="https://tinycomputers.io/images/retroshield2/IMG_4141.jpg" title="RetroShield Z80 mounted on Teensy adapter board"&gt;&lt;/p&gt;
&lt;p&gt;The Zilog Z80 CPU sits in the 40-pin DIP socket, with the Teensy 4.1 providing memory emulation and I/O handling beneath.&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 - angled view showing the Z80 chip" src="https://tinycomputers.io/images/retroshield2/IMG_4142.jpg" title="Close-up of the Z80 CPU in its socket"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 - side profile" src="https://tinycomputers.io/images/retroshield2/IMG_4143.jpg" title="Side view of the RetroShield stack"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 - full assembly" src="https://tinycomputers.io/images/retroshield2/IMG_4144.jpg" title="Complete RetroShield Z80 assembly"&gt;&lt;/p&gt;
&lt;p&gt;The physical hardware runs identically to the browser emulator above - the same ROMs, the same interpreters, the same authentic Z80 execution.&lt;/p&gt;
&lt;h3&gt;Why Build a Browser Emulator?&lt;/h3&gt;
&lt;p&gt;Having built several interpreters and tools for the RetroShield, I found myself constantly cycling through the development loop: edit code, compile, flash to Arduino, test, repeat. A software emulator would speed this up significantly, but I also wanted something I could share with others who might not have the hardware.&lt;/p&gt;
&lt;p&gt;WebAssembly seemed like the perfect solution. It runs at near-native speed in any modern browser, requires no installation, and can be embedded directly in a web page. Someone curious about retro computing could try out a Fortran 77 interpreter or Forth environment without buying any hardware.&lt;/p&gt;
&lt;h3&gt;Building the Emulator in Rust&lt;/h3&gt;
&lt;p&gt;I chose Rust for the emulator implementation for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Excellent WASM support&lt;/strong&gt;: Rust's &lt;code&gt;wasm-bindgen&lt;/code&gt; and &lt;code&gt;wasm-pack&lt;/code&gt; tools make compiling to WebAssembly straightforward&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Rust compiles to efficient code, important for cycle-accurate emulation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The rz80 crate&lt;/strong&gt;: Andre Weissflog's &lt;a href="https://go.tinymachines.io/GfWHuG"&gt;rz80&lt;/a&gt; provides a battle-tested Z80 core&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The emulator architecture is straightforward:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="err"&gt;┌─────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Browser&lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌───────────────────────────────────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;JavaScript&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Terminal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CRT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Keyboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handling&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────────────┬─────────────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wasm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bindgen&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────────────▼─────────────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;Rust&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;WebAssembly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────────────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rz80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="n"&gt;KB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Emulation&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RAM&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────────────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────────────────────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Emulation&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MC6850&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ACIA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="o"&gt;/$&lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Intel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8251&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USART&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="o"&gt;/$&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────────────────────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└───────────────────────────────────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└─────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Dual Serial Chip Support&lt;/h4&gt;
&lt;p&gt;One challenge was supporting ROMs that use different serial chips. The RetroShield ecosystem has two common configurations:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MC6850 ACIA&lt;/strong&gt; (ports $80/$81): Used by many homebrew projects including MINT, Firth Forth, and my own Fortran and Pascal interpreters. The ACIA has four registers (control, status, transmit data, receive data) mapped to two ports, with separate read/write functions per port.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Intel 8251 USART&lt;/strong&gt; (ports $00/$01): Used by Grant Searle's popular BASIC port and the EFEX monitor. The 8251 is simpler with just two ports - one for data and one for control/status.&lt;/p&gt;
&lt;p&gt;The emulator detects which chip to use based on ROM metadata and configures the I/O handlers accordingly.&lt;/p&gt;
&lt;h4&gt;Memory Layout&lt;/h4&gt;
&lt;p&gt;The standard RetroShield memory map looks like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address Range&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$0000-$7FFF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32KB&lt;/td&gt;
&lt;td&gt;ROM/RAM (program dependent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$8000-$FFFF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32KB&lt;/td&gt;
&lt;td&gt;Extended RAM (Teensy adapter)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most of my interpreters use a layout where code occupies the lower addresses and data/stack occupy higher memory. The Fortran interpreter, for example, places its program text storage at $6700 and variable storage at $7200, with the stack growing down from $8000.&lt;/p&gt;
&lt;h3&gt;The CRT Effect&lt;/h3&gt;
&lt;p&gt;No retro computing experience would be complete without the warm glow of a CRT monitor. I implemented several visual effects using pure CSS:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scanlines&lt;/strong&gt;: A repeating gradient overlay creates the horizontal line pattern characteristic of CRT displays:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;crt-container&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Chromatic aberration&lt;/strong&gt;: CRT displays have slight color fringing due to the electron beam hitting phosphors at angles. I simulate this with animated text shadows that shift red and blue components:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;keyframes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;textShadow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="mf"&gt;-0.4&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* ... animation continues */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Flicker&lt;/strong&gt;: Real CRTs had subtle brightness variations. A randomized opacity animation creates this effect without being distracting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vignette&lt;/strong&gt;: The edges of CRT screens were typically darker than the center, simulated with a radial gradient.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The font&lt;/strong&gt;: I'm using the &lt;a href="https://go.tinymachines.io/fvDGpq"&gt;Glass TTY VT220&lt;/a&gt; font, a faithful recreation of the DEC VT220 terminal font from the 1980s. It's public domain and adds significant authenticity to the experience.&lt;/p&gt;
&lt;h3&gt;The Language Interpreters&lt;/h3&gt;
&lt;p&gt;The emulator comes pre-loaded with several language interpreters, each running as native Z80 code:&lt;/p&gt;
&lt;h4&gt;Fortran 77 Interpreter&lt;/h4&gt;
&lt;p&gt;This is my most ambitious RetroShield project: a subset of Fortran 77 running interpretively on an 8-bit CPU. It supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REAL numbers&lt;/strong&gt; via BCD (Binary Coded Decimal) floating point with 8 significant digits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INTEGER and REAL variables&lt;/strong&gt; with implicit typing (I-N are integers)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arrays&lt;/strong&gt; up to 3 dimensions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DO loops&lt;/strong&gt; with optional STEP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block IF/THEN/ELSE/ENDIF&lt;/strong&gt; statements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SUBROUTINE and FUNCTION&lt;/strong&gt; subprograms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intrinsic functions&lt;/strong&gt;: ABS, MOD, INT, REAL, SQRT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's a sample session:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;FORTRAN&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt; &lt;span class="n"&gt;Interpreter&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;RetroShield&lt;/span&gt; &lt;span class="n"&gt;Z80&lt;/span&gt;
&lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PROGRAM&lt;/span&gt; &lt;span class="n"&gt;FACTORIAL&lt;/span&gt;
  &lt;span class="kt"&gt;INTEGER&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;
  &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="kr"&gt;DO&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;
  &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;CONTINUE&lt;/span&gt;
  &lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="cm"&gt;(*,*)&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'! ='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;
  &lt;span class="kr"&gt;END&lt;/span&gt;
&lt;span class="n"&gt;Program&lt;/span&gt; &lt;span class="n"&gt;entered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;RUN&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RUN&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5040&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The interpreter is written in C and cross-compiled with SDCC. At roughly 21KB of code, it pushes the limits of what's practical on the base RetroShield, which is why it requires the Teensy adapter.&lt;/p&gt;
&lt;h4&gt;MINT (Minimal Interpreter)&lt;/h4&gt;
&lt;p&gt;MINT is a wonderfully compact stack-based language. Each command is a single character, making it incredibly memory-efficient:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;1 2 + .&lt;/span&gt;
3
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;: SQ D * ;&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;5 SQ .&lt;/span&gt;
25
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Firth Forth&lt;/h4&gt;
&lt;p&gt;A full Forth implementation by John Hardy. Forth's stack-based paradigm and extensibility made it popular on memory-constrained systems:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;: FACTORIAL ( n -- n! ) 1 SWAP 1+ 1 DO I * LOOP ;&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;7 FACTORIAL .&lt;/span&gt;
5040
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Grant Searle's BASIC&lt;/h4&gt;
&lt;p&gt;A port of Microsoft BASIC that provides the classic BASIC experience:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;Z80 BASIC Ver 4.7b
Ok
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;10 FOR I = 1 TO 10&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;20 PRINT I * I&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;30 NEXT I&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;RUN&lt;/span&gt;
1
4
9
...
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Technical Challenges&lt;/h3&gt;
&lt;p&gt;Building this project involved solving several interesting problems:&lt;/p&gt;
&lt;h4&gt;Memory Layout Debugging&lt;/h4&gt;
&lt;p&gt;The Fortran interpreter crashed mysteriously when entering lines with statement labels. After much investigation, I discovered the CODE section had grown to overlap with the DATA section. The linker was told to place data at $5000, but code had grown past that point. The fix was updating the memory layout to give code more room:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c"&gt;# Before: code overlapped data&lt;/span&gt;
&lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--data-loc&lt;span class="w"&gt; &lt;/span&gt;0x5000

&lt;span class="c"&gt;# After: proper separation&lt;/span&gt;
&lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--data-loc&lt;span class="w"&gt; &lt;/span&gt;0x5500
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This kind of bug is particularly insidious because it works fine until the code grows past a certain threshold.&lt;/p&gt;
&lt;h4&gt;BCD Floating Point&lt;/h4&gt;
&lt;p&gt;Implementing floating-point math on a Z80 without hardware support is challenging. I chose BCD (Binary Coded Decimal) representation because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Exact decimal representation&lt;/strong&gt;: No binary floating-point surprises like 0.1 + 0.2 != 0.3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simpler conversion&lt;/strong&gt;: Reading and printing decimal numbers is straightforward&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reasonable precision&lt;/strong&gt;: 8 BCD digits give adequate precision for an educational interpreter&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each BCD number uses 6 bytes: 1 for sign, 1 for exponent, and 4 bytes holding 8 packed decimal digits.&lt;/p&gt;
&lt;h4&gt;Cross-Compilation with SDCC&lt;/h4&gt;
&lt;p&gt;The &lt;a href="https://go.tinymachines.io/pLDqMS"&gt;Small Device C Compiler&lt;/a&gt; (SDCC) targets Z80 and other 8-bit processors. While it's an impressive project, there are quirks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No standard library functions that assume an OS&lt;/li&gt;
&lt;li&gt;Limited optimization compared to modern compilers&lt;/li&gt;
&lt;li&gt;Memory model constraints require careful attention to data placement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wrote a custom &lt;code&gt;crt0.s&lt;/code&gt; startup file that initializes the stack, sets up the serial port, and calls &lt;code&gt;main()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Running the Emulator&lt;/h3&gt;
&lt;p&gt;The emulator runs at roughly 3-4 MHz equivalent speed, depending on your browser and hardware. This is actually faster than the original Z80's typical 4 MHz, but the difference isn't noticeable for interactive use.&lt;/p&gt;
&lt;p&gt;To try it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://tinycomputers.io/pages/z80-emulator.html"&gt;Z80 Emulator page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select a ROM from the dropdown (try Fortran 77)&lt;/li&gt;
&lt;li&gt;Click "Load ROM"&lt;/li&gt;
&lt;li&gt;Click on the terminal to focus it&lt;/li&gt;
&lt;li&gt;Start typing!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For Fortran, try entering a simple program:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;PROGRAM&lt;/span&gt; &lt;span class="n"&gt;HELLO&lt;/span&gt;
&lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="cm"&gt;(*,*)&lt;/span&gt; &lt;span class="s"&gt;'HELLO, WORLD!'&lt;/span&gt;
&lt;span class="kr"&gt;END&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then type &lt;code&gt;RUN&lt;/code&gt; to execute it.&lt;/p&gt;
&lt;h3&gt;What's Next&lt;/h3&gt;
&lt;p&gt;There's always more to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More ROM support&lt;/strong&gt;: Expanding to additional retro languages like LISP, Logo, or Pilot&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugger integration&lt;/strong&gt;: Showing registers, memory, and allowing single-stepping&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save/restore state&lt;/strong&gt;: Persisting the emulator state to browser storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile support&lt;/strong&gt;: Touch-friendly keyboard for tablets and phones&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Source Code and Links&lt;/h3&gt;
&lt;p&gt;Everything is open source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/RyKPqQ"&gt;Emulator source (Rust/WASM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/bkKd9Y"&gt;Fortran interpreter source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/vpBf6E"&gt;RetroShield hardware designs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/A9LeRz"&gt;RetroLang compiler&lt;/a&gt; - my custom language for Z80&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The RetroShield hardware is available from &lt;a href="https://go.tinymachines.io/CkAc8A"&gt;8bitforce on Tindie&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Acknowledgments&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://go.tinymachines.io/pPBdMZ"&gt;Erturk Kocalar&lt;/a&gt;&lt;/strong&gt; - Creator of the RetroShield platform&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://go.tinymachines.io/rWyqQ5"&gt;Andre Weissflog&lt;/a&gt;&lt;/strong&gt; - Author of the rz80 Z80 emulator core&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://go.tinymachines.io/VHk5vu"&gt;Grant Searle&lt;/a&gt;&lt;/strong&gt; - Z80 BASIC and reference designs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://go.tinymachines.io/LFHB7z"&gt;John Hardy&lt;/a&gt;&lt;/strong&gt; - Author of Firth Forth, MINT, and Monty&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's something magical about running 49-year-old CPU architectures in a modern web browser. The Z80 powered countless home computers, embedded systems, and arcade games. With this emulator, that legacy is just a click away.&lt;/p&gt;</description><category>emulator</category><category>forth</category><category>fortran</category><category>retro computing</category><category>retroshield</category><category>rust</category><category>wasm</category><category>webassembly</category><category>z80</category><guid>https://tinycomputers.io/posts/browser-based-z80-emulator-retroshield.html</guid><pubDate>Tue, 09 Dec 2025 03:00:00 GMT</pubDate></item></channel></rss>