· 9 min read

2020 Year in Review

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Background

2020 was a chaotic year, but it was also a year that prompted me to rethink the essence of computers. Throughout the year, I tried many things outside my comfort zone, mostly revolving around a single theme: rediscovering the low level.

In high school (more accurately, vocational high school), I majored in electronics. In an era where advancing to higher education was the mainstream path, although we had lab classes, it mostly felt like going through the motions, and I spent the majority of my time studying and solving exam problems. My only saving grace was that the academic workload wasn’t as heavy as in a regular high school; otherwise, I probably wouldn’t have made it into Taiwan Tech.

One regrettable thing about vocational school was that I didn’t have much interest in hands-on building. A large portion of the curriculum was spent teaching soldering, breadboarding, keeping wires from overlapping, and avoiding jumper wires—which was honestly quite agonizing for someone as clumsy with their hands as me.

Back then, while studying electronics, I wasn’t particularly interested in the subject, nor did I ever anticipate that I would later make a living by writing code. Yet because of that past, I’ve recently started developing a genuine interest in these low-level fundamentals.

Returning to the question itself, the answer depends on how deeply you grasp computers. Diodes and transistors, registers, CPU operation, operating systems—a lifetime might not even be enough to fully explore any single one of these domains. It really is that profound.

For me, this year was about truly understanding what a computer is. Even though I’m no expert, I found a lot of joy in the process, and I also connected with colleagues at work who share the same interest in the low level.

Overlap with Electronics

When learning electronics in vocational school, I never imagined that this knowledge would prove so useful later on. Things like bridge rectifiers, diodes, filtering, and voltage regulation are circuits frequently used in chargers. Not only did I calculate these hundreds of times for exams, but we were even tasked with building a bridge rectifier circuit in lab classes.

When a CPU performs addition, it needs to implement an adder, which is divided into half adders and full adders. These, too, were things we learned step-by-step through truth tables in class, building them gate by gate on breadboards (or, of course, using ICs directly).

A CPU needs a clock to operate, typically provided by a quartz crystal oscillator or an external clock generator. Wow—that NE555 circuit I struggled so hard to learn back then can actually generate clock signals to drive a CPU! (Though nowadays, they are all built-in, of course.)

The flip-flops that seemed so baffling back then turned out to be the very foundation of registers. As I understood more and more fundamentals, I realized that I had already walked this path once in vocational school; I just hadn’t connected the dots back then.

There is an air of mystery around this low-level knowledge. On one hand, the barrier to entry is relatively high—after all, nobody buys chips to plug into breadboards every day, and you need a baseline understanding across multiple topic areas. On the other hand, there is remarkably little information online; you might search keywords for a long time only to find the answer buried in the n-th comment of some obscure forum. A lot of the time, when you run into problems, you just have to figure out a way to solve them yourself.

Returning to the Fundamentals

In light of this, one of my goals for 2020 was to get as close to the low level as possible. It could be hardware, it could be how operating systems work, or it could be understanding how programming languages run under the hood. Anything that helped me understand the low level was fair game.

So at the beginning of the year, I bought an Arduino. Some might think: isn’t an Arduino just something pre-packaged by others? True, but I figured I could start from here. For a given sensor, instead of reaching for a pre-written library right away, I’d flip through the datasheet to see how to use it, and only look at library implementations if I really got stuck.

In July of this year, I built an air quality monitoring application using an Arduino and an ESP32. It involved MQTT, DHT11, and MH-Z14A, with UART used for data communication. Rather than just stringing libraries together, for the MH-Z14A part, I solidly read through the datasheet and implemented it myself. Although I still used libraries for Wi-Fi and MQTT, I still learned a tremendous amount from the experience.

But that wasn’t enough. I didn’t want the Arduino abstraction layer standing in my way. So, I searched on Amazon for the legendary MOS 6502.

