<?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 emulator)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/emulator.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 A.C. Jokela 
&lt;!-- div style="width: 100%" --&gt;
&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /&gt; Creative Commons Attribution-ShareAlike&lt;/a&gt;&amp;nbsp;|&amp;nbsp;
&lt;!-- /div --&gt;
</copyright><lastBuildDate>Mon, 06 Apr 2026 22:13:04 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Running CP/M 2.2 on the RetroShield Z80 Emulator</title><link>https://tinycomputers.io/posts/cpm-on-retroshield-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;There's something magical about watching a 45-year-old operating system boot on modern hardware. CP/M 2.2, the operating system that launched a thousand microcomputers and paved the way for MS-DOS, still has lessons to teach us about elegant system design.&lt;/p&gt;
&lt;p&gt;This post documents my journey getting CP/M 2.2 running on the &lt;a href="https://baud.rs/2uKnpv"&gt;RetroShield Z80 emulator&lt;/a&gt;, a &lt;a href="https://baud.rs/R1fDfb"&gt;Rust&lt;/a&gt;-based Z80 emulator I've been developing. The result is a fully functional CP/M system that can run classic software like Zork and WordStar.&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/cpm-on-retroshield-z80_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;7 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;What is CP/M?&lt;/h3&gt;
&lt;p&gt;CP/M (Control Program for Microcomputers) was created by &lt;a href="https://baud.rs/sv0xWH"&gt;Gary Kildall&lt;/a&gt; at Digital Research in 1974. It became the dominant operating system for &lt;a href="https://baud.rs/BttXOW"&gt;8-bit microcomputers&lt;/a&gt; in the late 1970s and early 1980s, running on machines like the Altair 8800, IMSAI 8080, Osborne 1, and Kaypro.&lt;/p&gt;
&lt;p&gt;CP/M's genius was its portability. The system separated into three layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CCP&lt;/strong&gt; (Console Command Processor) - The command line interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BDOS&lt;/strong&gt; (Basic Disk Operating System) - File and I/O services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BIOS&lt;/strong&gt; (Basic Input/Output System) - Hardware abstraction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only the BIOS needed to be rewritten for each machine. This architecture directly influenced MS-DOS and, by extension, every PC operating system that followed.&lt;/p&gt;
&lt;h3&gt;The RetroShield Z80 Emulator&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://baud.rs/gpbDmS"&gt;RetroShield&lt;/a&gt; is a hardware shield that lets you run vintage CPUs on modern microcontrollers. My emulator takes this concept further by providing a complete software simulation of the Z80 and its peripherals.&lt;/p&gt;
&lt;p&gt;The emulator includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full Z80 CPU emulation (via the &lt;code&gt;rz80&lt;/code&gt; crate)&lt;/li&gt;
&lt;li&gt;MC6850 ACIA serial port (console I/O)&lt;/li&gt;
&lt;li&gt;SD card emulation with DMA block transfers&lt;/li&gt;
&lt;li&gt;TUI debugger with memory viewer, disassembly, and single-stepping&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Challenge: Disk I/O&lt;/h3&gt;
&lt;p&gt;Getting CP/M's console I/O working was straightforward. The real challenge was disk I/O. CP/M expects to read and write 128-byte sectors from floppy disks. I needed to emulate this using files on the host system.&lt;/p&gt;
&lt;p&gt;The standard 8" single-sided, single-density floppy format that CP/M uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;77 tracks&lt;/li&gt;
&lt;li&gt;26 sectors per track&lt;/li&gt;
&lt;li&gt;128 bytes per sector&lt;/li&gt;
&lt;li&gt;256KB total capacity&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;DMA Block Transfers&lt;/h4&gt;
&lt;p&gt;Rather than transferring bytes one at a time through I/O ports (which would be painfully slow), I implemented DMA block transfers. The BIOS sets up a DMA address and issues a single command to transfer an entire 128-byte sector:&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;
&lt;span class="nx"&gt;ld&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;hl&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="nx"&gt;DMAADR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;ld&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SD_DMA_LO&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="nx"&gt;ld&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;
&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SD_DMA_HI&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Issue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;
&lt;span class="nx"&gt;xor&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SD_BLOCK&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;a&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="nx"&gt;SD_BLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;ret&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="nx"&gt;A&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="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OK&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On the emulator side, this triggers a direct memory copy from the disk image file into emulated RAM.&lt;/p&gt;
&lt;h4&gt;The Bug That Almost Defeated Me&lt;/h4&gt;
&lt;p&gt;After implementing everything, CP/M would boot and print its banner, but then hang or show garbage. The debug output revealed the BDOS was requesting insane track numbers like 0x0083 instead of track 2.&lt;/p&gt;
&lt;p&gt;The culprit? A classic use-after-move bug in the BIOS:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nx"&gt;SELDSK&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="nx"&gt;Calculate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DPH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HL&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ld&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ld&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;h&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hl&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="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ld&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DPH0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;OPENDISK&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="nx"&gt;BUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;overwrites&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HL&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ret&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="nx"&gt;Returns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;garbage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DPH&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;OPENDISK&lt;/code&gt; subroutine was using HL internally, destroying the Disk Parameter Header address that SELDSK was supposed to return. The BDOS would then read garbage from the wrong memory location for its disk parameters.&lt;/p&gt;
&lt;p&gt;The fix was simple:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;hl&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;OPENDISK&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;hl&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="nx"&gt;Restore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DPH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;24-bit Seek Positions&lt;/h4&gt;
&lt;p&gt;Another issue: the disk images are 256KB, but I initially only supported 16-bit seek positions (64KB max). I added an extended seek port for the high byte:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SD_SEEK_LO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&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="mh"&gt;0x14&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bits 0-7&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SD_SEEK_HI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&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="mh"&gt;0x15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bits 8-15&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SD_SEEK_EX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&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="mh"&gt;0x19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bits 16-23&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;The Memory Map&lt;/h3&gt;
&lt;p&gt;CP/M's memory layout for a 56KB TPA (Transient Program Area):&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="n"&gt;FF&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FCB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;0100&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;DFFF&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;TPA&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;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="n"&gt;KB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;E000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;E7FF&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;CCP&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;Console&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Processor&lt;/span&gt;
&lt;span class="n"&gt;E800&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;F5FF&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;BDOS&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;Basic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Disk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Operating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;
&lt;span class="n"&gt;F600&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;FFFF&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;BIOS&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;Hardware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;abstraction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The BIOS is only about 1KB of &lt;a href="https://baud.rs/EsBekO"&gt;Z80 assembly&lt;/a&gt;, handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Console I/O via the MC6850 ACIA&lt;/li&gt;
&lt;li&gt;Disk I/O via SD card emulation&lt;/li&gt;
&lt;li&gt;Drive selection and track/sector positioning&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Running Classic Software&lt;/h3&gt;
&lt;p&gt;With CP/M booting successfully, I could run classic software:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zork I&lt;/strong&gt; - Infocom's legendary text adventure runs perfectly:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/zork.png" alt="Zork I running on CP/M in the RetroShield TUI emulator" class="img-fluid"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WordStar 3.3&lt;/strong&gt; and &lt;strong&gt;SuperCalc&lt;/strong&gt; also run, though they need terminal escape codes configured properly (the Kaypro version uses ADM-3A codes).&lt;/p&gt;
&lt;h3&gt;Try It Yourself&lt;/h3&gt;
&lt;p&gt;The code is available on GitHub:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://baud.rs/2uKnpv"&gt;RetroShield Z80 Emulator&lt;/a&gt; - The Rust emulator with SD card DMA support&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/mrxmpi"&gt;RetroShield CP/M&lt;/a&gt; - BIOS, boot loader, and CP/M system files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To run:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;emulator/rust
cargo&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--release
./target/release/retroshield_tui&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;storage&lt;span class="w"&gt; &lt;/span&gt;path/to/boot.bin
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Press F5 to run, then type &lt;code&gt;zork1&lt;/code&gt; at the &lt;code&gt;A&amp;gt;&lt;/code&gt; prompt.&lt;/p&gt;
&lt;h3&gt;Lessons Learned&lt;/h3&gt;
&lt;p&gt;Building this system reinforced some timeless principles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Abstraction layers work.&lt;/strong&gt; CP/M's BIOS/BDOS/CCP split made porting trivial. Only 1KB of code needed to be written for a completely new "hardware" platform.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Debug output is essential.&lt;/strong&gt; Adding hex dumps of track/sector values immediately revealed the SELDSK bug.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Read the documentation.&lt;/strong&gt; The &lt;a href="https://tinycomputers.io/data/CPM_2.2_Alteration_Guide_1979.pdf"&gt;CP/M 2.2 System Alteration Guide&lt;/a&gt; is remarkably well-written and explained exactly what the BIOS functions needed to do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Old code still runs.&lt;/strong&gt; With the right emulation layer, 45-year-old binaries execute flawlessly. The &lt;a href="https://baud.rs/EsBekO"&gt;Z80 instruction set&lt;/a&gt; is eternal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There's a certain satisfaction in seeing that &lt;code&gt;A&amp;gt;&lt;/code&gt; prompt appear. It's the same prompt that greeted users in 1977, now running on code I wrote in 2025. The machines change, but the software endures.&lt;/p&gt;</description><category>cp/m</category><category>emulator</category><category>retro computing</category><category>retroshield</category><category>rust</category><category>z80</category><guid>https://tinycomputers.io/posts/cpm-on-retroshield-z80.html</guid><pubDate>Wed, 31 Dec 2025 16: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://baud.rs/zSCNiC"&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;32 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://baud.rs/dqAGfn"&gt;open source hardware&lt;/a&gt; and available on &lt;a href="https://baud.rs/7HAxS8"&gt;Tindie&lt;/a&gt;. For larger programs, the &lt;a href="https://baud.rs/ywxYHT"&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://baud.rs/ST34BV"&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://baud.rs/vWwOWL"&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://baud.rs/XOHX1N"&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://baud.rs/3QBPQL"&gt;Emulator source (Rust/WASM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/Vbb4MU"&gt;Fortran interpreter source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/dqAGfn"&gt;RetroShield hardware designs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/pX8HqT"&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://baud.rs/7HAxS8"&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://baud.rs/zSCNiC"&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://baud.rs/a1YDGs"&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://baud.rs/NgAY2i"&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://baud.rs/XhhL5j"&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><item><title>An Exploration into the TI-84+</title><link>https://tinycomputers.io/posts/exploring-ti-84%2B.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&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/exploring-ti-84+_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;19 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;An Exploration into the TI-84+&lt;/h2&gt;
&lt;h3&gt;0. Preamble&lt;/h3&gt;
&lt;p&gt;I came across several TI-85 calculators in a closet in the house I grew up in.  These got me thinking: graphing calculators are essentially &lt;em&gt;tiny computers&lt;/em&gt;.  Graphing calculators are potentially the first tiny computers.  Long before Raspberry Pi, Pine64, Orange Pi, Banana Pi, and the long list of other contemporary tiny computers that use modern Arm processors, graphing calculators were using Motorola 68000s and Zilog Z80s.  The first Texas Instruments graphing calculator was the TI-81 which was introduced in 1990; it contained a Z80 processor.  I have fond memories of the TI-85 in high school.  Transferring games and other programs between TI-85s before physics or trigonometry class using a transfer cable -- there was no Bluetooth or WiFi.  But, the TI-81, TI-85 and, discussed briefly in the introduction, TI-89 are not the subject of this writeup.  The subject is, in fact, a graphing calculator that I managed to never use: the TI-84.&lt;/p&gt;
&lt;h3&gt;1. Introduction&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/ti-84-plus/signal-2023-08-01-191121_002.thumbnail.jpeg" loading="lazy" style="box-shadow: 0 30px 40px rgba(0,0,0,.1); float: left; padding: 20px 20px 20px 20px;"&gt;The Texas Instruments TI-84 Plus graphing calculator has been a significant tool in the realm of &lt;a href="https://baud.rs/q3nnOl"&gt;education&lt;/a&gt; since its launch in 2004. Its use spans the gamut from middle school math classrooms to university-level courses. Traditionally, students in algebra, geometry, precalculus, calculus, and statistics classes, as well as in some science courses, have found this calculator to be a fundamental tool for understanding complex concepts. The TI-84 Plus allowed students to graph equations, run statistical tests, and perform advanced calculations that are otherwise difficult or time-consuming to do by hand. Its introduction marked a significant shift in how students could interact with mathematics, making abstract concepts more tangible and understandable.  I, being over forty, never used a TI-84+ calculator in any of my schooling.  I entered high school in the mid-1990s and calculator of choice for math and science was the &lt;a href="https://baud.rs/Jg9IJ5"&gt;TI-85&lt;/a&gt;.  The TI-85 also utilized a Z80 processor.  As I progressed mathematically and engineeringly in the early 2000s, I used a &lt;a href="https://baud.rs/hTpEDF"&gt;TI-89&lt;/a&gt;. It was an amazing tool for differential equations and linear algebra. The 89 used a &lt;a href="https://baud.rs/fJY2GX"&gt;M68k&lt;/a&gt; processor; as an aside, I plan on writing a piece on the M68k.  Even as I entered graduate school in my mid-30s, my TI-89 found use in a few of my courses.&lt;/p&gt;
&lt;h3&gt;2. The Humble TI-84+ Graphing Calculator&lt;/h3&gt;
&lt;p&gt;One might wonder why, nearly two decades later, the TI-84 Plus is still in widespread use. There are several reasons for this. First, its durable design, user-friendly interface, and robust suite of features have helped it withstand the test of time. The device is built for longevity, capable of years of regular use without significant wear or loss of functionality. Second, Texas Instruments has kept the calculator updated with new apps and features that have kept it relevant in a continually evolving educational landscape. Perhaps most importantly, the TI-84 Plus is accepted on all major standardized tests, including the SAT, ACT, and Advanced Placement exams in the U.S. This widespread acceptance has cemented the TI-84 Plus as a standard tool in math and science education, despite the advent of newer technologies. Additionally, there's a significant advantage for students and teachers in having a standardized tool that everyone in a class knows how to use, reducing the learning curve and potential technical difficulties that could detract from instructional time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Model Evolution&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TI-84 Plus (2004)&lt;/strong&gt;: The original model runs on a Zilog Z80 microprocessor, has 480 kilobytes of ROM and 24 kilobytes of RAM, and features a 96x64-pixel monochrome LCD. It is powered by four AAA batteries and a backup battery.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TI-84 Plus Silver Edition (2004)&lt;/strong&gt;: Launched alongside the original, this version comes with an expanded 1.5-megabyte flash ROM, enabling more applications and data storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TI-84 Plus C Silver Edition (2013)&lt;/strong&gt;: The first model to offer a color display, it comes with a full-color, high-resolution backlit display, and a rechargeable lithium-polymer battery.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TI-84 Plus CE (2015)&lt;/strong&gt;: Maintains the Zilog Z80 processor but boasts a streamlined design, a high-resolution 320x240-pixel color display, a rechargeable lithium-ion battery, and an expanded 3-megabyte user-accessible flash ROM.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Texas Instruments Operating System (TI-OS)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TI-OS, the operating system on which all TI-84 Plus models run, is primarily written in Z80 assembly language, with certain routines, particularly floating-point ones, in C. As a single-user, single-tasking operating system, it relies on a command-line interface.&lt;/p&gt;
&lt;p&gt;The core functionality of TI-OS involves the management of several key system resources and activities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Input and Output Management&lt;/strong&gt;: It controls inputs from the keypad and outputs to the display, ensuring the calculator responds accurately to user commands.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory Management&lt;/strong&gt;: TI-OS manages the allocation and deallocation of the calculator's memory, which includes the read-only memory (ROM) and random access memory (RAM). This ensures efficient usage of the memory and avoids memory leaks that could otherwise cause the system to crash or slow down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Program Execution&lt;/strong&gt;: TI-OS supports the execution of programs written in TI-BASIC and Z80 assembly languages. Users can develop and run their own programs, extending the calculator's functionality beyond standard computations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;File System&lt;/strong&gt;: It also handles the file system, which organizes and stores user programs and variables. The file system is unique in that it's flat, meaning all variables and programs exist on the same level with no folder structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: It also manages error handling. When the user enters an invalid input or an error occurs during a computation, TI-OS responds with an appropriate error message.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Driver Management&lt;/strong&gt;: The OS also communicates with hardware components such as the display and keypad via drivers, and facilitates functions such as powering the system on and off, putting it to sleep, or waking it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Texas Instruments periodically releases updates to TI-OS, introducing new features, security updates, and bug fixes, ensuring a continually improved user experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Software and Functionality&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The TI-84 Plus series maintains backward compatibility with TI-83 Plus software, providing access to a wide library of resources. Texas Instruments has fostered third-party software development for the TI-84 Plus series, resulting in a rich variety of applications that expand the calculator's functionality beyond mathematical computations.&lt;/p&gt;
&lt;h3&gt;3. The Humble Z80 Processor&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://baud.rs/IboIHD"&gt;Zilog Z80 microprocessor&lt;/a&gt; found its way into a myriad of systems, from early personal computers to game consoles, embedded systems, and graphing calculators like the TI-84 Plus. Despite being a nearly 50-year-old technology, it still finds application today, and there are several reasons for this.&lt;/p&gt;
&lt;p&gt;The Z80's design is simple, robust, and reliable. Despite being a &lt;a href="https://baud.rs/8OWUqC"&gt;CISC architecture&lt;/a&gt;, it has a relatively small instruction set that is easy to program, which makes it a good choice for teaching purposes in computer science and electronic engineering courses. The Z80 is also relatively inexpensive and energy-efficient, which can be crucial in certain embedded systems applications.&lt;/p&gt;
&lt;p&gt;The longevity of the Z80 can also be attributed to its presence in legacy systems. A lot of older, yet still functioning machinery (be it industrial, medical, or scientific) rely on Z80 chips for their operation. Replacing these systems entirely just to update the microprocessor might be prohibitively expensive or practically unfeasible, especially when they continue to perform their intended functions adequately.&lt;/p&gt;
&lt;p&gt;The Z80 is not exactly a new piece of technology, and much of the documentation on it is rather old, but there are a number of books available: &lt;a href="https://baud.rs/EZ3Bwg"&gt;here&lt;/a&gt;, &lt;a href="https://baud.rs/3hw1CF"&gt;here&lt;/a&gt; and &lt;a href="https://baud.rs/kiLcPY"&gt;here&lt;/a&gt;.  There is also an official &lt;a href="https://baud.rs/EESjG1"&gt;Zilog Z80 CPU User Manual&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;4. Z80 Assembly Language: Hello World&lt;/h3&gt;
&lt;p&gt;Consider the 'Hello World' program in Z80 assembly language:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c1"&gt;#include "ti83plus.inc"&lt;/span&gt;
&lt;span class="na"&gt;.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;$BB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="no"&gt;$6D&lt;/span&gt;
&lt;span class="na"&gt;.org&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="no"&gt;D95h&lt;/span&gt;
&lt;span class="na"&gt;.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t2ByteTok&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;ld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="no"&gt;txtHello&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;bcall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;_puts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;
&lt;span class="nl"&gt;txtHello:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="na"&gt;.end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The given code is a Z80 assembly program designed for the TI-84+ calculator, which uses a Z80 processor. The code is meant to display the "Hello World" message on the calculator's screen. Here's an explanation of each part:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;#include "ti83plus.inc"&lt;/code&gt;: This line includes the &lt;a href="https://tinycomputers.io/data/ti83plus.inc"&gt;&lt;code&gt;ti83plus.inc&lt;/code&gt;&lt;/a&gt; file, which usually contains definitions of constants and routines specific to the TI-83+/TI-84+ calculators. It helps the assembler to understand specific labels, constants, and ROM calls used in the code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.org 9D95h&lt;/code&gt;: The &lt;code&gt;.org&lt;/code&gt; directive is used to set the program counter to a specific address, here &lt;code&gt;0x9D95&lt;/code&gt;. It is specifying where in memory the following code should be loaded.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ld hl,txtHello&lt;/code&gt;: This line loads the address of the label &lt;code&gt;txtHello&lt;/code&gt; into the register pair HL. In this context, it's preparing to display the text string located at that address.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;bcall(_puts)&lt;/code&gt;: The &lt;code&gt;bcall&lt;/code&gt; instruction is specific to the TI-83+/TI-84+ calculators and is used to call a routine from the calculator's ROM. In this case, it's calling the &lt;code&gt;_puts&lt;/code&gt; routine, which is typically used to print a null-terminated string to the screen. The address of the string is already loaded into HL, so this call will print "Hello World" to the display.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ret&lt;/code&gt;: This is the return instruction, which will return to whatever code called this routine. If this code is the main program, it effectively ends the program.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;txtHello:&lt;/code&gt;: This is a label used to mark the location of the "Hello World" string.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.db "Hello World",0&lt;/code&gt;: This directive defines a sequence of bytes representing the ASCII characters for "Hello World", followed by a null byte (&lt;code&gt;0&lt;/code&gt;). This null-terminated string is what gets printed by the &lt;code&gt;_puts&lt;/code&gt; routine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.end&lt;/code&gt;: This directive marks the end of the source file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;5. Assembling&lt;/h3&gt;
&lt;h4&gt;Downloading, Compiling, and Running the Z80 Assembler SPASM-ng&lt;/h4&gt;
&lt;p&gt;The Z80 Assembler SPASM-ng is an open-source assembler for the Z80 microprocessor.&lt;/p&gt;
&lt;h4&gt;Section 1: Downloading SPASM-ng&lt;/h4&gt;
&lt;h5&gt;1.1 Requirements&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Git (for cloning the repository)&lt;/li&gt;
&lt;li&gt;A compatible C compiler&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;1.2 Process&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Open the terminal or command prompt.&lt;/li&gt;
&lt;li&gt;Clone the repository using the following command:
   &lt;code&gt;git clone https://github.com/alberthdev/spasm-ng.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Navigate to the downloaded directory:
   &lt;code&gt;cd spasm-ng&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Section 2: Compiling SPASM-ng&lt;/h4&gt;
