The 8051 “MCU” on the SG2000
Assessment date: 2026-10-13 Boards: Milk-V Duo S (SG2000), Pine64 Oz64 (SG2000) Status: Reasoned from source — not board-tested. Everything below is taken from the SG2000 TRM (Chapter 14 “8051 Subsystem”, Chapter 5 “Power Management”, Chapter 6 “Real Time Clock”), the official Sophgo 8051_Core_Quick_Start_Guide, the Duo S / Oz64 schematics, and the NatTuck/sophgo-sg200x-debian image tree. No custom 8051 firmware has been built or loaded on hardware yet.
This is the third core, and it is not either of the two C906s the other hints target. If you arrived here from
fishwaldo-arduino.md, note that the little RISC-V core (cv181x-c906_1) and the 8051 are completely different pieces of silicon.
Summary
The SG2000 is a quad-core chip: 2 x C906, 1 x Arm Cortex A53, 1 x 8051 (TRM §2.1). The 8051 is a small, always-on microcontroller that lives inside the RTC subsystem, on a power rail that stays up when the rest of the SoC is asleep. Its designed job is to run while the main system is off, decide when to wake it, and (optionally) talk to external chips over the RTC-domain peripherals.
For our images, three things matter:
- It is not in the boot chain. The FIP carries BL2, OpenSBI, U-Boot and the C906L coprocessor images — no 8051 component. Nothing on the normal boot path needs it.
- Our images ship no 8051 firmware and hold the core in reset. The images built by
sophgo-sg200x-debiancontain nomars_mcu_fw.bin, no8051_up, no DDR carveout for it, and never releasereg_soft_rstn_mcu. The 8051 is inert. - It is optional, and its value is narrow. The always-on power-management machinery (the RTC power state machine, wake sources, rail sequencing) is hardware and is already driven by Linux via the
cvitek,rtcdriver. The 8051 only adds something in the window where the AP is powered off and you want programmable behaviour during that window.
Where it lives, and the “MCU” naming trap
The 8051 subsystem is inside RTCSYS, in the No-die (always-on) domain (TRM §14.1, §5.1.1.2). RTCSYS has two sub-domains:
- AO (Always-On) domain — RTC, WDT, interrupt controller, GPIO, SARADC, IRRX.
- MCU domain (
VDDC_SW_MCU) — the 8051 + 8 KB AHB SRAM, 2x 32-bit counters, SPI-NOR control, 2x mailbox, I2C, UART. This domain can be powered off and on independently.
The chip has a low-power mode called “MCU-Only (32k-less) mode” (§5.1.2) in which only the No-die domain runs: “a small MCU system with its own clock, timer, uart, i2c, gpio, adc … can replace the SoC’s external MCU. The MCU in the No-die Domain can wake up the main system and return to Active Mode after receiving and filtering external input.” Idle current in that state is ~200 µA.
Naming trap. The word “MCU” means two different cores in the vendor ecosystem:
Context “MCU” means Evidence SG2000 TRM the 8051 §5.1.1.2 “An 8051 microcontroller (MCU)”, the “MCU domain”, “MCU-Only mode” Vendor SDK / Linux the little C906 ( cv181x-c906_1)firmware c906-mcu.elf, the FSBL’sMCU_STATUS_*enum,fast_image_mcuWhen the TRM says MCU it means the 8051. When the SDK says mcu it almost always means the C906L.
What it can do
Core (TRM §14.2):
| Feature | Value |
|---|---|
| ISA | standard 8051 (8-bit) |
| Frequency | 25 MHz – 300 MHz |
| Data access | 32-bit data access supported |
| Code size | code banking, max 64 x 64 KB (4 MB) |
| Boot source | reset vector configurable to AHB SRAM / DRAM / SPINOR |
| Low power | WFI (clock gating) |
| Debug | single-step, jump2pc, snapshot of PSW / DPTR / PC |
Peripherals it can drive (TRM §5.1.1.2, §14.1, quick-start guide):
- Its own: 3x 16-bit timers, 1 UART (muxed to
PWR_UART_RX/TX). - RTC/No-die domain: I2C, SPI-NOR, SD, WDT, GPIO, SARADC, IRRX.
- When the AP is powered, the whole
0x0–0xFFFF_FFFFAP peripheral space — but only AP interrupts routed through the “aptortc” table can reach it.
Interrupts (TRM Table 14.1):
| # | Source | # | Source |
|---|---|---|---|
| 0 | Vbat_det (power-down) |
8 | timer_int1 |
| 1 | mbox_int0 (mailbox) |
9 | irq_ap2rtc[0] |
| 2 | reserved | 10 | irq_ap2rtc[1] |
| 3 | irrx |
11 | i2c_int |
| 4 | gpio_int (PWR GPIO) |
12 | st_change_int (RTC status) |
| 5 | uart_int (PWR UART) |
13 | hw_thm_shdn (overheat) |
| 6 | spinor1_int |
14 | saradc |
| 7 | timer_int0 |
15 | wdt_int |
Inter-core comms: 2x Mailbox (ACPU <-> 8051), each with 2 spinlock fields and 4x 32-bit info fields (RTCSYS_MBOX @ 0x0502_4000, ap_mailbox @ 0x0190_0000). External interrupts arrive on int0_n/int1_n; the AP side arrives as irq_ap2rtc[0]/[1].
Power management: mechanism vs. policy
This is the part that is easy to get wrong. “Power management” on this chip is mostly internal — it is about the SoC’s own power state and the board’s power rails, not about peripheral chatter.
- Mechanism = the RTC block (
RTC_CORE @ 0x0502_6000,RTC_CTRL @ 0x0502_5000). A pure-hardware, always-on power state machine plus counters and comparators. Its state is readable asrtc_fsm_st(RTC_CTRL_STATUS0 bits 29:26):ST_ON,ST_OFF,ST_SUSP,ST_PWR_CYC,ST_WARM_RESET. - Policy = whoever programs it. On our images that is Linux (see below). The 8051 is an alternative policy engine — a software-defined power manager the vendor can re-flash per product without new silicon.
What the 8051-as-policy-engine would do:
- Drive the power-state requests in
RTC_CTRL0(0x0502_5008, write-protected; unlock by writing0xAB18toRTC_CTRL0_UNLOCKKEY @ 0x0502_5004):req_suspend(bit 7),req_shdn(bit 0),req_pwr_cyc(bit 3),req_warm_rst(bit 4),req_sw_wdg_rst(bit 5) /hw_wdg_rst_en(bit 6),req_sw_thm_shdn(bit 1) /hw_thm_shdn_en(bit 2). Each is gated by the matchingRTC_EN_*_REQinRTC_CORE(e.g.RTC_EN_SUSPEND_REQ @ 0x0502_60E4,RTC_EN_PWR_WAKEUP @ 0x0502_60BC). - Select and filter wake sources:
RTC_EN_PWR_WAKEUPis a bitmask ([0] = PWR_WAKEUP0, …); the block has per-source debounce (RTC_DB_PWR_ON,RTC_DB_BUTTON1,RTC_DB_PWR_VBAT_DET) and a 7-second long-press reset (RTC_EN_7SEC_RST). - Run an RTC alarm / periodic wake (
RTC_ALARM_TIME,RTC_ALARM_ENABLE, seconds counterRTC_SEC_CNTR_VALUEthat survives power-off). - Monitor internally, with no external bus: battery/low-voltage (
RTC_PWR_DET_COMP/RTC_PWR_DET_SEL), the No-die SARADC (0x0502_C000, readsPWR_GPIO1/PWR_GPIO2/PWR_VBAT_DET), thermal shutdown (hw_thm_shdn), watchdog, IRRX, PWR GPIO. - Set the rail-sequencing timings (
RTC_UP_SEQ0..3,RTC_DN_SEQ0..3,RTC_UP_IF_EN,RTC_UP_RSTN) that drive the board’sPWR_SEQ0..3outputs. - Manage the 32 kHz clock (
reg_rtc_mode,reg_clk32k_cg_en) and its calibration (RTC_FC_*), and the keep-alive config (reg_rtcsys_rstn_src_selmust be 1 so the RTC subsystem survives power-down;RTC_EN_AUTO_POWER_UP).
The pins this needs are the PWR_* nets — PWR_SEQ1/2/3 to the regulators, PWR_BUTTON1/PWR_ON/PWR_WAKEUP0/1 to the buttons, PWR_VBAT_DET to the battery divider, GPIO_ZQ to the ZQ resistor. Those live on the board’s power-management net, not the user header.
How you program it
Toolchain: SDCC (Small Device C Compiler, 8051 target) — not GCC/LLVM. The vendor toolkit ships an sdcc/ tree with a project called “mars”:
cd sdcc/mars/project/base_project
make clean && make # -> output/mars_mcu_fw.binThe prebuilt firmware and the loader live in a separate toolkit download (firmware.zip contains the firmware + 8051_up); they are not in the public SDK git and not in our images.
Memory model. The 8051 has a 16-bit address / 8-bit data bus. Two bridges map it onto the SoC:
- SFR -> AHB bridge: extended SFRs (
reg_51robot_*) let firmware read/write AP registers (the SDK’srobot_read/robot_write). - XDATA bridge: maps “external ROM” and “external RAM” onto 32-bit on-chip spaces (AHB SRAM
0x0520_0000, TPU SRAM0x0C00_0000, DDR0x8000_0000, SPINOR).
Boot / TCM mapping (quick-start guide, RTC_CTRL):
- irom first address =
0x0520_0000 + reg_51irom_ioffset[4:0] * 2 KB(reg_51irom_ioffsetis inrtcsys_mcu51_ctrl1 @ 0x0502_5024). - The core boots from irom only when
reg_51_mem_ea_n = 1(“external ROM does not exist”); otherwise it boots from external ROM. reg_51_rom_addr_def(bit 7 ofrtcsys_mcu51_ctrl0 @ 0x0502_5020) must be 1 for SG200x (“mars” definition, 2 KB granularity).reg_51xdata_ioffset0/reg_51xdata_doffset0(bits 31:11 of the two ctrl registers) place external ROM / RAM; first address = field x 2 KB.
Bring-up sequence (TRM §14.3.2):
- Hold in reset:
reg_soft_rstn_mcu = 0(rtcsys_rst_ctrl @ 0x0502_5018, bit 1; reset default is 0). - Configure
reg_51_rom_addr_size(instruction TCM size). - Configure
reg_51irom_ioffset(where in AHB SRAM it executes). - Release reset:
reg_soft_rstn_mcu = 1.
Powering the MCU domain (§14.3.1):
- off: soft reset = 0;
reg_mcu_iso_en = 1(rtc_ip_iso_ctrl @ 0x0502_5084, bit 1);reg_mcu_pwr_req = 0(rtc_ip_pwr_req @ 0x0502_5080, bit 2). - on:
reg_mcu_pwr_req = 1; pollreg_mcu_pwr_ack = 1(bit 18);reg_mcu_iso_en = 0; soft reset = 1.
Loading the firmware. Two vendor paths, both run from Linux at runtime:
- Into AHB SRAM (<= 8 KB). Copy
8051_up,mars_mcu_fw.bin,blink.shto the board, run8051_up, thenblink.sh(which messages the core over the mailbox to blink an LED). - Into DDR (> 8 KB). Add
CHIP8051_SIZE/CHIP8051_ADDRto the SDK’smemmap.py, push the C906L FreeRTOS block down, rebuild and reflash; then run8051_upwith an8051_boot_cfg.ini. SG2000 DDR formula:0x8000_0000 + 512 MiB - size.
Status on our images
The images built by NatTuck/sophgo-sg200x-debian contain no 8051 firmware and leave the core held in reset. Evidence:
- The FIP has no 8051 component. The FSBL is cloned from
sophgo-fsbl(licheervnano-cvisdk, commit1e73867) and built withBLCP_2ND_PATH=.../blank.binandLOADER_2ND_PATH=.../u-boot.bin(configs/chip/sg200x/chip.mk). Itsfip.mk/fiptool.pypack the FIP from exactly: BL2, MONITOR (OpenSBI), LOADER_2ND (U-Boot), BLCP (fast_image_mcuortest/empty.bin), BLCP_2ND. There is no MCU entry. - The coprocessor slot is deliberately blanked:
@printf '\163\000\120\020\157\360\337\377' > .../fsbl/blank.bin(an 8-byte stub) is used asBLCP_2ND_PATH. - No DDR carveout.
configs/duos/memmap.pyandconfigs/oz64/memmap.pyreserve DRAM for FreeRTOS, OpenSBI, ION, boot logo, U-Boot and the kernel — there is noCHIP8051_SIZE/CHIP8051_ADDR, which is exactly what the vendor guide says you must add to run the 8051 from DDR. - No firmware in the rootfs/packages. The only firmware shipped is the AIC8800 WiFi firmware, the vcodec firmware (
coda980.bin,monet.bin) and two camera ISP params. Nomars_mcu_fw.bin, no8051_up, no SDCC project. - The only “8051” strings are descriptive —
notes/oz64.md(SoC blurb) and a commented-outtransfer_config_s.mcu_status = MCU_STATUS_NONOS_DONEin an FSBL patch (and that is the C906L, not the 8051).
Consequence: the 8051 never comes out of reset, and there is no Linux driver for it. Power management on these images is Linux-driven: the kernel carries a cvitek,rtc driver (configs/chip/sg200x/patches/linux/0007-…) with read_alarm/set_alarm/alarm_irq_enable, suspend/resume, shutdown, and device_init_wakeup(&pdev->dev, 1); and a SARADC driver (0008-…) that maps the RTC-domain ADC (0x0502_C000) as well as the top-domain one. So the ACPU already drives RTC-domain hardware directly — the 8051 is not needed for any of it.
If we wanted to add custom 8051 firmware
Could we? Yes — and the runtime route needs no image rebuild. The 8051’s instruction memory is plain on-chip SRAM the ACPU can write, so you can load firmware from Linux exactly the way the vendor’s 8051_up tool does:
- Hold in reset (
reg_soft_rstn_mcu = 0). - Write the firmware bytes into
0x0520_0000(the AHB SRAM) via/dev/mem. - Configure
rtcsys_mcu51_ctrl0/1:reg_51_rom_addr_def = 1,reg_51_mem_ea_n = 1(boot from irom),reg_51irom_ioffset= 0 (or the offset you wrote to),reg_51_rom_addr_sizeas needed. - Release reset (
reg_soft_rstn_mcu = 1).
A reasoned sketch of a userspace loader (untested):
import mmap, os, struct
SRAM = 0x05200000 # RTCSYS AHB SRAM (irom)
RTC_CTRL = 0x05025000
fw = open("mars_mcu_fw.bin", "rb").read()
assert len(fw) <= 8192, "SRAM method: firmware must be <= 8 KB"
f = os.open("/dev/mem", os.O_RDWR | os.O_SYNC)
sram = mmap.mmap(f, 0x2000, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE, offset=SRAM)
ctrl = mmap.mmap(f, 0x1000, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE, offset=RTC_CTRL)
def rd(off):
ctrl.seek(off); return struct.unpack("<I", ctrl.read(4))[0]
def wr(off, v):
ctrl.seek(off); ctrl.write(struct.pack("<I", v))
# 1. hold in reset
wr(0x18, rd(0x18) & ~(1 << 1)) # rtcsys_rst_ctrl: reg_soft_rstn_mcu = 0
# 2. copy firmware into irom
sram.seek(0); sram.write(fw)
# 3. configure: mars define, boot from irom, TCM size, irom offset 0
c0 = rd(0x20)
c0 = (c0 & ~0x7F) | 0xC | (1 << 7) | (1 << 5) # size=0xC, rom_addr_def=1, mem_ea_n=1
wr(0x20, c0)
wr(0x24, (rd(0x24) & ~0x1F) | 0x0) # reg_51irom_ioffset = 0
# 4. release reset
wr(0x18, rd(0x18) | (1 << 1))The exact control-field values should be matched against the vendor’s
8051_up/marsdefaults before trusting this; treat the numbers above as reasoned, not verified.
To make it persistent, add a systemd unit that runs the loader at boot — the same pattern the repo already uses for duo-pinmux / oled-setup.
Other routes:
- > 8 KB firmware (run from DDR): add
CHIP8051_SIZE/CHIP8051_ADDRtoconfigs/<board>/memmap.pyand rebuild (vendor “method II”). - Before Linux (in the FIP): not available — the FIP format has no MCU entry and the FSBL never touches the 8051. You would have to patch the FSBL.
Caveats:
RTC_CTRLis shared with the Linuxcvitek,rtcdriver (it maps0x0502_5000). Coordinate the offsets you touch (the driver uses the alarm / wake-source registers; the 8051 config lives at+0x018/+0x020/+0x024).- The core is currently parked; releasing reset while the rest of the system assumes it is parked is worth testing carefully.
- The
PWR_*pads are board power-management nets, so there is no easy way to attach an external sensor.
What it would let us do that we can’t do now
Start from the current state: Linux owns everything, and when the AP is off the only thing running is the fixed hardware RTC state machine — it can wake on an RTC alarm, a button, or VBAT detect, and that is all. No code executes during sleep.
The 8051 adds a programmable agent that runs while the AP is off:
- Custom wake policy, not just “alarm at time T” — e.g. wake every minute, read the internal SARADC, and power the AP up only if a voltage crosses a threshold; or distinguish short / long / double press of a wake input.
- Autonomous use of the always-on peripherals — RTC SARADC, IRRX, RTC GPIO, RTC I2C/UART — during sleep, which Linux can only use while awake.
- Drive the RTC power state machine itself as a stateful supervisor (suspend / power-cycle / wake).
- Faster, deterministic reaction to wake events (its own clock, no Linux wake latency).
- A mailbox co-processor for the ACPU.
Honest limits:
- The 8051’s usable pins are the
PWR_*power-management nets, not header pins (the RTC I2C is onPWR_GPIO1/PWR_GPIO2; the RTC UART onPWR_GPIO0/PWR_WAKEUP0). You cannot readily hang an external sensor on it. - It is an 8-bit 8051 with ~8 KB of code (or a DDR carveout).
- It overlaps Linux, which already has RTC-alarm wake and a SARADC driver. The 8051’s value is only in the window where the AP is off.
So the realistic win is a programmable always-on supervisor: autonomous low-power monitoring + custom wake logic + power-state control, running with the AP powered down. For an always-on Linux SBC — which is what our images are — that is a narrow benefit. For a course, the bigger payoff is pedagogical: a third ISA (8051 via SDCC) on the same die, in the always-on domain, loadable at runtime from Linux — a clean parallel to the C906L/remoteproc lab.
Reference: RTC-domain addresses
| Block | Base |
|---|---|
| RTCSYS_Timer | 0x0502_0000 |
| RTCSYS_GPIO | 0x0502_1000 |
| RTCSYS_UART | 0x0502_2000 |
| RTCSYS_INTR | 0x0502_3000 |
| RTCSYS_MBOX | 0x0502_4000 |
| RTCSYS_CTRL | 0x0502_5000 |
| RTCSYS_CORE (RTC) | 0x0502_6000 |
| RTCSYS_IO | 0x0502_7000 |
| RTCSYS_WDT | 0x0502_D000 |
| RTCSYS_I2C | 0x0502_B000 |
| RTCSYS_SARADC | 0x0502_C000 |
| RTCSYS_IRRX | 0x0502_E000 |
| RTCSYS_SRAM (8051 irom) | 0x0520_0000 (8 KB) |
| ap_mailbox | 0x0190_0000 |
RTC_CTRL @ 0x0502_5000 registers used here:
| Offset | Name | Notes |
|---|---|---|
+0x004 |
RTC_CTRL0_UNLOCKKEY |
write 0xAB18 to unlock RTC_CTRL0 |
+0x008 |
RTC_CTRL0 |
req_suspend(7) req_shdn(0) req_pwr_cyc(3) req_warm_rst(4) req_sw_wdg_rst(5) hw_wdg_rst_en(6) req_sw_thm_shdn(1) hw_thm_shdn_en(2) reg_rtc_mode(10) reg_clk32k_cg_en(11) |
+0x00c |
RTC_CTRL_STATUS0 |
rtc_fsm_st[29:26] rtc_alarm_o(21) rtc_pwr_button1_o(2) rtc_pwr_on_o(4) rtc_pwr_wakeup0_o(5) |
+0x018 |
rtcsys_rst_ctrl |
bit 1 reg_soft_rstn_mcu (reset 0) |
+0x020 |
rtcsys_mcu51_ctrl0 |
reg_51_rom_addr_size[4:0] reg_51_mem_ea_n(5) reg_51_xdata_mode(6) reg_51_rom_addr_def(7) reg_51xdata_ioffset0[31:11] |
+0x024 |
rtcsys_mcu51_ctrl1 |
reg_51irom_ioffset[4:0] reg_51_pf_mode[9:6] reg_51xdata_doffset0[31:11] |
+0x05c |
rtcsys_mcu51_dbg |
8051 debug |
+0x07c |
rtcsys_mcu51_ictrl1 |
reg_51_int1_src_mask |
+0x080 |
rtc_ip_pwr_req |
reg_mcu_pwr_req(2) reg_mcu_pwr_ack(18) |
+0x084 |
rtc_ip_iso_ctrl |
reg_mcu_iso_en(1) |
+0x098 |
rtcsys_sram_ctrl |
reg_mcu_sram_slp/sd |
+0x09c |
rtcsys_io_ctrl |
reg_i2c_mux_opt0(0): PWR_IIC on PWR_GPIO6/8 |
Sources
pdfs/sg2000_trm_en.pdf— Chapter 14 “8051 Subsystem” (§14.1–14.5), §5.1 (power domains / MCU-Only mode), §6.6.3.6/.8/.9/.28/.29 (RTC control registers), §21.1 (I2C controllers), §21.5 (GPIO), Chapter 10 (pinmux).- Sophgo
sophgo-doc:SG200X/8051/8051_Core_Quick_Start_Guide_en.md,SG200X/TRM/contents/en/mcu/*, and thed8051_db.pdfdatasheet. - Schematics:
duo_s_SCH_v1.1.pdf(Milk-V),PINE64_Oz64_Schematic-20240517.pdf(Pine64). NatTuck/sophgo-sg200x-debian—configs/chip/sg200x/chip.mk,configs/{duos,oz64}/memmap.py,configs/chip/sg200x/patches/{fsbl,linux}/.
Open questions
- Does a stock (vendor) Duo S / Oz64 image run any 8051 firmware? The public SDK and the FIP do not carry one, but the FSBL references
MCU_FLAG 0x8051and a warm-boot handoff through RTC SRAM, so the vendor suspend/resume flow may involve it. Not confirmed either way. - Can the 8051, in MCU-Only mode, request the RTC state machine to power the AP up directly (via
RTC_CTRL0), or does it rely on a wake line / mailbox? The TRM says it “can wake up the main system”; the mechanism is not spelled out. - Exact
rtcsys_mcu51_ctrl0/1values the vendor8051_upuses for the SG2000 (to validate the loader sketch above). - Whether releasing the 8051 reset interacts with the Linux
cvitek,rtcdriver’s register writes.