The MOS 6502 is iconic because it was cheap enough while delivering relatively impressive performance, which is why systems like the Famicom and Apple II both adopted this 8-bit CPU. Furthermore, compared to modern CPUs, its instruction set and architecture are far simpler, making it much easier to understand how a CPU works.

At first, I excitedly placed the order and waited for it to arrive. Only later did it hit me that the MOS 6502 doesn’t have built-in EEPROM; you have to write your program to an EEPROM for the MOS 6502 to read. However, living in Japan at the moment, I didn’t have a good channel to buy the EEPROM I needed, so I had to put that path on hold for the time being.

Instead, I turned to AVR. There happened to be an electronic components shop near where I live, selling various ATmega series microcontrollers. So I picked up a few at random (ATmega328, which happens to be the MCU used by the Arduino UNO).

The advantage of using the ATmega328 is, first, its form factor fits nicely on a breadboard. Second, its instruction set is relatively simple. On top of that, it comes with several AVR-specific advantages:

  • 32 registers—far more than other architectures (x86 has 8, ARM has 16)
  • Most instructions execute in just a single clock cycle
  • Most AVR chips have built-in flash and EEPROM, eliminating the need for an external EEPROM and making reads more efficient

Exploring Assembly Language and Hardware Logic

In the hardware world, everything becomes very straightforward—and very inconvenient. Suppose you want to set a certain pin to output high; in pure AVR assembly, you might write something like:

ldi r16, 0x01
out DDRB, r16
out PORTB, r16

First, load 0x01 into register r16, then set the DDRB register (data direction) to 0x01, and finally set PORTB to 0x01. What it’s actually doing is very similar to digitalWrite.

Surprisingly, assembly language wasn’t as difficult as I had imagined, though I imagine writing production-grade assembly for real-world industry use would be quite a challenge.

A Deeper Understanding of Interrupts

I haven’t completely mastered how the interrupt mechanism works from the ground up (including hardware circuitry, etc.), but I’ve gained a much deeper understanding of interrupts.

Generally, a CPU defines an Interrupt Vector to describe what the CPU should do when an interrupt occurs, and whether an interrupt is triggered typically depends on the Global Interrupt Enable bit in SREG. Since this requires a good grasp of register manipulation and bit shifting, AVR usually provides several corresponding helper functions (via avr/interrupt.h).

This part is fascinating, but things can easily go sideways. It’s fine for toy projects, but I really wonder how people manage and debug interrupt mechanisms in production.

Understanding Operating Systems

Around March and April of this year, I worked through an operating systems course on Coursera on and off. From PC (program counter), IR (instruction register), kernel mode, user mode, atomics, and threads, all the way to semaphores—I was still completely in the dark when it came to implementation details. But at least I gained a relatively better conceptual understanding. Recently, I came across a book called Make an OS in 30 Days. Although it looks pretty rudimentary and definitely skips a lot of details, it seems like a lot of fun. Maybe when I have some free time, I’ll give it a shot. Rather than running emulators, I still prefer testing directly on bare metal!

operating system-30

Programming Languages

Early this year, I stumbled upon Yukihiro Matsumoto’s book まつもとゆきひろ 言語のしくみ (How Languages Work). In it, he implements a programming language from scratch (using yacc/lex) and explains the underlying principles and implementation step by step. I was quite amazed by how complete it was—it wasn’t just a simple calculator doing addition and subtraction, but included strings, time handling, numbers, randomness, arrays, atomics, and more. Essentially all the basics a programming language should have were implemented. While I’d love to build something similar, I genuinely struggled to follow the yacc/lex documentation. I’ll take another crack at it once I have a firmer grasp of C.

Additionally, during the iT Home Ironman competition, I tried implementing a lightweight Svelte from scratch (without the reactivity features), writing a simple parser for it. You could say I dipped my toes into programming languages XD.

There’s still so much to learn moving forward! Let’s all keep pushing through this chaotic year together!

Related Posts

Explore Other Topics