logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·31.10.26
← Back to news

Decoding the NEC V20 Microcode

Original article
martypc.blogspot.com|32 points|0 comments|by mariuz|Sep 4, 2026

Unlocking the Secrets of the NEC V20 Microcode

The path toward achieving flawless emulation of the 8088 CPU was paved in 2020 when reenigne successfully decoded its microcode. While I had already integrated NEC V20 support into MartyPC, the implementation was flawed: it lacked cycle-accuracy.

Essentially, my V20 core was merely an 8088 in disguise—a copy of the 8088 core with V20-specific instructions appended. While functional, it wasn't ideal. However, attempting to achieve cycle-exact timing through blind trial and error felt like an exhausting and discouraging prospect.

The Physical Evidence

To solve this, I commissioned InfoSecDJ to capture high-resolution die photographs of an NEC V20. Interestingly, the chip used was a second-source version manufactured by Sharp, though it remains a V20 at its heart.

The resulting photomosaic is staggering in scale: 5.6 Gigapixels (70,478×80,67270,478 \times 80,67270,478×80,672 pixels). The image is so massive that it exceeds the limits of the standard JPEG format.

Analyzing the ROM

The main microcode ROM is located in the rectangular area just below the center of the die.

FeatureSpecification
ROM Array Dimensions258×116258 \times 116258×116
Total Bit Count29,92829,92829,928 bits
Word Length292929 bits
Calculated Words1,0321,0321,032 words

While we expected 1,0241,0241,024 words, the math 29,92829=1,032\frac{29,928}{29} = 1,0322929,928​=1,032 suggests a slightly larger array.

Upon closer inspection of the ROM:

  • Bright horizontal lines: Metal layer traces.
  • Yellowish dots: Interconnects between the metal and the underlying polysilicon.
  • Vertical bars: Polysilicon layers.

Crucially, I noticed that gaps in the polysilicon on either side of an interconnect indicate the presence of a transistor, which represents a binary 1.

The Extraction Process

Extracting nearly 30,000 bits manually is impossible. I initially tried using MaskRomTool by Travis Goodspeed to automate the process.

  • Define bit coordinates in MaskRomTool
  • Use automated thresholding to detect bits →\rightarrow→ FAILED

The thresholding failed because the contrast between "bit" and "non-bit" was too low, partly due to the interference of the bright metal layer. I didn't want to risk damaging the chip by asking InfoSecDJ to remove the metal layer.

A New Strategy: Machine Learning

Instead of automated thresholding, I decided to use a Convolutional Neural Network (CNN).

  1. Coordinate Export: I used MaskRomTool to export the bit positions as a .json file.
  2. Image Slicing: I wrote a Python script to crop a 42×4242 \times 4242×42 pixel square around every single bit position, saving them as individual PNGs. This resulted in a 105MB ZIP file.
  3. Training Data: To teach the AI, I needed a labeled dataset. I built a simple tkinter tool to manually classify bits by pressing 0 or 1 on my keyboard.

"The problem is quite literally 'Hotdog or Not Hot Dog'."

I manually sorted roughly 1,000 bits to create the training and validation sets. Although I had no prior experience with CNNs, I was able to implement the model in a single afternoon using available tutorials.

The Pipeline

Training Results

The CNN learned rapidly. Here is the log of the training epochs:

[Epoch 01] train: loss=0.6945 acc=0.7273 f1=0.0164 | val: loss=0.6924 acc=0.7876 f1=0.0000
[Epoch 02] train: loss=0.6860 acc=0.7151 f1=0.3826 | val: loss=0.6257 acc=0.7965 f1=0.0729
[Epoch 03] train: loss=0.3751 acc=0.8914 f1=0.7213 | val: loss=0.3900 acc=0.7655 f1=0.6327
[Epoch 04] train: loss=0.1159 acc=0.9523 f1=0.9139 | val: loss=0.0495 acc=0.9912 f1=0.9773
[Epoch 05] train: loss=0.0251 acc=0.9945 f1=0.9888 | val: loss=0.0460 acc=0.9867 f1=0.9744
[Epoch 06] train: loss=0.0319 acc=0.9933 f1=0.9802 | val: loss=0.0438 acc=0.9823 f1=0.9659
[Epoch 07] train: loss=0.0185 acc=0.9945 f1=0.9212 | val: loss=0.0274 acc=0.9956 f1=0.9891
[Epoch 08] train: loss=0.0141 acc=0.9956 f1=0.9913 | val: loss=0.0271 acc=0.9956 f1=0.9891
[Epoch 09] train: loss=0.0101 acc=0.9978 f1=0.9940 | val: loss=0.0447 acc=0.9867 f1=0.9735
[Epoch 10] train: ...

Visualizing the Inference

The transition from the first attempt to the final model was stark:

Initial Inference:

Final Inference:

By leveraging AI, the daunting task of manually identifying 29,928 bits was transformed into a manageable afternoon project. The source code for this process is available on my GitHub.