&lt;p&gt;Once downloaded, SPASM-ng needs to be compiled.&lt;/p&gt;
&lt;h5&gt;2.1 Install dependencies&lt;/h5&gt;
&lt;p&gt;Suggested packages for Ubuntu/Debian:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build-essential&lt;/li&gt;
&lt;li&gt;libssl-dev&lt;/li&gt;
&lt;li&gt;zlib1g-dev&lt;/li&gt;
&lt;li&gt;libgmp-dev&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;2.2 Compiling on Linux/Unix&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Compile the source code:
   &lt;code&gt;make&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install:
   &lt;code&gt;sudo make install&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Section 3: Running SPASM-ng&lt;/h4&gt;
&lt;p&gt;Once compiled, SPASM-ng can be used to assemble Z80 programs.&lt;/p&gt;
&lt;h5&gt;3.1 Basic Usage&lt;/h5&gt;
&lt;p&gt;The basic command for assembling a file is:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;./spasm input.asm output.8xp
&lt;/pre&gt;&lt;/div&gt;

&lt;h5&gt;3.2 Additional Options&lt;/h5&gt;
&lt;p&gt;SPASM-ng offers various command-line options for different assembly needs. Run:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;./spasm -h
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to see all available options.&lt;/p&gt;
&lt;h3&gt;6. Running the Program&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/ti-84-plus/screenshot.png" loading="lazy" style="box-shadow: 0 30px 40px rgba(0,0,0,.1); float: left; padding: 20px 20px 20px 20px; zoom: 200%;"&gt;The last step is running the 'Hello World' program on the TI-84+ calculator. The TI-84+ calculator interface has several buttons similar to a physical calculator, which are used to interact with the software. Here's how to execute the program:&lt;/p&gt;
&lt;p&gt;To initiate the process, select &lt;code&gt;2ND&lt;/code&gt; followed by &lt;code&gt;0&lt;/code&gt; (CATALOG), select &lt;code&gt;ASM&lt;/code&gt;. Press the &lt;code&gt;PRGM&lt;/code&gt; button on the calculator. This action opens a list of available programs on the calculator. Navigate this list using the arrow keys provided on the calculator's interface.&lt;/p&gt;
&lt;p&gt;Once you locate your program (named after your .8xp file) press &lt;code&gt;ENTER&lt;/code&gt;. This action displays the name of the program on the calculator's home screen.&lt;/p&gt;
&lt;p&gt;Close the parentheses - &lt;code&gt;)&lt;/code&gt; - to run the program, press &lt;code&gt;ENTER&lt;/code&gt; again. With this action, the TI-84+ calculator executes the program. If the program has been correctly written and uploaded, you should see the 'Hello World' message displayed on the screen. This signals that your program ran successfully.&lt;/p&gt;</description><category>emulator</category><category>spasm</category><category>texas instructments</category><category>ti-84</category><category>z80</category><guid>https://tinycomputers.io/posts/exploring-ti-84%2B.html</guid><pubDate>Sat, 29 Jul 2023 23:35:11 GMT</pubDate></item></channel></rss>