From ZX Spectrum to Blazor: Emulating a ZX Spectrum with .NET 7 and Blazor
Every journey begins with a single step; mine started with the hum of a ZX Spectrum. Picture this – the year is 1985, and a wide-eyed seven-year-old me is unwrapping my first ever computer, a Sinclair ZX Spectrum. The device might seem quaint by today's standards, but back then, it was a portal to a world of infinite possibilities.
The first order of business? A simple yet profound line of code:
- 10 PRINT "Jimmy"
- 20 GOTO 10
- This wasn't just code. It was a proclamation, a declaration of my intent to bend the machine to my will. And in that electrifying moment, the seed was planted – I knew I was destined to become a developer.
- The ZX Spectrum was just the beginning, the gateway into an expanding universe of Commodore 64, Amiga 500, and more. But it was the Spectrum, that humble start, that secured an irreplaceable spot in the annals of my heart.
As I grew and honed my skills, a tantalizing question kept gnawing at me - "Could I, perhaps, build an emulator?" And thus began a quest, an odyssey across the vast landscapes of development that I'm eager to share with you in this session.
From nostalgic 1985 to the cutting-edge era of XNA, Zune, XBOX, HoloLens, and finally landing in the realm of Blazor, I've traversed a diverse spectrum of tech. We'll delve into the nuts and bolts - registers, memory management, the symphony of sound, the dance of input and output, all within the tapestry of a Blazor WebAssembly application.
So join me, as we embark on this thrilling journey, retracing the steps of my path from a starry-eyed child to a seasoned developer. Let's turn back time, dive into the depths of coding, and rekindle the magic of our own origin stories.
- Jimmy Engström has been developing ever since he was 7 years old and got his first computer.
- He loves to be on the cutting edge of technology, trying new things.
- When he got wind of Blazor, he immediately realized the potential and adopted it already when it was in beta. He has been running Blazor in production since it was launched by Microsoft.
- He is the author of "Web Development with Blazor" and the co-host of Coding After Work (podcast and stream.
- His passion for the .NET industry and community has taken him around the world, speaking about development. Microsoft has recognized this passion by awarding him the Microsoft Most Valuable Professional award 9 years in a row.
- He occasionally blogs at engstromjimmy.com, codingafterwork.com
- LinkedIn, Twitter
Heroes and villains always have an origin story. Now, I don't want to say that I'm a hero or a villain, but this is my origin story. While making this presentation, I saw that almost anything in my life can be connected to the ZX Spectrum. So, this became a more personal presentation than I first expected it to be. I will take you on a journey, my journey, from the 80s till today. But we will start with a bit of history. This presentation starts in 1980. In January of 1980, the Sinclair ZX80 was released. It was available in the UK for less than a hundred pounds. The same year, the VIC-20 and the TRS-80 also came out. In 1981, the ZX-81 was released. It was 50 pounds as a kit, or you could get it for 70 pounds as a pre-built machine.
In April of 1982, the Sinclair ZX Spectrum was released in Europe. It became the best-selling British computer, selling about 5 million devices. In August, the Commodore 64 came out and sold around 13 to 17 million devices. The C64 was the second most popular computer in the UK. What's really cool about the ZX Spectrum is that it has clones all over the world—five official clones and more than 70 unofficial clones. If we add the clones and the official devices, we get over 13 million sold devices. This brings us to 1985.
The second most important device in the UK is the ZX Spectrum. The second most important thing that happened to me in 1985 was that I saw Empire Strikes Back. The really annoying thing was that I had already seen Return of the Jedi. So, let me introduce the new way of watching Star Wars: The Jimmy way. You will see 4-6-5. It was kind of, yeah, let's not go into that one. But that was also the year my parents got me into Star Wars. I remember that I had already bought my first computer, and you probably guessed it, it was the ZX Spectrum. Now I remember that my parents bought it, and my brother brought it home. I connected everything, and the first thing I did was type 10 PRINT Jimmy, 20 GOTO 10. This was my code. I made the computer do things, and this was a very defining moment for me because it was when I realized and decided that I wanted to become a developer.
Back then, you didn't bring out the camera to take pictures of everyday things or whatever you had for dinner. So, this is the only image I found with me and a ZX Spectrum from around that time period. As you may or may not see, it's a black and white TV. The really, really cool thing about the ZX Spectrum was that it had 15 different colors. It was one of the first home computers that had all of these colors, but I had 15 shades of gray. You can see my sister sitting there beside me as well. But here's where I come into the story.
My name is Jimmy, and I'm a Microsoft MVP. I've been running Blazor in production since the beginning of last year, since it came out. But we will come back to Blazor in a couple of years. Back then, we didn't have the internet. We had books containing code, and this book, for example, was translated into many different languages. We also had magazines that we could buy containing a lot of code. Basically, what you did was take the book and start typing. I mean, this one is pretty easy to understand, but there are others that were a whole lot more difficult to understand when you're just typing it in. Imagine that this is kind of like the eighties GitHub. This was how we source-controlled things back then. My brother worked for a software company, specifically the Swedish newspaper called Expressen. He did both coding and reviewed games and other content in the daily press. We also had a community radio that broadcasted games or programs over the air. I would argue that the community radio broadcasting was the equivalent of Pirate Bay during that time.
Now, why am I talking about this? Well, it kind of proves how mainstream the ZX Spectrum was. It was in the daily press, it was on radio stations, and so on. It was a huge machine here in Sweden. As the years passed, I got a ZX Spectrum 128 or ZX Spectrum Plus, a Commodore 64, a Commodore Amiga, followed by a PC. I was pretty interested in emulation, and one of my first web pages that I created was about emulation. Now, I can't believe I'm actually going to show you this, but this was that page. And if you look over there, apparently, I thought that Microsoft FrontPage was something to be proud of. I want to show this off. I made this in FrontPage.
Despite all of this, I actually landed my first job as a web developer in 1999, working with ASP. I had a huge interest in emulation, mostly Amiga at that point. When the Xbox came out in 2002, the idea of emulation kind of resurfaced in my mind. So I thought, maybe I can build an emulator for the ZX Spectrum for the original Xbox. Now, there were a couple of problems here. The first one was that I didn't know C++. But then I found an emulator called Pocket Clive, a port of another ZX Spectrum emulator called Fuse. The nice thing was that Pocket Clive was written by a Swede. So I figured out how to contact him, and he helped me with what I needed to look at. He helped me to figure out what I needed and, more importantly, what I didn't need.
Now, I knew enough C++, so I actually got it working. This is a screenshot of what that emulator looked like. The problem back then was that the XTK was not open. You had to have a hacked Xbox to even be able to write code for the Xbox, for the original Xbox. So the dream of releasing that emulator kind of died. But you will see the ZXbox return in a couple of minutes. So that is the origin of the name of the emulator. In 2002, .NET came out. I dove right into VB.NET, thinking, 'Oh yes, this is so cool.' I had a colleague who said, 'No, no, no. This is what we're going to do. We are going to focus on C#.' And I'm like, 'What? No, no, no. We know VB. We know ASP. We are already set. We can do this. VB.NET, that's the thing.' They said, 'Well, we've been developing in VB and ASP for a very, very long time. We have a lot of old habits. So what we're going to do is learn something new. We're going to learn C# and not bring over all of those bad habits.' That was great advice.
Around 2006, the first version of XNA came out. Suddenly, it was possible to create games for the Xbox 360. The idea popped back into my head. It's time. It is time to build an emulator in C#. So I went on eBay and got this book. Now, it's apparently completely transparent: 'The Programming, the Z80' by Rodney Sachs. So I took the book. I turned the page and started implementing the first instruction. Before we do that, we need to talk about what it takes. If we go back a bit and discuss the different things a computer can do, a CPU can perform operation codes or opcodes. Essentially, these are assembler instructions. We'll break this down into what that actually means, but in short, it's all about adding things, moving things in memory.
Now, the ROM differs in various languages. For instance, this is the Swedish ZX Spectrum, which includes Swedish characters. However, distributing the ROM is not allowed, which also means you cannot distribute the emulator. This is why emulation is often not allowed in the Microsoft Store or any other stores. In 1986, Amstrad bought Sinclair and stated that if you don't tamper with it and don't remove the Sinclair logo, you're fine to distribute it. This was fantastic news for those working with emulators. The ZX Spectrum has different registers. Think of them as bytes, labeled A, F, B, C, D, E, H, L, and their prime versions. You can combine these 8-bit registers into 16-bit registers. It also has a special register, the F register, which contains flags such as sign, zero, half carry, parity, etc.
I started thinking about how to implement that register in the emulator. I had two options: use a byte or use bools. I chose bools because it was easier to set all these flags, considering some instructions required setting the half carry, for example. The sign bit is the most significant bit in the A register, and so on. It may sound math-y, but it's well-documented in 'The Programming, the Z80' book, which we'll revisit shortly. There are other registers too, like the program counter, pointing to a location in memory, the stack pointer (SP), and index registers (IX and IY). We also have some special registers, but we won't delve into those in this talk.
When looking at how the registers were accessed, we'll come back to that shortly. I realized I could put them into an array, the easiest way to access registers. This wasn't the first iteration, and I might turn that back to bytes soon. I have no idea why I decided it should be 'INS' at some point during the emulator development. I'll probably change that back to 'bytes' very soon. Then, there are some nice properties like this one, allowing me to get the A register from just an 'A' property. That's a whole lot easier. And then, I have the combined 16-bit versions as well. I'm a self-taught developer. So, all of these bit shifts and AND or OR operators, this was totally new for me. It's something that I definitely don't usually use when I develop. So, it really keeps me on my toes.
The program counter points to a place in the memory, and whatever is in that memory position gets executed. Once it's done, it moves to the next position. For example, if it's pointing at position two, which has the opcode 78, let's look a bit deeper into that one. The program counter (PC) is pointing to 78, represented in binary as shown. The assembler instruction is LDAB, which loads the A register with the value from the B register. In simpler terms, it instructs to take the value from the B variable and put it into the A variable. Consulting the ZX80 book, we see that 78 corresponds to the opcode for loading A with B. The book provides timing information, stating that it takes four T states (which we'll come back to later). There's also a matrix filled with opcodes, confirming that the code 78 loads A with B and has no effect on flags.
Now, if we want to extract the value, we take the first three bits of the opcode. For example, with hex value 78, a bit mask of 7 leaves us with 0000. Referring back to the book, 000 corresponds to the B register. Initially, I could have duplicated this code seven times for different source and destination registers, resulting in redundant code. However, the 78 opcode encompasses both loading A with B and subtracting the value from B into the A register. Instead, I used a method to get the value out of the register, making it applicable for all cases.
This code reduction was significant. What used to be around 80 lines of code is now reduced to 10, showcasing the power of working with emulators and pushing the boundaries. Even with the release of C# 9, I could rewrite and make it more concise, although readability might vary. Nonetheless, it's fewer lines of code, which is fantastic. Looking at the different instructions, we have no prefix opcodes, meaning that 78 is a no prefix instruction. Then we have ED, which is a prefix with 78 different opcodes. CD prefix has 256 different opcodes. The DD prefix has 85, and the DD-CB has 256 different opcodes. When you put all of this together, including some undocumented ones, you end up with about a thousand different instructions.
Now, let's talk about timing. The Z80 is running at 3.5 MHz with 50 frames per second, resulting in 69,880 T-states per frame update. This gives us 20 milliseconds per frame update. Taking a look at the memory, we have the ROM, followed by screen memory and screen attributes. We also have the printer buffer, a bit of system, some reserved space, and an addressable chunk of memory where we can do some cool stuff. It ends with a bit of system memory. This memory is addressable through a 16-bit register, requiring the combination of two 8-bit registers. In total, we have 16 kilobytes of ROM and 48 kilobytes of RAM, giving us 65 kilobytes accessible through two 8-bit registers.
The 128k Spectrum uses memory banks to address 128k but still uses only two 8-bit registers. The screen is 256 times 192 with a border around it. The emulator has a setting to control the border size. The screen has two segments for pixels and color attributes. Pixel data is stored interestingly, with a memory fill from top to bottom appearing like a zigzag pattern. It's stored in three segments: top, middle, and bottom. The screen provides one pixel per bit, but we can add attributes to these pixels for color, named paper or ink. The ZX Spectrum has 15 colors, each with a bright version. The attributes are stored in memory, with each segment being 8 by 8 pixels. This setup allows changing colors for each 8 by 8 section. In these 8 by 8 pixels, there are three bits for the ink, three bits for the paper, one bit for brightness, and one bit for blinking (switching between ink and paper). Let's take a look at how that would look. This, by the way, is one of my favorite games. As we load it, you can hear the distinctive sound of a ZX Spectrum loading. The process is sped up significantly. You can observe the zigzag pattern with three segments: top, middle, and bottom. The colors and blinking are applied when the attributes load.
Some challenges involve converting hardware information to software, understanding how to code hardware, and translating concepts like a resistor between the mic and the ear into code. Avoiding the garbage collector is crucial because if it kicks in within those 20 milliseconds, it can result in poor sound or choppy graphics. I use temporary variables as fields to prevent unnecessary allocations. Working with bits, bitshift, and logical operations is not typical in my daily life as a developer. Tests are essential, so I converted Fuse emulator tests into Visual Studio tests. This allows me to refactor confidently, knowing that as long as the tests hit the same points, it should work. Around 2006-2007, I got the first version running on Xbox 360 with keyboard support but no sound. Here are some screenshots of what it looked like.
After someone released an emulator just before I was done, my interest waned. In 2008, after a long day at work, a new version of XNA was released with Zune deployment support. Despite exhaustion, I tested it, and it worked on my Zune, earning me a nomination for Geek of the Year. In 2011, I attended Tech Days, received the Geek of the Year award, and showcased my work at Microsoft Headquarters in Sweden, opening doors for new opportunities. And I received the MVP award. It was really unexpected, and I felt honored to be recognized for my contributions to the community.
With the MVP award, I continued my journey of sharing knowledge and experiences with the community. I've been writing articles, creating videos, and speaking at various conferences and events. One of the key topics I've been passionate about is the intersection of technology and human experience, exploring how innovations like artificial intelligence, augmented reality, and more can enhance our lives. As we move forward, I'm excited to see how technology will continue to evolve and shape our world. Whether it's breakthroughs in AI, advancements in virtual reality, or new ways of connecting with each other, the possibilities are endless. I'll keep learning, sharing, and exploring the frontiers of technology, always with the aim of making a positive impact on the world.
Thank you for joining me on this journey, and I look forward to the exciting developments that lie ahead. If you have any questions, thoughts, or topics you'd like to explore together, feel free to share them. Let's keep the conversation going! And I showed my colleague, 'Hey, I just want to show you this.' I think it was a select box or something like that. I just showed him, and he was like, 'We're doing this. This is what we're going to do. This is the feature that we've been waiting for.' So seven days later, we were running it in production. Being a fan of Blazor, I wrote a book about it. The second edition just came out. And this is the only Blazor book that has raccoons on the cover. But this leads us into the more Blazor-specific things in the implementation.
Let's start the emulator here. You'll see that it starts, and I can even load a game. If I turn on the sound, you will hear the sound working as well. Here, I can play the game and do stuff. Well, you can try it yourself. As you have seen, I have made Manic Miner for many different platforms. I have spent hours testing it, getting it to work, trying to beat the first level of Manic Miner. I have still not, to this day, been able to beat the first level. So if you do, please let me know. I'm pretty sure you can't even beat it. I actually have a colleague that sometimes calls me up on Teams and says, 'Jimmy, Jimmy, you gotta check this out.' Oh, I'm intrigued. What is it? And he shares his screen, showing me how to finish just the last seconds of the first level. See, it can be beaten.
So what does the future hold? Well, I would love to be able to implement the Car Micro Speech. The next thing I think I will do, though, is to implement the printer. Now, I never had one of these printers. My brother had one. It has aluminum foil that gets burned, so it has a very, very small, very special smell. And I think that the implementation of this should be fairly easy to do. Not the smell, but the printing. So that is one of the things that I would really love to do. The ZX Spectrum is the reason I decided that I wanted to become a developer. It's the reason that I became a developer, which is the reason that I met my wife. It's the reason I became a developer.